自己动手发明编程语言
❯
教程介绍📘
❯
在线体验编译器🚀
❯
为什么要发明一个新的编程语言?☕
❯
开始🔛
❯
开发编译器的流程 🚀
❯
基础理论和原理 ⚛️
❯
计算机指令基础 (CPU)🖥️
❯
自定义虚拟机指令(VM) 🕹️
❯
C语言崩溃栈信息🐞
❯
设计强化虚拟机指令(VM) 🕹️
❯
手搓编写词法分析(Lexer)📜
❯
手搓表达式编译 🔥
❯
手搓 if-else 语法编译 ⚙️🔗
❯
手搓 for 语法编译 ⚙️🔗
❯
手搓 while 语法编译 ⚙️🔗
❯
手搓 函数调用 语法编译 ⚙️🔗
❯
虚拟机高性能优化 🚀
❯
try catch异常捕获简单实现🐞
❯
手搓即时编译(JIT)入门 ⚡️
❯
包管理器开发入门 📦📦
❯
开发故事
# 基础理论和原理 ⚛️ 机器指令 => 汇编语言 => 高级语言 ## 编译器在解决什么问题? 因为使用汇编语言非常繁琐,难以阅读且容易出错。人们发明了高级语言,使用更直观的变量、if-else、函数等概念来写程序。使用编译器把这些语言代码编译成汇编或直接到机器指令。 比如汇编代码片段 ````asm fun: # @fun push rbp mov rbp, rsp mov dword ptr [rbp - 4], edi mov dword ptr [rbp - 8], esi mov eax, dword ptr [rbp - 4] add eax, dword ptr [rbp - 8] pop rbp ret ```` 其实是下面这个C语言程序的对应汇编代码 ```c int fun(int a,int b) { return a+b; } ```` 可以很直观看到的C语言代码更容易阅读理解。编译器要做的事情就是把类似C语言代码编译到对应的汇编代码或机器指令上。 可能有人会好奇最终编译的机器指令长什么样,这里贴一下 x86-64 clang 10.0.1 编译器给出的结果: ````c 55 48 89 e5 89 7d fc 89 75 f8 8b 45 fc 03 45 f8 5d c3 ```` 可以看出来直接使用机器指令进行编程,是非常困难的事情。 推荐大家使用 https://godbolt.org/ 这个在线编译器查看对应的汇编指令和机器指令,有很多编译器可选。 ## 编译原理为什么那么枯燥? 很多编译原理的教程,通常用很多篇幅来讲解 形式语言、自动机理论、语法泛式、文法分析等等。通常看了几十上百页,还没到可以动手写代码的阶段。 我认为这些并非都是必须的。两周开发的 NextScript 简单编译器和虚拟机,并实际跑起来了。我都没有整理出对应的 BNF 语法泛式。但从实际结果来看,这个编译器应该可以算是入门了。 这个简单的编译器甚至都没有其他编译原理书籍上经常使用的外部工具 Bison、Yacc 等等,完全手搓实现编译器和虚拟机。 所以,尽快开始 Coding 可能是编译器入门的好办法之一。通过边学边实践,边加深理解。 没有语法泛式,那么怎么确定这个新语言的语法呢?其实很简单,我们先选择实现某个现有编程语言的子集即可,比如这次我选择了最熟悉的C语言的子集。 ## 字符串处理和转化 源代码是字符串,把高级语言转为汇编语言也还是字符串,所以本质上在按一定规则做字符串的编译处理。 比如 `return a+b;` 应该怎么编译呢? 首先,我们看到的是关键词 return ,按照约定 后面应该是个表达式,也就是这里的 a+b 。 ## 计算 a+b 那么计算机如何计算 a+b 呢? 在计算器 CPU 中通常有很多寄存器,因为在CPU内部,所以CPU访问这些寄存器要比内存快多了。所以计算机指令或者说CPU指令通常是在操作寄存器。 这里我们需要用到两个指令 mov 和 add : - mov 在内存和寄存器之间做数据移动 - add 加法指令 以x86指令为例,add 指令支持两个参数,暂且叫参数1和参数2 。 ````asm add 参数1, 参数2 ```` 这条汇编指令的效果是 参数1的数值被更新为 参数1+参数2 的结果。 在通常的汇编语言书籍中,会这样讲解: ````asm ADD 目标操作数, 源操作数 ```` 目标操作数:可以是一个寄存器或内存位置,同时也是加法运算的结果存放的地方。 源操作数:可以是一个寄存器、内存位置或立即数(直接写在指令中的数值)。 我们可以使用寄存器 eax 来存储 a+b 的结果。我们先让 eax 的值等 a,再使用 add 指令,让 eax 的数值 加 b ,最终eax 就是 a + b 的结果。 ````asm mov eax, a ; 先让eax 的值等 a add eax, b ; 再让 eax 的数值 加 b,最终eax的数值就等于 a+b ```` x86 中函数返回值通常保存在 eax 寄存器中,这也是我们选用 eax 寄存器来实现 return 语句的原因。 综上所述, return a+b 可以被编译为如下汇编指令: ````asm mov eax, a add eax, b ret ```` 在实际生成的汇编指令中 a 和 b 需要替换为 内存位置 或寄存器名称。ret 正是 return 的缩写,表示从函数过程退出了。 更完整的把这个 C语言代码 ````c int fun(int a,int b) { return a+b; } ```` 手工编译到汇编指令,可以这样写: ````asm fun: mov eax, edi ; 第一个参数 a 在 edi 寄存器 add eax, esi ; 第二个参数 b 在 esi 寄存器,累加到 eax 上 ret ; 结果在寄存器 eax 作为函数返回值 ```` 相信你还有很多关于汇编语言的疑问。比如 push rbp 或 dword 或 edi 等含义。这些我们都将在后续内容里提到。 将高级编程语言的代码手工编译到汇编指令的技能将是编译器开发中非常重要的一个技能之一,可以说非常Amazing!
Hello!请先完成登录验证
微信公众号
哈希空间
扫码进入公众号回复 9 即可完成验证,实现自动登录网站。
关注就是支持,更好的服务提供给粉丝