stage-1
郭高旭 ggx21@mails.tsinghua.edu.cn 2021010803
实验内容
跟着文档阅读学习实验框架代码.
实验步骤:通过例子学习编译全流程 - Step1
-
词法分析 & 语法分析
- 使用lex/yacc库将C程序字符流转化为AST。
-
语义分析
- 检查是否存在主函数。
-
中间代码生成
- 将AST翻译为三地址码(TAC),提取返回语句的常量。
-
目标代码生成
- 将TAC翻译为32位RISC-V汇编代码,设置返回值为2023。
stage-1
整个stage-1的实验内容就是完成常量表达式expression
的语法编译.具体而言包括
-
一元表达式
-
二元表达式
-
比较和逻辑表达式
实验思路
-
定义 新增的
Unary
、TacUnaryOp
、RvUnaryOp
(以及BinaryOp)三种不同的一元(二元)运算符类型,如下例所示 -
确定
astOp
–tacOp
–risvOp
的对应关系 -
frontend/tacgen时设计中间代码(TAC)表示
-
后端代码生成:
我将 riscv和ast不能一一对应的语法提前在上一步TAC表示时处理好,所以TAC生成后端代码时只需要添加操作符并构建好操作符间一一对应关系即可.
step-1
1.没有namer.transform
, typer.transform
步骤,以下代码能正常编译吗
1 | int main(){ |
答:可以正常编译,namer
和typer
的作用分别是符号表构建和类型检查,而上述代码根本没有用到变量也没有额外scope
,故不需要构建符号表,也不需要类型检查.所以仍然能正常编译出tac结果如下
1 | FUNCTION<main>: |
2.对于main函数没有返回值的情况是在哪一步处理的?报的是什么错?
main.py
,调用 frontend.parser.parser
来完成语法分析的工作.parser
模块中定义了return语句的语法结构,如下所示
1 | def p_return(p): |
这一函数的docstring中定义了return的语法结构,要求return+expression+";"
缺失expression将报错Syntax error
1 | Syntax error: line 1, column 20 |
3.为什么框架定义了 Unary
、TacUnaryOp
、RvUnaryOp
三种不同的一元运算符类型?
这对应了MiniDecaf 编译器的三个部分:前端、中端、后端。每种运算符用来构造不同部分的语法树的结点.
-
Unary:通过编译器前端,可以读入 MiniDecaf 源程序,然后通过词法分析和语法分析将源程序转化为一个抽象语法树(AST)
-
TacUnaryOp:中端通过扫描 AST 生成中间代码 —— 三地址码(TAC)
-
RvUnaryOp:后端将三地址码转换为 RISC-V 汇编代码
step-2
1 | -(~2147483647) |
step-3
左操作数-2147483648
,右操作数-1
,此时RISCV-32 的 qemu 模拟器运行结果是
1 | 2021010803@compiler-lab:~/lab/minidecaf-2021010803/test$ qemu-riscv32 a.out |
在我x86-64的系统中报错
Floating point exception
step-4
-
性能优化:短路求值可以在某个条件已经确定的情况下避免不必要的计算。如果在逻辑表达式中的某个条件已经能够确定整个表达式的值,那么后续的条件将不会被计算,从而提高了程序的性能和效率。
-
避免副作用:在某些情况下,逻辑表达式中的条件可能包含副作用,例如函数调用或变量修改。通过短路求值,可以确保只有在必要时才会执行这些副作用,避免不必要的操作,从而增强了程序的可维护性和可预测性。
-
代码简洁性:使用短路求值可以编写更简洁、更具可读性的代码。程序员可以使用逻辑表达式来表达条件,而无需手动添加额外的控制结构,例如if语句。
-
错误避免:通过短路求值,可以避免一些潜在的错误,例如除以零或访问未初始化的变量。如果某个条件可以确定表达式的结果,那么不会发生这些错误。
-
逻辑简化:在某些情况下,短路求值可以使逻辑更简单和更直观。程序员可以使用短路求值来表达逻辑条件,而无需手动构建复杂的条件组合。