现代编译器设计与实现 课件 李诚 第4-7章 龙芯汇编自动生成- 静态单赋值中间代码转化_第1页
现代编译器设计与实现 课件 李诚 第4-7章 龙芯汇编自动生成- 静态单赋值中间代码转化_第2页
现代编译器设计与实现 课件 李诚 第4-7章 龙芯汇编自动生成- 静态单赋值中间代码转化_第3页
现代编译器设计与实现 课件 李诚 第4-7章 龙芯汇编自动生成- 静态单赋值中间代码转化_第4页
现代编译器设计与实现 课件 李诚 第4-7章 龙芯汇编自动生成- 静态单赋值中间代码转化_第5页
已阅读5页,还剩321页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第4讲

龙芯汇编自动生成编译原理和技术CONTENT龙芯汇编介绍栈式分配介绍汇编程序示例实验代码框架下期再见!Thanks!龙芯汇编介绍龙芯汇编介绍编译原理课程组中国科学技术大学龙芯汇编介绍龙芯汇编介绍龙芯架构简述龙芯架构LoongArch(简称LA)由龙芯中科技术股份有限公司研发,具有自主产权的国产指令集架构精简指令集计算机(RISC)风格分为32位和64位两个版本分别称为LA32架构和LA64架构本实验基于LA64架构采用基础部分+拓展部分的组织形式寄存器介绍LoongArch基础部分中的寄存器程序计数器PC32个通用寄存器GR32个浮点寄存器FR8个浮点条件标志寄存器FCR一系列控制状态寄存器CSR4个浮点控制状态寄存器FCSR寄存器介绍本实验编译器后端会使用到的寄存器程序计数器PC32个通用寄存器GR32个浮点寄存器FR8个条件标志寄存器CFR本实验编译器后端不会使用的寄存器一系列控制状态寄存器CSR4个浮点控制状态寄存器FCSR寄存器介绍在龙芯架构的汇编代码中,寄存器用“$+寄存器名”标识,比如通用寄存器$r0$sp$zero浮点寄存器$f0$ft0$fa0条件标志寄存器$fcc0寄存器介绍:程序计数器与通用寄存器32个通用寄存器GR记为$r0~$r31零号寄存器$r0

的值恒为0通用寄存器的位宽与架构的位宽一致位宽在LA32下为32比特,在LA64下为64比特用于整数数据和指针的操作程序计数器PC记录着当前指令的地址,不能被直接修改位宽与架构的位宽一致寄存器介绍:通用寄存器的使用规范通用寄存器的使用应该遵循下面的规定通用寄存器别名用途在调用中是否保留$r0$zero常数0(常数)$r1$ra返回地址否$r2$tp线程指针(不可分配)$r3$sp栈指针是$r4-$r5$a0-$a1传参寄存器、返回值寄存器否$r6-$r11$a2-$a7传参寄存器否$r12-$r20$t0-$t8临时寄存器否$r21保留寄存器(不可分配)$r22$fp/$s9栈帧指针、静态寄存器是$r23-$r31$s0-$s8静态寄存器是本实验编译器后端不会用到$r2、$r21

和$r23~$r31寄存器介绍:通用寄存器的使用规范通用寄存器的使用应该遵循下面的规定通用寄存器别名用途在调用中是否保留$r0$zero常数0(常数)$r1$ra返回地址否$r3$sp栈指针是$r4-$r5$a0-$a1传参寄存器、返回值寄存器否$r6-$r11$a2-$a7传参寄存器否$r12-$r20$t0-$t8临时寄存器否$r22$fp/$s9栈帧指针、静态寄存器是在汇编代码中,可以直接使用寄存器名,也可以使用寄存器的别名若“在调用中是否保留”这一栏为“是”,则在函数调用过程中,被调用函数在返回时需要恢复寄存器原来的值寄存器介绍:浮点相关寄存器32个浮点寄存器FR记为$f0~$f31不论什么架构,位宽均为64比特用于单/双精度浮点数的操作8个条件标志寄存器CFR记为$fcc0~$fcc7位宽为1比特寄存器介绍:条件标志寄存器8个条件标志寄存器CFR用于存储浮点比较指令的结果比较结果为真时值为1,否则为0fcmp.slt.s$fcc0,$f0,$f1将$f0和$f1中的单精度浮点数进行比较,

如果前者小于后者则置$fcc0为1,否则置$fcc0为0浮点分支指令的判断条件源于CFRbcnez$fcc0,label_true若$fcc0不为0,则跳转到label_true,否则不跳转寄存器介绍:浮点寄存器的使用规范浮点寄存器的使用应该遵循下面的规定通用寄存器别名用途在调用中是否保留$f0-$f1$fa0-$fa1传参寄存器、返回值寄存器否$f2-$f7$fa2-$fa7传参寄存器否$f8-$f23$ft0-$ft15临时寄存器否$f24-$f31$fs0-$fs7静态寄存器是本实验编译器后端不会用到$f24-$f31在汇编代码中,可以直接使用寄存器名,也可以使用寄存器的别名若“在调用中是否保留”这一栏为“是”,则在函数调用过程中,被调用函数在返回时需要恢复寄存器原来的值指令介绍:指令编码格式指令编码格式所有指令均采用32位固定长度指令的地址都要求4字节边界对齐指令地址不对齐时将触发地址错例外指令介绍:指令助记格式为了方便汇编编程人员和编译器开发人员,龙芯架构对指令名的前、后缀进行了统一考虑前缀:用于指示指令类型整数指令:无前缀,如add.wld.dlu12i.w浮点指令:前缀为f,如fadd.sfst.d指令介绍:指令助记格式为了方便汇编编程人员和编译器开发人员,龙芯架构对指令名的前、后缀进行了统一考虑前缀:用于指示指令类型整数指令:无前缀,如add.wld.dlu12i.w浮点指令:前缀为f,如fadd.sfst.d后缀:用于指示指令的操作对象类型后缀整数指令浮点指令.b.h.w.d.s.d.w.l操作对象类型字节半字字双字单精度浮点数双精度浮点数字双字位宽1byte2bytes4bytes8bytes4bytes8bytes4bytes8bytes指令介绍:整数指令二元运算指令无立即数算术指令:add/sub/mul/div(后缀.w/.d)比较指令:slt/sltu位运算指令:and/or/nor/xor/andn/orn有立即数算术指令:addi(后缀.w/.d,12位有符号立即数)比较指令:slti/sltui(12位有符号立即数)位运算指令:andi/ori/xori(12位无符号立即数)示例有立即数:add.w$t2,$t0,$t1#$t2=$t0+$t1无立即数:addi.w$t0,$zero,-12#$t0=-12指令介绍:整数指令立即数加载指令lu12i.w:用于设置寄存器的[31:12]

位lu32i.d:用于设置寄存器的[51:32]

位lu52i.d:用于设置寄存器的[63:52]

位示例:加载不同长度的立即数加载长度小于等于12位的立即数使用ori

加载12位无符号立即数(0~4095)

将2255写入$r0:

ori$r0,$zero,2255 #$r0=0x0000_0000_0000_08CF使用addi加载12位有符号立即数(-2048~2047)

将-1841写入$r0:

addi.w$r0,$zero,-1841 #$r0=0xFFFF_FFFF_FFFF_F8CF

指令介绍:整数指令立即数加载指令lu12i.w:用于设置寄存器的[31:12]

位lu32i.d:用于设置寄存器的[51:32]

位lu52i.d:用于设置寄存器的[63:52]

位示例:加载不同长度的立即数加载长度大于12位,小于等于32位的有符号立即数(-231

~231-1)使用ori

和lu12i.w

将0xFFFF_FFFF_8765_4321

写入$t0:

lu12i.w$t0,-0x789AC #$t0=0xFFFF_FFFF_8765_4000

ori$t0,$t0,0x321 #$t0=0xFFFF_FFFF_8765_4321因为lu12i.w指令中的立即数是20位有符号立即数,

所以汇编代码中使用-0x789AC

而不是0x87654指令介绍:整数指令立即数加载指令lu12i.w:用于设置寄存器的[31:12]

位lu32i.d:用于设置寄存器的[51:32]

位lu52i.d:用于设置寄存器的[63:52]

位示例:加载不同长度的立即数加载长度大于32位,小于等于52位的有符号立即数(-251

~251-1)使用ori、lu12i.w和lu32i.d

将0x0003_4567_0123_4567

写入$t0:

lu12i.w$t0,0x1234 #$t0=0x0000_0000_0123_4000

ori$t0,$t0,0x567 #$t0=0x0000_0000_0123_4567

lu32i.d$t0,0x34567 #$t0=0x0003_4567_0123_4567指令介绍:整数指令立即数加载指令lu12i.w:用于设置寄存器的[31:12]

位lu32i.d:用于设置寄存器的[51:32]

位lu52i.d:用于设置寄存器的[63:52]

位示例:加载不同长度的立即数加载长度大于52位,小于等于64位的有符号立即数(-263

~263-1)使用ori、lu12i.w、lu32i.d

和lu52i.d

将0x1234_5678_1234_5678

写入$t0:

lu12i.w$t0,0x12345 #$t0=0x0000_0000_1234_5000

ori$t0,$t0,0x678 #$t0=0x0000_0000_1234_5678

lu32i.d$t0,0x45678 #$t0=0x0004_5678_1234_5678

lu52i.d$t0,$t0,0x123 #$t0=0x1234_5678_1234_5678指令介绍:整数指令分支指令有条件跳转beq/bne/blt/bge/bltu/bgeu:寄存器之间比较示例:blt$t0,$t1,label_true将$t0

$t1

视作有符号数进行比较,如果前者小于后者,

则跳转到label_true,否则不跳转bltu$t0,$t1,label_true将$t0

$t1

视作无符号数进行比较,如果前者小于后者,

则跳转到label_true,否则不跳转beqz/bnez:寄存器与0比较示例:beqz$t0,label_true如果$t0

中的值为0则跳转到label_true,否则不跳转指令介绍:整数指令分支指令无条件跳转b无条件跳转(类似于C语言goto)示例:blabel_xxx #跳转到label_xxx

处bl无条件跳转,同时保存PC+4

的值到$ra用于调用函数(类似于X86汇编call指令)示例:blfunc_xxx #跳转到func_xxx

处,并将PC+4

写入$rajirl:无条件跳转,目标地址由其中一个操作数寄存器加上偏移量计算得到,同时保存PC+4

的值到目的寄存器示例:jirl$t0,$t1,0 #跳转到地址$t1+0

处,并将PC+4

写入$t0一般使用jirl$zero,$ra,0

来实现返回语句(类似于X86汇编ret指令)等价于宏指令jr$ra,返回地址为$ra无条件跳转回bl语句的下一条语句指令介绍:整数指令访存指令读取内存ld.b:读取字节(8位)ld.h:读取半字(16位)ld.w:读取字(32位)ld.d:读取双字(64位)写入内存st.b:写入字节(8位)st.h:写入半字(16位)st.w:写入字(32位)st.d:写入双字(64位)指令介绍:整数指令访存指令读取内存ld.b/ld.h/ld.w/ld.d:读取字节/半字/字/双字到寄存器写入内存st.b/st.h/st.w/st.d:将寄存器中的字节/半字/字/双字写入存储器示例ld.w$t0,

$fp,

-24从地址

($fp

-

24)

处读取一个字(32位),符号拓展后写入

$t0

中st.b$t0,$fp,-28将

$t0

的低

8

位写入地址

($fp

-

28)指令介绍:整数指令宏指令la.local将标签的地址写入寄存器常用于加载全局变量示例:label_xxx: #假设地址为0x1000_0000.space4#为

label_xxx分配四字节空间

#...

la.local$t0,label_xxx#$t0=&label_xxx=0x1000_0000jrjr$rj

等价于jirl$zero,$rj,0jr$ra

常用于函数返回指令介绍:浮点指令浮点算术指令fadd.s:单精度浮点数加法fsub.s:单精度浮点数减法fmul.s:单精度浮点数乘法fdiv.s:单精度浮点数除法对于单精度浮点算术指令,结果浮点寄存器的高32位可以是任意值示例:fadd.s$ft2,

$ft0,

$ft1#$ft2=$ft0

+

$ft1fsub.s$ft2,

$ft0,

$ft1#$ft2=$ft0

-

$ft1fmul.s$ft2,

$ft0,

$ft1#$ft2=$ft0

*

$ft1fdiv.s$ft2,

$ft0,

$ft1#$ft2=$ft0

/

$ft1指令介绍:浮点指令浮点搬运指令movgr2fr.w将通用寄存器的低32位搬运到浮点寄存器的低32位浮点寄存器的[63:32]

位值不确定示例:$t0

中的值为0x4108_0000,

执行movgr2fr.w$ft0,$t0

后,

$ft0

的低32位变为0x4108_0000(8.5的单精度表示)movfr2gr.s将浮点寄存器的低32位符号扩展后搬运到通用寄存器示例:$ft0

的低32位中的值为-8.5(0xC108_0000),

执行movfr2gr.s$t0,$ft0

后,

$t0

中的值变为0xFFFF_FFFF_C108_0000指令介绍:浮点指令浮点转换指令ffint.s.w将4字节整数值转换为单精度浮点数示例:$ft0

的低32位值为0x0000_0008,

执行ffint.s.w$ft1,$ft0

后,

$ft1

的低32位值为0x4100_0000(8.0的单精度表示)ftintrz.w.s将单精度浮点数转换为4字节整数值采用“向零方向舍入”作为舍入模式示例:$ft0

的低32位值为8.5(0x4108_0000),执行ftintrz.w.s$ft1,$ft0

后,

$ft1

中的值为0x0000_0000_0000_0008$ft0

的低32位值为-8.5(0xC108_0000),执行ftintrz.w.s$ft1,$ft0

后,

$ft1

中的值为0x0000_0000_FFFF_FFF8(低32位为-8)指令介绍:浮点指令浮点转换指令示例:翻译类型转换语句将$t0

中的整数转换为浮点数写入$ft0($ft0=(float)$t0)movgr2fr.w$ft0,$t0#

将$t0

中的整数搬运到$ft0ffint.s.w$ft0,$ft0#

将$ft0

中的整数转换为浮点数,写入$ft0将$ft0

中的浮点数转换为整数写入$t0($t0=(int)$ft0)ftintrz.w.s$ft0,$ft0#

将$ft0

中的浮点数转换为整数,写入$ft0movfr2gr.s$t0,$ft0#

将$ft0

中的数据(整数)搬运到$t0将$ft0

中的浮点数转换为整数写入存储器(Mem[addr]=(int)$ft0)#

假设地址为

$fp-24ftintrz.w.s$ft0,$ft0#

将$ft0

中的浮点数转换为整数,写入$ft0fst.s$ft0,$fp,-24#

将$ft0

中的数据(整数)写入存储器指令介绍:浮点指令浮点比较指令fcmp.cond.s:比较单精度浮点数值,将结果写入条件标志寄存器cond

可以为seq/sne/slt/sle/…对应的指令为fcmp.seq.s/fcmp.sne.s/…示例:fcmp.slt.s$fcc0,$ft0,$ft1将$f0和$f1中的单精度浮点数进行比较,如果前者小于后者则置$fcc0为1,否则置为0浮点分支指令bceqz/bcnez:根据条件标志寄存器的值决定是否跳转示例:bceqz$fcc0,label_true若$fcc0为0,则跳转到label_true,否则不跳转指令介绍:浮点指令浮点访存指令fld.s/fld.d:读取内存中的单/双精度浮点数fst.s/fst.d:将浮点寄存器中的单/双精度浮点数写入内存示例:fld.s$ft0,

$fp,

-24从地址

($fp

-

24)

处读取单精度浮点数写入

$ft0

中fst.d$ft0,$fp,-28将

$ft0

中的双精度浮点数写入地址

($fp

-

28)栈帧与函数调用栈帧每个未完成函数在堆栈中占用的一段连续区域保存了函数的上下文信息用于支持函数的调用与返回调用者栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址当前函数栈帧栈增长方向栈帧与函数调用:栈帧栈帧每个未完成函数在堆栈中占用的一段连续区域保存了函数的上下文信息用于支持函数的调用与返回龙芯架构ABI规定使用两个寄存器来访问堆栈:栈帧指针$fp($r22)指向栈帧的底部堆栈指针$sp($r3)指向栈帧的顶部这两个寄存器的值应该对齐到16字节,即龙芯架构中栈帧大小必须是128位的整数倍调用者栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址$fp$sp当前函数栈帧栈增长方向栈帧与函数调用:函数调用函数调用过程函数调用的过程由调用者和被调用函数共同完成函数调用过程中使用到的寄存器:$sp($r3)

和$fp($r22)用于维护程序堆栈$ra($r1)

用于记录返回地址$a0

-

$a7($r4

-

$r11)

$fa0

-

$fa7($f0

-

$f7)

用于传递参数$a0($r4)、$a1($r5)、$fa0($f0)和$fa1($f1)

用于传递返回值本实验编译器后端只关心$a0($r4)和$fa0($f1)

调用者栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址$fp$sp当前函数栈帧栈增长方向栈帧与函数调用:函数调用示例:父函数

caller()

调用子函数

callee()父函数调用子函数调用方(父函数)将参数加载到适当的寄存器和堆栈位置执行跳转指令:blcallee#跳转到子函数子函数进入初始化堆栈并保存上下文信息子函数退出将返回值放入对应寄存器($a0/$a1/$fa0/$fa1)中释放栈帧并恢复各个保留寄存器返回到父函数:jirl$zero,$ra,0(等价于jr$ra)父函数恢复如果调用方在调用时为参数分配了额外的堆栈空间,则需要将这些空间释放函数调用的返回值位于$a0/$a1/$fa0/$fa1

中栈帧与函数调用:函数调用–示例1.父函数调用子函数调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向caller:#父函数

caller_entry:

...

caller_labels_before_call:

...

caller_call_callee:栈帧与函数调用:函数调用–示例1.父函数调用子函数调用方(父函数)将参数加载到适当的寄存器和堆栈位置caller:#父函数

caller_entry:

...

caller_labels_before_call:

...

caller_call_callee:

#加载参数

ld.w$a0,$fp,-ARG1_OFFSET

ld.w$a1,$fp,-ARG2_OFFSET

addi.w$a3,$zero,ARG3调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向栈帧与函数调用:函数调用–示例1.父函数调用子函数调用方(父函数)将参数加载到适当的寄存器和堆栈位置执行跳转指令:blcallee#跳转到子函数caller:#父函数

caller_entry:

...

caller_labels_before_call:

...

caller_call_callee:

#加载参数

ld.w$a0,$fp,-ARG1_OFFSET

ld.w$a1,$fp,-ARG2_OFFSET

addi.w$a3,$zero,ARG3

#调用子函数

blcallee调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向栈帧与函数调用:函数调用–示例2.子函数进入callee:#子函数调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向栈帧与函数调用:函数调用–示例2.子函数进入初始化堆栈并保存上下文信息栈帧大小必须为16字节的整数倍调用者(父函数)栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址$sp栈增长方向callee:#子函数

callee_entry:

st.d$ra,$sp,-8

#保存返回地址

st.d$fp,$sp,-16

#保存栈帧指针的值

addi.d$fp,$sp,0

#将新的栈底值写入$fp

addi.d$sp,$sp,-N#分配新栈帧$fp子函数栈帧栈帧与函数调用:函数调用–示例2.子函数进入初始化堆栈并保存上下文信息栈帧大小必须为16字节的整数倍然后子函数开始执行计算需要的指令调用者(父函数)栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址$sp栈增长方向callee:#子函数

callee_entry:

st.d$ra,$sp,-8

#保存返回地址

st.d$fp,$sp,-16

#保存栈帧指针的值

addi.d$fp,$sp,0

#将新的栈底值写入$fp

addi.d$sp,$sp,-N#分配新栈帧callee_compute_labels:

...$fp子函数栈帧栈帧与函数调用:函数调用–示例3.子函数退出调用者(父函数)栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址$sp栈增长方向callee:#子函数

callee_entry:

st.d$ra,$sp,-8

#保存返回地址

st.d$fp,$sp,-16

#保存栈帧指针的值

addi.d$fp,$sp,0

#将新的栈底值写入$fp

addi.d$sp,$sp,-N#分配新栈帧callee_compute_labels:

...$fp子函数栈帧栈帧与函数调用:函数调用–示例3.子函数退出将返回值放入对应寄存器($a0

等寄存器)中调用者(父函数)栈帧返回地址备份调用者$fp

备份局部变量等信息程序堆栈高地址低地址$sp栈增长方向callee:#子函数

callee_entry:

st.d$ra,$sp,-8

#保存返回地址

st.d$fp,$sp,-16

#保存栈帧指针的值

addi.d$fp,$sp,0

#将新的栈底值写入$fp

addi.d$sp,$sp,-N#分配新栈帧callee_compute_labels:

...

callee_return_value:

ori$a0,$t0,0#将返回值写入$a0

bcallee_exit$fp子函数栈帧栈帧与函数调用:函数调用–示例3.子函数退出将返回值放入对应寄存器($a0

等寄存器)中释放栈帧并恢复各个保留寄存器callee:#子函数

callee_entry:

st.d$ra,$sp,-8

#保存返回地址

st.d$fp,$sp,-16

#保存栈帧指针的值

addi.d$fp,$sp,0

#将新的栈底值写入$fp

addi.d$sp,$sp,-N#分配新栈帧callee_compute_labels:

...

callee_return_value:

ori$a0,$t0,0#将返回值写入$a0

bcallee_exitcallee_exit:

addi.d$sp,$sp,N

#恢复$sp

ld.d$fp,$sp,-16

#恢复$fp

ld.d$ra,$sp,-8

#恢复$ra调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向栈帧与函数调用:函数调用–示例3.子函数退出将返回值放入对应寄存器($a0

等寄存器)中释放栈帧并恢复各个保留寄存器返回到父函数:jr$racallee:#子函数

callee_entry:

st.d$ra,$sp,-8

#保存返回地址

st.d$fp,$sp,-16

#保存栈帧指针的值

addi.d$fp,$sp,0

#将新的栈底值写入$fp

addi.d$sp,$sp,-N#分配新栈帧callee_compute_labels:

...

callee_return_value:

ori$a0,$t0,0#将返回值写入$a0

bcallee_exitcallee_exit:

addi.d$sp,$sp,N

#恢复$sp

ld.d$fp,$sp,-16

#恢复$fp

ld.d$ra,$sp,-8

#恢复$ra

jr$ra

#返回调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向栈帧与函数调用:函数调用–示例4.父函数恢复如果调用方在调用时为参数分配了额外的堆栈空间,则需要将这些空间释放函数调用的返回值位于$a0

等寄存器中caller:#父函数

caller_entry:

...

caller_labels_before_call:

...

caller_call_callee:

#加载参数

ld.w$a0,$fp,-ARG1_OFFSET

ld.w$a1,$fp,-ARG2_OFFSET

addi.w$a3,$zero,ARG3

#调用callee

blcallee

#返回值在$a0中调用者(父函数)栈帧程序堆栈高地址低地址$fp$sp栈增长方向栈帧与函数调用:参数传递汇编程序怎么在函数调用时传递参数?使用规定的寄存器:$a0-$a7,$fa0-$fa7寄存器不够用时,调用者在堆栈上开辟一块空间用于传参栈帧与函数调用:参数传递汇编程序怎么在函数调用时传递参数?使用规定的寄存器:$a0-$a7,$fa0-$fa7寄存器不够用时,调用者在堆栈上开辟一块空间用于传参完整的参数传递规范比较复杂,本实验编译器后端使用简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中栈帧与函数调用:参数传递简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中示例:intfunction(参数类型参数寄存器inta,

intb[],

floatc,

intd,

floate)栈帧与函数调用:参数传递简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中示例:intfunction(参数类型参数寄存器inta,整型$a0

intb[],

floatc,

intd,

floate)栈帧与函数调用:参数传递简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中示例:intfunction(参数类型参数寄存器inta,整型$a0

intb[],指针$a1

floatc,

intd,

floate)栈帧与函数调用:参数传递简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中示例:intfunction(参数类型参数寄存器inta,整型$a0

intb[],指针$a1

floatc,浮点型$fa0

intd,

floate)栈帧与函数调用:参数传递简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中示例:intfunction(参数类型参数寄存器inta,整型$a0

intb[],指针$a1

floatc,浮点型$fa0

intd,整型$a2

floate)栈帧与函数调用:参数传递简化后的参数传递规范仅使用$a0-$a7

和$fa0-$fa7

用于参数传递整数类型(指针/整型)参数使用通用寄存器$a0-$a7浮点类型参数使用浮点寄存器$fa0-$fa7各个参数按照顺序依次放入对应的寄存器中示例:intfunction(参数类型参数寄存器inta,整型$a0

intb[],指针$a1

floatc,浮点型$fa0

intd,整型$a2

floate浮点型$fa1)下期再见!Thanks!栈式分配介绍栈式分配介绍编译原理课程组中国科学技术大学栈式分配介绍栈式分配介绍栈式分配:What将IR翻译为汇编指令:%op2

=addi32%op0,%op1add.w

$rd,

$rj,

$rk如何得到%op0与%op1的值?栈式分配:What栈式分配的定义程序的所有变量都保存在栈上只在参与计算时提取到寄存器中栈式分配的步骤变量分配:为每个变量分配栈帧位置指令选择load:提取源操作数至寄存器指令选择:根据指令类型选择合适的汇编指令store:将结果从寄存器写回栈帧

栈式分配:What将IR翻译为汇编指令:%op2

=addi32%op0,%op1ld.w

$t0,$fp,off1ld.w

$t1,$fp,off2add.w

$t0,

$t0,

$t1st.w

$t0,$fp,off3%op0%op1%op2$fpoff1off2off3程序部分栈帧栈式分配:Why寄存器分配目标:变量保存在寄存器中难以用算法实现最优解需要构建冲突图、循环迭代需要处理复杂的寄存器溢出……实现复杂!寄存器分配BuildSimplifyCoalescePotentialSpillSelectActualSpill下期再见!Thanks!汇编程序示例汇编程序示例编译原理课程组中国科学技术大学汇编程序示例汇编程序示例程序1:返回值definei32@main(){label_entry:

reti320}

.text

.globlmain

.typemain,@functionmain:

st.d$ra,$sp,-8

st.d$fp,$sp,-16

addi.d$fp,$sp,0

addi.d$sp,$sp,-16.main_label_entry:

addi.w$a0,$zero,0

bmain_exitmain_exit:

addi.d$sp,$sp,16

ld.d$ra,$sp,-8

ld.d$fp,$sp,-16

jr$ra$fp0-8-16程序栈帧$sp$ra$fp汇编代码IR程序程序2:局部变量赋值definevoid@main(){label_entry:

%op0=allocai32

storei321234,i32*%op0

retvoid} .text .globlmain .typemain,@functionmain: st.d$ra,$sp,-8 st.d$fp,$sp,-16 addi.d$fp,$sp,0 addi.d$sp,$sp,-32.main_label_entry: addi.d$t0,$fp,-28 st.d$t0,$fp,-24 ld.d$t0,$fp,-24 addi.w$t1,$zero,1234 st.w$t1,$t0,0 addi.w$a0,$zero,0 bmain_exitmain_exit: addi.d$sp,$sp,32 ld.d$ra,$sp,-8 ld.d$fp,$sp,-16 jr$ra汇编代码IR程序程序2:局部变量赋值definevoid@main(){label_entry:

%op0=allocai32

storei321234,i32*%op0

retvoid}......addi.d$t0,$fp,-28st.d$t0,$fp,-24

ld.d$t0,$fp,-24addi.w$t1,$zero,1234st.w$t1,$t0,0......汇编代码$fp0-8-16-24-28程序栈帧$sp$ra$fp%op0allocai32IR程序程序3:分支definei32@main(){label_entry:

%op0=icmpslti321,2

bri1%op0,Label%L1,%L2L1:

reti320L2:

reti321}IR程序程序3:分支 .text .globlmain .typemain,@functionmain: st.d$ra,$sp,-8 st.d$fp,$sp,-16 addi.d$fp,$sp,0 addi.d$sp,$sp,-32.main_label_entry: addi.w$t0,$zero,1 addi.w$t1,$zero,2 slt$t2,$t0,$t1 st.b$t2,$fp,-17 ld.b$t0,$fp,-17 bstrpick.w$t0,$t0,0,0 st.w$t0,$fp,-21 ld.w$t0,$fp,-21 addi.w$t1,$zero,0 xor$t2,$t0,$t1 sltu$t2,$zero,$t2 st.b$t2,$fp,-22 ld.b$t0,$fp,-22bstrpick.d$t0,$t0,0,0 bne$t0,$zero,.main_label3 b.main_label5.main_label3: addi.w$a0,$zero,0 bmain_exit.main_label4: addi.w$a0,$zero,0 bmain_exit.main_label5: addi.w$a0,$zero,1 bmain_exitmain_exit: addi.d$sp,$sp,32 ld.d$ra,$sp,-8 ld.d$fp,$sp,-16 jr$ra汇编代码程序3:分支definei32@main(){label_entry:

%op0=icmpslti321,2

bri1%op0,Label%L1,%L2L1:

reti320L2:

reti321}$fp0-8-16-17程序栈帧$sp$ra$fp%op0......addi.w$t0,$zero,1addi.w$t1,$zero,2slt$t2,$t0,$t1st.b$t2,$fp,-17

ld.b$t0,$fp,-17bne$t0,$zero,.L1b.L2......汇编代码IR程序程序4:全局变量@a=globali32zeroinitializerdefinevoid@main(){label_entry:

storei3210,i32*@a

retvoid} .text .section.bss,"aw",@nobits .globla .typea,@object .sizea,4a: .space4 .text .globlmain .typemain,@functionmain: st.d$ra,$sp,-8 st.d$fp,$sp,-16 addi.d$fp,$sp,0 addi.d$sp,$sp,-16.main_label_entry: la.local$t0,a addi.w$t1,$zero,10 st.w$t1,$t0,0 addi.w$a0,$zero,0 bmain_exitmain_exit: addi.d$sp,$sp,16 ld.d$ra,$sp,-8 ld.d$fp,$sp,-16 jr$ra汇编代码IR程序程序4:全局变量@a=globali32zeroinitializerdefinevoid@main(){label_entry:

storei3210,i32*@a

retvoid}.text.section.bss,"aw",@nobits.globla.typea,@object.sizea,4a:.space4.......main_label_entry:la.local$t0,aaddi.w$t1,$zero,10st.w$t1,$t0,0......汇编代码IR程序$fp0-8-16程序栈帧$sp$ra$fp程序5:函数调用 .text .globlfunc .typefunc,@functionfunc: jr$ra .globlmain .typemain,@functionmain: st.d$ra,$sp,-8 st.d$fp,$sp,-16 addi.d$fp,$sp,0 addi.d$sp,$sp,-32.main_label_entry: addi.w$a0,$zero,1 blfunc st.w$a0,$fp,-20 addi.w$a0,$zero,0 bmain_exitmain_exit: addi.d$sp,$sp,32 ld.d$ra,$sp,-8 ld.d$fp,$sp,-16 jr$ra汇编代码definei32@func(i32%arg0){label_entry:reti32%arg0}definevoid@main(){label_entry:%op0=calli32@func(i321)retvoid}IR程序$fp0-8-16-20程序栈帧$sp$ra$fp%op0下期再见!Thanks!实验代码框架实验代码框架编译原理课程组中国科学技术大学实验代码框架实验代码框架CodeGen类概述class

CodeGen{private:

void

allocate();

//变量分配

/*===以下为辅助函数===*///将数据在寄存器和栈帧间搬移

void

load_xxx(...);

void

store_xxx(...);

//添加汇编指令

void

append_inst(...);//基本块在汇编程序中的名字

static

std::stringlabel_name(BasicBlock*);/*===以上为辅助函数===*///需要补全的部分,进行代码生成的各个环节

void

gen_xxx(...);};程序流程程序流程进行栈帧分配按指令翻译输出汇编代码框架导读代码示例:添加指令以如下目标汇编代码为例:

.text

.globlmain

.typemain,@functionmain:

addi.w$a0,$zero,0

jr$ra代码示例:添加指令定义不同的gen_manually(),以生成不同的汇编程序voidgen_manually(CodeGen*codegen);

int

main(){

auto

*codegen=

new

CodeGen(nullptr);

gen_manually(codegen);

std::cout<<codegen->print();

deletecodegen;

return

0;}

.text

.globlmain

.typemain,@functionmain:

addi.w$a0,$zero,0

jr$ra代码示例:添加指令使用CodeGen::append_inst()添加指令:voidgen_manually(CodeGen*codegen){

codegen->append_inst(".text",ASMInstruction::Atrribute);

codegen->append_inst(".globlmain",ASMInstruction::Atrribute);

codegen->append_inst(".typemain,@function",ASMInstruction::Atrribute);

codegen->append_inst("main",ASMInstruction::Label);//main函数标签

codegen->append_inst("addi.w$a0,$zero,0");

codegen->append_inst("jr$ra");}

.text

.globlmain

.typemain,@functionmain:

addi.w$a0,$zero,0

jr$ra代码示例:辅助函数栈式分配的load环节:void

CodeGen::load_to_greg(Value

*v,

const

Reg

®);将IR变量v的值加载至寄存器reg中栈式分配的store环节:voidCodeGen::store_from_greg(Value

*v,

const

Reg

®);将寄存器reg中的值存入IR变量v的栈帧位置代码示例:辅助函数例:对二元IR指令的翻译context.inst代表当前翻译指令,比如%op2=addi32%op0,%op1Reg::t(i)定义了寄存器$tivoid

CodeGen::gen_binary(){

load_to_greg(context.inst->get_operand(0),Reg::t(0));

load_to_greg(context.inst->get_operand(1),Reg::t(1));

switch(context.inst->get_instr_type()){

case

Instruction::add:

append_inst("add.w$t2,$t0,$t1");

break;

...

}

store_from_greg(context.inst,Reg::t(2));}从栈上提取两个操作数按照IR指令的类型生成对应的汇编将结果保存回栈上下期再见!Thanks!Lab3:目标代码生成编译原理和技术编译原理课程组中国科学技术大学Lab3:目标代码生成实验要求实现编译器后端,使编译器能够生成正确的汇编代码补全src/codegen/CodeGen.cpp按需修改include/codegen/CodeGen.hpp等文件Lab3相关目录结构项目编译位于项目目录下(labn)创建并进入build目录:mkdirbuildcdbuild使用cmake生成Makefile等:cmake..(寻找CMakeLists.txt文件,读取项目编译信息,生成Makefile等)使用make编译出最新的程序并安装到系统路径make–j(-j表示开启多线程编译,使用全部CPU核心)sudomakeinstall(安装到系统路径下,普通用户执行需要sudo权限)每次改动代码都需要重新执行第3步Lab3:目标代码生成实验测试手动测试编译后最新的cminusfc程序能将cminus文件输出为龙芯汇编文件(.s文件)cminusfc–S<cminus文件>在LA64架构下,可使用gcc将汇编代码编译成二进制可执行程序gcc<.s文件>src/io/io.c–o<可执行文件名>在x86架构下,使用交叉编译的方式编译出LA64架构上的二进制可执行文件loongarch64-unknown-linux-gnu-gcc-static<.s文件>src/io/io.c–o<可执行文件名>仍然以前述test.cminus为例,演示操作流程在x86机器上测试编译器后端,cminusfc配合交叉编译环境使用示例cminus文件LA64汇编文件LA64可执行文件cminusfcgcc(LA64架构)交叉编译器(x86架构)源代码到可执行文件的编译流程Lab3:目标代码生成实验测试自动测试测试脚本:tests/3-codegen/eval_lab3.sh测试样例:tests/3-codegen/testcases、tests/testcases_general测试脚本工作原理同Lab2,其接受两个参数:测试样例目录:./testcases或../testcases_general,也可使用其他样例,选用相对路径评测模式:test或debug,debug将保留编译过程中的.ll文件测试结果直接输出,log文件保存在tests/3-codegen/log.txt中,编译过程中产生的文件保存在tests/3-codegen/output中

Lab3:目标代码生成实验测试自动测试eval_lab3.sh使用示例1,全部通过eval_lab3.sh使用示例2,最后一个样例输出错误下期再见!Thanks!Mem2Reg编译原理和技术Pass的概念Mem2Reg目录Pass的概念Mem2Reg目录机器无关的代码优化Pass是中间代码优化的基本单元,执行编译器的转换和优化。Pass就是“遍历一遍IR,可以同时对它做一些操作”的意思。Pass的概念ClangC/C++/ObjCFrontend

llvm-gccFrontend

GHCFrontend

LLVMOptimizerLLVMX86BackendLLVMARMBackendLLVMLoongarchBackendCFortanHaskellPassIRLLVMIRPassPassPassLLVMIRx86ARM龙芯机器无关的代码优化Pass是中间代码优化的基本单元,执行编译器的转换和优化。Pass就是“遍历一遍IR,可以同时对它做一些操作”的意思。Pass分类分析型Pass:对IR进行分析,得到一些数据流/控制流信息,用于后续Pass使用,如循环查找。转换型Pass:对IR进行等价变化,提升代码性能,如常量传播。Pass的概念机器无关的代码优化Pass是中间代码优化的基本单元,执行编译器的转换和优化。Pass就是“遍历一遍IR,可以同时对它做一些操作”的意思。Pass分类分析型Pass:对IR进行分析,得到一些数据流/控制流信息,用于后续Pass使用,如循环查找。转换型Pass:对IR进行等价变化,提升代码性能,如常量传播。优化目标运行时间更短占用空间更小Pass的概念Lab4中新加的Pass分析型PassDominators:支配树分析FuncInfo:纯函数分析转换型PassMem2Reg:提升内存变量为寄存器变量TODODeadCode:死代码删除Pass的概念Pass的概念Mem2Reg目录Mem2RegMem2Reg编译原理课程组中国科学技术大学LightIR(LLVM)引入了alloca指令来表示栈变量。前端可以简单地将每个局部变量映射到一个alloca指令,并通过load/store操作读写相应的内存变量,简化了前端设计难度;Mem2Regint

main(void){

inta;

a=

1

+

1;

return

a;}definei32@main(){label_entry:

%op0=allocai32

%op1=addi321,1

storei32%op1,i32*%op0

%op2=loadi32,i32*%op0

reti32%op2}前端生成的IR代码该IR存在的问题不必要的alloca/load/store指令->访存多,性能差不是严格的SSA形式->不利于后续中间代码优化Mem2Regdefinei32@main(){label_entry:

%op0=allocai32

%op1=addi321,1

storei32%op1,i32*%op0

%op2=loadi32,i32*%op0

reti32%op2}Mem2Reg:自动将内存变量提升为寄存器变量删除不必要的访存指令使得IR符合SSA形式Mem2Regdefinei32@main(){label_entry:

%op1=addi321,1

reti32%op1}Mem2Regdefinei32@main(){label_entry:

%op0=allocai32

%op1=addi321,1

storei32%op1,i32*%op0

%op2=loadi32,i32*%op0

reti32%op2}Mem2Reg基本思想:对内存变量使用基于栈的到达定义分析按行扫描该基本块中的所有指令对alloca指令,建立相关变量的栈;对store指令,将需store的值入栈;对load指令,使用栈顶元素替换目标寄存器的所有使用;Mem2RegMem2Reg过程:对内存变量使用基于栈的到达定义分析按行扫描该基本块中的所有指令对alloca指令,建立相关变量的栈;Mem2RegMem2Reg过程:对内存变量使用基于栈的到达定义分析按行扫描该基本块中的所有指令对store指令,将需store的值入栈;Mem2RegMem2Reg过程:对内存变量使用基于栈的到达定义分析按行扫描该基本块中的所有指令对load指令,使用栈顶元素替换目标寄存器的所有使用;Mem2RegMem2Reg过程:对内存变量使用基于栈的到达定义分析按行扫描该基本块中的所有指令对store指令,将需store的值入栈;Mem2RegMem2Reg过程:对内存变量使用基于栈的到达定义分析按行扫描该基本块中的所有指令扫描结束后,删除所有alloca/load/store指令;Mem2Regdefinei32@main(){label_entry:

%op1=addi321,2

%op3=muli32%op1,4

reti320}definei32@main(){label_entry:

%op0=allocai32

%op1=addi321,2

storei32%op1,i32*%op0

%op2=loadi32,i32*%op0

%op3=muli32%op2,4

storei32%op3,i32*%op0

reti320}删除已经处理完毕的内存操作考虑更一般的IR情况int

main(){

intcond;

intx;

cond=

1;

if(cond>

0)

x=

1;

else

x=

-1;

returnx;}%7的值即可能取1,也可能取%9,此时有多个定义,基于栈的到达定义分析无法处理,怎么办?Mem2RegEntry%1=allocai32%2=al

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论