编译原理:cminus_compiler-2021-fall Lab3

唠嗑

感觉全部四次编译原理实验中(没错,我没有做第五次实验),第三次实验真的是最有意思的一次实验了。
这次的实验感觉和计算机系统拆炸弹的那个实验一样写起来好有意思的。然而当时我还是很菜很菜的水平,所以基本上都是看着哈士奇学长的代码一步一步推理出来我自己的代码应该怎么解决。而这一次,我是完完整整的与我的好朋友们相互帮助之下完成的这次实验(我和我的朋友们真的是太棒了)

0. 前言

本次实验作为Lab4的前驱实验,独立于Lab1、Lab2。
本次实验的目的是让大家熟悉Lab4所需要的相关知识: LLVM IR、 LightIR(LLVM IR的轻量级C++接口)和 Visitor Pattern(访问者模式)。
在开始实验之前,如果你使用的不是助教提供的虚拟机镜像,请根据之前的环境准备确保LLVM的版本为10.0.1,且PATH环境变量配置正确。可以通过lli --version命令是否可以输出10.0.1的版本信息来验证。

主要工作

1.第一部分: 了解LLVM IR。通过clang生成的.ll,了解LLVM IR与c代码的对应关系。完成1.3
2.第二部分: 了解LightIR。通过助教提供的c++例子,了解LightIR的c++接口及实现。完成2.3
3.第三部分: 理解Visitor Pattern。
4.实验报告:在report.md中回答3个问题。

1. LLVM IR部分

1.1 LLVM IR介绍

根据维基百科的介绍,LLVM是一个自由软件项目,它是一种编译器基础设施,以C++写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。IR的全称是Intermediate Representation,即中间表示。LLVM IR是一种类似于汇编的底层语言。
LLVM IR的具体指令可以参考Reference Manual。但是你会发现其内容庞杂。虽然助教认为,高效地查阅官方文档及手册是非常必要的一项技能,但是由于其手册过于复杂,因此助教筛选了后续实验中将要用到的子集,总结为了精简的IR Reference手册。
作为一开始的参考,你可以先阅读其中IR Features和IR Format两节,后续有需要再反复参考。实验的最后,你需要在report.md中回答问题3。

1.2 gcd例子: 利用clang生成的.ll

阅读tests/lab3/ta_gcd/gcd_array.c。
根据clang -S -emit-llvm gcd_array.c指令,你可以得到对应的gcd_array.ll文件.你需要结合gcd_array.c阅读gcd_array.ll,理解其中每条LLVM IR指令与c代码的对应情况。
通过lli gcd_array.ll; echo $?指令,你可以测试gcd_array.ll执行结果的正确性。其中,
· lli会运行*.ll文件
· $?的内容是上一条命令所返回的结果,而echo $?可以将其输出到终端中
后续你会经常用到这两条指令。

1.3 你的提交1: 手动编写.ll

助教提供了四个简单的c程序,分别是tests/lab3/c_cases/目录下的assign.c、fun.c、if.c和while.c.你需要在tests/lab3/stu_ll/目录中,手工完成自己的assign_hand.ll、fun_hand.ll、if_handf.ll和while_hand.ll,以实现与上述四个C程序相同的逻辑功能.你需要添加必要的注释…ll文件的注释是以";"开头的。
必要的情况下,你可以参考clang -S -emit-llvm的输出,但是你提交的结果必须避免同此输出一字不差。
助教会用lli检查你结果的正确性,并用肉眼检查你的注释。

2. LightIR部分

2.1 LightIR - LLVM IR的C++接口

由于LLVM IR官方的C++接口的文档同样过于冗长,助教提供了LightIR这一C++接口库。你需要阅读LightIR核心类的介绍。
lab4部分会要求大家通过LightIR根据AST构建生成LLVM IR。所以你需要仔细阅读文档了解其接口的设计。

2.2 gcd例子: 利用LightIR + cpp 生成.ll

为了让大家更直观地感受并学会LightIR接口的使用,助教提供了tests/lab3/ta_gcd/gcd_array_generator.cpp。该cpp程序会生成与gcd_array.c逻辑相同的LLVM IR文件。助教提供了非常详尽的注释,一定要好好利用!
该程序的编译与运行请参考4.2节。

2.3 你的提交2: 利用LightIR + cpp编写生成.ll的程序

你需要在tests/lab3/stu_cpp/目录中,编写assign_generator.cpp、fun_generator.cpp、if_generator.cpp和while_generator.cpp,以生成与1.3节的四个C程序相同逻辑功能的.ll文件。你需要添加必要的注释。你需要在report.md中回答问题1。

3. Lab4的准备

3.1 了解Visitor Pattern

Visitor Pattern(访问者模式)是一种在LLVM项目源码中被广泛使用的设计模式。在遍历某个数据结构(比如树)时,如果我们需要对每个节点做一些额外的特定操作,Visitor Pattern就是个不错的思路。
Visitor Pattern是为了解决稳定的数据结构和易变的操作耦合问题而产生的一种设计模式。解决方法就是在被访问的类里面加一个对外提供接待访问者的接口,其关键在于在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。这里举一个应用实例来帮助理解访问者模式: 您在朋友家做客,您是访问者;朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
有关 Visitor Pattern 的含义、模式和特点,有梯子的同学可参考维基百科。
下面的例子可以清晰地展示Visitor Pattern的运作方式。这是助教编写的计算表达式 4 * 2 - 2 / 4 + 5 结果的C++程序。
其中较为重要的一点原则在于,C++中对函数重载特性的支持。在代码treeVisitor.visit(node)中,根据node对象具体类型的不同,编译器会在visit(AddSubNode& node)、visit(NumberNode& node)、visit(MulDivNode& node)三者中,选择对应的实现进行调用。你需要理解下面这个例子中tree是如何被遍历的。请在report.md中回答问题2。

例子:简单的表达式计算 - visitor.cpp
该文件的执行结果如下:

$ g++ visitor.cpp -std=c++14; ./a.out
4 * 2 - 2 / 4 + 5 evaluates: 13

4. 实验要求

4.1 目录结构

除了下面指明你所要修改或提交的文件,其他文件请勿修改。

.
├── CMakeLists.txt
├── Documentations
│   ├── ...
|   ├── common                          <- LightIR 相关文档
│   └── lab3
│       └── README.md                   <- lab3实验文档说明(你在这里)
├── include                             <- 实验所需的头文件
│   ├── ...
│   ├── lightir
├── README.md
├── Reports
│   ├── ...
│   └── lab3
│       └── report.md                   <- lab3所需提交的实验报告,含3个问题(你要交)
├── src
│   ├── ...
│   └── lightir
└── tests
    ├── CMakeLists.txt
    ├── ...
    └── lab3                            <- lab3文件夹
        ├── c_cases                     <- 4个c程序
        │   ├── assign.c
        │   ├── fun.c
        │   ├── if.c
        │   └── while.c
        ├── CMakeLists.txt              <- 你在2.3节需要去掉注释(我们不收,你要改)
        ├── stu_cpp                     <- lab3所需提交的cpp目录(你要交)
        │   ├── assign_generator.cpp
        │   ├── fun_generator.cpp
        │   ├── if_generator.cpp
        │   └── while_generator.cpp
        ├── stu_ll                      <- lab3所需提交的.ll目录(你要交)
        │   ├── assign_hand.ll
        │   ├── fun_hand.ll
        │   ├── if_hand.ll
        │   └── while_hand.ll
        └── ta_gcd
            ├── gcd_array.c
            └── gcd_array_generator.cpp <- 助教提供的生成gcd_array.ll的cpp

4.2 编译、运行和验证

·编译与运行 在 ${WORKSPACE}/build/ 下执行:

# 如果存在 CMakeCache.txt 要先删除
# rm CMakeCache.txt
cmake ..
make
make install

你可以得到对应gcd_array_generator.cpp的可执行文件。
在完成2.3时,在$ {WORKSPACE}/tests/lab3/CMakeLists.txt中去掉对应的注释,再在$ {WORKSPACE}/build/下执行cmake …与make指令,即可得到对应的可执行文件。
·验证
本次试验测试案例只有${WORKSPACE}/tests/lab3/c_cases中的4个样例。请大家自行验证。
助教会执行你们的代码,并使用 diff 命令进行验证。

4.3 提交要求和评分标准

·提交要求
本实验的提交要求分为两部分: 实验部分的文件和报告,git提交的规范性。

·实验部分:
·需要完成 ./tests/lab3/stu_ll目录下的4个文件
·需要完成 ./tests/lab3/stu_cpp目录下的4个文件
·需要在 ./Report/lab3/ 目录下撰写实验报告
·实验报告内容包括:
·实验要求、3个问题、实验难点、实验反馈(具体参考report.md)
·本次实验报告参与评分标准.
·本次实验收取 ./tests/lab3/stu_ll 目录、./tests/lab3/stu_cpp 目录和 ./Report/lab3 目录

·git提交规范:
·不破坏目录结构(report.md如果需要放图片,请放在./Reports/lab3/figs/下)
·不上传临时文件(凡是自动生成的文件和临时文件请不要上传)
·git log言之有物(不强制,请不要git commit -m ‘commit 1’,git commit -m ‘sdfsdf’,每次commit请提交有用的comment信息)

·评分标准: 本次实验的测试样例较为简单,所以为了拿高分请注意report.md
·1.3节.ll运行结果正确(一个5分,共20分)
·2.3节.cpp运行结果正确(一个10分,共40分)
·report.md(40分)
·禁止执行恶意代码,违者本次实验0分处理

代码

assign.ll

;int main(){
define dso_local i32 @main() #0 {

;int a[10]
%1 = alloca [10 x i32]	;%1为a[10]的起始地址

;a[0] = 10;
%2 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i64 0, i64 0	;%2为a[0]
store i32 10, i32* %2	;%2赋值为10,完成a[0]赋值

;a[1] = a[0] * 2;
%3 = load i32, i32* %2	;%3加载%2的数据
%4 = mul nsw i32 %3, 2	;%4赋值为%3的二倍
%5 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i64 0, i64 1	;%5存储a[1]
store i32 %4, i32* %5	;%4的值赋值给%5,完成a[1]赋值

;return a[1];
%6 = load i32, i32* %5	;%6存储%5
ret i32 %6		;返回%6

;}
}

fun.ll

;int callee(int a){
define dso_local i32 @callee(i32 %0) #0 {
%2 = alloca i32		;开辟空间	为什么不能%1???
store i32 %0, i32* %2	;%2存储参数a
%3 = load i32, i32* %2	;%3保存%2

;return 2 * a;
%4 = mul nsw i32 2, %3	;%4存储二倍的%3
ret i32 %4		;返回%4

;}
}

;int main(){
define dso_local i32 @main() #0 {

;return callee(110);
%1 = call i32 @callee(i32 110)	;%1存储callee()返回值	为什么不能%0???
ret i32 %1			;返回%1

;}
}

if.ll

;int main(){
define dso_local i32 @main() #0 {

;float a = 5.555;
%1 = alloca i32	;开辟空间
%2 = alloca float	;开辟float空间
store float 0x40163851E0000000, float* %2	;%2赋值为5.555

;if(a > 1)
%3 = load float, float* %2	;%2的数据加载到%3
%4 = fcmp ogt float %3, 1.000000e+00	;%31比较
br i1 %4, label %5, label %6	;4是则跳转到54否则跳转到6

;return 233;
5:
store i32 233, i32* %1	;%1赋值为233
br label %7		;跳转到7

;return 0;
6:
store i32 0, i32* %1		;%1赋值为0
br label %7		;跳转到7

7:
%8 = load i32, i32* %1	;%1的数据加载到%8
ret i32 %8		;返回%8

;}
}

while.ll

;int main(){
define dso_local i32 @main() #0 {

;int a;
%1 = alloca i32	;开辟空间

;int i;
%2 = alloca i32	;开辟空间

;a = 10;
store i32 10, i32* %1	;%1赋值为10

;i = 0;
store i32 0, i32* %2		;%2赋值为0
br label %3		;跳转到3

;while(i < 10){
3:
%4 = load i32, i32* %2	;%2的值加载到%4
%5 = icmp slt i32 %4, 10	;比较%410的大小
br i1 %5, label %6, label %11	;5是则跳转到65否则跳转到11

;i = i + 1;
6:
%7 = add nsw i32 %4, 1	;%7保存加一后的值
store i32 %7, i32* %2	;存储回%2

;a = a + i;
%8 = load i32, i32* %1	;a的数据加载到%8
%9 = load i32, i32* %2	;i的数据加载到%9
%10 = add nsw i32 %8, %9	;%10保存相加的数据
store i32 %10, i32* %1	;存储回%1
br label %3		;跳转到3

;}

;return a;
11:
%12 = load i32, i32* %1	;a的数据加载到%12
ret i32 %12		;返回%12

;}
}

assign_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG	// 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;	// 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module)	// 得到常数值的表示,方便后面多次用到

int main()
{
	auto module = new Module("Cminus code");  // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	Type *Int32Type = Type::get_int32_type(module);
	auto mainFunTy = FunctionType::get(Int32Type, {});                  	// 通过返回值类型与参数类型列表得到函数类型
	auto mainFun = Function::create(mainFunTy, "main", module);             // 通过函数类型得到函数
	auto bb = BasicBlock::create(module, "entry", mainFun);
	builder->set_insert_point(bb);                                      	// 将当前插入指令点的位置设在bb


	
	//int a[10];
	auto *arrayType = ArrayType::get(Int32Type, 10);                    	// 在内存中为数组分配空间,参数表示数组的元素类型和元素个数
	auto aAlloca = builder->create_alloca(arrayType);
	//a[0]=10;
	auto a0GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(0)});// 获取a[0]地址
	builder->create_store(CONST_INT(10), a0GEP);                        	// a[0]=10
	a0GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(0)});     // 更新a[0]
	//a[1] = a[0] * 2;
	auto a0Load = builder->create_load(a0GEP);                          	// 加载a[0]
	auto a0mul2 = builder->create_imul(a0Load, CONST_INT(2));           	// a[0]*2
	auto a1GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(1)});// 获取a[1]地址
	builder->create_store(a0mul2, a1GEP);                               	// 将a[0]*2存入a[1]
	a1GEP = builder->create_gep(aAlloca, {CONST_INT(0), CONST_INT(1)});     // 更新a[1]
	//return a[1];
	auto a1Load = builder->create_load(a1GEP);                          	// 加载a[1]
	builder->create_ret(a1Load);                                        	// 返回a[1]
	
	
	std::cout << module->print();
	delete module;
	return 0;
}

fun_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG	// 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;	// 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module)	// 得到常数值的表示,方便后面多次用到

int main()
{
	auto module = new Module("Cminus code");  // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	Type *Int32Type = Type::get_int32_type(module);

	//callee函数
	std::vector<Type *> Ints(1, Int32Type);	// 函数参数类型的vector
	auto calleeFunTy = FunctionType::get(Int32Type, Ints);	// 通过返回值类型与参数类型列表得到函数类型
	auto calleeFun = Function::create(calleeFunTy, "callee", module);	// 通过函数类型得到函数
	auto bb = BasicBlock::create(module, "entry", calleeFun);
	builder->set_insert_point(bb);	// 将当前插入指令点的位置设在bb

	auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置
	auto aAlloca = builder->create_alloca(Int32Type);     // 在内存中分配参数a的位置

	std::vector<Value *> args;  // 获取callee函数的形参,通过Function中的iterator
	for (auto arg = calleeFun->arg_begin(); arg != calleeFun->arg_end(); arg++)
	{
		args.push_back(*arg);   // * 号运算符是从迭代器中取出迭代器当前指向的元素
	}

	builder->create_store(args[0], aAlloca);  // 将参数a store下来
	auto aLoad = builder->create_load(aAlloca);	// 将参数a load上来
	auto mul = builder->create_imul(CONST_INT(2), aLoad);  //2*a

	builder->create_store(mul, retAlloca);
	auto retLoad = builder->create_load(retAlloca);
	builder->create_ret(retLoad);

	//main函数
	auto mainFunTy = FunctionType::get(Int32Type, {});	// 通过返回值类型与参数类型列表得到函数类型
	auto mainFun = Function::create(mainFunTy, "main", module);	// 通过函数类型得到函数
	bb = BasicBlock::create(module, "entry", mainFun);
	builder->set_insert_point(bb);	// 将当前插入指令点的位置设在bb
	
	retAlloca = builder->create_alloca(Int32Type);

	auto call = builder->create_call(calleeFun, {CONST_INT(110)});
	builder->create_ret(call);
	
	std::cout << module->print();
	delete module;
	return 0;
}

if_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG	// 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;	// 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module)	// 得到常数值的表示,方便后面多次用到

int main()
{
	auto module = new Module("Cminus code");  // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	Type *Int32Type = Type::get_int32_type(module);
	Type *FloatType = Type::get_float_type(module);
	
	//main函数
	auto mainFunTy = FunctionType::get(Int32Type, {});	// 通过返回值类型与参数类型列表得到函数类型
	auto mainFun = Function::create(mainFunTy, "main", module);	// 通过函数类型得到函数
	auto bb = BasicBlock::create(module, "entry", mainFun);
	builder->set_insert_point(bb);	// 将当前插入指令点的位置设在bb
	
	auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置

	//float a =5.555;
	auto aAlloca = builder->create_alloca(FloatType);     // 在内存中分配浮点数a的位置
	builder->create_store(CONST_FP(5.555), aAlloca);
	auto aLoad = builder->create_load(aAlloca);	//load上来
	

	//if(a>1)
	auto fcmp = builder->create_fcmp_gt(aLoad, CONST_FP(1));		//比较大小
	auto trueBB = BasicBlock::create(module, "trueBB", mainFun);	// true分支
	auto falseBB = BasicBlock::create(module, "falseBB", mainFun);	// false分支
	auto retBB = BasicBlock::create(module, "", mainFun);	// return分支,提前create,以便true分支可以br

	auto br = builder->create_cond_br(fcmp, trueBB, falseBB);  // 条件BR
	DEBUG_OUTPUT // 我调试的时候故意留下来的,以醒目地提醒你这个调试用的宏定义方法
	
	//return 233;
	builder->set_insert_point(trueBB);  // if true; 分支的开始需要SetInsertPoint设置
	builder->create_store(CONST_INT(233), retAlloca);
	builder->create_br(retBB);  // br retBB

	//return 0;
	builder->set_insert_point(falseBB);  // if false
	builder->create_store(CONST_INT(0), retAlloca);
	builder->create_br(retBB);  // br retBB

	builder->set_insert_point(retBB);  // ret分支
	auto retLoad = builder->create_load(retAlloca);
	builder->create_ret(retLoad);
	
	std::cout << module->print();
	delete module;
	return 0;
}

while_generator.cpp

#include "BasicBlock.h"
#include "Constant.h"
#include "Function.h"
#include "IRBuilder.h"
#include "Module.h"
#include "Type.h"

#include <iostream>
#include <memory>

#ifdef DEBUG	// 用于调试信息,大家可以在编译过程中通过" -DDEBUG"来开启这一选项
#define DEBUG_OUTPUT std::cout << __LINE__ << std::endl;	// 输出行号的简单示例
#else
#define DEBUG_OUTPUT
#endif

#define CONST_INT(num) \
    ConstantInt::get(num, module)

#define CONST_FP(num) \
    ConstantFP::get(num, module)	// 得到常数值的表示,方便后面多次用到

int main()
{
	auto module = new Module("Cminus code");  // module name是什么无关紧要
	auto builder = new IRBuilder(nullptr, module);
	Type *Int32Type = Type::get_int32_type(module);
	
	//main函数
	auto mainFunTy = FunctionType::get(Int32Type, {});	// 通过返回值类型与参数类型列表得到函数类型
	auto mainFun = Function::create(mainFunTy, "main", module);	// 通过函数类型得到函数
	auto bb = BasicBlock::create(module, "entry", mainFun);
	builder->set_insert_point(bb);	// 将当前插入指令点的位置设在bb
	
	auto retAlloca = builder->create_alloca(Int32Type);   // 在内存中分配返回值的位置

	//int a;int i;
	auto aAlloca = builder->create_alloca(Int32Type);       // 在内存中分配a的位置
	auto iAlloca = builder->create_alloca(Int32Type);       // 在内存中分配i的位置

	//a=10;i=0;
	builder->create_store(CONST_INT(10), aAlloca);
	
	builder->create_store(CONST_INT(0), iAlloca);
	

	//while(i<10)	//if(i<10)
	auto whileBB = BasicBlock::create(module, "whileBB", mainFun);	//while分支
	builder->create_br(whileBB);
	builder->set_insert_point(whileBB); 
	//auto aLoad = builder->create_load(aAlloca);	//a load上来
	auto iLoad = builder->create_load(iAlloca);	//i load上来

	auto icmp = builder->create_icmp_lt(iLoad, CONST_INT(10));	//比较大小
	auto trueBB = BasicBlock::create(module, "trueBB", mainFun);	// true分支
	auto falseBB = BasicBlock::create(module, "falseBB", mainFun);	// false分支

	auto br = builder->create_cond_br(icmp, trueBB, falseBB);  // 条件BR
	DEBUG_OUTPUT // 我调试的时候故意留下来的,以醒目地提醒你这个调试用的宏定义方法

	//while内部
	builder->set_insert_point(trueBB);  // if true; 分支的开始需要SetInsertPoint设置
	auto addi = builder->create_iadd(iLoad, CONST_INT(1));	//i+1
	builder->create_store(addi, iAlloca);
	iLoad = builder->create_load(iAlloca);	//i load上来
	auto adda = builder->create_iadd(iLoad, aLoad);		//a+1
	builder->create_store(adda, aAlloca);
	aLoad = builder->create_load(aAlloca);	//a load上来
	
	builder->create_br(whileBB);  // br whileBB

	builder->set_insert_point(falseBB);  // if false
	builder->create_store(aLoad, retAlloca);
	auto retLoad = builder->create_load(retAlloca);
	builder->create_ret(retLoad);
	
	std::cout << module->print();
	delete module;
	return 0;
}

思路之类的

我记得我代码的注释写的挺清楚的,就看注释吧

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lab1-Lab4分别完成了词法分析,语法分析;语义分析;中间代码生成和目标代码生成. Lab1实验报告 词法分析: 能够查出C--源代码中词法未定义的字符以及任何不符合词法单元定义的字符; 识别合法的八进制,如035、072; 识别合法的十六进制数,如0x23、0X4a; 识别合法的指数形式的浮点数,如1.2、.2、1.2e+4; 语法分析: 能够查出C--源代码中的语法错误; 没有词法和语错误的情况,则打印语法树 Lab2实验报告 语义分析: 可对输入的*.cmm文件进行语义分析,并检查如下类型的错误: 错误类型1:变量在使用时未定义。 错误类型2:函数在调用时未经定义。 错误类型3:变量出现重复定义,或变量与前面定义过的结构体名字重复。 错误类型4:函数出现重复定义。 错误类型5:赋值号两边的表达式类型不匹配。 错误类型6:赋值号左边出现一个只有右值的表达式。 错误类型7:操作数类型不匹配或操作数类型与操作符不匹配。 错误类型8:return语句的返回类型与函数定义的返回类型不匹配。 错误类型9:函数调用时实参与形参的数目或类型不匹配。 错误类型10:对非数组型变量使用“[…]”(数组访问)操作符。 错误类型11:对普通变量使用“(…)”或“()”(函数调用)操作符。 错误类型12:数组访问操作符“[…]”中出现非整数。 错误类型13:对非结构体型变量使用“.”操作符。 错误类型14:访问结构体中未定义过的域。 错误类型15:结构体中域名重复定义(同一结构体中),或在定义时对域进行初始化。 错误类型16:结构体的名字与前面定义过的结构体或变量的名字重复。 错误类型17:直接使用未定义过的结构体来定义变量。 Lab3实验报告 中间代码生成: 在词法分析、语法分析和语义分析的基础上,可对输入的*.cmm文件进行中间代码生成。但不支持结构体类型的变量,不支持高维数组的变量以及不支持一维数组作为函数参数出现。 Lab4实验报告 目标代码生成: 在词法分析、语法分析、语义分析和中间代码生成程序的基础上,将C--源代码翻译为MIPS32指令序列(可以包含伪指令),并在SPIM Simulator上运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值