编译原理实验三

news2024/11/22 16:32:29

编译原理实验三

问题1: cpp与.ll的对应

请描述你的cpp代码片段和.ll的每个BasicBlock的对应关系。描述中请附上两者代码。

assign

对应的.ll代码如下:

define i32 @main() #0 {
    %1 = alloca [10 x i32]    ;int a[10]
    %2 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i32 0, i32 0        ;a[0] addr    
    %3 = getelementptr inbounds [10 x i32], [10 x i32]* %1, i32 0, i32 1        ;a[1] addr
    store i32 10, i32* %2     ;a[0] = 10
    %4 = load i32, i32* %2    ;%4 = a[0]
    %5 = mul i32 %4,2         ;%5 = a[0]*2
    store i32 %5, i32* %3     ;a[1] = %5 = a[0]*2
    %6 = load i32, i32* %3    ;%6 = a[1]
    ret i32 %6                ;return a[1]
}

这一段程序仅有一个函数,一个BasicBlock。仿照gcd_array.cpp的写法,创建Module和BasicBlock,将.ll中的指令使用IRBuilder插入到BasicBlock的指令链表当中。与.ll中指令的对应关系在以下的cpp代码片段的注释中说明。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("Assign");                 
    auto builder = new IRBuilder(nullptr, module);                      //使用IRBuilder创建指令
    //创建函数与BasicBlock
    Type *Int32Type = Type::get_int32_type(module);
    auto mainTy = FunctionType::get(Int32Type, {});                     //返回值为i32,参数为空
    auto main = Function::create(mainTy, "main", module);            
    auto BB = BasicBlock::create(module, "BasicBlock1" , main);
    builder->set_insert_point(BB);                                      //设置插入指令的BasicBlock
    //插入.ll中对应的指令
    auto *arrayType = ArrayType::get(Int32Type, 10);                    //数组的类型为[10 x i32]
    auto a = builder->create_alloca(arrayType);                         //分配数组
    auto a0P = builder->create_gep(a, {CONST_INT(0), CONST_INT(0)});    //计算a[0]地址
    auto a1P = builder->create_gep(a, {CONST_INT(0), CONST_INT(0)});    //计算a[1]地址
    builder->create_store({CONST_INT(10)}, a0P);                        //a[0] = 10
    auto a0 = builder->create_load(a0P);                                //取出a[0]
    auto temp = builder->create_imul(a0, {CONST_INT(2)});               //a[0]*2
    builder->create_store(temp, a1P);                                   //a1 = a[0]*2
    auto a1 = builder->create_load(a1P);       
    builder->create_ret(a1);                                            //return a1 = a[0]*2
    //指令创建结束
    std::cout << module->print();
    delete module;
    return 0;
}

make之后运行程序进行验证,生成的指令和.ll中的指令相同。

在这里插入图片描述

fun

对应的.ll代码如下:

define i32 @callee(i32 %0) #0 {
    %2 = mul i32 %0, 2               ;%2 = a*2
    ret i32 %2
}
define i32 @main() #0 {
    %1 = call i32 @callee(i32 110)   ;%1 = callee(110)
    ret i32 %1                       ;return
}

共有两个函数,相应有两个BasicBlock,分别插入指令。与.ll中指令的对应关系在注释中说明,相应的cpp代码如下:

//头文件和宏定义省略
int main(){
    auto module = new Module("fun");
    auto builder = new IRBuilder(nullptr, module);
    //创建callee函数
    Type *Int32Type = Type::get_int32_type(module);
    auto calleeTy = FunctionType::get(Int32Type, {Int32Type});            //返回值为i32,一个i32类型的参数
    auto callee = Function::create(calleeTy, "callee", module); 
    auto BB = BasicBlock::create(module, "callee_BasicBlock" , callee);
    builder->set_insert_point(BB);
    //插入callee的BasicBlock中的指令
    std::vector<Value *> args;                                            //获取函数的形参,通过Function中的iterator
    for (auto arg = callee->arg_begin(); arg != callee->arg_end(); arg++) {
        args.push_back(*arg);                                             //* 号运算符是从迭代器中取出迭代器当前指向的元素
    }
    auto mul = builder->create_imul(args[0], CONST_INT(2));             //mul = a*2
    builder->create_ret(mul);
    //创建main函数
    auto mainTy = FunctionType::get(Int32Type, {});
    auto main = Function::create(mainTy, "main", module); 
    auto BB1 = BasicBlock::create(module, "main_BasicBlock" , main);
    builder->set_insert_point(BB1);
    //插入main的BasicBlock中的指令
    auto callret = builder->create_call(callee, {CONST_INT(110)});       //callret = callee(110)
    builder->create_ret(callret);                                        //return callret
    //指令创建结束
    std::cout << module->print();
    delete module;
    return 0;
}

运行程序,生成的指令和.ll中的指令相同。

在这里插入图片描述

if

对应的.ll代码如下:

define i32 @main() #0 {
    %1 = alloca float                    ;%1 = float a addr
    store float 0x40163851E0000000, float* %1
    %2 = load float, float* %1           ;%2 = a
    %3 = fcmp ugt float %2, 1.0          ;a>1   
    br i1 %3, label %4, label %5         ;if(a>1) goto 4 else goto 5
4:
    ret i32 233
5:
    ret i32 0
}

本段指令有3个BasicBlock,相比与前两个多了fcmp和br跳转指令。相应的.cpp文件如下:

//头文件和宏定义省略
int main(){
    auto module = new Module("if");                 
    auto builder = new IRBuilder(nullptr, module);                      //使用IRBuilder创建指令
    //创建main函数
    Type *Int32Type = Type::get_int32_type(module);
    auto mainTy = FunctionType::get(Int32Type, {});                     //返回值为i32,参数为空
    auto main = Function::create(mainTy, "main", module);
    auto BBEntry = BasicBlock::create(module, "entry" , main);
    builder->set_insert_point(BBEntry); 
    //entryBasicBlock插入指令,对应.ll的BasicBlock0
    Type *FloatType = Type::get_float_type(module);                     //浮点类型
    auto aP = builder->create_alloca(FloatType);                        //为float a分配空间并返回指针
    builder->create_store(CONST_FP(5.555),aP);                          //a = 5.555
    auto a = builder->create_load(aP);                                  //取出a
    auto fcmp = builder->create_fcmp_gt(a, CONST_FP(1.0));              //fcmp = if a>1
    //创建true, false对应的BasicBlock
    auto BBTrue = BasicBlock::create(module, "true" , main);
    auto BBFalse = BasicBlock::create(module, "false", main);
    builder->create_cond_br(fcmp, BBTrue, BBFalse);                     //br跳转指令
    //true和false对应的BasicBlock插入指令,对应.ll的BasicBlock4,5
    builder->set_insert_point(BBTrue);
    builder->create_ret(CONST_INT(233));
    builder->set_insert_point(BBFalse);
    builder->create_ret(CONST_INT(0));
    //指令创建结束
    std::cout << module->print();
    delete module;
    return 0;
}

运行程序验证:

在这里插入图片描述

while

对应的.ll代码如下:

define i32 @main() #0 {
    %1 = alloca i32        ;%1 = a addr
    %2 = alloca i32        ;%2 = i addr
    store i32 0, i32* %2   ;i = 0
    store i32 10, i32* %1  ;a = 10
    br label %3             

;if i<10
3:
    %4 = load i32, i32* %2        ;%4 = i
    %5 = icmp slt i32 %4, 10      ;i < 10?
    br i1 %5, label %6, label %10
    
6:
    %7 = add nsw i32 %4, 1        ;i + 1
    store i32 %7, i32* %2         ;i = i + 1
    %8 = load i32, i32* %1        ;%8 = a
    %9 = add nsw i32 %7, %8       ;%9 = a + i
    store i32 %9, i32* %1         ;a = a + i
    br label %3
    
10:
    ret i32 %9                    ;return a
}

以上的.ll程序共有四个BasicBlock,逐个添加指令即可。.cpp中的BasicBlock与.ll的对应在注释中说明。.cpp的代码如下:

int main(){
    auto module = new Module("while");                 
    auto builder = new IRBuilder(nullptr, module);                      //使用IRBuilder创建指令
    //创建main函数和4个BasicBlock
    Type *Int32Type = Type::get_int32_type(module);
    auto mainTy = FunctionType::get(Int32Type, {});                     //返回值为i32,参数为空
    auto main = Function::create(mainTy, "main", module);
    auto BBEntry = BasicBlock::create(module, "entry" , main);
    auto BBWhile = BasicBlock::create(module, "while" , main);
    auto BBTrue = BasicBlock::create(module, "true" , main);
    auto BBFalse = BasicBlock::create(module, "false" , main);
    //entryBasicBlock插入指令,即.ll的BasicBlock0
    builder->set_insert_point(BBEntry);
    auto aP = builder->create_alloca(Int32Type);                        //分配a的空间,返回指针aP
    auto iP = builder->create_alloca(Int32Type);                        //分配i的空间,返回指针iP
    builder->create_store(CONST_INT(0), iP);                            //i = 0
    builder->create_store(CONST_INT(10), aP);                           //a = 10
    builder->create_br(BBWhile);                                        //br跳转到while循环的判断BasicBlock
    //whileBasicBlock插入指令,即.ll的BasicBlock3
    builder->set_insert_point(BBWhile);
    auto temp = builder->create_load(iP);
    auto icmp = builder->create_icmp_lt(temp, CONST_INT(10));
    builder->create_cond_br(icmp, BBTrue, BBFalse);                     //br跳转到True或False的BasicBlock
    //TrueBasicBlock插入指令,即.ll的BasicBlock6
    builder->set_insert_point(BBTrue);
    auto newi = builder->create_iadd(temp, CONST_INT(1));               //i + 1
    builder->create_store(newi, iP);                                    //i = i + 1
    auto a = builder->create_load(aP);
    auto newa = builder->create_iadd(newi, a);                          //a + i
    builder->create_store(newa, aP);                                    //a = a + i
    builder->create_br(BBWhile);                                        //br跳转到while循环的判断BasicBlock
    //FalseBasicBlock插入指令,即.ll的BasicBlock10
    builder->set_insert_point(BBFalse);
    builder->create_ret(newa);                                          //return a
    //指令创建结束
    std::cout << module->print();
    delete module;
    return 0;
}

运行程序验证:

在这里插入图片描述

以上的.ll代码和.cpp编译运行后的.ll代码运行后均能产生正确的结果,与原.c程序的逻辑相同.

问题2: Visitor Pattern

请指出visitor.cpp中,treeVisitor.visit(exprRoot)执行时,以下几个Node的遍历序列:numberA、numberB、exprC、exprD、exprE、numberF、exprRoot。
序列请按如下格式指明:
exprRoot->numberF->exprE->numberA->exprD

根据visitor.cpp中的内容,每个节点会返回自身的引用,visitor会进行访问该节点,计算该节点的值,计算的方式是访问其左右节点并获取值,然后根据节点的操作类型完成值的计算,然后返回。子节点的值的获取也是相同的,直到访问到数值节点,就直接返回相应的数值。可以看出访问的过程是自顶向下递归访问的过程。main函数中创建的计算树如下:

在这里插入图片描述

根据visit()函数中的实现,对于AddSubNode的访问,是先访问右子节点,然后访问左子节点,对于MulDivNode的访问,先访问左子节点,后访问右子节点。因此可以得出visitor.cpp中访问该树的序列为:

exprRoot->numberF->exprE->exprD->numberB->numberA->exprC->numberA->numberB

问题3: getelementptr

请给出IR.md中提到的两种getelementptr用法的区别,并稍加解释:

  • %2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0
  • %2 = getelementptr i32, i32* %1 i32 %0

第一种用法:

指针类型为[10 x i32]*,指向的数据类型为[10 x i32],因此首先用i32 0表示偏移为0,这表示直接取第一个[10 x i32]数组,然后的i32 %0表示在第一个[10 x i32]数组内,偏移%0的元素地址。

第二种用法:

指针类型为i32*,%1表示的是数组的起始地址,偏移量为%0,直接取出了数组偏移%0位置的元素地址。

区别:

在第一种用法中,指针类似于指针数组,首先确定在这个指针数组上的偏移,才能得到一个数组的指针,然后通过偏移找到元素的地址。而在第二种用法中,直接对数组的指针进行偏移,找到元素的地址。

当定义全局数组或结构体时,定义的是指针,例如在给出的gcd_array.c中声明的全局数组x,y,就是[1 x i32]*类型,因此取出元素时使用的是第一种用法。

//全局数组x[1]
@x = common dso_local global [1 x i32] zeroinitializer, align 4
//取出元素地址
getelementptr inbounds ([1 x i32], [1 x i32]* @x, i64 0, i64 0)

实验难点

1.编写.ll

在编写if.c对应的.ll时,提示error: floating point constant invalid for type,不能用浮点常量给浮点数赋值。经查询,这是因为浮点数5.55不能用浮点数精确表示,因此不能直接给浮点数赋值。如果是4.0等可以精确表示的数,就可以直接赋值。此处只能使用十六进制的浮点数机器表示进行赋值,为0x40163851E0000000。

编写while.c对应的.ll时注意到,编号顺序必须是连续的,除了if对应的块跳转语句可以出现不连续的编号,其他编号都必须是连续的。因此br语句中的跳转编号应该最后填写。

编写.ll后,与.c源文件产生的.ll文件进行了比较,clang产生的.ll文件中的指令更多,对于值进行了更多的存储和取出的操作,但是逻辑上是和自行编写的.ll指令是相同的。自行编写时,临时变量直接使用寄存器存储,还有一些寄存器值也重复使用,相当于对直接产生的.ll指令优化后的结果。

2.编写cpp

编写cpp时,由于对接口不熟悉,创建对应的指令有些困难,在经过阅读gcd_array.cpp中创建指令的部分和整体结构,仿照并逐个指令完成assign.cpp后,后三个cpp代码的编写才比较顺利。

创建比较指令时,指令名使用了lightIR中的ugt来表示小于,编译时提示不存在这条指令,从lightIR.h中找到,比较的指令使用的是下面的表示:

public:
    enum CmpOp
    {
        EQ, // ==
        NE, // !=
        GT, // >
        GE, // >=
        LT, // <
        LE  // <=
    };

因此修改相应的比较指令为以下形式:

auto fcmp = builder->create_fcmp_gt(a, CONST_FP(1.0)); 
auto icmp = builder->create_icmp_lt(temp, CONST_INT(10));

实验反馈

通过本次实验学习了llvm,LightIR相关知识。对于LightIR的接口,文档内容没有特别清晰,通过结合实例和.h文件中的内容才逐渐熟悉。编写.ll文件时,注意到了与clang编译代码的不同,自行编写可以在许多位置直接进行优化,而编译器可能第一步只能生成未优化的代码。对于自行编写的.ll指令,使用C++生成的过程也相对较为简洁。最后通过visitor.cpp了解了访问者模式,对于这种模式比较陌生,但是通过实例理解了其工作原理和访问过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/91315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用“博弈论”看什么是高质量的代币设计?

在每个领域&#xff0c;都有国王&#xff0c;皇后&#xff0c;小兵和其他玩家。它们决定了该行业赖以生存的质量和标准。在同一领域&#xff0c;必然有赢家和输家。对于加密货币和代币经济来说&#xff0c;情况也是如此。本文的重点是代币经济的博弈论。它涉及游戏本身、谁在玩…

【大数据技术Hadoop+Spark】HDFS概念、架构、原理、优缺点讲解(超详细必看)

一、相关基本概念 文件系统。文件系统是操作系统提供的用于解决“如何在磁盘上组织文件”的一系列方法和数据结构。 分布式文件系统。分布式文件系统是指利用多台计算机协同作用解决单台计算机所不能解决的存储问题的文件系统。如单机负载高、数据不安全等问题。 HDFS。英文…

freeswitch的distributor模块

概述 freeswitch 是一款简单好用的VOIP开源软交换平台。 当呼叫是同一个入中继&#xff0c;但是有多条出中继时&#xff0c;需要对出中继做负载均衡&#xff0c;mod_distributor模块可以完成对应的配置和路由。 mod_distributor是一个轻量级的线路分发模块&#xff0c;配置简…

【Redis技术探索】「底层架构原理」探索分析服务核心数据结构介绍和案例

Redis常用存储类型 Redis底层提供了5种数据结构&#xff1a;字符串、哈希、列表、集合、有序集合 下图非常形象的表示了数据结构&#xff1a; 字符串String 常用命令 EX seconds&#xff1a;设置失效时长&#xff0c;单位秒PX milliseconds&#xff1a;设置失效时长&#x…

过滤器工厂详解

内置过滤器 1 AddRequestHeader GatewayFilter Factory 添加请求头 2 AddRequestParameter GatewayFilter Factory 3 AddResponseHeader GatewayFilter Factory 4 DedupeResponseHeader GatewayFilter Factory 5 Hystrix GatewayFilter Factory 6 FallbackHeaders GatewayFil…

答对这 9 题你就超越了 83.3% 的图数据库 NebulaGraph 用户

熟悉 NebulaGraph 社区的小伙伴可能都知道一个技能认证叫做&#xff1a;NGCP&#xff0c;全称 NebulaGraph Certified Professional。用户在考试认证期间在 1 个小时内回答 100 道题目&#xff0c;并获得 60 分&#xff0c;便是 NebulaGraph 认证过的 NGCP 用户。NGCP 用户除了…

二、Node.js 模块基础 1.0

模块、模块化 在讲Node.js当中的模块之前先来简单了解什么是模块、模块化、以及模块化编程的演变过程&#xff1b;模块通常指的是编程语言提供的代码组织机制&#xff0c;利用此机制可将程序拆解位独立且通用的代码单元&#xff0c;表意文绉绉&#xff0c;你可以理解为能够组装…

Docker+Jenkins+Gitlab+SpringBoot 自动化部署项目

我这边Docker、Jenkins、Gitlab 都已准备完毕&#xff0c;Jenkins和GItlab 都是用Docker起的 我们先进入Jenkins&#xff0c;插件什么的按他推荐的装就可以了&#xff0c;另外使用gitlab,还需要额外安装下面的插件 然后我们开始在Jenkins上创建项目 然后点保存&#xff0c;接下…

行业说 | 建筑业面临失宠,越来越留不住年轻人?原因在这

大家好&#xff0c;这里是建模助手。 不知道大家有没有发现&#xff0c;现在建筑行业存在着一种现象&#xff0c;就是&#xff1a;年轻人建筑行业“不想去”&“留不住”。 在这种情况下&#xff0c;行业的老龄化趋势便愈发明显&#xff0c;据数据显示&#xff1a; 2021年…

跬智信息(Kyligence)荣登「甲子20」中国数据智能领域最具商业潜力科技企业榜

近日&#xff0c;为表彰中国科技产业与数字经济领域的杰出贡献者&#xff0c;中国科技产业智库甲子光年在 2022「甲子引力」年终盛典上公布多项榜单。凭借在数据智能领域的核心技术优势与高成长性的商业价值表现&#xff0c;跬智信息&#xff08;Kyligence&#xff09;最终荣登…

【C++11】三大神器之——包装器和绑定器

前言 如果你还不知道 C 11 引入的包装器和绑定器是什么&#xff0c;可以读读这篇文章&#xff0c;看看有什么 启发&#xff1b;如果你已经对包装器和绑定器了如指掌&#xff0c;也可以读读这篇文章&#xff0c;看看有什么 补充。欢迎交流~&#x1f60f; 可调用对象 C中存在【…

PCB叠层当中的“假八层”是什么意思呢?

大家在进行PCB设计的时候都是需要对我们的板子选择叠层方案的&#xff0c;一个好的层叠方案能使我们的信号质量变好&#xff0c;板子性能也会更稳定等等&#xff0c;大家可能或多或少的接触过多层板&#xff0c;也就是两层往上的板子&#xff0c;那么大家在做六层板的时候是否有…

Servlet:狂神Response源码分析【文件下载 + 动态图形验证码 + 重定向】

目录web.xmlindex.jspRequestTestlogin测试FileServlet文件下载测试动态图形验证码Servlet动态图形验证码测试总结web.xml <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi&quo…

15-16-17 - 保护模式中的特权级

---- 整理自狄泰软件唐佐林老师课程 文章目录1. 保护模式小结1.1 使用选择子访问段描述符表时&#xff0c;索引值的合法性检测1.2 内存段类型合法性检测1.3 实例分析2. 问题一3. 保护模式中的特权级3.1 特权级的表现形式3.2 初探特权级3.2.1 CPL和DPL的关系3.2.2 段描述符中的D…

手把手教你Spring Cloud Alibaba教程:使用nacos实现服务注册与发现

我们在上一篇&#xff1a;手把手教你Spring Cloud Alibaba教程:nacos安装 接下来我们来实现下基本的服务注册和发现 版本确认 我们需要确认spring Cloud Alibaba version对应的nacso version 主要如下 版本说明 alibaba/spring-cloud-alibaba Wiki Spring Cloud Alibaba Ve…

图解:基于HyperWorks螺纹升角的六面体划分攻略

导读&#xff1a;在实际工程应用中&#xff0c;基于HyperWorks六面体工程命令&#xff0c;对工程师朋友来说比较繁琐&#xff0c;甚至不容易掌握。于是经常参加一些线下培训&#xff0c;用以提升自己业务技能。今天&#xff0c;笔者从一个企业资深培训讲师的角度&#xff0c;结…

Docker安装Oracle

Docker安装Oracle 本次使用的系统是centOS7 &#xff08;文章部分参考&#xff1a;地址&#xff09; 安装Docker docker安装要求&#xff1a;CentOS内核版本高于3.10&#xff0c;可以通过命令 uname -r查看当前内核版本 下载工具 yum install -y yum-utils设置阿里云镜像 …

【MySQL】使用C语言连接数据库

文章目录下载Mysql的C接口库程序中引入Mysql头文件和库文件**Mysql接口介绍**创建句柄链接数据库设置字符编码执行SQL语句关闭链接mysql.ops.cc下载Mysql的C接口库 要使用C语言连接mysql,需要使用mysql官网提供的库,大家可以官网下载,实际上连接数据库的功能在mysql 8.0版本之…

(十五)Vue之过滤器

文章目录计算属性实现methods实现过滤器实现局部过滤器不传参传参多个过滤器使用全局过滤器Vue学习目录 上一篇&#xff1a;&#xff08;十四&#xff09;Vue之收集表单数据 先看一个需求&#xff1a;给一个时间戳&#xff0c;然后把时间戳格式化显示出来 时间戳数据&#xf…

哈希表题目:相交链表

文章目录题目标题和出处难度题目描述要求示例数据范围进阶解法一思路和算法代码复杂度分析解法二思路和算法证明代码复杂度分析题目 标题和出处 标题&#xff1a;相交链表 出处&#xff1a;160. 相交链表 难度 2 级 题目描述 要求 给你两个单链表的头结点 headA\texttt…