You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

23 KiB

SysYF IR

IR

IR Features

  • 采用类型化三地址代码的方式
    • 区别于 X86 汇编的目标和源寄存器共用的模式: ADD EAX, EBX
    • %2 = add i32 %0, %1
  • 静态单赋值 (SSA) 形式 + 无限寄存器
    • 每个变量都只被赋值一次
    • 容易确定操作间的依赖关系,便于优化分析
  • 强类型系统
    • 每个 Value 都具备自身的类型,
    • IR类型系统
      • i11位宽的整数类型
      • i3232位宽的整数类型
      • float:单精度浮点数类型
      • pointer:指针类型
        • 例如:i32*, [10 x i32*]
      • label bb的标识符类型
      • functiontype函数类型,包括函数返回值类型与参数类型(下述文档未提及)

IR Format

下面以easy.ceasy.ll为例进行说明。
通过命令clang -S -emit-llvm easy.c可以得到对应的easy.ll如下(其中增加了额外的注释)。.ll文件中注释以;开头。

  • easy.c:
    int main(){
      int a;
      int b;
      a = 1;
      b = 2;
      if(a < b)
        b = 3;
      return a + b;
    }
    
  • easy.ll:
    ; 注释: .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
    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组成。
      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是指令助记符,i32int32的类型,i32*是指向int32的地址类型,%2是源操作数,align 4表示对齐。

Instruction

Terminator Instructions

ret与br都是Terminator Instructions也就是终止指令在llvm基本块的定义里基本块是单进单出的因此只能有一条终止指令ret或br。当一个基本块有两条终止指令clang 在做解析时会认为第一个终结指令是此基本块的结束,并会开启一个新的匿名的基本块(并占用了下一个编号)。

Ret
  • 格式
    • ret <type> <value>
    • ret void
  • 例子:
    • ret i32 %0
    • ret void
  • 概念: ret指令用于将控制流(以及可选的值)从函数返回给调用者。ret指令有两种形式:一种返回值,然后终结函数,另一种仅终结函数。
Br
  • 格式:
    • br i1 <cond>, label <iftrue>, label <iffalse>
    • br label <dest>
  • 例子:
    • br i1 %cond label %truebb label %falsebb
    • br label %bb
  • 概念:br指令用于使控制流转移到当前功能中的另一个基本块。 该指令有两种形式,分别对应于条件分支和无条件分支。

Standard binary operators

Add FAdd
  • 格式:
    • <result> = add <type> <op1>, <op2>
    • <result> = fadd <type> <op1>, <op2>
  • 例子:
    • %2 = add i32 %1, %0
    • %2 = fadd float %1, %0
  • 概念:add指令返回其两个i32类型的操作数之和,返回值为i32类型,fadd指令返回其两个float类型的操作数之和,返回值为float类型
Sub FSub
  • 格式与例子与addfadd类似
  • 概念:sub指令返回其两个i32类型的操作数之差,返回值为i32类型,fsub指令返回其两个float类型的操作数之差,返回值为float类型
Mul FMul
  • 格式与例子与addfadd类似
  • 概念:mul指令返回其两个i32类型的操作数之积,返回值为i32类型,fmul指令返回其两个float类型的操作数之积,返回值为float类型
SDiv FDiv
  • 格式与例子与addfadd类似
  • 概念:sdiv指令返回其两个i32类型的操作数之商,返回值为i32类型,fdiv指令返回其两个float类型的操作数之商,返回值为float类型
SRem
  • 格式与例子与add类似
  • 概念:srem指令返回其两个i32类型的操作数之模,返回值为i32类型

Memory operators

Alloca
  • 格式:<result> = alloca <type>
  • 例子:
    • %ptr = alloca i32
    • %ptr = alloca [10 x i32]
  • 概念: alloca指令在当前执行函数的栈帧上分配内存,当该函数返回其调用者时将自动释放该内存。 始终在地址空间中为数据布局中指示的分配资源分配对象
Load
  • 格式:<result> = load <type>, <type>* <pointer>
  • 例子:%val = load i32, i32* %ptr
  • 概念:load指令用于从内存中读取。
Store
  • 格式:store <type> <value>, <type>* <pointer>
  • 例子:store i32 3, i32* %ptr
  • 概念:store指令用于写入内存

CastInst

ZExt
  • 格式:<result> = zext <type> <value> to <type2>
  • 例子:%1 = zext i1 %0 to i32
  • 概念:zext指令将其操作数扩展为type2类型。
FpToSi
  • 概念:fptosi指令将浮点值转换为type2(整数)类型。
  • 格式:<result> = fptosi <type> <value> to <type2>
  • 例子:%Y = fptosi float 1.0E-247 to i32
SiToFp
  • 格式:<result> = sitofp <type> <value> to <type2>
  • 例子:%X = sitofp i32 257 to float
  • 概念:sitofp指令将有符号整数转换为type2(浮点数)类型。

Other operators

ICmp FCmp
  • 格式:
    • <result> = icmp <cond> <type> <op1>, <op2>
      • <cond> = eq | ne | sgt | sge | slt | sle
    • <result> = fcmp <cond> <type> <op1>, <op2>
      • <cond> = eq | ne | ugt | uge | ult | ule
  • 例子:i1 %2 = icmp sge i32 %0, %1
  • 概念:icmp指令根据两个整数的比较返回布尔值,fcmp指令根据两个浮点数的比较返回布尔值。
Call
  • 格式:
    • <result> = call <return ty> <func name>(<function args>)
  • 例子:
    • %0 = call i32 @func( i32 %1, i32* %0)
    • call @func( i32 %arg)
  • 概念:call指令用于使控制流转移到指定的函数,其传入参数绑定到指定的值。 在被调用函数中执行ret指令后,控制流程将在函数调用后继续执行该指令,并且该函数的返回值绑定到result参数。
GetElementPtr
  • 格式:<result> = getelementptr <type>, <type>* <ptrval> [, <type> <idx>]
  • 例子:
    • %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

核心类概念图

BasicBlock

  • 继承:从value继承

  • 含义:基本块,是一个是单入单出的代码块,该类维护了一个指令链表,基本块本身属于 Value, 类型是 <label>,会被分支指令调用

  • 成员:

    • instr_list_指令链表
    • pre_bbs_ bb前驱集合
    • succ_bbs_bb后继集合
  • API:

    // 创建并返回BB块参数分别是BB块所属的Modulename是其名字默认为空BB块所属的Function
    static Ptr<BasicBlock> create(Ptr<Module> m, const std::string &name ,Ptr<Function> parent )
    // 返回BB块所属的函数
    Ptr<Function> get_parent();
    // 返回BB块所属的Module
    Ptr<Module> get_module();
    // 返回BB块的终止指令(ret|br)若BB块最后一条指令不是终止指令返回null
    Ptr<Instruction> get_terminator();
    // 将instr指令添加到此BB块指令链表结尾调用IRBuilder里来创建函数会自动调用此方法
    void add_instruction(Ptr<Instruction> instr);
    // 将instr指令添加到此BB块指令链表开头
    void add_instr_begin(Ptr<Instruction> instr);
    // 将instr指令从BB块指令链表中移除同时调用api维护好instr的操作数的use链表
    void delete_instr(Ptr<Instruction> instr);
    // BB块中指令数为空返回true
    bool empty();
    // 返回BB块中指令的数目
    int get_num_of_instr();
    //返回BB块的指令链表
    PtrList<Instruction> &get_instructions();
    // 将此BB块从所属函数的bb链表中移除
    void erase_from_parent();
    
    /****************api about cfg****************/
    PtrList<BasicBlock> &get_pre_basic_blocks() // 返回前驱块集合
    PtrList<BasicBlock> &get_succ_basic_blocks() // 返回后继块集合
    void add_pre_basic_block(Ptr<BasicBlock> bb) // 添加前驱块
    void add_succ_basic_block(Ptr<BasicBlock> bb) // 添加后继块
    void remove_pre_basic_block(Ptr<BasicBlock> bb) // 移除前驱块
    void remove_succ_basic_block(Ptr<BasicBlock> bb) // 移除后继块
    /****************api about cfg****************/
    
    

Constant

  • 继承:从User继承
  • 含义:常数,各种类型常量的基类
  • 子类:
    • ConstantInt

      • 含义int类型的常数

      • 成员

        • value_常数值
      • API

        int get_value() // 返回该常数类型中存的常数值
        static int get_value(Ptr<ConstantInt> const_val)// 返回该常数类型const_val中存的常数值
        static Ptr<ConstantInt> get(int val, Ptr<Module> m) // 以val值来创建常数类
        static Ptr<ConstantInt> get(bool val, Ptr<Module> m) // 以val值来创建bool常数类
        
    • ConstantFloat

      • 含义float类型的常数

      • 成员

        • value_常数值
      • API

        static Ptr<ConstantFloat> get(float val, Ptr<Module> m) // 以val值创建并返回浮点数常量类
        float get_value() // 返回该常数类型中存的常数值
        
    • ConstantZero

      • 含义用于全局变量初始化的常量0值。

      • API

        static Ptr<ConstantZero> get(Ptr<Type> ty, Ptr<Module> m);// 创建并返回ConstantZero常量类
        
    • ConstantArray

      • 含义:数组类型的常数

      • 成员

        • const_array数组常量值
      • APIcminus语法不需要数组常量的支持本次实验不需要用到在此不过多解释。感兴趣可以自行查看源代码。

Function

  • 继承:从Value继承

  • 含义:函数,该类描述 LLVM 的一个简单过程,维护基本块表,格式化参数表

  • 成员

    • basic_blocks_基本块列表
    • arguments_形参列表
    • parent_函数属于的module
  • API

    static Ptr<Function> create(Ptr<FunctionType> ty, const std::string &name, Ptr<Module> parent);
    // 创建并返回Function参数依次是待创建函数类型ty、函数名字name(不可为空)、函数所属的Module
    Ptr<FunctionType> get_function_type() const;
    // 返回此函数类的函数类型
    Ptr<Type> get_return_type() const;
    // 返回此函数类型的返回值类型
    void add_basic_block(Ptr<BasicBlock> bb);
    // 将bb添加至Function的bb链表上调用bb里的创建函数时会自动调用此函数挂在function的bb链表上
    unsigned get_num_of_args() const;
    // 得到函数形参数数量
    unsigned get_num_basic_blocks() const;
    // 得到函数基本块数量
    Ptr<Module> get_parent() const;
    // 得到函数所属的Module
    PtrList<Argument>::iterator arg_begin() 
    // 得到函数形参的list的起始迭代器
    PtrList<Argument>::iterator arg_end()
    // 得到函数形参的list的终止迭代器
    void remove(Ptr<BasicBlock>  bb)
    // 从函数的bb链表中删除一个bb
    PtrList<BasicBlock> &get_basic_blocks()
    // 返回函数bb链表
    PtrList<Argument> &get_args()
    // 返回函数的形参链表
    void set_instr_name();
    // 给函数中未命名的基本块和指令命名
    
  • 相关类

    • Argument
      • 含义:参数

      • 成员

        • arg_no_参数序号
        • parent_参数属于哪个函数
      • API

        Ptr<Function> get_parent() // 返回参数的所属函数
        unsigned get_arg_no() const // 返回参数在所在函数的第几个参数
        

GlobalVariable

  • 继承:从User继承
  • 含义:全局变量,该类用于表示全局变量,是 GlobalValue 的子类,根据地址来访问
  • 成员:
    • is_const是否为常量
    • init_val_初始值
  • API由于cminusf语义要求所有的全局变量都默认初始化为0GlobalVariable中成员和API再构造CminusFBuilder用不到

IRStmtBuilder

  • 含义生成IR的辅助类该类提供了独立的接口创建各种 IR 指令,并将它们插入基本块中, 该辅助类不做任何类型检查。

  • API

    Ptr<BasicBlock> get_insert_block()// 返回正在插入指令的BB
    void set_insert_point(Ptr<BasicBlock> bb)// 设置当前需要插入指令的bb
    ptr<XXXInst> create_[instr_type]()// 创建instr_type(具体名字参考IRStmtBuilder.h代码)的指令并对应插入到正在插入的BB块这种类型的指令看函数名字和参数名字和IR文档是一一对应的。
    

Instruction

  • 继承:从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

    Ptr<Type> get_void_type();
    // 得到IR中的void类型其他类型可以用类似的API得到(推荐取得类型采用lab3助教提供的方法Type::get())
    void add_function(Ptr<Function> f);
    // 将f挂在module的function链表上在function被创建的时候会自动调用此方法来添加function
    void add_global_variable(Ptr<GlobalVariable> g);
    // 将g挂在module的GlobalVariable链表上在GlobalVariable被创建的时候会自动调用此方法来添加GlobalVariable
    PtrList<GlobalVariable> &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

        unsigned get_num_bits();// 返回int的位数
        
    • FloatType

      • 含义float 类型
    • FunctionType

      • 含义:函数类型

      • 成员

        • result_返回值类型
        • args_参数类型列表
      • API

        static Ptr<FunctionType> get(Ptr<Type> result, PtrVec<Type> params);
        // 返回函数类型参数依次是返回值类型result形参类型列表params
        unsigned get_num_of_args() const;
        // 返回形参个数
        Ptr<Type> get_param_type(unsigned i) const;
        // 返回第i个形参的类型
        PtrVec<Type>::iterator param_begin() 
        // 返回形参类型列表的起始迭代器
        PtrVec<Type>::iterator param_end() 
        // 返回形参类型列表的终止迭代器    
        Ptr<Type> get_return_type() const;
        // 返回函数类型中的返回值类型
        
    • ArrayType

      • 含义:数组类型

      • 成员

        • contained_数组成员的类型
        • num_elements_数组维数
      • API

        static Ptr<ArrayType> get(Ptr<Type> contained, unsigned num_elements);
        // 返回数组类型,参数依次是 数组元素的类型contained数组元素个数num_elements
        Ptr<Type> get_element_type() const
        // 返回数组元素类型
        unsigned get_num_of_elements() const
        // 返回数组元素个数
        
    • PointerType

      • 含义:指针类型

      • 成员

        • contained_指针指向的类型
      • API

        Ptr<Type> get_element_type() const { return contained_; }
        // 返回指针指向的类型
        static Ptr<PointerType> get(Ptr<Type> contained);
        // 返回contained类型的指针类型
        Ptr<Type> get_pointer_element_type();// 若是PointerType则返回指向的类型若不是则返回nullptr。
        static Ptr<PointerType> create(Ptr<Type> contained);
        // 创建指向contained类型的指针类型
        
  • API

    bool is_void_type()// 判断是否是void类型其他类型有类似API请查看Type.h
    static Ptr<Type> get_void_type(Ptr<Module> m);// 得到void类型
    Ptr<Type> get_pointer_element_type();// 若是PointerType则返回指向的类型若不是则返回nullptr。
    Ptr<Type> get_array_element_type();// 若是ArrayType则返回指向的类型若不是则返回nullptr。
    

User

  • 继承:从value继承

  • 含义:使用者,提供一个操作数表,表中每个操作数都直接指向一个 Value, 提供了 use-def 信息,它本身是 Value 的子类, Value 类会维护一个该数据使用者的列表提供def-use信息。简单来说操作数表表示我用了谁该数据使用者列表表示谁用了我。这两个表在后续的优化实验会比较重要请务必理解。

  • 成员

    • operands_参数列表表示这个使用者所用到的参数
    • num_ops_表示该使用者使用的参数的个数
  • API

    Ptr<Value> get_operand(unsigned i) const;
    // 从user的操作数链表中取出第i个操作数
    void set_operand(unsigned i, Ptr<Value> v);
    // 将user的第i个操作数设为v
    void add_operand(Ptr<Value> 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

    Ptr<Type> get_type() const //返回这个操作数的类型
    std::list<Use> &get_use_list() // 返回value的使用者链表
    void add_use(Ptr<Value> val, unsigned arg_no = 0);
    // 添加val至this的使用者链表上
    void replace_all_use_with(Ptr<Value> new_val);
    // 将this在所有的地方用new_val替代并且维护好use_def与def_use链表
    void remove_use(Ptr<Value> val);
    // 将val从this的use_list_中移除
    

总结

在本文档里提供了为SysYF语言程序生成LLVM IR可能需要用到的SysYF IR应用编程接口如果对这些API有问题的请移步issue讨论本次SysYF IR应用编程接口由助教自行设计实现并做了大量测试如有对助教的实现方法有异议或者建议的也请移步issue讨论除了选做内容无需修改助教代码