函数(Functions)
LLVM函数定义由“define” 关键字,一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别,一个可选的调用约定,一个可选的 unnamed_addr 属性,一个返回值类型,一个可选的返回值的参数属性,一个函数名,一个(可能为空的)实参列表(每一个都带有可选的参数属性),可选的函数属性,一个可选的section,一个可选的对齐属性,一个可选垃圾回收期的名字,一个可选的前缀,一个左花括号,一个基本块列表和一个右花括号。
LLVM函数声明由 “declare” 关键字,一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类型,一个可选的调用约定,一个可选的 unnamed_addr 属性,一个返回值类型,一个可选的返回值类型的参数属性,一个函数名,一个可能为空的实参列表,一个可选对齐属性,一个可选垃圾回收器名和一个可选的前缀。
一个函数定义包含一个基本块的列表,可以为函数形成CFG(控制流图形)。每一个基本块可以(可选的)开始于一个label(给定这个基本快一个符号表入口),包含一个指令列表,并且结束与一个终止指令(例如分支或函数返回)。如果一个明确的label不被提供,一个快会被分配到一个隐式的编号label,使用的计数器与匿名临时变量使用同一个计数器。例如,如果一个函数入口块不具有一个显示的label,他会被分配到一个label "%0",然后第一个在这个块中的匿名临时变量将会是“%1”。
函数中的第一基本块特殊在两个方面: 她是在函数进入后马上执行的,并且它是不允许有前置基本块(即函数入口块不能拥有任何分支)。因为这个块没有前置块,所以也不能有任何 PHI nodes.
LLVM允许函数指定一个明确的section。如果目标平台支持它,它会放散函数到这个指定块。
一个显示的对齐属性可能会被指定到一个函数。如果没有指定,或者对齐属性被设置为0,那么这个函数的对齐属性将会根据目标平台的需要设定。如果一个显式对齐属性被指定,这个函数被迫至少想指定的那么多对齐。所有对齐属性必须为2的次幂。
如果 unnamed_addr 属性被指定了,函数的地址会被认为是不重要的且两个相同的函数之间可以被合并。
语法:
define [linkage] [visibility] [DLLStorageClass]
[cconv] [ret attrs]
<ResultType> @<FunctionName> ([argument list])
[fn Attrs] [section "name"] [align N]
[gc] [prefix Constant] { ... }
别名(Aliases)
别名作为可别名对象(包括函数,全局变量,另一个别名或全局值的bitcast)的“second name”。别名可以拥有一个可选的链接标识,一个可选的可见性模式,一个可选的DLL存储类别。
语法:
@<Name> = [Visibility] [DLLStorageClass] alias [Linkage] <AliaseeTy> @<Aliasee>
链接标识必须是 private, linker_private, linker_private_weak, internal, linkonce, weak, linkonce_odr, weak_odr, external.中的其中一个。注意一些系统链接器可能会不正确地处理一个降级的弱符号作为非弱别名。
具名元数据(Named Metadata)
具名元数据是一个元数据的集合。Metadata nodes (但非元数据字符串) 是唯一对于具名元数据有效的操作数。
语法:
; Some unnamed metadata nodes, which are referenced by the named metadata.
!0 = metadata !{metadata !"zero"}
!1 = metadata !{metadata !"one"}
!2 = metadata !{metadata !"two"}
; A named metadata.
!name = !{!0, !1, !2}
参数属性(Parameter Attributes)
这个返回类型和每一个函数的参数类型可能拥有一个参数属性集。参数属性是用于交流函数的返回值和参数的额外信息。参数类型被认为是函数的一部分,但不是函数类型的一部分,所以用不同的参数属性的函数可以拥有同一个函数类型。
参数属性是是以下的一些跟在类型后的简单的关键字。如果需要多个参数属性,他们需要被空白符分隔开。
例如:
declare i32 @printf(i8* noalias nocapture, ...)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()
注意,任何对于函数结果nounwind, readonly)属性跟随在实参列表后。
当前,仅有以下的参数属性被定义:
- zeroext
- 这表明了在代码生成时,参数或返回值应该被0扩展到目标平台ABI要求调用者(即参数长度)和被调用者(即返回值长度)的长度范围(一般来说是32bits,但对于x86-64的i1来说是8bits)。Note:是因为寄存器大小和要求压栈操作数大小?
- signext
- 这表明了代码生成时,参数或返回值应该被符号扩展到目标平台ABI要求调用者(即参数长度)和被调用者(即返回值长度)的长度范围(一般来说是32bits)。Note:对于有符号数即需要符号位扩展
- inreg
- 这表明参数和返回值在发散函数调用或返回值的期间,需要应该以一个依赖于特定平台的样式对待(通常来说,把参数和返回值放置到寄存器而不是放置到存储器中,即使一些目标平台使用它来区分两种不同的寄存器)。这个属性的使用的针对指定平台的。
- byval
- 这表明指针参数应该已传至方式传递到函数中。这个属性蕴含了在调用者和被调用者之间的一个隐藏的指针复制操作,因此使被调用者不能修改在调用者里的值。这个属性仅在LLVM指针实参里是有效的。这通常用于传递结构体和数组的值(寄存器只能存放这届数据的指针),但这对于scalar object的指针也是有效的。
- 这个复制被认为是从属于调用者而不是被调用者的(例如,readonly 函数不应该写一个 byval 的参数)。这个属性对于返回值是无效的。
- 这个 byval 属性同样支持指定对齐属性。这指明了stack slot的对齐格式且指定调用地点的指针对齐属性。如果对齐属性不被指定的话,代码生成器会做一个平台相关的猜测。
inalloca
注意
这个特性是不稳定的且没有完全实现。
inalloca 实参属性允许调用者带有即将离开stack的实参地址。一个 inalloca 实参必须是一个使用 alloca 指令创建指向栈内存的指针的地址。这个分配的堆栈空间或实参分配必须也以inalloca关键字标识。只有过去的实参可以使用inalloca属性且这个实参保证在内存中传递。
一个实参分配可能被函数调用使用至少一次因为这个函数调用可能会释放它。这个 inalloca 属性不可能与其他会影响实参存储的属性结合使用,像inreg, nest, sret, or byval。这个 inalloca 属性也禁止LLVM隐式降低大型聚合返回值,这意味着前端作者必须通过 sret 降低他们。
当到达调用地点时,实参分配必须是仍然活的最新栈分配,或者结果是未定义的。在一个实参分配后和这个实参的调用地点前额外分配堆栈空间是可能的,但这必须清除llvm.stackrestore。
详见 Design and Usage of the InAlloca Attribute
- sret
- 这表明这个指针参数指向一个在源程序中作为函数返回值的结构体的地址。这个指针必须由调用者保证是有效的:这个结构的加载和存储必须与被调用者所假定的方式是一致的。这只适用于第一个参数。这对于返回值并不是一个有效的属性。
- noalias
- 这表明基于实参或者返回值的指针值不是一个不基于它们的别名指针值,忽略明确的“irrelevant”依赖。对于一个父函数的调用,在函数调用前后的内存引用和函数调用内部的内存引用之间的依赖对于带有 noalias 关键字的实参和返回值是“irrelevant”。这个调用者与被调用者共享这个关系以确定这些要求得到满足。更多的细节,请查看alias analysis中的noalias响应的讨论。
- 注意 noalias 的定义故意定义得与C99中的函数实参的 restrict 定义相似,尽管 restrict 的定义稍微弱一些。
- 因为对于函数返回至,C99的 restrict 是无意义的而LLVM的 noalias 是有意义的。
- nocapture
- 这表明被调用者不对生存期比被调用者长的指针做出任何复制。对于返回值这不是一个有效的属性。
- nest
- 这表明指针参数可以使用trampoline intrinsics删除。这不是一个有效的返回值属性,只能被应用于一个参数。
- returned
- 这表明函数始终返回这个参数作为它的返回值。代码生成器生成调用者时的一个优化提示,允许尾部调用优化和在某种情况下忽略寄存器的保存和恢复;在省城被调用者是,它不会被检查或执行。参数和函数返回类型必须是对于bitcast指令有效的操作数。这不是一个有效的返回值属性,只能应用于一个参数。