试用GO开发pyhton编译器:字节码基础

2024-04-30 21:48

本文主要是介绍试用GO开发pyhton编译器:字节码基础,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

掌握一门编程语言最好的办法或许是将它的编译器设计出来。毫无疑问那些开发Python编译器的人应该是世界上对Python了解最深刻的人群之一。我用python开发过不少程序,但是每次反思或复盘的时候总是感觉对Python的认知还不到位,由此也看了很多讲Python的书,但看的时候感觉好像懂了,但过了一段时间后又忘了,也就是说单纯看书很难将某一项技术完全内化。当然技能的掌握必然要从实践中来,但是我发现在使用Python开发程序时,我总是使用它的一部分功能就够了,或者说居于我的思维模式限制,我在使用python开发时总是落入一个套路,这使得我只能掌握python技术的冰山一角,就如同井底之蛙一样只了解一小块内容,为了能够打破认知局限,让我自己能更全面的对python的设计原理有更深入的了解,我打算尝试做一个能运行的python编译器。

我在标题中使用了”试用”,也就是这是一个尝试性质,编译器的技术难度足够大,我不清楚能做到哪一步,所以采取了走一步看一步的方式,能做多少就多少,也有可能尝试后发现太难而做不下去,因此是”试用“的由来。

我计划用Go语言来实现python编译器,这样完成这个项目后我们能收获一箭双雕的好处,一是掌握如何使用GO来开发一个复杂程序,一是对python的设计原理能有深入的了解和掌握。首先我们来尝试做一个简单的,基于栈的虚拟机,后面我们会把python编译成字节码,然后在我们设计的虚拟机上运行,这个过程跟java类似。

对虚拟机而言,首先需要字节码,它们是针对虚拟机的一系列操作指令,例如push 1, push 2, add,这三条字节码会把数值1,2压入虚拟机,然后弹出栈顶的两个数值进行相加,把相加结果放到堆栈的顶部,如下图所示:
请添加图片描述
首先我们要实现的是字节码,所谓字节码就是一个操作指令,后面跟着0个或1个操作数,例如push 1, add等,每个操作指令用一个数值表示,一旦虚拟机读取到对应的数值时就执行相应操作,如果我们使用0x01表示push,那么当虚拟机读取到数字0x01时,它就会把跟在这个指令后面的4个字节对应的数值压入堆栈。

我们先创建一个文件夹叫GO_Python,然后在里面再创建一个文件夹叫code,接着创建文件code.go,它对应字节码的实现代码,我们先完成一些基本定义:

package code import ("encoding/binary""fmt"
)type Instructions [] byte //字节码集合type Opcode byte  //操作码const (opConstant opcode = iotaOpPop  //弹出堆栈OpAdd  //将栈顶两个数弹出并相加,把结果压入堆栈OpSubOpMul OpDiv//后面还有更多操作码需要定义
)

假设我们把python代码编译成字节码后,它们就对应Instructions,在字节码中有一些字节代表了特定操作,例如push, add, sub等,这些操作就对应操作码。操作码可以使用不同的数值来区分,因此代码中定义了枚举类型数值来对应操作码,注意到操作码的类型为byte,这意味着我们的虚拟机最多支持128种不同操作。

我们还需要对操作码做进一步描述,例如给定操作码OpPop后,我们希望能找到它对应的字符串,例如"POP",同时不同操作码其实对应不同的操作数,例如OpAdd就对应两个操作数,所有这些信息我们都需要以代码的方式记录下来,因此我们继续添加如下定义:

type Definition struct {Name string //操作码对应的字符串OperandWidths [] int //操作数对应的字节数
}var definitions = map[Opcode]*Definition {OpConstant: {"OpConstant", []int{2}},
}func Lookup(op byte) (*Definition, error ) {//给定操作码,返回它对应的信息定义def, ok := definitions[Opcode(op)]if !ok {return nil, fmt.Errorf("opcode %d undefined", op)}return def, nil
}

代码中需要说明的一点是operandWidths,它对应操作数的字节长度,例如对于表达式 255 + 1,我们需要使用操作码opAdd将两个数值弹出,然后相加,由于255对应两个字节,1对应1个字节,于是对应的definition就是{“OpAdd", []{2, 1}}。由此我们可以理解上面代码中操作码"OpConstant"对应的操作数有2个字节的长度,OpConstant操作符的作用是在一个常量数组中查找对应数组,它的操作数就是数组下标,我们会把代码中所定义的一切常量都放入到一个特定的常量数组中,相关内容后续我们会进一步解释。

接下来我们看如何将操作码以及操作数转换成一条可以被虚拟机执行的指令,所谓”指令“其实就是byte数组,数组的第一个字节对应操作符的数值,后续字节对应操作数的内容。假设有一条字节码为 OpConstant 65534, 那么将它转换为指令时,第一个字节就对应操作码OpConstant的数值,也就是0,接下来就对应操作数的字节内容,由于65534拆解成两个最字节就是0xFF, 0xFE,于是这条操作码转换为”指令“后就是[]byte{0x0, 0xFF, 0xFE}, 我们看对应的代码实现:

func Make(op Opcode, operands ...int) []byte {//给定操作码,创建字节码指令def , ok := definitions[op]if !ok {return []byte{}}//一条指令的字节长度包括操作码对应的长度加上操作数对应的长度instructionLen :=1  //操作码长度始终为1for _, w := range def.OperandWidths {instructionLen += w }//一条指令由一系列字节组成,第一个字节就是操作码,接下来的字节对应操作数instructions := make([]byte, instructionLen) instructions[0] = op //设置操作码对应的字节offset := 1for i, o := range operands {width := def.OperandWidths[i]switch width {case 2://把一个16比特数,也就是uint16类型的数值拆解成2个byte放到数组中binary.BigEndian.PutUint16(instruction[offset:], uint16(o))}offset += width }return instruction
}

于是当我们的虚拟机在执行指令[]byte{0x0, 0xFF, 0xFE}时,它发现第一个字节为0,于是它就知道要执行OpConstant操作,也就是要从常量数组中把对应的内容拿出来,同时根据definitions结构体可以知道,对应的操作数有两个字节,于是它把接下来的两个字节也就是0xFF,0xFE组合起来称为一个操作数,也就是获得了65534这个数值,然后将65534作为数组的下标,从常量数组中把下标为65534的元素取出来。

最好的学习方式就是即时反馈,所以我们有了一些代码后,要尽快把它们运行起来,看看执行结果,这样我们才能通过实验来验证逻辑或者是清除头脑中的疑惑,如果没有即时反馈,那么我们很快就会因为困惑积累过多而放弃努力,因此我们将以测试的方式把上面代码跑起来,在同一目录下创建明文code_test.go的文件,在里面添加测试代码如下:

package code 
import "testing"func TestMake(t *testing.T) {tests := []struct {op Opcode operands []int expected []byte } {{OpConstant, []int{65534}, []byte{0x0, 0xFF, 0xFE}},}for _, tt := range tests {instruction := Make(tt.op, tt.operands...)if len(instruction) != len(tt.expected) {t.Errorf("instruction has wrong length. want=%d, got=%d",len(tt.expected), len(instruction))}for i , b := range tt.expected {if instruction[i] != tt.expected[i] {t.Errorf("wrong byte at pos %d, want=%d, got = %d", i, b, instruction[i])}}}
}

完成上面代码后,进入到code目录然后执行:

go  test

这样就能将测试用例跑起来,通过结果可以看到用例能通过,也就是Make函数准确的将操作码及其对应的操作数转换成了一条指令字节数组,为了好消化,我们一次不要搞太多,先在这里停止。

完整代码请查看GitHub

这篇关于试用GO开发pyhton编译器:字节码基础的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/950005

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧