# SysYF IR - [SysYF IR](#sysyf-ir) - [IR](#ir) - [IR Features](#ir-features) - [IR Format](#ir-format) - [Instruction](#instruction) - [Terminator Instructions](#terminator-instructions) - [Ret](#ret) - [Br](#br) - [Standard binary operators](#standard-binary-operators) - [Add FAdd](#add-fadd) - [Sub FSub](#sub-fsub) - [Mul FMul](#mul-fmul) - [SDiv FDiv](#sdiv-fdiv) - [SRem](#srem) - [Memory operators](#memory-operators) - [Alloca](#alloca) - [Load](#load) - [Store](#store) - [CastInst](#castinst) - [ZExt](#zext) - [FpToSi](#fptosi) - [SiToFp](#sitofp) - [Other operators](#other-operators) - [ICmp FCmp](#icmp-fcmp) - [Call](#call) - [GetElementPtr](#getelementptr) - [C++ APIs](#c-apis) - [核心类概念图](#核心类概念图) - [BasicBlock](#basicblock) - [Constant](#constant) - [Function](#function) - [GlobalVariable](#globalvariable) - [IRStmtBuilder](#irstmtbuilder) - [Instruction](#instruction-1) - [Module](#module) - [Type](#type) - [User](#user) - [Value](#value) - [总结](#总结) ## IR ### IR Features - 采用类型化三地址代码的方式 - 区别于 X86 汇编的目标和源寄存器共用的模式: ADD EAX, EBX - %2 = add i32 %0, %1 - 静态单赋值 (SSA) 形式 + 无限寄存器 - 每个变量都只被赋值一次 - 容易确定操作间的依赖关系,便于优化分析 - 强类型系统 - 每个 Value 都具备自身的类型, - IR类型系统: - `i1`:1位宽的整数类型 - `i32`:32位宽的整数类型 - `float`:单精度浮点数类型 - `pointer`:指针类型 - 例如:`i32*, [10 x i32*]` - `label` bb的标识符类型 - `functiontype`函数类型,包括函数返回值类型与参数类型(下述文档未提及) ### IR Format 下面以`easy.c`与`easy.ll`为例进行说明。 通过命令`clang -S -emit-llvm easy.c`可以得到对应的`easy.ll`如下(其中增加了额外的注释)。`.ll`文件中注释以`;`开头。 - `easy.c`: ``` c int main(){ int a; int b; a = 1; b = 2; if(a < b) b = 3; return a + b; } ``` - `easy.ll`: ``` c ; 注释: .ll文件中注释以';'开头 ; ModuleID = 'easy.c' source_filename = "easy.c" ; 注释: target的开始 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" ; 注释: target的结束 ; 注释: 全局main函数的定义 ; Function Attrs: noinline nounwind optnone uwtable define dso_local i32 @main() #0 { ; 注释: 第一个基本块的开始 %1 = alloca i32, align 4 %2 = alloca i32, align 4 %3 = alloca i32, align 4 store i32 0, i32* %1, align 4 store i32 1, i32* %2, align 4 store i32 2, i32* %3, align 4 %4 = load i32, i32* %2, align 4 %5 = load i32, i32* %3, align 4 %6 = icmp slt i32 %4, %5 br i1 %6, label %7, label %8 ; 注释: 第一个基本块的结束 ; 注释: 第二个基本块的开始 7: ; preds = %0 store i32 3, i32* %3, align 4 br label %8 ; 注释: 第二个基本块的结束 ; 注释: 第三个基本块的开始 8: ; preds = %7, %0 %9 = load i32, i32* %2, align 4 %10 = load i32, i32* %3, align 4 %11 = add nsw i32 %9, %10 ret i32 %11 ; 注释: 返回语句 ; 注释: 第三个基本块的结束 } attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } !llvm.module.flags = !{!0} !llvm.ident = !{!1} !0 = !{i32 1, !"wchar_size", i32 4} !1 = !{!"clang version 10.0.1 "} ``` 每个program由1个或多个module组成,每个module对应1个程序文件,module之间由LLVM Linker进行链接形成1个可执行文件或者库。 每个module组成如下: - Target Information: ``` c target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" ``` - Global Symbols: main函数的定义 - Others:尾部其他信息 每个函数的组成如下: - 头部:函数返回值类型、函数名、函数参数 - 一个或多个基本块: - 每个基本块由Label和Instruction组成。 ``` c 8: ; preds = %7, %0 %9 = load i32, i32* %2, align 4 %10 = load i32, i32* %3, align 4 %11 = add nsw i32 %9, %10 ret i32 %11 ``` 这个例子中,`8`就是Label。 `%9 = load i32, i32* %2, align 4`中的`%9`是目的操作数,`load`是指令助记符,`i32`是`int32`的类型,`i32*`是指向`int32`的地址类型,`%2`是源操作数,`align 4`表示对齐。 ### Instruction #### Terminator Instructions **注**:ret与br都是Terminator Instructions也就是终止指令,在llvm基本块的定义里,基本块是单进单出的,因此只能有一条终止指令(ret或br)。当一个基本块有两条终止指令,clang 在做解析时会认为第一个终结指令是此基本块的结束,并会开启一个新的匿名的基本块(并占用了下一个编号)。 ##### Ret - 格式 - `ret ` - `ret void` - 例子: - `ret i32 %0` - `ret void` - 概念:` ret`指令用于将控制流(以及可选的值)从函数返回给调用者。`ret`指令有两种形式:一种返回值,然后终结函数,另一种仅终结函数。 ##### Br - 格式: - `br i1 , label , label ` - `br label ` - 例子: - `br i1 %cond label %truebb label %falsebb` - `br label %bb` - 概念:`br`指令用于使控制流转移到当前功能中的另一个基本块。 该指令有两种形式,分别对应于条件分支和无条件分支。 #### Standard binary operators ##### Add FAdd - 格式: - ` = add , ` - ` = fadd , ` - 例子: - `%2 = add i32 %1, %0` - `%2 = fadd float %1, %0` - 概念:`add`指令返回其两个`i32`类型的操作数之和,返回值为`i32`类型,`fadd`指令返回其两个`float`类型的操作数之和,返回值为`float`类型 ##### Sub FSub - 格式与例子与`add`,`fadd`类似 - 概念:`sub`指令返回其两个`i32`类型的操作数之差,返回值为`i32`类型,`fsub`指令返回其两个`float`类型的操作数之差,返回值为`float`类型 ##### Mul FMul - 格式与例子与`add`,`fadd`类似 - 概念:`mul`指令返回其两个`i32`类型的操作数之积,返回值为`i32`类型,`fmul`指令返回其两个`float`类型的操作数之积,返回值为`float`类型 ##### SDiv FDiv - 格式与例子与`add`,`fadd`类似 - 概念:`sdiv`指令返回其两个`i32`类型的操作数之商,返回值为`i32`类型,`fdiv`指令返回其两个`float`类型的操作数之商,返回值为`float`类型 ##### SRem - 格式与例子与`add`类似 - 概念:`srem`指令返回其两个`i32`类型的操作数之模,返回值为`i32`类型 #### Memory operators ##### Alloca - 格式:` = alloca ` - 例子: - `%ptr = alloca i32` - `%ptr = alloca [10 x i32]` - 概念: `alloca`指令在当前执行函数的栈帧上分配内存,当该函数返回其调用者时将自动释放该内存。 始终在地址空间中为数据布局中指示的分配资源分配对象 ##### Load - 格式:` = load , * ` - 例子:`%val = load i32, i32* %ptr` - 概念:`load`指令用于从内存中读取。 ##### Store - 格式:`store , * ` - 例子:`store i32 3, i32* %ptr` - 概念:`store`指令用于写入内存 #### CastInst ##### ZExt - 格式:` = zext to ` - 例子:`%1 = zext i1 %0 to i32` - 概念:`zext`指令将其操作数**零**扩展为`type2`类型。 ##### FpToSi - 概念:`fptosi`指令将浮点值转换为`type2`(整数)类型。 - 格式:` = fptosi to ` - 例子:`%Y = fptosi float 1.0E-247 to i32` ##### SiToFp - 格式:` = sitofp to ` - 例子:`%X = sitofp i32 257 to float` - 概念:`sitofp`指令将有符号整数转换为`type2`(浮点数)类型。 #### Other operators ##### ICmp FCmp - 格式: - ` = icmp , ` - ` = eq | ne | sgt | sge | slt | sle` - ` = fcmp , ` - ` = eq | ne | ugt | uge | ult | ule` - 例子:`i1 %2 = icmp sge i32 %0, %1` - 概念:`icmp`指令根据两个整数的比较返回布尔值,`fcmp`指令根据两个浮点数的比较返回布尔值。 ##### Call - 格式: - ` = call () ` - 例子: - `%0 = call i32 @func( i32 %1, i32* %0)` - `call @func( i32 %arg)` - 概念:`call`指令用于使控制流转移到指定的函数,其传入参数绑定到指定的值。 在被调用函数中执行`ret`指令后,控制流程将在函数调用后继续执行该指令,并且该函数的返回值绑定到`result`参数。 ##### GetElementPtr - 格式:` = getelementptr , * [, ]` - 例子: - `%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0` - `%2 = getelementptr i32, i32* %1 i32 %0` - 参数解释:第一个参数是计算基础类型,第二第三个参数表示索引开始的指针类型及指针,`[]`表示可重复参数,里面表示的数组索引的偏移类型及偏移值。(思考指针类型为`[10 x i32]`指针和`i32`指针`getelementptr`用法的不同) - 概念:`getelementptr`指令用于获取数组结构的元素的地址。 它仅执行地址计算,并且不访问内存。 ## C++ APIs ### 核心类概念图 ![](figs/核心类概念图.png) ### BasicBlock - 继承:从[value](#value)继承 - 含义:基本块,是一个是单入单出的代码块,该类维护了一个指令链表,基本块本身属于 Value, 类型是 \,会被分支指令调用 - 成员: - instr_list_:指令链表 - pre_bbs_: bb前驱集合 - succ_bbs_:bb后继集合 - API: ```c++ // 创建并返回BB块,参数分别是BB块所属的Module,name是其名字默认为空,BB块所属的Function static Ptr create(Ptr m, const std::string &name ,Ptr parent ) // 返回BB块所属的函数 Ptr get_parent(); // 返回BB块所属的Module Ptr get_module(); // 返回BB块的终止指令(ret|br),若BB块最后一条指令不是终止指令返回null Ptr get_terminator(); // 将instr指令添加到此BB块指令链表结尾,调用IRBuilder里来创建函数会自动调用此方法 void add_instruction(Ptr instr); // 将instr指令添加到此BB块指令链表开头 void add_instr_begin(Ptr instr); // 将instr指令从BB块指令链表中移除,同时调用api维护好instr的操作数的use链表 void delete_instr(Ptr instr); // BB块中指令数为空返回true bool empty(); // 返回BB块中指令的数目 int get_num_of_instr(); //返回BB块的指令链表 PtrList &get_instructions(); // 将此BB块从所属函数的bb链表中移除 void erase_from_parent(); /****************api about cfg****************/ PtrList &get_pre_basic_blocks() // 返回前驱块集合 PtrList &get_succ_basic_blocks() // 返回后继块集合 void add_pre_basic_block(Ptr bb) // 添加前驱块 void add_succ_basic_block(Ptr bb) // 添加后继块 void remove_pre_basic_block(Ptr bb) // 移除前驱块 void remove_succ_basic_block(Ptr bb) // 移除后继块 /****************api about cfg****************/ ``` ### Constant - 继承:从[User](#user)继承 - 含义:常数,各种类型常量的基类 - 子类: - ConstantInt - 含义:int类型的常数 - 成员 - value_:常数值 - API ```cpp int get_value() // 返回该常数类型中存的常数值 static int get_value(Ptr const_val)// 返回该常数类型const_val中存的常数值 static Ptr get(int val, Ptr m) // 以val值来创建常数类 static Ptr get(bool val, Ptr m) // 以val值来创建bool常数类 ``` - ConstantFloat - 含义:float类型的常数 - 成员 - value_:常数值 - API ```cpp static Ptr get(float val, Ptr m) // 以val值创建并返回浮点数常量类 float get_value() // 返回该常数类型中存的常数值 ``` - ConstantZero - 含义:用于全局变量初始化的常量0值。 - API ```cpp static Ptr get(Ptr ty, Ptr m);// 创建并返回ConstantZero常量类 ``` - ConstantArray - 含义:数组类型的常数 - 成员 - const_array:数组常量值 - API:cminus语法不需要数组常量的支持(本次实验不需要用到),在此不过多解释。感兴趣可以自行查看源代码。 ### Function - 继承:从[Value](#value)继承 - 含义:函数,该类描述 LLVM 的一个简单过程,维护基本块表,格式化参数表 - 成员 - basic_blocks_:基本块列表 - arguments_:形参列表 - parent_:函数属于的module - API ```cpp static Ptr create(Ptr ty, const std::string &name, Ptr parent); // 创建并返回Function,参数依次是待创建函数类型ty、函数名字name(不可为空)、函数所属的Module Ptr get_function_type() const; // 返回此函数类的函数类型 Ptr get_return_type() const; // 返回此函数类型的返回值类型 void add_basic_block(Ptr bb); // 将bb添加至Function的bb链表上(调用bb里的创建函数时会自动调用此函数挂在function的bb链表上) unsigned get_num_of_args() const; // 得到函数形参数数量 unsigned get_num_basic_blocks() const; // 得到函数基本块数量 Ptr get_parent() const; // 得到函数所属的Module PtrList::iterator arg_begin() // 得到函数形参的list的起始迭代器 PtrList::iterator arg_end() // 得到函数形参的list的终止迭代器 void remove(Ptr bb) // 从函数的bb链表中删除一个bb PtrList &get_basic_blocks() // 返回函数bb链表 PtrList &get_args() // 返回函数的形参链表 void set_instr_name(); // 给函数中未命名的基本块和指令命名 ``` - 相关类 - Argument - 含义:参数 - 成员 - arg_no_:参数序号 - parent_:参数属于哪个函数 - API ```cpp Ptr get_parent() // 返回参数的所属函数 unsigned get_arg_no() const // 返回参数在所在函数的第几个参数 ``` ### GlobalVariable - 继承:从[User](#user)继承 - 含义:全局变量,该类用于表示全局变量,是 GlobalValue 的子类,根据地址来访问 - 成员: - is_const:是否为常量 - init_val_:初始值 - API:由于cminusf语义要求所有的全局变量都默认初始化为0,故`GlobalVariable`中成员和API再构造CminusFBuilder用不到 ### IRStmtBuilder - 含义:生成IR的辅助类,该类提供了独立的接口创建各种 IR 指令,并将它们插入基本块中, 该辅助类不做任何类型检查。 - API ```cpp Ptr get_insert_block()// 返回正在插入指令的BB void set_insert_point(Ptr bb)// 设置当前需要插入指令的bb ptr create_[instr_type]()// 创建instr_type(具体名字参考IRStmtBuilder.h代码)的指令并对应插入到正在插入的BB块,这种类型的指令看函数名字和参数名字和IR文档是一一对应的。 ``` ### Instruction - 继承:从[User](#user)继承 - 含义:指令,该类是所有 LLVM 指令的基类,主要维护指令的操作码(指令类别),指令所属的基本块,指令的操作数个数信息 - 成员 - parent_:指令所属的BasicBlock - op_id_:指令的类型id - num_ops_指令的操作数个数 - 子类 - BinaryInst:双目运算指令包括add、sub、mul、div - 其他子类和前述文档中提到的指令一一对应,不在此赘述。 - API:所有指令的创建都要通过 IRStmtBuilder 进行,不需要关注Instruction类的实现细节,(**注**:不通过 IRStmtBuilder 来创建指令,而直接调用指令子类的创建方法未经助教完善的测试) ### Module - 含义:一个编译单元,在此源语言的意义下是一个文件 - 成员 - function_list_:函数链表,记录了这个编译单元的所有函数 - global_list_:全局变量链表 - instr_id2string_:通过指令类型id得到其打印的string - module_name_, source_file_name:未使用 - 从module中能取到的基本类型 - API ```cpp Ptr get_void_type(); // 得到IR中的void类型其他类型可以用类似的API得到(推荐取得类型采用lab3助教提供的方法Type::get()) void add_function(Ptr f); // 将f挂在module的function链表上,在function被创建的时候会自动调用此方法来添加function void add_global_variable(Ptr g); // 将g挂在module的GlobalVariable链表上,在GlobalVariable被创建的时候会自动调用此方法来添加GlobalVariable PtrList &get_global_variable(); // 获取全局变量列表 std::string get_instr_op_name( Instruction::OpID instr ); // 获取instr对应的指令名(打印ir时调用) void set_print_name(); // 设置打印ir的指令与bb名字; ``` ### Type - 含义:IR的类型,该类是所有类型的超类 - 成员 - tid_:枚举类型,表示type的类型(包含VoidType、LabelType、FloatType、Int1、Int32、ArrayType、PointerType) - 子类 - IntegerType - 含义:int 类型 - 成员 - num_bits:长度(i1或者i32) - API ```cpp unsigned get_num_bits();// 返回int的位数 ``` - FloatType - 含义:float 类型 - FunctionType - 含义:函数类型 - 成员 - result_:返回值类型 - args_:参数类型列表 - API ```cpp static Ptr get(Ptr result, PtrVec params); // 返回函数类型,参数依次是返回值类型result,形参类型列表params unsigned get_num_of_args() const; // 返回形参个数 Ptr get_param_type(unsigned i) const; // 返回第i个形参的类型 PtrVec::iterator param_begin() // 返回形参类型列表的起始迭代器 PtrVec::iterator param_end() // 返回形参类型列表的终止迭代器 Ptr get_return_type() const; // 返回函数类型中的返回值类型 ``` - ArrayType - 含义:数组类型 - 成员 - contained_:数组成员的类型 - num_elements_:数组维数 - API ```cpp static Ptr get(Ptr contained, unsigned num_elements); // 返回数组类型,参数依次是 数组元素的类型contained,数组元素个数num_elements Ptr get_element_type() const // 返回数组元素类型 unsigned get_num_of_elements() const // 返回数组元素个数 ``` - PointerType - 含义:指针类型 - 成员 - contained_:指针指向的类型 - API ```cpp Ptr get_element_type() const { return contained_; } // 返回指针指向的类型 static Ptr get(Ptr contained); // 返回contained类型的指针类型 Ptr get_pointer_element_type();// 若是PointerType则返回指向的类型,若不是则返回nullptr。 static Ptr create(Ptr contained); // 创建指向contained类型的指针类型 ``` - API ```cpp bool is_void_type()// 判断是否是void类型其他类型有类似API请查看Type.h static Ptr get_void_type(Ptr m);// 得到void类型 Ptr get_pointer_element_type();// 若是PointerType则返回指向的类型,若不是则返回nullptr。 Ptr get_array_element_type();// 若是ArrayType则返回指向的类型,若不是则返回nullptr。 ``` ### User - 继承:从[value](#value)继承 - 含义:使用者,提供一个操作数表,表中每个操作数都直接指向一个 Value, 提供了 use-def 信息,它本身是 Value 的子类, Value 类会维护一个该数据使用者的列表,提供def-use信息。简单来说操作数表表示我用了谁,该数据使用者列表表示谁用了我。这两个表在后续的**优化实验**会比较重要请务必理解。 - 成员 - operands_:参数列表,表示这个使用者所用到的参数 - num_ops_:表示该使用者使用的参数的个数 - API ```cpp Ptr get_operand(unsigned i) const; // 从user的操作数链表中取出第i个操作数 void set_operand(unsigned i, Ptr v); // 将user的第i个操作数设为v void add_operand(Ptr v); // 将v挂到User的操作数链表上 unsigned get_num_operand() const; // 得到操作数链表的大小 void remove_use_of_ops(); // 从User的操作数链表中的所有操作数处的use_list_ 移除该User; void remove_operands(int index1, int index2); // 移除操作数链表中索引为index1-index2的操作数,例如想删除第0个操作数:remove_operands(0,0) ``` ### Value - 含义:最基础的类,代表一个操作数,代表一个可能用于指令操作数的带类型数据 - 成员 - use_list_:记录了所有使用该操作数的指令的列表 - name_:名字 - type_:类型,一个type类,表示操作数的类型 - API ```cpp Ptr get_type() const //返回这个操作数的类型 std::list &get_use_list() // 返回value的使用者链表 void add_use(Ptr val, unsigned arg_no = 0); // 添加val至this的使用者链表上 void replace_all_use_with(Ptr new_val); // 将this在所有的地方用new_val替代,并且维护好use_def与def_use链表 void remove_use(Ptr val); // 将val从this的use_list_中移除 ``` ### 总结 在本文档里提供了为SysYF语言程序生成LLVM IR可能需要用到的SysYF IR应用编程接口,如果对这些API有问题的请移步issue讨论,本次`SysYF IR`应用编程接口由助教自行设计实现,并做了大量测试,如有对助教的实现方法有异议或者建议的也请移步issue讨论,**除了选做内容无需修改助教代码**。