LLVM IR(二)——IR基本语法及高级结构(High Level Structure)

可以转载,请注明出处!

这一章后续会持续更新,直到这一句话没了!

2.1 标识符

LLVM标识符有两种基本类型:全局标识符和局部标识符。全局标识符包括函数和全局变量,以’@‘字符开头;局部标识符包括局部变量和type,以’%'字符开头。此外,为了不同的目的,标识符有三种不同的格式:

  • 1)命名的值由前缀(@或者%)加字符串表示,比如%foo等。识别变量的正则表达式为[%@][-a-zA-Z$._][-a-zA-Z$._0-9]*这种全局变量和局部变量,就相当于C语言中的指针变量,函数就相当于C中的函数指针。
  • 2)未命名的值由签注加无符号数字表示,比如%2等。目前我称其为零时变量,后面觉得有更好的解释再改。这种零时变量就相当于C语言中的普通变量。
  • 3)常量,对于常量后面再做详细的介绍。

例如:@.str是一个全局变量;%call是一个局部变量。

【注】标识符由前缀开头有两个方面的原因:一是编译器不用担心其与保留字冲突;二是编译器可以方便的给未命名的值设置临时变量而不需要考虑符号表冲突。所谓临时变量,就是当计算结果没有分配给指定值时,就会创建未命名的临时变量,未命名的临时文件是按顺序编号的,从0开始,比如%0%1

2.2 注释

LLVM的注释只有一种表示形式,以字符“”开始,持续到改行的结束。

例如:

define i32 @main() #0 {		;这是一个注释,这里定义了一个函数
entry:
   ret i32 0
}

2.3 IR代码结构

  • Module:LLVM程序由模块组成,每个输入程序都对应一个模块,模块包括函数、全局变量和符号表。多个模块可以被LLVM 链接器(linker)组合在一起。可以将LLVM中的Module类比为C程序中的源文件。一个C源文件中包含函数和全局变量定义、外部函数和外部函数声明,一个Module中包含的内容也基本上如此,只不过C源文件中是源码来表示,Module中是用IR来表示。

  • Function:被Module所包含,LLVM的Function包含函数名、函数的返回值和参数类型,Function内部则包含BasicBlock。

  • BasicBlock:与编译技术中常见的基本块(basic block)的概念是一致的,BasicBlock必须有一个进入标签和一条结束指令。结束指令一般是br跳转指令或者ret返回执行。

  • Instruction:就是上面提到过的“指令”,LLVM IR的最基本的单位,Instruction被包含在BasicBlock中,一个BasicBlock可以有多条Instruction。

2.4 链接类型(Linkage Types)

全局的值(全局变量和函数)都由指向某个特定位置的指针表示,并且有一个链接类型:

  • 1)private。private类型的全局变量只能被当前模块内的对象直接访问。而且,当将代码链接到一个包含private类型值的模块的时候,这个private值可能被重命名以避免冲突。由于这个值是模块私有的,所以所有的引用都可以直接更新。private的值不会出现在目标代码的符号表中。
  • 2)internal。与private类似,不过它的值会作为局部变量出现在目标文件中。类似C语言中static的概念。
  • 3)available_externally。该类型的变量不会被输出到对应模块的目标文件中。对于链接者来说,该类型相当于一个外部声明。这种类型只允许在定义时使用,而不能在声明时使用。
  • 4)linkonce。链接时,这种类型的变量会和其他同名变量merge。该类型的变量如果没被使用则可以被丢弃。这种类型的函数不允许优化器将其内联到调用者的函数体中,这个它有可能被重写。如果想允许内联或者其他优化,请使用“linkonce_odr”类型
  • 5)weak。与linkonce类似,但即使不被使用,也不能丢弃。类似C语言中的“weak”类型的变量(多个变量拥有相同的名字不会造成冲突,只要其中一个是weak类型的。链接时,weak类型的定义被忽略而使用正常的定义,如果没有正常的定义,则使用weak的定义)。
  • 6)common。与weak类似,用于C语言中的临时定义,比如全局作用域中的“int x;”。函数和别名没有common类型
  • 7)appending。只用于数组类型的指针。两个这种类型的变量链接时,对应的全局数组被连接在一起。
  • 8)extern_weak。这种类型在链接之前是weak的,如果不被链接,这个变量是null而不是undefined reference。
  • 9)linkonce_odr, weak_odr。ODR(one definition rule),只有等效的变量才能被merge。这个链接类型就表示只能跟等效的变量合并。
  • 10)external。如果没有指定上面的任意类型,那么就是external的。

【注】函数声明只能使用external或者extern_weak

2.5 可见性(Visibility Styles)

所有的全局变量和函数都有一种可见性样式:

  • default”,默认样式。对于使用ELF(Executable and Linking Format)格式的目标文件,这种可见性样式意味着声明对其他模块可见;在共享库中,意味着声明的实体可以被重写;在Darwin,声明对其他模块可见。
  • hidden”。具有这种可见性样式的声明引用相同的对象,如果它们在相同的共享对象中的话。通常,具有这种可见性样式的符号不会出现在动态符号表中,所有其他模块不能直接引用它。
  • protected”。对于ELF,这种样式表明符号会放置在动态符号表中,但是在定义这个符号的模块中的引用会绑定到局部变量,也就是说这个符号不能被其他模块重写。

【注】用于internal或者private链接类型的符号必须是default类型的。

2.6 调用约定(Calling Conventions)

LLVM支持的调用约定如下:

  • ccc”,C调用约定。如果没有指定其他类型,那么就默认是这种类型。这个类型支持可变参数的函数调用并且允许声明的原型和函数声明的实现之间有一些不匹配。
  • fastcc”,快速调用约定。使生成的目标代码尽可能快,可以使用任意tricks。不支持可变参数,并且原型和实现要严格匹配。
  • coldcc”,冷调用。假定这种调用不经常发生。不支持可变参数,并且原型和实现要严格匹配。
  • cc 10”,GHC调用。 Glasgow Haskell Compiler 专用。支持尾调用优化,但是要求caller和callee都是它。
  • cc 11”,HiPE调用约定。
  • webkit_jscc”, webkit的js调用约定。
  • anyregcc” ,代码补丁的动态调用。
  • preserve_mostcc”,PreserveMost调用约定。目前是实验性的,未来会被objectiveC使用。
  • preserve_allcc”。也是实验性的。
  • cxx_fast_tlscc”。Clang生成访问函数去访问C++风格的TLS。访问函数包括入口块、出口块和初始化块,入口和出口块可以访问一些TLS IR变量。这个调用约定就是通过保留尽可能多的寄存器变量来降低这种访问的开销。
  • swiftcc”,Swift语言的调用约定。
  • cc <n>”,从64开始。

2.7 函数(Function)

函数的定义就是一个个基本块(basicblock)构成了一个CFG(控制流程图),每个基本块由一条条指令组成,并且都有一个进入标签(entry lable)和一条终止指令(Terminator Instructions),终止指令有好多条,如brretswitch等。如果没有提供一个确切的标签名称,块(block)就会被分配一个隐式的标签编号,编号使用的是从计数器中返回下一个值,就如同未命名的临时变量一样。For example,如果函数入口块没有明确的标签,则会分配标签“%0”,那么该块中的第一个未命名的临时块将为“%1”,以此类推;如果有显示标签,将会与显示标签的数字相匹配。

函数中的第一个基本块有两个特殊点:只要进入该函数就立刻执行第一个基本块,并且不允许有祖先基本块(即不能有任何分支到函数的入口块)。由于该块可以没有前驱,所以它不能有任何PHI节点(有一个phi指令,可以生成这个节点,不过我目前还不知道这是干啥的)。

LLVM允许为函数指定一个确切的section,如果目标支持它(函数),会将函数发送给指定的section。另外,该函数可以放置在COMDAT(High Level Structure中的一个玩意,目前还不知道是干啥的)中。

可以为函数指定确切的对齐方式,如果没有指定或者指定的对方值是0,则目标机就会设置一个对其来说较为方便的值。如果指定了对其方式,则会强制至少按照指定的对齐值对齐。对齐值必须是2的幂次方。
关于对齐方式,IR并没有太多的解释,网上搜了一下就把它解释为内存对齐,解释的很好:https://blog.csdn.net/qq_42570601/article/details/107376591

定义一个函数的语法:

define [linkage] [PreemptionSpecifier] [visibility] [DLLStorageClass]
       [cconv] [ret attrs]
       <ResultType> @<FunctionName> ([argument list])
       [(unnamed_addr|local_unnamed_addr)] [AddrSpace] [fn Attrs]
       [section "name"] [comdat [($name)]] [align N] [gc] [prefix Constant]
       [prologue Constant] [personality Constant] (!name !N)* { ... }
  • LLVM函数由“define”关键字进行定义,每个函数都有一个可选的链接类型,一个可选的运行时抢占说明(runtime preemption specifier),一个可选的可见性模式,一个可选的DLL存储类别,一个可选的调用约定,一个可选的unnamed_addr属性(该属性表示地址不重要,只要内容),一个返回值类型,一个可选的返回类型的参数属性,一个函数名,一个(可能为空的)实参列表(每一个都带有可选的参数属性),可选的函数属性,一个可选的地址空间,一个可选的section(部分?这个词不知道该怎么翻译),一个可选的对齐属性,一个可选的comdat(common data,公用数据),一个可选的GC(垃圾回收器)名称,一个可选的前缀数据,一个可选的序言数据,一个可选的personality(有该属性的函数可以指定异常处理),一个可选的附加元数据列表,一个左花括号,一个基本块列表和一个右花括号

  • 使用declare关键字对函数进行声明, an optional linkage type, an optional visibility style, an optional DLL storage class, an optional calling convention, an optional unnamed_addr or local_unnamed_addr attribute, an optional address space, a return type, an optional parameter attribute for the return type, a function name, a possibly empty list of arguments, an optional alignment, an optional garbage collector name, an optional prefix, and an optional prologue.

这上面出现的所有属性,都是LLVM IR中的高级结构(High Level Structure)。

举例:

define i32 @max(i32 %a, i32 %b) #0 {
entry:
  %retval = alloca i32, align 4
  %a.addr = alloca i32, align 4
  %b.addr = alloca i32, align 4
  store i32 %a, i32* %a.addr, align 4
  store i32 %b, i32* %b.addr, align 4
  %0 = load i32, i32* %a.addr, align 4
  %1 = load i32, i32* %b.addr, align 4
  %cmp = icmp sgt i32 %0, %1
  br i1 %cmp, label %if.then, label %if.else

if.then:
  %2 = load i32, i32* %a.addr, align 4
  store i32 %2, i32* %retval, align 4
  br label %return

if.else:
  %3 = load i32, i32* %b.addr, align 4
  store i32 %3, i32* %retval, align 4
  br label %return

return:
  %4 = load i32, i32* %retval, align 4
  ret i32 %4
}

上面这个例子,看上去好长好复杂,但是等价的C结构其实也就这么简单(就这?):

int max(int a, int b){
    if(a > b)
		return a;
    else
		return b;
}

简单过一下上面的例子,define i32 @max(i32 %a, i32 %b) #0 {定义一个函数,该函数的返回类型是i32(不知道i32可以先跳到类型体系看一下整型类型),两个入参变量都是i32类型,#0引用一个属性组,暂时不用管它。

entry:
  %retval = alloca i32, align 4
  %a.addr = alloca i32, align 4
  %b.addr = alloca i32, align 4
  store i32 %a, i32* %a.addr, align 4
  store i32 %b, i32* %b.addr, align 4
  %0 = load i32, i32* %a.addr, align 4
  %1 = load i32, i32* %b.addr, align 4
  %cmp = icmp sgt i32 %0, %1
  br i1 %cmp, label %if.then, label %if.else

这个是一个基本块(basicblock),开始标签是entry,结束是br跳转指令。里面的alloca是一条申请内存的指令,分别为三个变量申请三块内存;store是一个存值的指令,将某一个值放到刚申请的内存中;load是一个读取值的指令,将指定内存中的值读取出来;icmp是一个比较的指令,根据比较规则比较两个值的关系,返回一个布尔值,sgt就是比较规则,两个有符号的操作数进行大于比较;br是跳转指令,有条件跳转和无条件跳转,这里是条件跳转,根据%cmp的值来决定跳转到if.then基本块还是if.else基本块。(指令部分会在后面做一个专门的介绍,这里只是大概提一下)

if.then:
  %2 = load i32, i32* %a.addr, align 4
  store i32 %2, i32* %retval, align 4
  br label %return

if.else:
  %3 = load i32, i32* %b.addr, align 4
  store i32 %3, i32* %retval, align 4
  br label %return

return:
  %4 = load i32, i32* %retval, align 4
  ret i32 %4
}

这三个分别是三个基本块(basicblock),都有开始标签和结束指令,br label %return是一个无条件跳转;从这里可以看出来,llvm中没有if语句,而是用跳转实现了if,其实llvm也没有while循环,没有switch分支,全部都是用判断+跳转来实现的。

总结】在这个@max函数中总共有四个基本块,每个基本块有若干条指令,这些基本块构成一个CFG(控制流程图),来实现函数的功能。

2.8 全局变量(Global Variables)

LLVM中全局变量的三个关键点:

  • 1)全局变量的定义是在编译期为其分配内存,而不是运行时。
  • 2)定义一个全局变量,必须要将其初始化。
  • 3)全局变量也可以声明在其他的编译单元,声明没有初始化程序(initializer), 所以不能将其初始化。

语法:

@<GlobalVarName> = [Linkage] [PreemptionSpecifier] [Visibility]
                   [DLLStorageClass] [ThreadLocal]
                   [(unnamed_addr|local_unnamed_addr)] [AddrSpace]
                   [ExternallyInitialized]
                   <global | constant> <Type> [<InitializerConstant>]
                   [, section "name"] [, comdat [($name)]]
                   [, align <Alignment>] (, !name !N)*

@<GlobalVarName>是变量名。
<global | constant>这两个关键字必须要有一个。如果是global就是定义一个全局变量,如果是constant就是定义一个全局常量,全局常量不可以修改其值。
<Type>这玩意就是后面定义的常量或者变量的数据类型。

例如:

@sum = common global i32 0, align 4	;相当于C语言中的 int sum;

定义一个全局变量@sumcommon链接类型表示并没有为变量显示初始化一个值,而是初始化了一个i32类型的默认值0;global表示这是一个全局变量;i32是变量的数据类型;0是初始化给变量的值,由于前面是common修饰,所以这个值是默认值;align 4内存对齐值为4,也就是说存放这个变量的首地址必须是4的倍数,函数的介绍中说过内存对齐,也给了一个介绍的链接。

@.str = private unnamed_addr constant [10 x i8] c"sum = %d\0A\00", align 1

定义一个全局常量;private表示该常量只能在当前模块中使用;unnamed_addr属性表示该常量的地址不重要,表在意;constant表示的是你定义的是一个常量;[10*i8]表示常量是一个长度为10的i8数组;c"sum = %d\0A\00"就是常量值,没错c不能少,常量值中\0A对应的是转义字符\n\00就是字符串的结束符;align 1对齐值为1。

【注】在llvm中,只要是关于字符串的使用,都会将其定义为一个全局常量,只要用到该字符串直接引用常量值就OK。

2.13 别名(Aliases)

Aliases与函数或变量不同,不会创建任何新数据,它们只是现有位置的新符号和元数据。

Aliases有一个名称和一个别名(aliasee,这个别名并不是Aliases),这个别名是全局值或常量表达式。

Aliases可以拥有一个可选的链接标识,一个可选的可见性样式,一个可选的DLL存储类别和一个可选的TLS模型。

语法:

@<Name> = [Linkage] [PreemptionSpecifier] [Visibility] [DLLStorageClass] [ThreadLocal] [(unnamed_addr|local_unnamed_addr)] alias <AliaseeTy>, <AliaseeTy>* @<Aliasee>

链接标识必须是private,linker_private,linker_private_weak,internal,linkonce,weak,linkonce_odr,weak_odr,external中的一个。

如果给定了local_unnamed_addr属性,则表明该地址在模块中不重要。

由于别名只是第二个名称,会有一些使用限制,其中一些只能在产生目标文件时被检查:

  • 1)定义别名的表达式在编译(assembly)时必须是可计算的。因为它只是一个名称,所以不能使用重定位。
  • 2)表达式中的别名不能是weak(虚弱的,我没明白weak在这里表示的是weak链接类型还是只是一个单词,根据我的判断应该是链接类型),因为在对象文件中无法表示被覆盖的中间别名。
  • 3)表达式中的全局值不能是一个被声明的值,因为这需要重新定位,而这是行不通的。

2.14 IFuncs

IFuncs和别名一样,不产生任何新的数据或函数。它们仅仅一个动态链器调用一个解析函数在运行时解析(resolves)的新符号。

IFuncs有一个名字和一个通过动态链接器返回另外一个函数地址获得的相关名称的函数调用解析器。

IFuncs有一个可选的链接类型和一个可选的可见性样式。

语法:

@<Name> = [Linkage] [Visibility] ifunc <IFuncTy>, <ResolverTy>* @<Resolver>

2.15 Comdats

2.16命名元数据(Named Metadata)

2.17参数属性(Parameter Attributes)

2.18垃圾回收策略名称(Garbage Collector Strategy Names)

2.19前缀数据(Prefix Data)

2.20序言数据(Prologue Data)

2.21 Personality函数(Personality Function)

2.22属性组(Attribute Groups)

2.23函数属性(Function Attributes)

2.24全局属性(Global Attributes)

2.25操作数集(Operand Bundles)

2.26逆优化操作数集(Deoptimization Operand Bundles)

2.27 Funclet操作数集(Funclet Operand Bundles)

2.28 GC转换操作数包(GC Transition Operand Bundles)

2.29模块级内联汇编(Module-Level Inline Assembly)

2.30数据布局(Data Layout)

2.31目标Triple(Target Triple)

2.32指针别名规则(Pointer Aliasing Rules)

2.33易失存储器访问(Volatile Memory Accesses)

2.34并发操作的内存模型(Memory Model for Concurrent Operations)

2.35原子存储器顺序约束(Atomic Memory Ordering Constraints)

2.36浮点环境(Floating-Point Environment)

2.37 Fast-Math标记(Fast-Math Flags)

2.38 Use列表顺序指令(Use-list Order Directives)

2.39源文件名(Source Filename)

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值