编译原理:抽象语法树是怎么变成机器码的( 二 )


跳转的源位置和跳转的目的位置,都是一个新的基本块的开始位置:
因为它们(前面或后面)的代码的执行分支是可能变化的,所以要单独划到一个基本块里 。

编译原理:抽象语法树是怎么变成机器码的

文章插图
for循环的流程图
如果在某个基本块里修改了某个变量,就要在末尾把它保存到内存 。
如果在某个基本块里要使用某个变量,就要在开头把它加载到寄存器 。
如上图:
对 i 赋值 0 之后要把它保存到内存,见绿色的save i指令;
在比较i < 10之前要把它从内存加载到寄存器,见绿色的load i指令 。
这样生成指令,就可以保证运行结果的正确 。
但为了让它运行的更快,一般会把循环内的加载放到循环的开头,把循环内的保存放到循环的末尾 。
当然,这么做的前提是:循环的出口和入口必须都是唯一的 。
goto不受待见,从编译器的层面来看,就是goto会导致循环的出口和入口有多个 。
4,变量之间的冲突,
互相冲突的变量不能使用相同的寄存器,这是寄存器分配的原则 。
怎么判断变量之间互相冲突呢?
就是某个变量如果在后面还要用,那么它跟其他变量就是冲突的 。
例如:
a = 1;
b = 2;
c = a + b;
【编译原理:抽象语法树是怎么变成机器码的】那么a和b就是冲突的 。
因为第3行代码都要用到它们两个,所以在第2行代码的时候:a占用的寄存器不能腾出来,b必须找其他寄存器用 。
第3行代码之后,a和b都没用了,c可以跟a或b的任何一个使用相同的寄存器 。
所以上面的代码翻译成汇编,可以这样:
mov eax, 1
mov ebx, 2
add eax, ebx
a和c都使用eax,b使用ebx 。
但是a和b不能都使用eax,否则结果就是错的 。
变量之间是否冲突,要从后往前看 。
编译器里在检查变量的冲突时,都是从函数的末尾往开头反向遍历每一条代码,看看哪些变量之间是冲突的 。
有兴趣的可以看看我写的scf编译器框架的代码,在文件scf_basic_block.c里 。
5,寄存器的分配,
确定了变量之间的冲突情况之后,就可以给代码里的变量分配寄存器了 。
例如:
a = 2;
b = 3;
c = 4;
d = a + b + c;
这个例子的abc三者之间都是冲突的,因为在第4行同时用到了它们3个 。
画个变量的冲突图如下:
编译原理:抽象语法树是怎么变成机器码的

文章插图
变量的冲突图
a, b, c之间是互相冲突的,每一对冲突的变量之间有一条连线,所以正好画成一个三角形 。
在分配寄存器时,abc互相之间不能分配相同的寄存器 。
也就是说,把寄存器的编号作为颜色的话,上图三角形的3个顶点不能着同样的颜色 。
在变量的冲突图上,每一条边的两个端点必须着不同的颜色 。
这就是用于寄存器分配的图的着色算法 。
分配完寄存器之后就好办了,按照每种CPU的手册生成指令的机器码就可以了 。
例如:
给a, b, c分别分配eax, ebx, ecd,
d与a共用eax,
那么汇编指令就是:
mov eax, 2
mov ebx, 3
mov ecx, 4
add eax, ebx
add eax ecx.
x64的机器码我实在记不住,但在汇编层面,我还是可以充当个人形编译器的[大笑]
有兴趣的可以看看我的scf编译器框架的源码,非常的清晰 。
编译原理(龙书)确实没怎么细说后端是怎么写的,只是提了一下图的着色算法 。
不过对于我来说,我只要知道有这么个软件,我就能把它写出来!
求个赞赏[捂脸]
虽然重阳一生不弱于人,但还是要靠九阴真经来破解古墓派的武功啊 。




推荐阅读