LLVM语言参考手册
- 1. 摘要(Abstract)
- 2. 简介(Introduction)
- 3. 标识符(Identifiers)
- 4. 高级结构(High Level Structure)
- 4.1 模块结构(Module Structure)
- 4.2 链接类型(Linkage Types)
- 4.3 调用约定(Calling Conventions)
- 4.4 可见性风格(Visibility Styles)
- 4.5 DLL存储类(DLL Storage Classes)
- 4.6 线程本地存储模型(Thread Local Storage Models)
- 4.7 运行时抢占说明符(Runtime Preemption Specifiers)
- 4.8 结构类型(Structure Types)
- 4.9 非整型指针类型(Non-Integral Pointer Type)
- 4.10 全局变量(Global Variables)
- 4.11 函数(Functions)
- 4.12 别名(Aliases)
- 4.13 IFuncs
- 4.14 Comdats(此选项允许编译器以封装函数)
- 4.15 命名元数据(Named Metadata)
- 4.16 参数属性(Parameter Attributes)
- 4.17 垃圾回收策略名称(Garbage Collector Strategy Names)
- 4.18 前缀数据(Prefix Data)
- 4.19 序言数据(Prologue Data)
- 4.20 Personality函数(Personality Function)
- 4.21 属性组(Attribute Groups)
- 4.22 函数属性(Function Attributes)
- 4.23 全局属性(Global Attributes)
- 4.24 操作数包(Operand Bundles)
- 4.25 模块级内联汇编(Module-Level Inline Assembly)
- 4.26 数据布局(Data Layout)
- 4.27 目标Triple(Target Triple)
- 4.28 指针别名规则(Pointer Aliasing Rules)
- 4.29 易失存储器访问(Volatile Memory Accesses)
- 4.30 并发操作的内存模型(Memory Model for Concurrent Operations)
- 4.31 原子存储器顺序约束(Atomic Memory Ordering Constraints)
- 4.32 浮点环境(Floating-Point Environment)
- 4.33 Fast-Math标记(Fast-Math Flags)
- 4.34 Use列表顺序指令(Use-list Order Directives)
- 4.35 源文件名(Source Filename)
- 5. 类型系统(Type System)
- 6. 常量(Constants)
- 7. 其他值(Other Values)
- 8. 元数据(Metadata)
- 9. 模块标记元数据(Module Flags Metadata)
- 10.自动链接器标记命名元数据(Automatic Linker Flags Named Metadata)
- 11. ThinLTO总结(ThinLTO Summary)
- 12. 固有的全局变量(Intrinsic Global Variables)
- 13. 指令参考(Instruction Reference)
- 14. 固有的功能(Intrinsic Functions)
此文为译文,点击 此处查看原文
1. 摘要(Abstract)
这篇文档是LLVM
汇编语言(assembly language
)的参考手册。LLVM是一个基于静态单赋值(Static Single Assignment
,简写为SSA
)的表示形式,它提供了类型安全(type safety
)、低级别操作(low-level operations
)、灵活性、清晰表示“所有”高级语言的能力。它是贯穿LLVM编译策略全阶段的通用代码表示。
2. 简介(Introduction)
LLVM
代码表示形式被设计为使用三种不同的格式:
- 作为在内存中的编译器中间语言(
Intermediate Representation
,简称IR
); - 作为在硬盘上的位码(
bitcode
)表示(适合JIT
编译器快速加载); - 作为适合人类阅读的汇编语言表示。
这些允许LLVM
为编译器的高效转换和分析提供强大的IR
,同时提供一个自然的方法来调试和可视化转换。这LLVM
的三种不同方式是等价的。本文档描述了人类可读的表示和标注。
LLVM
表示的目标是轻量级和低级别,同时变得更具表示力、类型化、可扩展性。它的目的是变成“通用IR
”序列,在一个足够的低级别上高级别思维可以清晰地映射到它(类似微处理器都是“通用IR”,使得众多源语言可以映射到它)。根据提供类型信息,LLVM
可以作为优化的目标:例如,通过指针分析证明,一个C
自动变量从没被当前函数以外的外部访问到,允许它提升到一个简单的SSA
值从而代替内存地址。
2.1 结构良好性(Well-Formedness)
注意这个文档描述“格式良好的”LLVM
汇编语言。这有区别于“可以被解释的就是(well-formed)格式良好的” 的概念。例如,下面的指令语法上是可接受的,但不是格式良好的:
%x = add i32 1, %x
因为%x
的定义并不支配它所有的使用者。LLVM
基础结构提供了一个验证pass,它可以用于验证LLVM
模块是否格式良好的。这个pass是在解释器解释输入汇编之后和优化器输出位码之前,由解释器和优化器自动运行的。被verifier pass指出的违规行为表现为转换passes中的bug或解释器的输入中的bug。
3. 标识符(Identifiers)
LLVM
标识符有两种基本类型:全局和局部。全局标识符(函数、全局变量)以‘@
’字符开始。局部标识符(寄存器名,类型)以‘%
’字符开始。此外,还有三种用于不同目的的标识符格式:
命名值
被表示为一个带有前缀字符的string。例如,%foo、@DivisionByZero、%a.really.long.identifier。它的正则表达式可以描述为‘[%@][-a-zA-Z$._][-a-zA-Z$._0-9]*
’。需要它们名称中其他字符的标识符可以使用引号包围。特殊字符使用“\xx”可能溢出,这里xx是字符十六进制的ASCII码。这种方式下,任何字符可以在一个name value中使用,甚至引号自身。“\01”前缀可以禁止全局变量的名字改编。未命名值
被表示为一个带有前缀的无符号数值。例如,%12、@2、%44。- 常量,将会在接下来的常量章节中详细描述。
LLVM
需要值以一个前缀开始的原因有两点:编译器不需要担心名称会和保留字冲突;保留字集在未来可以无危害的扩展。此外,未命名标识符允许编译器迅速提出一个临时变量,且避免和符号表冲突。
LLVM
的保留字和其他语言的保留字很相似。有不同的操作码opcode
(‘add
’、‘bitcast
’、‘ret
’等等),原始类型名(‘void
’、‘i32
’等等),以及其他关键词。这些保留字不可能和变量名冲突,因为它们不以‘%’
或者‘@’
前缀开始。
下面是整型变量%x
乘以8的LLVM
代码例子:
简单的表示:
%result = mul i32 %X, 8
强度削减(strength reduction
)后:
%result = shl i32 %X, 3
困难方法:
%0 = add i32 %X, %X ; yields i32:%0
%1 = add i32 %0, %0 ; yields i32:%1
%result = add i32 %1, %1
%X
乘以8的最后一种方法说明了LLVM
的几个重要的词法特点:
- 注释以‘;’分隔,直到这行结束。
- 未命名临时变量被创建,当计算结果不能赋值给一个命名值时。
- 未命名临时变量是按照数字顺序来的(使用每个函数从0开始的自增长计数器)。值得注意的是基本块和未命名函数参数被包含在这个编号方式中。例如,如果入口基本块未被赋予一个标签名称,以及所有的函数参数都被命名了,它就会给出数字0。
它也展示了在这个文档我们应该遵循的一条约定。当演示指令时,我们应该在这条指令后面添加注释,这个注释定义了被生成值的类型和名称。
4. 高级结构(High Level Structure)
4.1 模块结构(Module Structure)
LLVM
程序由Module
组成,每个模块都是输入程序的一个转换单元。每个模块由函数、全局变量、符号表条目组成。模块可能通过LLVM linker
组合在一起,LLVM linker
合并方法(和全局变量)定义,解决预声明,合并符号表条目。下面是“hello world”
模块的一个示例:
; Declare the string constant as a global constant.
@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00"
; External declaration of the puts function
declare i32 @puts(i8* nocapture) nounwind
; Definition of main function
define i32 @main() { ; i32()*
; Convert [13 x i8]* to i8 ...
%cast210 = getelementptr [13 x i8], [13 x i8] @.str, i64 0, i64 0
; Call puts function to write out the string to stdout.
call i32 @puts(i8* %cast210)
ret i32 0
}
; Named metadata
!0 = !{i32 42, null, !"string"}
!foo = !{!0}
这个例子由一个全局变量“.str”
、一个外部声明的“puts”
函数,一个函数定义“main”
和命名元数据“foo”
组成。
通常来讲,一个模块由全局值(函数和全局变量都是全局值)列表组成。全局值通过一个指向内存位置的指针来表示(在这个例子中,一个指向字符数组的指针,一个指向函数的指针),并且有下面的链接类型中的一个。
4.2 链接类型(Linkage Types)
所有全局变量和函数都有下面链接类型中的一种:
private
具有“private”
链接类型的全局值只能被当前模块中的对象直接访问。特别地,使用一个private全局值链接代码到一个模块,在必要情况下为了避免冲突可能会被重命名。因为这个符号对这个模块来说是private的,所有引用可能被更新。这不会出现在目标文件中的任何符号表中。internal
和private相似,但是在对象文件中这个值显示为一个局部符号(例如ELF中的STB_LOCAL)。这对应着C中的‘static’关键字的概念。available_externally
具有“available_externally”
链接类型的全局值永远不会被发送到与LLVM模块对应的对象文件中。从 linker 的观点来看,一个available_externally全局值等同于一个外部声明。它们的存在是为了允许内联和其他优化在已知全局定义的情况下进行,已知全局定义位于模块之外。具有“available_externally”链接类型的全局值允许随意定义,允许内联和其他优化行为。这个链接类型仅允许定义,而不能声明。linkonce
具有“linkonce”
链接类型的全局值当链接发生时与其他同名的全局值合并。这可能常用于实现一些内联函数、模板或者其他必须在每个转换单元中生成使用的代码等形式,但是之后主体可以被一个更详细的定义覆盖。未引用的linkonce全局值允许被丢弃。注意,linkonce链接实际上并不允许优化器来内联函数的函数体到调用者中,因为它不知道函数定义是程序的最终定义或是否将被一个更明确定义重写。使用“linkonce_odr”链接可以使得内联和其他优化有效。weak
除了未引用的具有“weak”链接标识的全局值可能会被抛弃外,“weak”链接拥有与“linkonce”相同的合并语义。这个标志被使用于在C源代码中被声明为“weak”的全局值。common
“common”链接更类似于“weak”链接,但是他们常用于C语言的初步定义(tentative definitions),例如在全局作用域的“int X;”
。具有“common”链接类型的符号和“weak”符号被合并的方式相同,但这些符号即使未被引用也不会被删除。“common”符号可能不会有一个明确的部分,必须有一个0初始化器,且不可能被标志为‘constant’。函数和别名不可以具有“common”链接类型。appending
“appending”链接只能用于指向数组类型的指针全局变量。但两个具有appending链接类型的全局变量被链接到一起,这两个全局数组被追加合并到一起。 当.o文件链接时,这是类型安全的LLVM具有系统链接器连接具有相同名称的“部分”等效。
不幸的是,这不能符合.o文件的任何特征,所以它仅被用于类似llvm.global_ctors
这些llvm特别解释的变量。extern_weak
这个链接标识的语义遵循ELF对象文件模型:除非被链接,否则带有extern_weak的符号都是weak的。如果没有被链接,该符号会变为null而不是一个未定义引用。linkonce_odr, weak_odr
某些语言允许不同的全局值被合并,例如两个具有不同语义的函数。其他语言,例如C++,确保只等效的全局值才可以合并(“one definition rule”
简写为“ODR ”
) 。这些语言可以使用linkonce_odr
和weak_odr
链接类型来表明全局值将只与等效的全局值合并。否则,这些链接类型与它们的non-odr
版本相同。external
如果上述标识符都没被使用,那么该全局值的是外部可见的,这意味着它参与链接,可用于处理外部符号引用。
一个函数声明拥有除“external”
或“extern_weak”
以外的链接类型是不合法的。
4.3 调用约定(Calling Conventions)
LLVM
的函数、call和invoke都有一个可选的调用约定来指定调用。每一对动态调用者/被调用者的调用约定必须匹配,或者程序行为是未定义的。下面的调用约定是LLVM
所支持的,未来将会添加更多的:
-
“ccc” —— C调用约定
这个调用约定(在没有指定其他调用约定的前提下是默认调用约定)匹配目标C调用约定。这个调用约定支持变长参数函数调用,允许在声明的原型中有一些不匹配以及实现了函数的声明(与普通C一样)。 -
“fastcc” —— 快速调用约定
这个调用约定试图使调用尽可能地快速(例如通过寄存器传递)。这种调用约定允许目标使用任何它想要为目标生成快速代码的技巧,而不必遵守外部指定的ABI(应用程序二进制接口,Application Binary Interface)。只有在使用GHC或HiPE约定时,才能优化Tail调用。这个调用约定不支持变长参数,且要求所有被调用对象的原型与函数定义的原型精确匹配。 -
“coldcc” —— 冷调用约定
这个调用约定尝试在假定调用通常不执行的情况下,使调用方中的代码尽可能高效。因此,这些调用通常保存所有寄存器,以便调用不会破坏调用方中的任何活动范围。这个调用约定不支持变长参数,且要求所有被调用对象的原型与函数定义的原型精确匹配。更进一步地讲,内联器不会考虑把这种函数进行内联。 -
“cc 10” —— GHC(Glasgow Haskell Compiler)约定
这个调用约定是专门为Glasgow Haskell编译器(GHC)实现的。它在寄存器中传递所有内容,通过禁用被调用者保存寄存器来达到这个目的。这个调用约定不应该轻轻地使用,这种调用约定不应该轻易使用,而应该只用于特定的情况,比如在实现函数式编程语言时经常使用的寄存器固定性能技术的替代方法。目前只有X86支持这个约定,它有以下限制:- 在X86-32上只支持最多4位类型参数。不支持浮点类型。
- 在X86-64上只支持最多10位类型参数和6个浮点参数。
这个调用约定支持 tail 调用优化,但是要求调用者和被调用者都使用它。
-
“cc 11” —— HiPE调用约定
这个调用约定是专门为HiPE (高性能Erlang,High-Performance Erlang)编译器实现的,它是Ericsson开源Erlang/OTP系统的本机代码编译器。它使用比普通C调用约定更多的寄存器来传递参数,并且定义了no callee-saved寄存器。这个调用约定正确地支持 tail 调用优化,但要求调用方和被调用方都使用它。它使用一种寄存器固定机制,类似于GHC约定
,用于将经常访问的运行时组件固定到特定的硬件寄存器。目前只有X86支持这种约定(32位和64位)。 -
“webkit_jscc” —— Webkit的JavaScript调用约定
这个调用约定为WebKit FTL JIT实现。它将栈上的参数从右向左传递(与cdecl一样),并在平台的常规返回寄存器中返回一个值。 -
“anyregcc” —— 用于代码修补的动态调用约定
这是一种特殊的约定,它支持在调用站点上修补一个任意代码序列。这个调用约定强制调用参数到寄存器中,但是允许它们被动态分配。目前只能在调用llvm.experimental.patchpoint时使用这个调用约定。因为只有这个内在的东西记录了它的参数在边表中的位置。参见LLVM中的堆栈映射和补丁点。 -
“preserve_mostcc” —— PreserveMost调用约定
这个调用约定试图使调用者中的代码尽可能不受干扰。这个约定在传递参数和返回值的方式上与C调用约定
相同,但是它使用一组不同的调用者/被调用者保存的寄存器。这减轻了在调用方中调用前后保存和恢复大型寄存器集的负担。如果参数在被调用者保存的寄存器中传递,那么它们将由被调用者在整个调用过程中保存。这不适用于在调用保存的寄存器中返回的值。- 在X86-64上,除R11外,被调用者保存所有通用寄存器。R11可用作一个暂存寄存器。浮点寄存器(XMMs/YMMs)不被保存,需要由调用者保存。
这个约定背后的思想是支持对具有一个热路径和一个冷路径的运行时函数的调用。热路径通常是一小段代码,不使用很多寄存器。冷路径可能需要调用另一个函数,因此只需要保存调用者保存的寄存器,这些寄存器还没有被调用者保存。就调用者/被调用者保存的寄存器而言,
PreserveMost调用约定
与cold调用约定
非常相似,但是它们用于不同类型的函数调用。coldcc是用于很少执行的函数调用,而preserve_mostcc函数调用是针对热路径的,并且肯定执行了很多次。此外,preserve_mostcc并不会阻止内联程序内联函数调用。
这个调用约定将被ObjectiveC运行时的一个未来版本所使用,因此在这个时候仍然应该被认为是实验性的。尽管创建此约定是为了优化对ObjectiveC运行时的某些运行时调用,但它并不限于此运行时,将来可能还会被其他运行时使用。目前的实现只支持X86-64,但其目的是在将来支持更多的体系结构。 -
“preserve_allcc” —— PreserveAll调用约定
这个调用约定试图使调用者中的代码比PreserveMost调用约定更具侵入性。这个调用约定在传递参数和返回值方面的行为也与C调用约定相同,但是它使用了一组不同的调用者/被调用者保存的寄存器。这消除了在调用方中调用前后保存和恢复大型寄存器集的负担。如果参数在被调用者保存的寄存器中传递,那么它们将由被调用者在整个调用过程中保存。这不适用于在调用保存的寄存器中返回的值。- 在X86-64上,除R11外,被调用者保存所有通用寄存器。R11可用作暂存寄存器。此外,它还保存所有浮点寄存器(XMMs/YMMs)。
这种约定背后的思想是支持对运行时函数的调用,而不需要调用任何其他函数。
与PreserveMost调用约定
一样,这个调用约定将被ObjectiveC运行时的一个未来版本使用,因此在这个时候仍然应该被认为是实验性的。 -
“cxx_fast_tlscc” —— 用于访问函数的CXX_FAST_TLS调用约定
Clang生成一个访问函数来访问C++风格的TLS。这个访问函数通常有一个入口块,一个出口块以及一个初始化块,这样就可以在第一时间运行。这个入口和出口块可以访问少量TLS IR变量,每次访问都将降低到特定于平台的顺序。
这个调用约定的目的是通过保存尽可能多的寄存器(所有驻留在快速路径上的寄存器,由入口块和出口块组成)来最小化调用者的开销。
这个调用约定在传递参数和返回值方面的行为与C调用约定
相同,但是它使用一组不同的调用者/被调用者保存的寄存器。
由于每个平台都有自己的降低序列,因此有自己的保存寄存器集,因此我们不能使用现有的PreserveMost。- 在X86-64上,除RDI和RAX外,被调用者保存所有通用寄存器。
-
“swiftcc” —— 用于Swift语言的调用约定
- 在X86-64上,RCX和R8都支持额外的整型返回,XMM2和XMM3都支持额外的浮点/向量返回。
- 在iOS平台上,使用的是AAPCS-VFP调用约定。
-
“cc ”-编号约定
任何一个调用约定可能有数字指定,允许使用目标明确的调用约定。目标明确调用约定是在64位开始的。
更多调用约定会以使用为依据被添加/定义,如支持Pascal
约定或者其他知名目的独立的约定。
4.4 可见性风格(Visibility Styles)
所有全局变量和函数有一个如下的可见性模式:
“default”-默认模式
在那些使用ELF对象文件格式的目的,默认可见性意味着声明对于其他模块是可见的,并且在可共享库的话意味着这个声明的实体是可被覆盖的。在Darwin平台,默认可见性意味着声明对于其他模块是可见的。默认可见性与在这种语言中的“external linkage”
是一致的。“hidden”-隐藏模式
如果一个带有隐藏可见性的对象的两个声明处于一个相同可共享对象,那么它们会被引用到同一对象。通常来说,隐藏可见性表明符号不会被放置到动态符号表,因此其他模块(可执行程序或共享库)不可以直接引用这个符号。“protected”-保护模式
在ELF中,保护可见性标明符号将会被防止在动态符号表中,但是在定义模块内的引用对本地符号表是捆绑的。因此符号是不可被其他模块覆盖的。
internal
和private
链接的符号表必须有默认可见性。
4.5 DLL存储类(DLL Storage Classes)
所有全局变量、函数和别名可以有一个如下DLL
存储类别:
dllimport
“dllimport”会导致编译器通过一个指向到被DLL导出符号创建的指针的全局指针,来引用一个函数或变量。在微软Windows目标,这个指针名由结合_imp和函数或变量的名称来规范。dllexport
“dllexport”会导致编译器提供一个指向一个在DLL的指针的全局指针,所以它可以被带有dllimport的属性引用到。在微软Windows目标,这个指针名由结合_imp和函数或变量的名称来规范。为了使编译器,汇编器和链接器知道某个符号是被外部引用并且防止它被删除,这个存储类别为了定义一个dll接口而存在。
4.6 线程本地存储模型(Thread Local Storage Models)
一个变量可能被定义为thread_local
,这意味着它不会被线程分享(每个线程有独立的变量拷贝)。并不是所有目标支持线程本地存储变量。作为一个选项,TLS
模型可能被指定:
localdynamic
标识仅用于当前可共享库的变量initialexec
标识在模块中不会被动态加载的变量localexec
标识只能定义且只能使用在可执行程序中的变量
如果没有给于明确类型,“general dynamic”
将会被使用。
这种模型与ELF TLS
模型是一致的;更多关于不同模块可能被使用的情况信息详见ELF Handling For Thread-Local Storage。如果指定的模型不支持或者有更合适的模型可供选择,目标可能会选择不同TLS
模型。
一个模型可以在别名中指定,但是它仅支配别名怎么访问。它不会在别名中有实际效果。
对于链接器不支持ELF TLS
模型的平台,-femulated-tls
标记可被使用生成GCC
兼容的竞争TLS
代码。
4.7 运行时抢占说明符(Runtime Preemption Specifiers)
全局变量、函数和别名可以有一个可选的运行时抢占说明符。如果没有显式地给出抢占说明符,则假定符号为dso_preemptable。
- dso_preemptable
指示可以在运行时用链接单元外部的符号替换函数或变量。 - dso_local
编译器可以假设一个标记为dso_local的函数或变量将解析为同一链接单元中的符号。即使定义不在此编译单元中,也将生成直接访问。
4.8 结构类型(Structure Types)
LLVM
的中间语言允许同时指定“identified”
和“literal”
结构类型。文字类型是唯一结构,但是确定类型永远不是唯一的。一个不透明结构类型也可以用于直接定义一个不可使用的类型。
确定结构说明的例子如下:
%mytype = type { %mytype*, i32 }
在LLVM 3.0
发布版本之前,确定类型是结构唯一的。仅文字类型在LLVM
最近版本是唯一的。
4.9 非整型指针类型(Non-Integral Pointer Type)
注意:非整型指针类型是在进程中执行的,它们此时被认为是实验的。
LLVM
中间语言选择允许前端代表在特定地址空间指针通过:ref:datalayout string<langref_datalayout>
作为“non-integral”
。非整型指针类型展示有未指明按位表现的指针,那是整型表现可能目标独立或者易变的(不被合适整型返回)。
inttoptr
指令转换整型到非整型指针类型是病态类型,因此ptrtoint
指令转换非整型指针类型值到整型。指令中提到的向量版本也是病态类型。
4.10 全局变量(Global Variables)
全局变量在编译期定义存储分配范围,而不是运行时。
全局变量定义必须被初始化。
全局变量在其他的转换单元也可以被声明,但在这种情况下它们没有初始化公式。
每个全局变量的定义或声明可以有明确部分来放置或者可能有可选明确的队列指定。
一个变量可以被定义为全局长了,它指明了变量内容永远不会被改变(为了更好优化,允许全局数据放置在执行块的只读部分,诸如此类)。注意的是需要运行时初始化的变量不可以标记constant
,那只能存储到变量。
LLVM
明确允许全局变量声明标记为常量,甚至全局变量的最终定义不是的。这个功能常用于程序轻微更好的优化,但是这要求语言定义保证基于‘constantness’
的优化对于不包含这个定义的编译单元是有效的。
作为SSA
值,全局变量定义为指针值,其作用域(例如它们的影响范围)是程序中的所有基本块。全局变量总是定义为一个其‘content’
所对应类型的指针,因为它们描述一个存储范围,所有这些LLVM
中的存储对象被通过这个指针访问。
全局变量可以被unnamed_addr
标识,表明它的地址是没有意义的,仅仅是指向对应内容。一个被这样标识的常量可以被合并到其他拥有相同初始化公式的常量。注意,一个地址有意义的常量可以被合并一个unnamed_addr
常量,并且合并结果是地址有意义的常量。
如果给于一个local_unnamed_addr
属性,在模块内的地址是没有意义的。
一个全局变量可能被声明驻留在一个目标指定的编号地址空间。对于支持它们的目标,地址空间会影响优化被怎么执行及使用什么指令来访问变量。默认的地址空间是0
。地址空间限定符必须放在其他任何属性前。
LLVM允许一个明确部分用于指定全局变量。如果目标支持它,它会发步全局变量到这个指定部分。 此外如果目标必要支持,全局变量可以存放在此选项允许编译器以封装函数。
默认下,全局初始化公式通过假设被定义全局变量优化,它们的来自在全局初始化公式开始之前初始值的模块是不会被修改。对于可能从外部访问的变量,包括外部链接、或者出现在@llvm.used
、或者 dllexported
变量,这种也是正确的。这种猜想会被带有externally_initialized
的变量抑制。
一个显式队列可能被标识于一个全局变量,这个一定是2的次幂。如果队列不存在,或者队列被设置为0,那么这个全局变量的队列将会被目标根据方便性设置。如果一个显式队列被标识,这个全局变量被迫完全拥有精确队列。如果这个全局变量有一个被分配到部分,目标和优化器不会允许全局变量超过对齐。在这种情况下,额外的队列是显而易见的:例如,代码可能猜想全局变量被集中放置到它们的部分中,并尝试以数组形式遍历它们,但队列填充会打破遍历过程。 最大的队列是1 << 29
。
全局变量同样可以拥有一个DLL
存储类别和可选的附加元数据列表,变量与别名可以有TLS模型。
语法如下:
@<GlobalVarName> = [Linkage] [Visibility] [DLLStorageClass] [ThreadLocal]
[(unnamed_addr|local_unnamed_addr)] [AddrSpace]
[ExternallyInitialized]
<global | constant> <Type> [<InitializerConstant>]
[, section "name"] [, comdat [($name)]]
[, align <Alignment>] (, !name !N)*
例如,下面定义了在编号地址空间的全局变量,它有初始化公式、部分和队列:
@G = addrspace(5) constant float 1.0, section "foo", align 4
下面的例子仅仅声明了全局变量:
@G = external global i32
下面的例子定义了一个带有initialexec
的TLS模型的线程本地全局变量:
@G = thread_local(initialexec) global i32 0, align 4
4.11 函数(Functions)
LLVM
函数定义由“define”
关键字,一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别,一个可选的调用约定,一个可选的unnamed_addr
属性,一个返回值类型,一个可选的返回值类型的参数属性,一个函数名,一个(可能为空的)参数列表(每一个都带有可选的参数属性),可选的函数属性,一个可选的部分,一个可选的队列,一个可选的此选项允许编译器以封装函数,一个可选垃圾回收期的名称,一个可选的前缀,一个序言,一个可选的特征,一个可选附加元数据列表,一个左括号,一个基本块列表和一个右括号。
LLVM
函数声明由“declare”
关键字,一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别,一个可选的调用约定,一个可选的unnamed_addr
属性,一个返回值类型,一个可选的返回值类型的参数属性,一个函数名,一个可能为空的参数列表,一个可选的队列,一个可选垃圾回收器名称,一个可选的前缀和一个可选的序言。
一个函数定义包含一个基本块的列表,可以为函数形成CFG
(控制流图形)。每一个基本块可选的开始于一个标签(给定这个基本块一个符号表入口),包含一个指令列表,并且以一个终止指令(例如分支或函数返回)结束。如果一个明确标签不被提供,一个块会分配到一个隐式编号标签,使用与匿名临时变量使用同一个计数器的下一个值。例如,如果一个函数入口块不具有一个显示标签,他会被分配到一个标签“%0”
,然后第一个在这个块中的匿名临时变量将会是“%1”
,诸如此类。
函数中的第一基本块特殊在两个方面:它是在函数进入后马上执行的,并且它是不允许有前置基本块(即函数入口块不能拥有任何分支)。因为这个块没有前置块,所以也不能有任何PHI
节点。
LLVM
允许函数指定一个明确部分。如果目标支持它,它会放步函数到这个指定块。此外,函数可以被放置在COMDAT
。
一个显示队列可能会被指定到一个函数。如果没有展示,或者队列被设置为0
,那么这个函数的队列将会根据目标的方便设定。如果一个显式队列被指定,这个函数被迫至少想指定的那么多队列。所有队列必须为2的次幂。
如果unnamed_addr
属性被给于,函数地址会被认为是没有意义,并且两个相同的函数之间可以被合并。
如果local_unnamed_addr
属性被给于,地址将会在模块内没有意义。
语法:
define [linkage] [visibility] [DLLStorageClass]
[cconv] [ret attrs]
<ResultType> @<FunctionName> ([argument list])
[(unnamed_addr|local_unnamed_addr)] [fn Attrs] [section "name"]
[comdat [($name)]] [align N] [gc] [prefix Constant]
[prologue Constant] [personality Constant] (!name !N)* { ... }
参数列表是逗号分隔参数序列,每个参数如下形式所示:
语法:
<type> [parameter Attrs] [name]
4.12 别名(Aliases)
别名和函数活变量不同,不产生任何新的数据。它仅仅是已存在位置的一个新的符号和元数据。
别名有一个名称和一个别名,不管是全局变量还是常量表达式。
别名可以拥有一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别和一个可选的tls模型。
语法:
@<Name> = [Linkage] [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
中的一个。注意一些系统链接器可能会不正确地处理一个降级弱符号作为非别名。
非unnamed_addr
的别名被别名表达式的同样地址保护。unnamed_addr
仅仅保护指向同样的内容。
如果local_unnamed_addr
属性被给于,地址将会在模块内没有意义。
因此别名仅仅是第二名称,一些在产生对象文件是仅被检查的约束提供:
- 表达式定义别名必须在组装时间可计算。因此它仅仅是个名称,没有重定位被使用。
- 表达式中没有别名可以弱化中间别名被覆盖而不展示在对象文件中的可能性。
- 表达式中没有全局值可以声明,因此需要一个重定位,但是不是可能的。
4.13 IFuncs
IFuncs
和别名一样,不产生任何新的数据或函数。它们仅仅一个动态链器调用一个解析函数在运行时解析(resolves)的新符号。
IFuncs
有一个名字和一个通过动态链接器返回另外一个函数地址获得的相关名称的函数调用解析器。
IFuncs
有一个可选的链接类型和一个可选的可视性模型。
语法:
@<Name> = [Linkage] [Visibility] ifunc <IFuncTy>, <ResolverTy>* @<Resolver>
4.14 Comdats(此选项允许编译器以封装函数)
Comdat IR提供了进入COFF
和ELF
对象文件的COMDAT
功能性。
Comdat
有个展示COMDAT key
的名称。如果链接器选择用key
覆盖其他的,所有指定key
的全局对象将会在最终对象中结束。如果真有的话,别名放置在和需要计算别名的COMDAT
一样的位置。
语法:
$<Name> = comdat SelectionKind
这种选择必须是下面的一种:
any
链接器可能选择任意COMDAT key
,这个选择是随意的。exactmatch
这个链接器可能选择任意COMDAT key
,但是选择部分必须包含同样的数据。largest
这个链接器将会选择包含最大COMDAT key
部分。noduplicates
这个链接器需要这个COMDAT key
存在的唯一部分。samesize
这个链接器可能选择任意COMDAT key
,但是部分必须包含相同数量的数据。
注意,Mach-O
平台不支持COMDAT
,ELF仅支持any
作为选项。
下面是一个如果COMDAT key
部分是最大的,函数仅将被选择的COMDAT组例子:
$foo = comdat largest
@foo = global i32 2, comdat($foo)
define void @bar() comdat($foo) {
ret void
}
作为一个语法糖,$name
如果和全局名称一致将会被遗漏:
$foo = comdat any
@foo = global i32 2, comdat
在COFF对象文件中,这将创建一个包含@foo符号内容的IMAGE_COMDAT_SELECT_LARGEST选择类型的COMDAT部分和另一个与COMDAT第一部分和包含@bar符号内容相关的IMAGE_COMDAT_SELECT_ASSOCIATIVE选择类型的COMDAT部分。
全局对象属性有一定的限制。当针对COFF时候,它,或别名,必须有和COMDAT组一样的名称。这个对象的内容和大小可能在链接时根据选择类型决定选择COMDAT组。因为对象名称必须与COMDAT组名称匹配,全局对象的链接器不能是局部的;如果局部符号在符号表与其他的冲突了可以修改名称。
COMDAT和部分属性的结合使用可以产生令人惊讶的结果。例如:
$foo = comdat any
$bar = comdat any
@g1 = global i32 42, section "sec", comdat($foo)
@g2 = global i32 42, section "sec", comdat($bar)
根据对象文件观点,这需要创建具有相同名称的两个部分。在对象文件层次,因为属于不同的COMDAT组和COMDAT的全局通过部分表现,所以这是必要的。
注意,特定中间语言构造全局变量和函数可以在除了任何使用COMDAT中间语言指定的对象文件创建COMDAT。这是当代码生成器被配置为在个别部分发出全局(比如-data-sections或者-function-sections支持llc)。
4.15 命名元数据(Named Metadata)
命名元数据是一个元数据的集合。元数据节点(但非元数据字符串)是唯一对于具名元数据有效的操作数。
1.命名元数据被表示为一个带有元数据前缀的字符串。元数据名称的规则与标识符相同,但不允许引用名称。“\xx”类型的反斜杠仍然有效,它允许任何字符成为名称的一部分。
语法:
; Some unnamed metadata nodes, which are referenced by the named metadata.
!0 = !{!"zero"}
!1 = !{!"one"}
!2 = !{!"two"}
; A named metadata.
!name = !{!0, !1, !2}
4.16 参数属性(Parameter Attributes)
返回类型和每一个函数的参数类型可能拥有一个参数属性集。参数属性是用于交流函数返回值和参数的额外信息。可以认为参数类型是函数的一部分,但不是函数类型的一部分,因此用不同参数属性的函数可以拥有相同的函数类型。
参数属性是跟随指定类型后的简单关键字。如果需要多个参数属性,它们需要被空白符分隔开。下面是详细例子:
declare i32 @printf(i8* noalias nocapture, ...)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
注意,任何对于函数结果(nounwind, readonly)的属性跟随在参数列表后。
当前仅定义了以下的参数属性:
zeroext
这表明了在代码生成器时,参数或返回值应该被扩充零到通过调用者(参数长度)和被调用者(返回值值)得到目标ABI。signext
这表明在代码生成器时,参数或返回值应该被符号扩展到通过调用者(参数长度)和被调用者(返回值值)得到目标ABI(一般来说是32位)。inreg
这表明参数和返回值在函数调用的发散代码或返回值的期间被一个特定目标独立方法处理(通常来说,把它们放到寄存器而不是放到内存中,即使一些目标使用它来区分两种不同的寄存器)。这个属性的使用的针对指定目标的。byval
这表明指针参数应该通过值方式传递给函数。这个属性包含了指针隐藏复制在调用者和被调用者之间,因此使被调用者不能修改在调用者值。这个属性仅在LLVM指针参数里是有效的。这通常用于传递值的结构体和数组,但这对于数量指针也是有效的。 这个复制被认为是从属于调用者而不是被调用者的(例如,readonly函数不应该写一个byval 的参数)。这个属性对于返回值是无效的。
byval属性也支持指定队列属性。这指明了栈槽格式和指定调用地点的指针队列属性。如果队列属性不被指定的话,代码生成器会做一个目标相关的猜测。inalloca
inalloca参数属性允许调用者带有即将离开栈参数地址。一个inalloca参数必须是一个使用 alloca 指令创建指向栈内存地址的指针。这个分配的堆栈空间或参数分配必须也以inalloca关键字标识。只有最后的参数可以使用inalloca属性,并且这个参数保证必须在内存中传递。
一个参数分配可能被函数调用至少一次因为这个调用可能会释放它。这个inalloca属性不可能与其他会影响参数存储的属性结合使用,例如inreg,nest,sret,和byval。这个inalloca属性也禁止LLVM隐式降低大量聚合返回值,这意味着前端发起者必须通过sret指针降低它们。
当到达调用地点时,参数分配必须是仍然存活的最新栈分配,或者结果是未定义的。在一个参数分配后和这个实参的调用地点前额外分配堆栈空间是可能的,但这必须清除llvm.stackrestore。
更多关于这个属性的使用,详见Design and Usage of the InAlloca Attributesret
这表明这个指针参数指向一个在源程序中作为函数返回值的结构体地址。这个指针必须由调用者保证是有效的:这个结构的加载和存储必须与被调用者所假定是一致的。这只适用于第一个参数。这对于返回值并不是一个有效的属性。align
这表明可以由优化器假定指针值具有指定队列。
注意当结合byval属性时,这个属性有额外的语义。noalias
这表明在执行函数时,通过基于参数或者返回值的指针值传递的访问对象是不可访问的,传递的指针值没有基于参数或者返回值。返回值的属性还有额外的语义描述。调用者与被调用者分享责任确保那些需求匹配。关于NoAlias的更多信息可以详见alias analysis。
注意noalias的定义故意定义得与C99中的函数实参的restrict定义相似,尽管restrict的定义稍微弱一些。
因为对于函数返回至,C99的restrict是无意义的,而LLVM的noalias是有意义的。此外,noalias返回值的属性语义比使用函数参数的属性语义强。在函数返回值上,noalias属性标明该函数的行为像一个系统的内存分配函数,返回一个指针分配存储不相交的可以访问其他任何对象的调用者。nocapture
这表明被调用者不对生存期比被调用者长的指针做出任何复制。对于返回值这不是一个有效的属性。
用于在易变操作中使用的地址认为是可被捕获的。nest
这表明指针参数可以使用弹性内联函数删除。这不是一个有效的返回值属性,只能被应用于一个参数。returned
这表明函数始终返回这个参数作为它的返回值。在生成调用者时使用优化器和代码生成器生成一个提示,允许尾部调用优化和在某种情况下忽略寄存器的保存和恢复;在省城被调用者是,它不会被检查或执行。参数和函数返回类型必须是对于bitcast指令有效的操作数。这不是一个有效的返回值属性,只能应用于一个参数。- null
这表示参数或返回指针不为空。此属性只能应用于指针类型的参数。LLVM没有检查或强制执行;如果参数或返回指针为空,则行为未定义。 - dereferenceable()
这表明参数或返回指针是可取消引用的。此属性只能应用于指针类型的参数。可撤销引用的指针可以从推测性加载,而不会有被捕获的风险。必须在括号中提供已知的可取消引用的字节数。字节数小于指针类型的大小是合法的。nonnull属性并不意味着可取消引用(考虑一个指向数组末尾后一个元素的指针),但是可取消引用()确实意味着addrspace(0)中的非null(0是默认地址空间)。 - dereferenceable_or_null()
这表明参数或返回值不是同时不可空和不可取消引用的(小于n>字节)。所有标记为dereferenceable_or_null()的非空指针都是可取消引用的()。对于地址空间0 dereferenceable_or_null()意味着一个指针恰好是可取消引用的()或null中的一个,而在其他地址空间中,dereferenceable_or_null()意味着一个指针至少是可取消引用的()或null中的一个(即它既可以是空的,也可以是可取消引用的())。此属性只能应用于指针类型的参数。 - swiftself
这表明该参数是self/context参数。这不是返回值的有效属性,只能应用于一个参数。 - swifterror
该属性的目的是建模和优化快速错误处理。它可以应用于指针类型指针或指针大小的alloca的参数。在调用站点上,与swifterror参数对应的实际参数必须来自调用者的swifterror alloca或swifterror参数。swifterror值(参数或alloca)只能被加载和存储,或者用作swifterror参数。这不是返回值的有效属性,只能应用于一个参数。
这些约束允许调用约定优化对swifterror变量的访问,方法是将它们与调用边界处的特定寄存器相关联,而不是将它们放在内存中。由于这确实改变了调用约定,因此对参数使用swifterror属性的函数与不兼容的函数是不兼容的。
这些约束还允许LLVM假设swifterror参数不会别名化函数中可见的任何其他内存,并且作为参数传递的swifterror alloca不会转义。 - immarg
这表明该参数必须是一个即时值。这必须是一个普通的立即整数或浮点常量。Undef或常量表达式无效。这仅对内部声明有效,不能应用于调用站点或任意函数。
4.17 垃圾回收策略名称(Garbage Collector Strategy Names)
每一个函数可以制定一个垃圾回收期的名称,这个名称是一个简单的字符串:
define void @f() gc "name" { ... }
编译器声明了这个名字的可能值。指定一个收集器将会导致编译器会为了支持这个垃圾回收算法修改它的输出。
4.18 前缀数据(Prefix Data)
前缀数据是与函数关联的数据,代码生成器将在函数的入口点之前立即发出该函数。该特性的目的是允许前端将特定于语言的运行时元数据与特定函数关联起来,并通过函数指针使其可用,同时仍然允许调用函数指针。
要访问给定函数的数据,程序可以将函数指针位转换为指向常量类型和取消引用索引-1的指针。这意味着IR符号指向的位置刚好超过前缀数据的末尾。例如,以一个用i32注释的函数为例,
define void @f() prefix i32 123 { ... }
前缀数据可以引用为:
%0 = bitcast void* () @f to i32*
%a = getelementptr inbounds i32, i32* %0, i32 -1
%b = load i32, i32* %a
前缀数据的布局就像它是前缀数据类型的全局变量的初始化器一样。函数的位置将使前缀数据的开头对齐。这意味着如果前缀数据的大小不是对齐大小的倍数,函数的入口点将不会对齐。如果需要对函数的入口点进行对齐,则必须向前缀数据添加填充。
函数可以有前缀数据,但没有正文。这与available_external链接具有类似的语义,因为数据可以由优化器使用,但不会在目标文件中发出。
4.19 序言数据(Prologue Data)
序言属性允许在函数体之前插入任意代码(以字节编码)。这可以用于启用功能热补丁和检测。
为了维护普通函数调用的语义,序言数据必须具有特定的格式。具体地说,它必须从一个字节序列开始,这个字节序列解码为一个机器指令序列,该指令序列对模块的目标有效,它将控制传输到紧接着序言数据的位置,而不执行任何其他可见操作。这允许内联程序和其他传递来推断函数定义的语义,而不需要推断序言数据。显然,这使得序言数据的格式高度依赖于目标。
x86架构的有效开场白数据的一个简单例子是i8 144,它编码了nop指令:
define void @f() prefix i8 144 { ... }
一般来说,前置数据可以跳过元数据通过编码一个相关的branch指令,就像下面这个例子中的对于X86_64体系有效的前置数据,它的前两个byte将被编码jmp.+10
%0 = type <{ i8, i8, i8* }>
define void @f() prologue %0 <{ i8 235, i8 8, i8* @md}> { ... }
函数可能只拥有前置数据但没有主体。这与 available_externally 链接标识有着相同的语义,即数据可能会被优化器使用的不会被发散到对象文件中。
4.20 Personality函数(Personality Function)
personality属性允许函数指定用于异常处理的函数。
4.21 属性组(Attribute Groups)
属性组是在IR中的对象引用的属性的集合。他们对于保持 .ll 可读有着重要意义,因为许都函数会使用相同的属性集。在退化的情况下一个 .ll 文件对应着单一的 .c 文件,这个单一的属性组讲捕获那些用于构建这个文件的重要命令行标识。
一个属性组是一个模块层次的对象。要使用一个属性组,一个对象可以引用这个属性组的ID(例如 #37)。一个对象可能引用超过一个属性组。在这种情况下来自于不同属性组的属性会被合并。
这里有一个规定一个函数应该内联的属性组的例子,并拥有一个堆栈对齐属性值为4,且不使用SSE指令
; Target-independent attributes:
attributes #0 = { alwaysinline alignstack=4 }
; Target-dependent attributes:
attributes #1 = { "no-sse" }
; Function @f has attributes: alwaysinline, alignstack=4, and "no-sse".
define void @f() #0 #1 { ... }
4.22 函数属性(Function Attributes)
函数属性用于函数交流额外信息的。函数属性被认为是函数的一部分,而不是函数类型的一部分,所以拥有不同函数属性的函数可以对应着同一个函数类型。
函数属性是紧跟在指定的函数类型后的简单的关键字。如果以后需要指定多个函数属性,那么使用空格符分隔它们。例如:
define void @f() noinline { ... }
define void @f() alwaysinline { ... }
define void @f() alwaysinline optsize { ... }
define void @f() optsize { ... }
- alignstack()
这个属性指明了,当发散序言和后记,后端应该强制对齐堆栈指针。在括号中指定所希望的对齐属性,其值必须是2的幂。 - allocsize(<EltSizeParam>[, <NumEltsParam>])
这个属性表示带注释的函数将始终返回至少给定数量的字节(或null)。它的参数是零索引的参数数;如果提供了一个参数,则假定至少有CallSite。Args[EltSizeParam]字节将在返回的指针处可用。如果提供了两个,则假定CallSite。Args (EltSizeParam) * CallSite。Args[NumEltsParam]字节是可用的。所引用的参数必须是整数类型。对于返回的内存块的内容不做任何假设。 - alwaysinline
该属性指明了内联器应该尽可能地内联这个函数到调用者中,而忽略这个调用者有效的内联阙值。 - builtin
这表明,在调用点被调用函数应该被认定为内建函数,即使该函数的声明使用了 nobuiltin 属性。这只在直接调用了声明了 nobuiltin 属性的函数的调用地点有效。 - cold
该属性表明了这个函数将很少被调用。在计算边权重时,被一个cold函数所控制的基本块也被认为是cold的,因此将被给予低权重。 - inlinehint
该属性表明,这份源代码包含一个提示,即内联函数是可行的(如C / C + +中的“inline”关键字)。这仅仅是一个提示,它不对内联器做出任何要求。 - minsize
该属性建议优化器pass和代码生成器pass保持函数的代码长度尽量的小且牺牲运行时效率来最小化生成的代码来进行优化。 - naked
这个属性禁止函数的序言/结尾发散。产生的结果是特定于系统的。 - nobuiltin
这表明,在调用点被调用函数不被识别为一个内建函数。 LLVM将保留原来的调用,而不是使用基于内建函数语义等价的代码替换它,除非调用点使用了 builtin 的属性。这个属性对于调用点和函数的声明和定义都是有效的。、 - noduplicate
该属性表明,函数的调用不能被复制。一个 noduplicate 函数的调用可能被移动到父函数,但不能被复制到父函数。
一个包含 noduplicate 函数仍然可能被内联,条件是该调用没有被重复内联。这意味着,该函数具有 internal 链接标识,并只有一个调用点,所以内联后,原始调用被删除。 - noimplicitfloat
该属性表明,禁用隐式的浮点指令 - noinline
此属性表明,在任何情况下,内联器都不应该内联该函数。此属性不能与alwaysinline 属性一起使用。 - nonlazybind
此属性表明,抑制函数的延迟绑定符号特性。如果函数在程序启动时不被调用,那么程序启动会耗费额外的启动时间,但这可能会使函数调用更快。 - noredzone
此属性表明,即使目标平台指定的ABI通常允许,该属性指示代码生成器也不应该使用一个red zone,。 - noreturn
此属性表明,该函数从不正常返回。如果该函数动态返回,这将产生不确定的运行期行为,。 - nounwind
此属性表明,该函数从不返回展开的或异常的控制流。如果函数不展开,其运行时的行为是未定义的。 - optnone
此此属性表明,该函数不能被除了过程间优化 pass 外的任何优化或代码生成器 pass 优化。这个属性不能与一起 alwaysinline 属性使用;这个属性也与 minsize 属性和 optsize 属性不兼容。( minsize 与 optsize 要求优化)
此属性要求在函数中同时指定该属性和 noinline 属性,所以该函数从不内联到任何调用者。只有具备alwaysinline 属性的函数才能内联到这个函数。 - optsize
此属性表明,优化 pass 和代码生成器 pass 应该保持这个函数的代码长度较小,否则进行特定的优化,不显著影响运行期性能的情况下减少代码长度。 - readnone
对于一个函数,这个属性表明该函数严格基于它的参数计算其结果(或决定展开异常),而不通过解引用任何指针参数或以其他方式访问任何调用者可见的可变状态(如内存,控制寄存器等)。它不通过任何指针参数(包括 byval 参数)写入和从不改变调用者可见的任何状态。这意味着它无法通过调用C + +的异常throw的方法展开异常。
对于一个参数,这个属性表明,该函数不能解引用指针参数,尽管如果通过其他指针它可以读取或写入指针参数指向内存(因为调用者可见的是指针的值而不是指针指向的值)。 - readonly
对于一个函数,这个属性表明该函数不通过任何指针参数(包括 byval 参数)或写其他方式修改任何调用者函数可见的状态(如内存,控制寄存器等)。它可能会解引用指针参数和读取在调用者中设置的状态。在使用相同的函数集和全局状态时,一个只读函数总是返回相同的值(或展开相同的异常)。它无法通过调用C + +的异常throw的方法展开异常。
上一个参数,这个属性表示该函数不通过这个指针参数写的,即使它可能写入内存的指针指向。 - returns_twice
该属性表明,该函数可以返回两次。该C setjmp 的是这样一种函数的一个例子。编译器禁用这些函数的调用者一些优化(如尾调用)。 - sanitize_address
该属性表明,为函数启用AddressSanitizer检查(动态地址安全分析)。 - sanitize_memory
该属性表明,为函数启用MemorySanitizer检查(未初始化内存访问的动态检测) - sanitize_thread
该属性表明,为函数启用ThreadSanitizer检查(动态线程安全性分析) - ssp
该属性表明,该函数应该发散一个堆栈溢出保护功能。它有一个“canary”形式 —( a random value placed on the stack before the local variables that’s checked upon return from the function to see if it has been overwritten.)。一个启发式用来确定一个函数需要的堆栈保护与否。在以下情况,这个被使用了启发式将启用函数保护器:
字符数组比 ssp-buffer-size 大(默认值为8)。
字符数组的集合比 ssp-buffer-size 大。
调用alloca()的变量长度或常量长度(alloca的参数)大于 ssp-buffer-size 。
被识别为需要保护的变量将被安排在堆栈,使得对于堆栈保护器它们是连续的。
如果一个函数,它有一个SSP属性被内联到一个不具有SSP属性的函数,然后将得到的函数将具有一个SSP属性。 - sspreq
该属性表明,该函数应该发散一个堆栈溢出保护功能。这将覆盖 ssp 函数属性。
被识别为需要保护的变量将被安排在堆栈,使得对于堆栈保护器它们是连续的。具体布局规则是:
- 大数组和含大数组的结构体(>= sp-buffer-size)是最接近堆栈保护器的。
- 小数组和含小数组的结构体(< sp-buffer-size)是第二接近堆栈保护器的。
- 采取了它们的地址的变量是第三接近堆栈保护器的。
如果一个函数,它有一个 sspreq 属性被内联到一个不具有 sspreq 属性或具有 ssp 或 sspstrong 属性的函数,那么得到的结果函数将具有 sspreq 属性。
- sspstrong
该属性表明,该函数应该发散一个堆栈溢出保护功能。在确定函数是否需要的堆栈保护器时,此属性会使用一个强有力的试探法。强大的启发式将为函数启用保护器:
任何长度和类型的数组
任何长度和类型的数组的集合。
调用了alloca( ) 。
采取了它们的地址的局部变量。
被识别为需要保护的变量将被安排在堆栈,使得对于堆栈保护器它们是连续的。具体布局规则是:
- 大数组和含大数组的结构体(>= sp-buffer-size)是最接近堆栈保护器的。
- 小数组和含小数组的结构体(< sp-buffer-size)是第二接近堆栈保护器的。
- 采取了它们的地址的变量是第三接近堆栈保护器的。
这将覆盖 ssp 功能属性。
如果一个函数,它有一个 sspstrong 属性被内联到一个不具有 sspstrong 属性的函数,那么最后得到的函数将具有 sspstrong 属性。
- uwtable
该属性表明,被指定的ABI要求为这个函数展开表实体,即使我们可以表明没有异常会被这个函数传递。。这通常是针对于ELF x86-64 ABI的情况,但是它可以为某些编译单元被禁用。
4.23 全局属性(Global Attributes)
属性可以设置为传递关于全局变量的附加信息。与函数属性不同,全局变量上的属性被分组为单个属性组。
4.24 操作数包(Operand Bundles)
操作数包是一组标记的SSA值,它们可以与某些LLVM指令相关联(目前仅调用s和调用s)。
语法:
operand bundle set ::= '[' operand bundle (, operand bundle )* ']'
operand bundle ::= tag '(' [ bundle operand ] (, bundle operand )* ')'
bundle operand ::= SSA value
tag ::= string constant
操作数束不是函数签名的一部分,给定的函数可以从具有不同类型操作数束的多个位置调用。这反映了一个事实,即操作数包在概念上是调用(或调用)的一部分,而不是被分派到的被调用方。
操作数包是一种通用机制,用于支持托管语言的运行时自检类功能。虽然操作数捆绑包的确切语义依赖于捆绑包标记,但是对于操作数捆绑包的存在对程序语义的影响程度有一定的限制。这些限制被描述为“未知”操作数包的语义。只要操作数包的行为在这些限制中是可描述的,LLVM就不需要对操作数包有专门的知识,就不会编译包含它的程序。
- 在将控件转移到被调用者或invokee之前,未知操作数的包操作数以未知的方式转义。
- 使用操作数包调用和调用在进入和退出时对堆具有未知的读/写影响(即使调用目标是readnone或readonly),除非使用callsite特定的属性覆盖它们。
- 调用站点上的操作数包不能更改被调用函数的实现。只要考虑到前两个属性,过程间优化就可以正常工作。
下面将描述更具体的操作数束类型。
4.24.1 逆优化操作数包(Deoptimization Operand Bundles)
反优化操作数束的特征是“deopt”操作数束标记。这些操作数包表示它们附加到的调用站点的另一种“安全”延续,合适的运行时可以使用它们在指定的调用站点对编译后的框架进行反优化。调用站点最多可以有一个“deopt”操作数包。反优化的具体细节超出了语言引用的范围,但它通常涉及将编译后的框架重写为一组解释框架。
从编译器的角度来看,反优化操作数包使它们附加到的调用站点至少为只读。它们读取所有指针类型的操作数(即使它们没有转义)和整个可见堆。反优化操作数束不会捕获它们的操作数,除非在反优化期间,在这种情况下,控制将不会返回到编译后的框架。
内联程序知道如何内联通过具有反优化操作数包的调用。就像通过一个普通调用站点进行内联涉及到组合正常延续和异常延续一样,通过一个具有反优化操作数包的调用站点进行内联需要适当地组合“安全”反优化延续。内联程序通过将父类的反优化延续前置到内联体中的每个反优化延续来实现这一点。例如,在下面的例子中把@f内联到@g中
define void @f() {
call void @x() ;; no deopt state
call void @y() [ "deopt"(i32 10) ]
call void @y() [ "deopt"(i32 10), "unknown"(i8* null) ]
ret void
}
define void @g() {
call void @f() [ "deopt"(i32 20) ]
ret void
}
将导致
define void @g() {
call void @x() ;; still no deopt state
call void @y() [ "deopt"(i32 20, i32 10) ]
call void @y() [ "deopt"(i32 20, i32 10), "unknown"(i8* null) ]
ret void
}
前端的职责是构造或编码反优化状态,使调用方的反优化状态在语法上前置到被调用方的反优化状态,在语义上等价于在被调用方的反优化延续之后组合调用方的反优化延续。
4.24.2 Funclet操作数包(Funclet Operand Bundles)
函数操作数包的特征是“函数”操作数包标记。这些操作数包表示调用站点位于特定函式内。一个调用站点最多可以有一个“funclet”操作数包,而且它必须恰好有一个操作数包。
如果任何funclet EH pad已经“输入”但没有“退出”(根据EH doc中的描述),则执行以下调用或调用是未定义的行为:
- 没有“函子”束,并且不是对名词性固有的调用,或者
- 有一个“funclet”包,其操作数不是最近输入但尚未退出的funclet EH pad。
类似地,如果没有函数EH pad被导入(但尚未退出),则使用“函数”包执行调用或调用是未定义的行为。
4.24.3 GC转换操作数包(GC Transition Operand Bundles)
GC转换操作数束的特征是“GC -transition”操作数束标记。这些操作数包将调用标记为具有一个GC策略的函数与具有不同GC策略的函数之间的转换。如果协调GC策略之间的转换需要在调用站点上生成额外的代码,那么这些bundle可能包含生成代码所需的任何值。有关更多细节,请参见GC转换。
4.25 模块级内联汇编(Module-Level Inline Assembly)
4.26 数据布局(Data Layout)
4.27 目标Triple(Target Triple)
4.28 指针别名规则(Pointer Aliasing Rules)
4.29 易失存储器访问(Volatile Memory Accesses)
4.30 并发操作的内存模型(Memory Model for Concurrent Operations)
4.31 原子存储器顺序约束(Atomic Memory Ordering Constraints)
4.32 浮点环境(Floating-Point Environment)
4.33 Fast-Math标记(Fast-Math Flags)
4.34 Use列表顺序指令(Use-list Order Directives)
4.35 源文件名(Source Filename)
5. 类型系统(Type System)
LLVM类型系统是中间表示最重要的特性之一。通过类型化,可以直接对中间表示执行许多优化,而不必在转换之前进行额外的分析。一个强大的类型系统可以更容易地读取生成的代码,并支持新的分析和转换,而这些分析和转换在普通的三地址码表示上是不可行的。
5.1 Void Type
概述:
void类型不代表任何值,也没有大小。
语法:
void
5.2 Function Type
概述:
函数类型可以看作是函数签名。它由一个返回类型和一个形式参数类型列表组成。函数类型的返回类型是void类型或第一类类型 —— label
和 metadata
类型除外。
语法:
<returntype> (<parameter list>)
其中'<parameter list>'
是一个类型说明符的逗号分隔列表。可选地,参数列表可以包含一个类型 …
,表示函数接受可变数量的参数。变量参数函数可以使用变量参数处理内在函数来访问它们的参数。'<returntype>'
是除 label
和 metadata
类型之外的任何类型。
5.3 First Class Types
第一类类型可能是最重要的。这些类型的值是唯一可以由指令生成的值。
5.3.1 Single Value Types
从 ·CodeGen· 的角度来看,这些类型在寄存器中是有效的。
5.3.1.1 Integer Type
概述:
整数类型是一种非常简单的类型,它为所需的整数类型指定任意的位宽。可以指定从1位到2^23-1(约800万)之间的任何位宽。
语法:
iN
该整数将占用的位数由 N
值指定。
例子:
i1是一个1位的整数。
i32是一个32位的整数。
i1942652是一个非常大的整数,超过一百万比特。
5.3.1.2 Floating-Point Types
half、float、double和fp128的二进制格式分别对应于binary16、binary32、binary64和binary128的IEEE-754-2008规范。
5.3.1.3 X86_mmx Type
概述:
x86_mmx
类型表示在 x86 机器上的 MMX 寄存器中持有的值。它所允许的操作非常有限:参数和返回值、加载和存储以及位转换。用户指定的MMX指令表示为带有这种类型的参数和/或结果的内部调用或asm调用。没有这种类型的数组、向量或常量。
语法:
x86_mmx
5.3.1.4 Pointer Type
概述:
指针类型用于指定内存位置。指针通常用于引用内存中的对象。
指针类型可以有一个可选的地址空间属性,该属性定义指向对象所在的编号地址空间。默认的地址空间是number 0。非零地址空间的语义是特定于目标的。
注意,LLVM不允许指针指向void (void*),也不允许指针指向label (label*)。使用i8 *代替。
语法:
<type> *
5.3.1.5 Vector Type
概述:
向量类型是表示元素向量的简单派生类型。当使用一条指令(SIMD)并行操作多个原语数据时,将使用向量类型。向量类型需要大小(元素数量)、底层基本数据类型和可伸缩属性来表示在编译时不知道确切硬件向量长度的向量。向量类型被认为是第一类。
语法:
< <# elements> x <elementtype> > ; Fixed-length vector
< vscale x <# elements> x <elementtype> > ; Scalable vector
元素个数为大于0的常数整数值;elementtype可以是任何整数、浮点或指针类型。大小为零的向量是不允许的。对于可伸缩向量,元素总数是指定元素数的常数倍(称为vscale);vscale是在编译时未知的正整数,在运行时对所有可伸缩向量都是相同的硬件依赖常数。因此,特定可伸缩向量类型的大小在IR中是恒定的,即使直到运行时才能确定其确切的字节大小。
5.3.2 Label Type
概述:
标签类型表示代码标签。
语法:
label
5.3.3 Token Type
概述:
当一个值与一条指令相关联,但该值的所有用法都不能尝试内省或使其模糊时,将使用token类型。因此,使用phi或select of type token是不合适的。
语法:
token
5.3.4 Metadata Type
概述:
metadata类型表示嵌入的metadata。除了函数参数外,不能从metadata创建派生类型。
语法:
metadata
5.3.5 Aggregate Types
聚合类型是派生类型的一个子集,可以包含多个成员类型。Arrays 和 Structs 是聚合类型。Vectors 不被认为是聚合类型。
5.3.5.1 Array Type
概述:
数组类型是一种非常简单的派生类型,它按顺序排列内存中的元素。数组类型需要一个大小(元素的数量)和一个底层数据类型。
语法:
[<# elements> x <elementtype>]
<# elements>
元素个数为常数整数值;elementtype
可以是任何具有大小的类型。
对于索引超出静态类型所暗示的数组末端没有限制(尽管在某些情况下对于索引超出已分配对象的边界有限制)。这意味着一维“可变大小数组”寻址可以在零长度数组类型的LLVM中实现。例如,LLVM中“pascal风格数组”的实现可以使用“{i32, [0 x float]}”类型。
5.3.5.2 Structure Type
概述:
结构类型用于表示内存中的数据成员集合。结构的元素可以是任何具有大小的类型。
通过使用’ getelementptr ‘指令获得指向字段的指针,可以使用’ load ‘和’ store '访问内存中的结构。使用“extractvalue”和“insertvalue”指令访问寄存器中的Structures。
结构可以选择“打包”结构,这表示结构的对齐方式是一个字节,并且元素之间没有填充。在非打包结构中,字段类型之间的填充是由模块中的DataLayout字符串定义的,这是匹配底层代码生成器所需的内容所必需的。
结构可以是“文字的”,也可以是“标识的”。文字结构是与其他类型(例如{i32, i32}*)内联定义的,而标识类型总是在顶层用名称定义。文字类型是由它们的内容所独特的,并且永远不可能是递归的或不透明的,因为没有方法来编写它们。识别的类型可以是递归的,可以是不透明的,而且从来都不是惟一的。
语法:
%T1 = type { <type list> } ; Identified normal struct type
%T2 = type <{ <type list> }> ; Identified packed struct type
5.3.5.3 Opaque Structure Types
概述:
不透明结构类型用于表示没有指定主体的命名结构类型。这对应(例如)前向声明结构的C概念。
语法:
%X = type opaque
%52 = type opaque