LLVM语言参考手册

翻译自:http://llvm.org/docs/LangRef.html
#######重启翻译

摘要

这篇文档是<code>LLVM</code>汇编语言(<code>assembly language</code>)的参考手册。LLVM是一个基于静态单赋值(<code>Static Single Assignment</code>,简写为<code>SSA</code>)的表示形式,它提供了类型安全(<code>type safety</code>)、低级别操作(<code>low-level operations</code>)、灵活性、清晰表示“所有”高级语言的能力。它是贯穿LLVM编译策略全阶段的通用代码表示。

介绍

<code>LLVM</code>代码表示常设计以下三种情况:作为在内存中的编译器中间语言(<code>Intermediate Representation</code>,简称<code>IR</code>),作为在硬盘上的位码表示(适合<code>JIT</code>编译器快速加载),作为适合人类阅读的汇编语言表示。它允许<code>LLVM</code>为编译器的高效转换和分析提供强大的<code>IR</code>,同时提供自然方法去调试和可视化转换。这<code>LLVM</code>的三种不同方式是等价的。这个文档描述的是适合人类阅读的表示和标注。

<code>LLVM</code>表示的目标是轻量级和低级别,同时变得更具表示力、类型化、可扩展行。它的目的是变成“通用<code>IR</code>”序列,在一个足够的低级别上高级别思维可以清晰地映射到它(类似微处理器都是“通用IR”,使得众多源语言映射到它)。根据提供类型信息,<code>LLVM</code>可以作为优化的目标:例如,通过指针分析证明,<code>C</code>自动变量从没被当前函数以外的外部访问到,允许它提升到一个简单的<code>SSA</code>值从而代替内存地址。

好的规范

重要的是记录这个文档描述<code>LLVM</code>汇编语言“好的规范”。这有别于被认为是“好格式”解释概念。例如,下面描述是语法可接受,但是不是好格式:

<code>
%x = add i32 1, %x
</code>

因为<code>%x</code>的定义并不支配它所有的使用者。<code>LLVM</code>基础结构提供了可以用于验证<code>LLVM</code>模块是否好格式的验证通过(<code>verification pass</code>)。这个通过是介于在解释器解释输入汇编和优化器输出位码之间自动运行的。这个验证通过指出违反表明在转换通过或者输入解释器中的<code>bug</code>。

标识符

<code>LLVM</code>标识符有两种基本类型:全局和局部。全局标识符(函数、全局变量)以‘@’字符开始。局部标识符(寄存器名,类型)以‘%’字符开始。此外,还有三种用于不同目的的标识符定义:

<pre>
1,具名值被表示为一个带有前缀字符的string。例如,%foo,@DivisionByZero,%a.really.long.identifier。它的正则表达式可以描述为‘[%@][-a-zA-Z$._][-a-zA-Z$._0-9]*’。标识符需要名称中的其他字符可以使用引号包围。特殊字符使用“\xx”可能溢出,,xx是字符十六进制ASCII码转义。这种方式下,任何字符可以在名称中使用,甚至引号自身。“\01”前缀可以禁止全局变量的名字改编。
2,未具名值使用带有前缀的无符号数值表示。例如,%12、%2、%44。
3,常量,将会在接下来的常量章节详细描述。
</pre>

<code>LLVM</code>需要值以前缀开始的原因有两点:编译器不需要担心名称会和保留字冲突;保留字集在未来可以无危害的扩展。此外,未具名标识符允许编译器迅速提出临时变量,且避免和符号表冲突。

<code>LLVM</code>的保留字和其他语言的保留字很相似。有不同的机内码(<code>opcode</code>,又可译为操作码)的关键字(‘<code>add</code>’,‘<code>bitcast</code>’,‘<code>ret</code>’,等等),原始类型名(‘<code>void</code>’, ‘<code>i32</code>’, 等等),以及其他。这些保留字不可能和变量名冲突,因为它们不以<code>‘%’</code>或者<code>‘@’</code>前缀开始。

下面是<code>LLVM</code>代码整型变量<code>%x</code>乘以8的例子:

简单的表示:
<pre>
%result = mul i32 %X, 8
</pre>

强度折减(<code>strength reduction</code>)后:
<pre>
%result = shl i32 %X, 3
</pre>

困难方法:
<pre>
%0 = add i32 %X, %X ; yields i32:%0
%1 = add i32 %0, %0 ; yields i32:%1
%result = add i32 %1, %1
</pre>

<code>%X</code>乘8的最后方式说明了<code>LLVM</code>的几个重要的词法特点:

<pre>
1,注释以‘;’分隔,直到这行结束。
2,未具名临时变量在计算结果不能赋值给一个具名变量时候创建。
3,未具名临时变量是按照数字顺序来的(使用每个函数从0开始的自增长计数器)。值得注意的是基本块和未具名函数参数被包含在这个编号方式中。例如,如果整个基本块未被赋予标签名称,以及函数参数被命名了,它就会被未具名为数字0。
</pre>

它也展示了在这个文档应该遵循一条约定。当演示指令时,我们应该在这条指令后面跟进定义被生成值的类型和名称的注释。

高级结构

模块结构

<code>LLVM</code>程序由<code>Module</code>组成,每个都是输入程序的转换单元。每个模块包括函数,全局变量,符号表组成。模块可能通过<code>LLVM</code>连接器组合在一起,包括合并方法(和全局变量)的定义,确定预声明,合并符号表。下面是简单的<code>“hello world”</code>模块:

<pre>
; 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}
</pre>

这个例子有一个全局变量<code>.str</code>、一个外部声明的<code>“puts”</code>函数,一个函数定义<code>“main”</code>和具名元数据<code>“foo”</code>组成。

通常来讲,一个模块有全局值(函数和全局变量成为全局值)列表组成。全局值通过指针指向内存的位置表示(在这个例子中,一个指针指向字符数组,一个指向函数),有下面的链接类型

链接类型

所有全局变量和函数有下面链接类型的一种:
<code>private</code>
<pre>
“private”链接的全局值仅仅被当前模块中的对象直接访问到。特别地,使用private全局变量链接代码到一个模块在必要情况下为了避免冲突可能会被重命名。因为这个符号对当前模块来说是private,所有引用可能被更新。这个不会再对象文件中展现任何符号表。
</pre>

<code>internal</code>
<pre>
和private一样,但是在对象文件中值常被显示为本地符号(例如ELF中的STB_LOCAL)。这对应着C语言中的‘static’关键字的概念。
</pre>

<code>available_externally</code>
<pre>
带有“available_externally”链接的全局值相对于LLVM模块不会存入对象文件中。从链接器的观点看,一个available_externally全局值等于一个外部声明。它们的存在是为了内联和其他优化行为的发生提供当前模块的一份全局定义,被认为在外部模块的某些地方。带有“available_externally”链接的全局值允许随意定义,允许内联和其他优化行为。这个链接类型仅允许定义,而不能声明。
</pre>

<code>linkonce</code>
<pre>
带有“linkonce”链接的全局值在链接过程中与其他同名的全局值合并。这可能常用于一些内联函数、模板或者其他必须在每个转换单元中生成使用的代码等形式,但是之后主体可以被一个更详细的定义覆盖。未引用的linkonce全局值允许被丢弃。注意,linkonce链接实际上并不允许优化器函数的函数体内联为调用者,因为它不知道函数定义是程序的最终定义或是否将被一个更明确定义重写。使用“linkonce_odr”链接可以内联和优化。
</pre>

<code>weak</code>
<pre>
除了未引用的带“weak”链接标识的全局值可能会被抛弃外,“weak”链接拥有与“linkonce”相同的合并语义。这个标志被使用于在C源代码中被声明为“weak”的全局值。
</pre>

<code>common</code>
<pre>
“common”链接类似于“weak”链接,但是他们常用于C语言的暂定的变量定义(tentative definitions),例如在全局作用域的“int X;”。带有“common”链接的符号和“weak”符号被合并的方式相同,但这些符号即使未被引用也不会被删除。“common”符号可能不会有一个明确的部分,单必须被初始化为0值,且不可能被标志为‘constant’。函数和切片不可以带有“common”链接标识。
</pre>

<code>appending</code>
<pre>
“appending”链接只能用于数组类型的全局指针变量。但两个带有appending链接标识的全局变量被链接到一起,这两个全局数组追加合并到一起。 当.o文件链接时,这是类型安全的LLVM具有系统链接器接连接具有相同名称的“部分”等效。
不幸的是,这不能符合.o文件的任何特征,所以它仅被用于类似llvm.global_ctors这些llvm特别解释的变量。
</pre>

<code>extern_weak</code>
<pre>
这个链接标识的语义遵循ELF对象文件模型:除非被链接,否则带有extern_weak的符号都是weak的。如果没有被链接,该符号会变为null而不是未定义引用。
</pre>

<code>linkonce_odr, weak_odr</code>
<pre>
某些语言允许不同的全局值被合并,例如两个具有不同语义的函数。其他语言,如C++,确保只等效的全局值才可以合并(<code>“one definition rule”</code>简写为<code>“ODR ”</code>) 。这些语言可以使用<code>linkonce_odr</code>和<code>weak_odr</code>链接标识来表明全局值将只与等效的全局值合并。这些链接标识类型的其他语义与其非<code>ODR</code>版本相同。
</pre>

<code>external</code>
<pre>
如果上述标识符都没被使用,那么该全局值的是外部可见的,这意味着它参与链接,可用于处理外部符号引用。
</pre>

一个函数声明拥有除<code>“external”</code>或<code>“extern_weak”</code>以外的链接标识是不合法的。

调用约定

<code>LLVM</code>的函数调用反射全可以有一个选择调用约定指定调用。每一对动态调用者/被调用者的调用约定必须匹配,或者程序行为是未定义的。接下来的调用匹配是<code>LLVM</code>支持的,更多的将会在未来添加:
<code>“ccc”-C语言调用约定</code>
<pre>
这个调用约定(在没有指定其他调用约定前提下是默认调用约定)匹配目标到C语言调用约定。这个调用约定支持变长参数函数调用并可容忍函数的声明和实现之间存在一些不匹配的地方(像正常的C语言一样)。
</pre>

<code>“fastcc”-快速调用约定</code>
<pre>
这个调用约定试图使调用尽可能地快速(例如通过寄存器传递)。这个调用约定允许目标使用任何技巧来为其产生快速的代码,而不要求符合外部指定的ABI(Application Binary Interface)。尾部调用只能在GHC或者HiPE规范时被使用的时候优化。这种调用约定不支持变长参数,且要求所有被调用者的原型与函数定义相匹配。
</pre>

<code>“coldcc”-冷调用约定</code>
<pre>
这种调用约定试图使调用者内的代码在假定这个调用不会被经常执行的情况下尽可能地高效。像这种情况,这些调用通常会保护所有寄存器,因此这个调用不会破坏任何调用者内的活动范围。这种调用约定不支持变长参数,且要求所有被调用者的原型与函数定义相匹配。更进一步地讲,内联器不会考虑把这种函数进行内联。
</pre>

<code>“cc 10” - GHC(Glasgow Haskell Compiler)约定</code>
<pre>
这种调用约定被明确的实现来用于Glasgow Haskell Compiler (GHC)。它传递所有东西到寄存器中,并通过停止被调用者节省寄存器来实现这种极端的方式。这种调用约定不应该轻轻地使用,只能除非像在实现函数式编程语言时,一个可选的the register pinning性能技术经常被使用的情况下。在目前只有X86支持这种约定切它有以下限制:

  • 在X86-32下只支持长度大于4bit的类型的参数。不支持浮点型。
  • 在X86-64下只支持长度大于10bit的类型参数且只支持6位浮点参数。
    这种调用约定支持尾部调用优化 要求调用者和被调用者都使用它。
    </pre>

<code>“cc 11” - HiPE调用约定</code>
<pre>
这种调用约定被明确实现来用于High-Performance Erlang (HiPE) 编译器,Ericsson’s Open Source Erlang/OTP system的本地代码编译器。它比普通C调用约定使用了更多寄存器实现实参传递,并且还定义非被调用者保存寄存器。这个调用约定正确地支持尾部调用优化,但调用者和被调用者都是用这个调用约定。它使用一个与GHC约定相似的寄存器钉扎机制来保持频繁访问运行时组件时候被压在指定硬件寄存器上的。在目前,只有X86支持这种调用约定(包括32位和64位)。
</pre>

<code>“webkit_jscc”-Webkit的JavaScript调用约定</code>
<pre>
这个调用约定为WebKit FTL JIT实现。它从右到左向栈传递参数,并返回一个值到平台所定义的返回寄存器中。
</pre>

<code>“anyregcc”-代码修复的动态调用约定</code>
<pre>
这是个特殊的约定支持修复任意代码序列来取代一个调用地点。这种调用约定强制调用参数到寄存器中,但是允许它们被动态分配。这种调用约定一般只能被llvm.experimental.patchpoint调用使用。因为这个本质记录参数在表中的位置。详见Stack maps and patch points in LLVM
</pre>

<code>“preserve_mostcc”-PreserveMost调用约定</code>
<pre>
这种调用约定试图使调用者中的代码尽可能地少受干扰。这种调用约定在如何传递参数与返回值上和C调用约定是完全一致,但它使用不同的调用者保存寄存器和非调用者保存寄存器。这样可以减轻在调用开始和结束需要保存和恢复大量寄存器集的负担。如果参数传递到非调用者保存寄存器,那么它们会被被调用者在调用过程中保护起来。它不能提供从非调用者保存寄存器返回值。
~ 在X86-64中,被调用者保护所有除了R11外的普通目的寄存器。R11可能被用作额外备用寄存器(scratch register)。浮点寄存器(XMMs/YMMs)不会被保护并且由被调用者保存。
这种调用约定背后的想法支持调用运行时函数,而不需要调来其他函数。
类似PreserveMost的调用约定的这个调用约定将被用于为了Objective-C运行时的版本,此时需要考虑实验性。
</pre>

<code>cxx_fast_tlscc-为访问函数的CXX_FAST_TLS调用约定</code>
<pre>
Clang生成一个访问函数来访问C++风格的TLS。这个访问函数通常有一个入口块,一个出口块以及一个初始化块,这样就第一时间可以运行。入口和出口块可以访问少量TLS中间语言变量,每个访问将低于平台特征顺序。
这个调用约定目标是保存尽可能多寄存器的调用者中实现最小覆盖(所有寄存器都是存储在与入口块和出口块组成的快速路劲)。
这个调用约定行为与C调用约定在参数和返回值如何传递上是一样,但是它使用了不同调用者保存寄存器和非调用者保存寄存器集。
已知每个平台都有它自己的低序列,因此它拥有保存寄存器集,我们不能使用存在的PreserveMost。
~在X86-64上,被调用者保存除了RDI和RAX外的所有通用寄目的寄存器。
</pre>

<code>“swiftcc”-用于Swift语言的调用约定</code>
<pre>
~在X86-64上,RCX和R8都支持额外的整型返回,XMM2和XMM3都支持额外的浮点/向量返回。
~在iOS平台上,使用的是AAPCS-VFP调用约定。
</pre>

<code>“cc <n>”-编号约定</code>
<pre>
任何一个调用约定可能有数字指定,允许使用目标明确的调用约定。目标明确调用约定是在64位开始的。
</pre>

更多调用约定会以使用为依据被添加/定义,如支持<code>Pascal</code>约定或者其他知名目的独立的约定。

可见性模式

所有全局变量和函数有一个如下的可见性模式:

<code>“default”-默认模式</code>
<pre>
在那些使用ELF对象文件格式的目的,默认可见性意味着声明对于其他模块是可见的,并且在可共享库的话意味着这个声明的实体是可被覆盖的。在Darwin平台,默认可见性意味着声明对于其他模块是可见的。默认可见性与在这种语言中的<code>“external linkage”</code>是一致的。
</pre>

<code>“hidden”-隐藏模式</code>
<pre>
如果一个带有隐藏可见性的对象的两个声明处于一个相同可共享对象,那么它们会被引用到同一对象。通常来说,隐藏可见性表明符号不会被放置到动态符号表,因此其他模块(可执行程序或共享库)不可以直接引用这个符号。
</pre>

<code>“protected”-保护模式</code>
<pre>
在ELF中,保护可见性标明符号将会被防止在动态符号表中,但是在定义模块内的引用对本地符号表是捆绑的。因此符号是不可被其他模块覆盖的。
</pre>

<code>internal</code>和<code>private</code>链接的符号表必须有默认可见性。

DLL存储类别

所有全局变量、函数和别名可以有一个如下<code>DLL</code>存储类别:
<code>dllimport</code>
<pre>
“dllimport”会导致编译器通过一个指向到被DLL导出符号创建的指针的全局指针,来引用一个函数或变量。在微软Windows目标,这个指针名由结合_imp和函数或变量的名称来规范。
</pre>

<code>dllexport</code>
<pre>
“dllexport”会导致编译器提供一个指向一个在DLL的指针的全局指针,所以它可以被带有dllimport的属性引用到。在微软Windows目标,这个指针名由结合_imp和函数或变量的名称来规范。为了使编译器,汇编器和链接器知道某个符号是被外部引用并且防止它被删除,这个存储类别为了定义一个dll接口而存在。
</pre>

线程本地存储模型

一个变量可能被定义为<code>thread_local</code>,这意味着它不会被线程分享(每个线程有独立的变量拷贝)。并不是所有目标支持线程本地存储变量。作为一个选项,<code>TLS</code>模型可能被指定:
<code>localdynamic</code>
<pre>
标识仅用于当前可共享库的变量
</pre>

<code>initialexec</code>
<pre>
标识在模块中不会被动态加载的变量
</pre>

<code>localexec</code>
<pre>
标识只能定义且只能使用在可执行程序中的变量
</pre>

如果没有给于明确类型,<code>“general dynamic”</code>将会被使用。

这种模型与<code>ELF TLS</code>模型是一致的;更多关于不同模块可能被使用的情况信息详见ELF Handling For Thread-Local Storage。如果指定的模型不支持或者有更合适的模型可供选择,目标可能会选择不同<code>TLS</code>模型。

一个模型可以在别名中指定,但是它仅支配别名怎么访问。它不会在别名中有实际效果。

对于链接器不支持<code>ELF TLS</code>模型的平台,<code>-femulated-tls</code>标记可被使用生成<code>GCC</code>兼容的竞争<code>TLS</code>代码。

结构类型

<code>LLVM</code>的中间语言允许同时指定<code>“identified”</code>和<code>“literal”</code>结构类型。文字类型是唯一结构,但是确定类型永远不是唯一的。一个不透明结构类型也可以用于直接定义一个不可使用的类型。

确定结构说明的例子如下:
<pre>
%mytype = type { %mytype*, i32 }
</pre>

在<code>LLVM 3.0</code>发布版本之前,确定类型是结构唯一的。仅文字类型在<code>LLVM</code>最近版本是唯一的。

非整型指针类型

注意:非整型指针类型是在进程中执行的,它们此时被认为是实验的。

<code>LLVM</code>中间语言选择允许前端代表在特定地址空间指针通过<code>:ref:datalayout string<langref_datalayout></code>作为<code>“non-integral”</code>。非整型指针类型展示有未指明按位表现的指针,那是整型表现可能目标独立或者易变的(不被合适整型返回)。

<code>inttoptr</code>指令转换整型到非整型指针类型是病态类型,因此<code>ptrtoint</code>指令转换非整型指针类型值到整型。指令中提到的向量版本也是病态类型。

全局变量

全局变量在编译期定义存储分配范围,而不是运行时。

全局变量定义必须被初始化。

全局变量在其他的转换单元也可以被声明,但在这种情况下它们没有初始化公式。

每个全局变量的定义或声明可以有明确部分来放置或者可能有可选明确的队列指定。

一个变量可以被定义为全局长了,它指明了变量内容永远不会被改变(为了更好优化,允许全局数据放置在执行块的只读部分,诸如此类)。注意的是需要运行时初始化的变量不可以标记<code>constant</code>,那只能存储到变量。

<code>LLVM</code>明确允许全局变量声明标记为常量,甚至全局变量的最终定义不是的。这个功能常用于程序轻微更好的优化,但是这要求语言定义保证基于<code>‘constantness’</code>的优化对于不包含这个定义的编译单元是有效的。

作为<code>SSA</code>值,全局变量定义为指针值,其作用域(例如它们的影响范围)是程序中的所有基本块。全局变量总是定义为一个其<code>‘content’</code>所对应类型的指针,因为它们描述一个存储范围,所有这些<code>LLVM</code>中的存储对象被通过这个指针访问。

全局变量可以被<code>unnamed_addr</code>标识,表明它的地址是没有意义的,仅仅是指向对应内容。一个被这样标识的常量可以被合并到其他拥有相同初始化公式的常量。注意,一个地址有意义的常量可以被合并一个<code>unnamed_addr</code>常量,并且合并结果是地址有意义的常量。

如果给于一个<code>local_unnamed_addr</code>属性,在模块内的地址是没有意义的。

一个全局变量可能被声明驻留在一个目标指定的编号地址空间。对于支持它们的目标,地址空间会影响优化被怎么执行及使用什么指令来访问变量。默认的地址空间是<code>0</code>。地址空间限定符必须放在其他任何属性前。

LLVM允许一个明确部分用于指定全局变量。如果目标支持它,它会发步全局变量到这个指定部分。 此外如果目标必要支持,全局变量可以存放在此选项允许编译器以封装函数。

默认下,全局初始化公式通过假设被定义全局变量优化,它们的来自在全局初始化公式开始之前初始值的模块是不会被修改。对于可能从外部访问的变量,包括外部链接、或者出现在<code>@llvm.used</code>、或者<code> dllexported</code>变量,这种也是正确的。这种猜想会被带有<code>externally_initialized</code>的变量抑制。

一个显式队列可能被标识于一个全局变量,这个一定是2的次幂。如果队列不存在,或者队列被设置为0,那么这个全局变量的队列将会被目标根据方便性设置。如果一个显式队列被标识,这个全局变量被迫完全拥有精确队列。如果这个全局变量有一个被分配到部分,目标和优化器不会允许全局变量超过对齐。在这种情况下,额外的队列是显而易见的:例如,代码可能猜想全局变量被集中放置到它们的部分中,并尝试以数组形式遍历它们,但队列填充会打破遍历过程。 最大的队列是<code>1 << 29</code>。

全局变量同样可以拥有一个<code>DLL</code>存储类别和可选的附加元数据列表,变量与别名可以有TLS模型

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

例如,下面定义了在编号地址空间的全局变量,它有初始化公式、部分和队列:
<pre>
@G = addrspace(5) constant float 1.0, section "foo", align 4
</pre>

下面的例子仅仅声明了全局变量:
<pre>
@G = external global i32
</pre>

下面的例子定义了一个带有<code>initialexec</code>的TLS模型的线程本地全局变量:
<pre>
@G = thread_local(initialexec) global i32 0, align 4
</pre>

函数

<code>LLVM</code>函数定义由<code>“define”</code>关键字,一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别,一个可选的调用约定,一个可选的<code>unnamed_addr</code>属性,一个返回值类型,一个可选的返回值类型的参数属性,一个函数名,一个(可能为空的)参数列表(每一个都带有可选的参数属性),可选的函数属性,一个可选的部分,一个可选的队列,一个可选的此选项允许编译器以封装函数,一个可选垃圾回收期的名称,一个可选的前缀,一个序言,一个可选的特征,一个可选附加元数据列表,一个左括号,一个基本块列表和一个右括号。

<code>LLVM</code>函数声明由<code>“declare”</code>关键字,一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别,一个可选的调用约定,一个可选的<code>unnamed_addr</code>属性,一个返回值类型,一个可选的返回值类型的参数属性,一个函数名,一个可能为空的参数列表,一个可选的队列,一个可选垃圾回收器名称,一个可选的前缀和一个可选的序言。

一个函数定义包含一个基本块的列表,可以为函数形成<code>CFG</code>(控制流图形)。每一个基本块可选的开始于一个标签(给定这个基本块一个符号表入口),包含一个指令列表,并且以一个终止指令(例如分支或函数返回)结束。如果一个明确标签不被提供,一个块会分配到一个隐式编号标签,使用与匿名临时变量使用同一个计数器的下一个值。例如,如果一个函数入口块不具有一个显示标签,他会被分配到一个标签<code>“%0”</code>,然后第一个在这个块中的匿名临时变量将会是<code>“%1”</code>,诸如此类。

函数中的第一基本块特殊在两个方面:它是在函数进入后马上执行的,并且它是不允许有前置基本块(即函数入口块不能拥有任何分支)。因为这个块没有前置块,所以也不能有任何<code>PHI</code>节点

<code>LLVM</code>允许函数指定一个明确部分。如果目标支持它,它会放步函数到这个指定块。此外,函数可以被放置在<code>COMDAT</code>。

一个显示队列可能会被指定到一个函数。如果没有展示,或者队列被设置为<code>0</code>,那么这个函数的队列将会根据目标的方便设定。如果一个显式队列被指定,这个函数被迫至少想指定的那么多队列。所有队列必须为2的次幂。

如果<code>unnamed_addr</code>属性被给于,函数地址会被认为是没有意义,并且两个相同的函数之间可以被合并。

如果<code>local_unnamed_addr</code>属性被给于,地址将会在模块内没有意义。

语法:
<pre>
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)* { ... }
</pre>

参数列表是逗号分隔参数序列,每个参数如下形式所示:

语法:
<pre>
<type> [parameter Attrs] [name]
</pre>

别名

别名和函数活变量不同,不产生任何新的数据。它仅仅是已存在位置的一个新的符号和元数据。

别名有一个名称和一个别名,不管是全局变量还是常量表达式。

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

语法:
<pre>
@<Name> = [Linkage] [Visibility] [DLLStorageClass] [ThreadLocal] [(unnamed_addr|local_unnamed_addr)] alias <AliaseeTy>, <AliaseeTy>* @<Aliasee>
</pre>

链接标识必须是<code>private</code>,<code>linker_private</code>,<code>linker_private_weak</code>,<code>internal</code>,<code>linkonce</code>,<code>weak</code>,<code>linkonce_odr</code>,<code>weak_odr</code>,<code>external</code>中的一个。注意一些系统链接器可能会不正确地处理一个降级弱符号作为非别名。

非<code>unnamed_addr</code>的别名被别名表达式的同样地址保护。<code>unnamed_addr</code>仅仅保护指向同样的内容。

如果<code>local_unnamed_addr</code>属性被给于,地址将会在模块内没有意义。

因此别名仅仅是第二名称,一些在产生对象文件是仅被检查的约束提供:

~表达式定义别名必须在组装时间可计算。因此它仅仅是个名称,没有重定位被使用。
~表达式中没有别名可以弱化中间别名被覆盖而不展示在对象文件中的可能性。
~表达式中没有全局值可以声明,因此需要一个重定位,但是不是可能的。

IFuncs

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

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

<code>IFuncs</code>有一个可选的链接类型和一个可选的可视性模型。

语法:
<pre>
@<Name> = [Linkage] [Visibility] ifunc <IFuncTy>, <ResolverTy>* @<Resolver>
</pre>

此选项允许编译器以封装函数

此选项允许编译器以封装函数的中间语言提供了进入<code>COFF</code>和<code>ELF</code>对象文件的<code>COMDAT</code>功能性。

<code>Comdat</code>有个展示<code>COMDAT key</code>的名称。如果链接器选择用<code>key</code>覆盖其他的,所有指定<code>key</code>的全局对象将会在最终对象中结束。如果真有的话,别名放置在和需要计算别名的<code>COMDAT</code>一样的位置。

语法:
<pre>
$<Name> = comdat SelectionKind
</pre>

这种选择必须是下面的一种:
<code>any</code>
<pre>
链接器可能选择任意<code>COMDAT key</code>,这个选择是随意的。
</pre>

<code>exactmatch</code>
<pre>
这个链接器可能选择任意<code>COMDAT key</code>,但是选择部分必须包含同样的数据。
</pre>

<code>largest</code>
<pre>
这个链接器将会选择包含最大<code>COMDAT key</code>部分。
</pre>

<code>noduplicates</code>
<pre>
这个链接器需要这个<code>COMDAT key</code>存在的唯一部分。
</pre>

<code>samesize</code>
<pre>
这个链接器可能选择任意<code>COMDAT key</code>,但是部分必须包含相同数量的数据。
</pre>

注意,<code>Mach-O</code>平台不支持<code>COMDAT</code>,ELF仅支持<code>any</code>作为选项。

下面是一个如果<code>COMDAT key</code>部分是最大的,函数仅将被选择的COMDAT组例子:

<pre>
$foo = comdat largest
@foo = global i32 2, comdat($foo)

define void @bar() comdat($foo) {
ret void
}
</pre>

作为一个语法糖,<code>$name</code>如果和全局名称一致将会被遗漏:
<pre>
$foo = comdat any
@foo = global i32 2, comdat
</pre>

在COFF对象文件中,这将创建一个包含@foo符号内容的IMAGE_COMDAT_SELECT_LARGEST选择类型的COMDAT部分和另一个与COMDAT第一部分和包含@bar符号内容相关的IMAGE_COMDAT_SELECT_ASSOCIATIVE选择类型的COMDAT部分。

全局对象属性有一定的限制。当针对COFF时候,它,或别名,必须有和COMDAT组一样的名称。这个对象的内容和大小可能在链接时根据选择类型决定选择COMDAT组。因为对象名称必须与COMDAT组名称匹配,全局对象的链接器不能是局部的;如果局部符号在符号表与其他的冲突了可以修改名称。

COMDAT和部分属性的结合使用可以产生令人惊讶的结果。例如:
<pre>
$foo = comdat any
$bar = comdat any
@g1 = global i32 42, section "sec", comdat($foo)
@g2 = global i32 42, section "sec", comdat($bar)
</pre>

根据对象文件观点,这需要创建具有相同名称的两个部分。在对象文件层次,因为属于不同的COMDAT组和COMDAT的全局通过部分表现,所以这是必要的。

注意,特定中间语言构造全局变量和函数可以在除了任何使用COMDAT中间语言指定的对象文件创建COMDAT。这是当代码生成器被配置为在个别部分发出全局(比如-data-sections或者-function-sections支持llc)。

具名元数据

具名元数据是一个元数据的集合。元数据节点(但非元数据字符串)是唯一对于具名元数据有效的操作数。
<pre>
1.具名元数据被表示为一个带有元数据前缀的字符串。元数据名称的规则与标识符相同,但不允许引用名称。“\xx”类型的反斜杠仍然有效,它允许任何字符成为名称的一部分。
</pre>

语法:
<pre>
; Some unnamed metadata nodes, which are referenced by the named metadata.
!0 = !{!"zero"}
!1 = !{!"one"}
!2 = !{!"two"}
; A named metadata.
!name = !{!0, !1, !2}
</pre>

参数属性

返回类型和每一个函数的参数类型可能拥有一个参数属性集。参数属性是用于交流函数返回值和参数的额外信息。可以认为参数类型是函数的一部分,但不是函数类型的一部分,因此用不同参数属性的函数可以拥有相同的函数类型。

参数属性是跟随指定类型后的简单关键字。如果需要多个参数属性,它们需要被空白符分隔开。下面是详细例子:
<pre>
declare i32 @printf(i8* noalias nocapture, ...)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
</pre>

注意,任何对于函数结果(nounwind, readonly)的属性跟随在参数列表后。

当前仅定义了以下的参数属性:

<code>zeroext</code>
<pre>
这表明了在代码生成器时,参数或返回值应该被扩充零到通过调用者(参数长度)和被调用者(返回值值)得到目标ABI。
</pre>

<code>signext</code>
<pre>
这表明在代码生成器时,参数或返回值应该被符号扩展到通过调用者(参数长度)和被调用者(返回值值)得到目标ABI(一般来说是32位)。
</pre>

<code>inreg</code>
<pre>
这表明参数和返回值在函数调用的发散代码或返回值的期间被一个特定目标独立方法处理(通常来说,把它们放到寄存器而不是放到内存中,即使一些目标使用它来区分两种不同的寄存器)。这个属性的使用的针对指定目标的。
</pre>

<code>byval</code>
<pre>
这表明指针参数应该通过值方式传递给函数。这个属性包含了指针隐藏复制在调用者和被调用者之间,因此使被调用者不能修改在调用者值。这个属性仅在LLVM指针参数里是有效的。这通常用于传递值的结构体和数组,但这对于数量指针也是有效的。 这个复制被认为是从属于调用者而不是被调用者的(例如,readonly函数不应该写一个byval 的参数)。这个属性对于返回值是无效的。

byval属性也支持指定队列属性。这指明了栈槽格式和指定调用地点的指针队列属性。如果队列属性不被指定的话,代码生成器会做一个目标相关的猜测。
</pre>

<code>inalloca</code>
<pre>
inalloca参数属性允许调用者带有即将离开栈参数地址。一个inalloca参数必须是一个使用 alloca 指令创建指向栈内存地址的指针。这个分配的堆栈空间或参数分配必须也以inalloca关键字标识。只有最后的参数可以使用inalloca属性,并且这个参数保证必须在内存中传递。

一个参数分配可能被函数调用至少一次因为这个调用可能会释放它。这个inalloca属性不可能与其他会影响参数存储的属性结合使用,例如inreg,nest,sret,和byval。这个inalloca属性也禁止LLVM隐式降低大量聚合返回值,这意味着前端发起者必须通过sret指针降低它们。

当到达调用地点时,参数分配必须是仍然存活的最新栈分配,或者结果是未定义的。在一个参数分配后和这个实参的调用地点前额外分配堆栈空间是可能的,但这必须清除llvm.stackrestore
更多关于这个属性的使用,详见Design and Usage of the InAlloca Attribute
</pre>

<code>sret</code>
<pre>
这表明这个指针参数指向一个在源程序中作为函数返回值的结构体地址。这个指针必须由调用者保证是有效的:这个结构的加载和存储必须与被调用者所假定是一致的。这只适用于第一个参数。这对于返回值并不是一个有效的属性。
</pre>

<code>align<n></code>
<pre>
这表明可以由优化器假定指针值具有指定队列。
注意当结合byval属性时,这个属性有额外的语义。
</pre>

<code>noalias</code>
<pre>
这表明在执行函数时,通过基于参数或者返回值的指针值传递的访问对象是不可访问的,传递的指针值没有基于参数或者返回值。返回值的属性还有额外的语义描述。调用者与被调用者分享责任确保那些需求匹配。关于NoAlias的更多信息可以详见alias analysis

注意noalias的定义故意定义得与C99中的函数实参的restrict定义相似,尽管restrict的定义稍微弱一些。

因为对于函数返回至,C99的restrict是无意义的,而LLVM的noalias是有意义的。此外,noalias返回值的属性语义比使用函数参数的属性语义强。在函数返回值上,noalias属性标明该函数的行为像一个系统的内存分配函数,返回一个指针分配存储不相交的可以访问其他任何对象的调用者。
</pre>

<code>nocapture</code>
<pre>
这表明被调用者不对生存期比被调用者长的指针做出任何复制。对于返回值这不是一个有效的属性。

用于在易变操作中使用的地址认为是可被捕获的。
</pre>

<code>nest</code>
<pre>
这表明指针参数可以使用弹性内联函数删除。这不是一个有效的返回值属性,只能被应用于一个参数。
</pre>

<code>returned</code>
<pre>
这表明函数始终返回这个参数作为它的返回值。在生成调用者时使用优化器和代码生成器生成一个提示,允许尾部调用优化和在某种情况下忽略寄存器的保存和恢复;在省城被调用者是,它不会被检查或执行。参数和函数返回类型必须是对于bitcast指令有效的操作数。这不是一个有效的返回值属性,只能应用于一个参数。
</pre>

PS,未完待续...

本文转载请注明原作者呆萌院长,如果你对这篇文章有更好的见解可以通过微信联系我。利益相关:本篇文章所有涉及到的软件均为笔者日常所用工具,无任何广告费用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,505评论 1 19
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 前言 2000年,伊利诺伊大学厄巴纳-香槟分校(University of Illinois at Urbana-...
    星光社的戴铭阅读 15,737评论 8 180
  • 现在的女人即便在月经期间,做任何事情都是一种毫无禁忌,殊不知些这些事情正危害女人月经来潮的健康状态!下面伊凡就告诉...
    伊凡讲师阅读 528评论 4 2
  • 余于庚寅年八月廿九日携友曾游越秀山镇海楼,因先贤元孝题有“五岭北来峰在地,九州南尽水浮天”句,感慨不已,苦无佳句,...
    摩诃摩瑜利阅读 355评论 0 0