LLVM Language Reference Manual

1.摘要

本文档是LLVM汇编语言的参考手册。LLVM是一种基于静态单任务(SSA)的表示,它提供了类型安全、低级操作、灵活性和干净地表示“所有”高级语言的能力。它是在LLVM编译策略的所有阶段中使用的通用代码表示形式。

2.介绍

LLVM代码表示被设计成三种不同的形式:作为内存编译器IR,作为磁盘上的位代码表示(适用于即时编译器的快速加载),以及作为人类可读的汇编语言表示。这允许LLVM为高效的编译器转换和分析提供强大的中间表示,同时提供调试和可视化转换的自然方法。三种不同形式的LLVM都是等价的。本文档描述了人类可读的表示和符号。

LLVM表示的目标是轻量级和低层的,同时具有表达性、类型化和可扩展性。它的目标是成为某种意义上的“通用IR”,通过处于足够低的级别,高级思想可以清晰地映射到它(类似于微处理器是“通用IR”,允许许多源语言映射到它们)。通过提供类型信息,LLVM可以作为优化的目标:例如,通过指针分析,它可以证明,C自动变量从来不是当前函数之外的访问,允许它被提升到一个简单的SSA值(查看https://blog.csdn.net/qq_29674357/article/details/78731713)而不是一个内存位置。

3.Well-Formedness

需要注意的是,本文档描述了“格式良好”的LLVM汇编语言。解析器接受的内容与被认为“格式良好”的内容之间存在差异。例如,下面的指令在语法上是正确的,但格式不正确: 

%x = add i32 1, %x

 因为%x的定义并不支配它的所有用途。LLVM基础结构提供了一个验证通道,该通道可用于验证LLVM模块是否格式良好。此pass在解析输入程序集之后由解析器自动运行,在输出位代码之前由优化器自动运行。验证器pass指出违规行为导致的bug通过转换pass或输入到解析器

4.标识符

LLVM标识符有两种基本类型:全局标识符和本地标识符。全局标识符(函数、全局变量)以“@”字符开头。本地标识符(寄存器名、类型)以“%”字符开头。此外,标识符有三种不同的格式,用于不同的目的:

  1. 命名值表示为带前缀的字符串。例如,%foo, @DivisionByZero, %a.really.long.identifier。实际使用的正则表达式的'[%@][-a-zA-Z$._][-a-zA-Z$._0-9]*’。名称中需要其他字符的标识符可以用引号括起来。特殊字符可以使用“\xx”转义,其中xx是十六进制字符的ASCII代码。这样,任何字符都可以在name值中使用,甚至可以引用它们自己。可以在全局值上使用“\01”前缀来抑制误操作。
  2. 未命名值表示为带前缀的无符号数值。例如,%12,@2,%44。
  3. 常量,在下面的常量部分中进行描述。

LLVM要求值以前缀开头有两个原因:编译器不必担心名称与保留字冲突,保留字集将来可能会扩展而不会受到惩罚。此外,未命名的标识符允许编译器快速生成临时变量,而不必避免符号表冲突。

llvm中的保留字与其他语言中的保留字非常相似。有不同操作码的关键字(“add”、“bitcast”、“ret”等)、基元类型名(“void”、“i32”等)以及其他。这些保留字不能与变量名冲突,因为它们都没有以前缀字符(“%”或“@”)开头。

下面是LLVM代码的例子,将整数变量“%x”乘以8:

简单的方法:

%result = mul i32 %X, 8

强度降低后:

%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的几个重要词汇特征:

  1. 注释用“;”分隔,直到行尾。
  2. 当计算结果未分配给命名值时,将创建未命名的临时值。
  3. 未命名的临时文件按顺序编号(使用每个函数递增计数器,从0开始)。请注意,基本块和未命名的函数参数包含在此编号中。例如,如果条目基本块没有给定标签名称,并且所有函数参数都被命名,那么它将得到数字0。

它还显示了我们在本文档中遵循的约定。在演示指令时,我们将遵循一条带有注释的指令,该注释定义所生成值的类型和名称。

5.High Level Structure

5.1模块结构

LLVM程序由模块组成,每个模块都是输入程序的翻译单元。每个模块由函数、全局变量和符号表条目组成。模块可以与LLVM链接器组合在一起,LLVM链接器合并函数(和全局变量)定义,解析正向声明,并合并符号表条目。以下是“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”组成。

通常,模块由全局值列表组成(其中函数和全局变量都是全局值)。全局值由指向内存位置的指针(在本例中,是指向char数组的指针,以及指向函数的指针)表示,并具有以下链接类型之一。

链接类型

所有全局变量和函数都具有以下类型的链接之一:             

private            

具有“private”链接的全局值只能由当前模块中的对象直接访问。特别是,将代码链接到具有私有全局值的模块中可能会导致在必要时重命名私有,以避免冲突。因为该符号是模块专用的,所以可以更新所有引用。这不会显示在对象文件中的任何符号表中。

internal             

类似于private,但该值在对象文件中显示为本地符号(对于elf,为stb_local)。这与C中“static”关键字的概念相对应。             

available_externally

具有“available_externally”链接的全局不会发送到与LLVM模块对应的对象文件中。从链接器的角度来看,一个可用的外部全局相当于一个外部声明。它们的存在允许进行内联和其他优化,因为已知全局的定义在模块之外的某个地方。允许随意丢弃具有可用外部链接的全局,并允许内联和其他优化。只有定义才允许使用此链接类型,而不是声明。             

linkonce

具有“linkonce”链接的全局在发生链接时与其他同名全局合并。这可以用于实现某些形式的内联函数、模板或其他代码,这些函数、模板或其他代码必须在使用它的每个翻译单元中生成,但在这种情况下,主体稍后可能会被更明确的定义覆盖。允许丢弃未引用的全局链接。请注意,linkonce链接实际上不允许优化器将此函数的主体内联到调用方中,因为它不知道函数的此定义是否是程序中的最终定义,也不知道是否将被更强大的定义覆盖。要启用内联和其他优化,请使用“linkonce_odr”链接。

weak

“weak”链接与linkonce链接具有相同的合并语义,只是具有弱链接的未引用全局不能丢弃。这用于在C源代码中声明为“weak”的全局。             

Common

“common”链接与“weak”链接最为相似,但它们用于c中的暂定定义,如全局范围内的“int x”。具有“common”链接的符号以与weak symbols相同的方式合并,如果未引用,则不能删除。公共符号不能有显式节,必须有零初始值设定项,并且不能标记为“constant”。函数和别名可能没有公共链接。             

appending

“appending”链接只能应用于指向数组类型的指针的全局变量。当两个具有附加链接的全局变量链接在一起时,两个全局数组将附加在一起。这是llvm、typesafe,相当于在链接.o文件时让系统链接器将具有相同名称的“节”附加在一起。             

不幸的是,这与.o文件中的任何功能都不对应,因此它只能用于llvm.global目录等变量,llvm会对这些变量进行特殊解释。             

Extern_weak

这个链接的语义遵循ELF对象文件模型:符号在链接之前是弱的,如果不链接,符号将变为空而不是未定义的引用。             

linkonce-odr,weak_odr

有些语言允许合并不同的全局,例如两个语义不同的函数。其他语言,如C++,确保只有统一的全局被合并(“一个定义规则”-“ODR”)。这样的语言可以使用linkone-odr和weak-odr链接类型来表示全局将只与等效全局合并。这些链接类型在其他方面与它们的非ODR版本相同。

External

如果没有使用上述标识符,则全局是外部可见的,这意味着它参与链接,并可用于解析外部符号引用。

函数声明具有非external或external_weak类型是非法的。

调用约定

llvm函数、调用和调用都可以具有为调用指定的可选调用约定。任何一对动态调用方/被调用方的调用约定都必须匹配,或者程序的行为未定义。LLVM支持以下调用约定,将来可能会增加更多:

“ccc”—C呼叫约定

此调用约定(如果未指定其他调用约定,则为默认值)与目标C调用约定匹配。此调用约定支持varargs函数调用,并允许在声明的原型和实现的函数声明中存在某些不匹配(与普通C一样)。

“fastcc”—快速呼叫约定

这种呼叫约定试图尽可能快地进行呼叫(例如通过在寄存器中传递事物)。这种调用约定允许目标使用它想要为目标生成快速代码的任何技巧,而不必遵守外部指定的ABI(应用程序二进制接口)。只有在使用GHC或HIPE约定时,才能优化尾调用。此调用约定不支持varargs,并且要求所有被调用方的原型与函数定义的原型完全匹配。

“Coldcc”—冷电话会议             

这种调用约定试图使调用方中的代码尽可能高效,前提是调用通常不被执行。因此,这些调用通常保留所有寄存器,以便调用不会中断调用方中的任何活动范围。此调用约定不支持varargs,并且要求所有被调用方的原型与函数定义的原型完全匹配。此外,inliner不考虑这样的函数调用。              

“CC 10”-GHC公约             

此调用约定是为格拉斯哥Haskell编译器(GHC)专门使用而实现的。它传递寄存器中的所有内容,通过禁用被调用方保存寄存器来达到这一目的。这种调用约定不应被轻率地使用,而应仅用于特定的情况,例如在实现函数式编程语言时经常使用的寄存器固定性能技术的替代方法。目前,只有x86支持此约定,它有以下限制:             

在x86-32上,最多只支持4位类型参数。不支持浮点类型。             

在x86-64上,最多只支持10位类型参数和6个浮点参数。             

此调用约定支持尾调用优化,但要求调用方和被调用方都使用它。             

“CC 11”—HIPE呼叫约定             

这一调用约定是专门为爱立信开放源代码erlang/otp系统的本机代码编译器高性能erlang(hipe)编译器实现的。它使用比普通C调用约定更多的寄存器来传递参数,并且不定义被调用方保存的寄存器。调用约定正确地支持尾调用优化,但要求调用方和被调用方都使用它。它使用一个寄存器固定机制,类似于GHC的约定,将频繁访问的运行时组件固定到特定的硬件寄存器。目前,只有x86支持这种约定(32位和64位)。

“webkit_jscc”-webkit的javascript调用约定             

此调用约定已针对WebKit FTL JIT实现。它从右向左传递堆栈上的参数(就像cdecl一样),并在平台的常规返回寄存器中返回一个值。             

“anyregcc”-代码修补的动态调用约定             

这是一种特殊的约定,支持在调用站点的位置修补任意代码序列。此约定强制将调用参数放入寄存器,但允许动态分配它们。这当前只能用于调用llvm.experimental.patchpoint,因为只有此内部函数才能记录其参数在侧表中的位置。请参见LLVM中的堆栈映射和修补点。              “preserve_mostcc”— PreserveMost的调用约定              

这种调用约定试图使调用方中的代码尽可能不具有干扰性。此约定的行为与C调用约定的相同,C调用约定是关于如何传递参数和返回值的,但它使用一组不同的调用者/被调用者保存的寄存器。这减轻了在调用者中调用之前和之后保存和恢复大型寄存器集的负担。如果参数在被调用方保存的寄存器中传递,那么被调用方将在整个调用中保留这些参数。这不适用于被调用方保存的寄存器中返回的值。

在x86-64上,被调用方保留了除R11之外的所有通用寄存器。R11可用作划痕寄存器。浮点寄存器(xmms/ymms)不被保留,需要由调用方保存。

此约定背后的思想是支持对具有热路径和冷路径的运行时函数的调用。热路径通常是一小段不使用许多寄存器的代码。冷路径可能需要调用另一个函数,因此只需要保留调用方保存的寄存器,而调用方尚未保存这些寄存器。PreserveMost调用约定在调用方/被调用方保存的寄存器方面与冷调用约定非常相似,但它们用于不同类型的函数调用。coldcc是用于很少执行的函数调用,而preserve-mostcc函数调用是为了在热路径上执行,而且执行的肯定很多。此外,preserve_mostcc不会阻止inliner内联函数调用。             

这个调用约定将被未来版本的Objective EC运行时使用,因此此时仍应被视为实验性的。虽然创建此约定是为了优化对Objective EC运行时的某些运行时调用,但它不仅限于此运行时,而且将来可能也会被其他运行时使用。当前的实现只支持x86-64,但其目的是将来支持更多的体系结构。

preserve_allcc”—PreserveAll所有呼叫约定             

这种调用约定试图使调用方中的代码比保留的调用约定更不具有侵入性。此调用约定的行为也与C调用约定的相同,即如何传递参数和返回值,但它使用一组不同的调用方/被调用方保存的寄存器。这消除了在调用者调用之前和之后保存和恢复大型寄存器集的负担。如果参数在被调用方保存的寄存器中传递,那么被调用方将在整个调用中保留这些参数。这不适用于被调用方保存的寄存器中返回的值。             

在x86-64上,被调用方保留了除R11之外的所有通用寄存器。R11可用作划痕寄存器。此外,它还保留所有浮点寄存器(xmms/ymms)。             

此约定背后的思想是支持对运行时函数的调用,这些函数不需要调用任何其他函数。              这个调用约定,像preservemost调用约定一样,将被未来版本的Objective EC运行时使用,此时应被视为是实验性的。             

“cxx-fast-tlscc”-访问功能的cxx-fast-tls调用约定             

CLAN生成一个访问C++ C++风格TLS的访问函数。访问函数通常有一个入口块、一个出口块和一个首次运行的初始化块。入口和出口块可以访问几个TLS IR变量,每个访问都将降低到平台特定的顺序。             

此调用约定旨在通过尽可能多地保留寄存器(由入口和出口块组成的快速路径上的所有寄存器)来最小化调用方的开销。             

此调用约定的行为与C调用约定的相同,即如何传递参数和返回值,但它使用一组不同的调用方/被调用方保存的寄存器。             

由于每个平台都有自己的降低顺序,因此它有自己的保留寄存器集,因此我们不能使用现有的保留寄存器。             

在x86-64上,被调用方保留所有通用寄存器,RDI和RAX除外。

“swiftcc”-此呼叫约定用于swift语言。             

在x86-64上,rcx和r8可用于额外的整数返回,xmm2和xmm3可用于额外的fp/vector返回。             

在iOS平台上,我们使用aapcs-vfp调用约定。             

“cc<n>”-编号约定             

任何调用约定都可以通过数字来指定,从而允许使用特定于目标的调用约定。特定于目标的调用约定从64开始。             

可以根据需要添加/定义更多的调用约定,以支持Pascal约定或任何其他众所周知的独立于目标的约定。

可见性样式

所有全局变量和函数都具有以下可见性样式之一:             

“默认”-默认样式             

对于使用ELF对象文件格式的目标,默认可见性表示声明对其他模块可见,而在共享库中,则表示声明的实体可能被重写。在达尔文,默认可见性意味着声明对其他模块可见。默认可见性对应于语言中的“外部链接”。

“隐藏”-隐藏样式

具有隐藏可见性的对象的两个声明引用同一对象(如果它们位于同一共享对象中)。通常,隐藏可见性表示符号不会被放置到动态符号表中,因此没有其他模块(可执行或共享库)可以直接引用它。

“受保护”-受保护样式

在ELF上,受保护的可见性表示符号将被放置在动态符号表中,但定义模块中的引用将绑定到本地符号。也就是说,符号不能被另一个模块重写。             

具有内部或私有链接的符号必须具有默认可见性。

DLL存储类

所有全局变量、函数和别名都可以具有以下dll存储类之一:             

dllimport          

“dllimport”使编译器通过指向由导出符号的dll设置的指针的全局指针引用函数或变量。在Microsoft Windows Targets上,指针名称是由__imp_和函数或变量名组合而成的。  

dllexport

“dllexport”使编译器提供指向dll中指针的全局指针,以便可以用dllimport属性引用它。在Microsoft Windows Targets上,指针名称是由__imp_和函数或变量名组合而成的。由于存在用于定义dll接口的存储类,编译器、汇编程序和链接器知道它是外部引用的,因此必须避免删除符号。

线程本地存储模型

变量可以定义为thread_local,这意味着它不会被线程共享(每个线程都有一个单独的变量副本)。并非所有目标都支持线程局部变量。或者,可以指定一个TLS模型:             

Localdynamic

用于仅在当前共享库中使用的变量。

initialexec

对于模块中不会动态加载的变量。

Localexec

用于在可执行文件中定义并仅在其中使用的变量。

如果没有给出明确的模型,则使用“通用动态”模型。

这些模型对应于ELF TLS模型;有关在何种情况下可以使用不同模型的详细信息,请参阅ELF处理线程本地存储。如果不支持指定的模型,或者可以更好地选择模型,则目标可以选择其他TLS模型。             

模型也可以在别名中指定,但它只控制如何访问别名。它在别名中没有任何效果。              对于不支持ELF-TLS模型的链接器的平台,可以使用-femulated-tls标志生成与gcc兼容的仿真tls代码。

运行时抢占说明符

全局变量、函数和别名可能具有可选的运行时抢占说明符。如果没有显式地给出抢占说明符,则假定符号是dso_抢占。

dso_preemptable

指示函数或变量可以在运行时由链接单元外部的符号替换。

dso_local            

编译器可以假定标记为dso_local的函数或变量将解析为同一链接单元内的符号。即使定义不在此编译单元内,也将生成直接访问。

结构类型             

llvm ir允许您指定“已标识”和“文本”结构类型。文本类型在结构上是唯一的,但标识的类型从不是唯一的。不透明结构类型还可以用于转发声明尚不可用的类型。             

已识别结构规范的一个示例是:

%mytype= type { %mytype*,i32}

在LLVM 3.0发布之前,已识别的类型在结构上是唯一的。在最新版本的llvm中,只有文本类型是唯一的。

非整数指针类型             

注:非整数指针类型是正在进行的工作,此时应将其视为实验性的。             

llvm ir可选地允许前端通过datalayout字符串将某些地址空间中的指针表示为“非整数”。非整型指针类型表示具有未指定位表示的指针;也就是说,整型表示可能与目标相关或不稳定(不由固定整数支持)。             

将整数转换为非整型指针类型的inttoptr指令类型错误,将非整型指针类型的值转换为整数的ptrtoint指令类型错误。所述指令的矢量版本也是错误类型。

全局变量

全局变量定义在编译时而不是运行时分配的内存区域。

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

其他翻译单元中的全局变量也可以声明,在这种情况下,它们没有初始值设定项。              全局变量定义或声明可以有一个要放置的显式节,并且可以指定可选的显式对齐。如果变量声明的显式或推断节信息与其定义不匹配,则结果行为未定义。

变量可以定义为全局常量,表示永远不会修改变量的内容(启用更好的优化,允许将全局数据放在可执行文件的只读部分等)。请注意,需要运行时初始化的变量不能标记为常量,因为该变量有一个存储。

LLVM显式允许全局变量的声明被标记为常量,即使全局的最终定义不是常量。此功能可用于对程序进行稍微更好的优化,但需要语言定义来确保基于“恒定性”的优化对不包含定义的翻译单元有效。

作为SSA值,全局变量定义了范围内(即它们支配)程序中所有基本块的指针值。全局变量总是定义指向其“内容”类型的指针,因为它们描述一个内存区域,并且LLVM中的所有内存对象都是通过指针访问的。

全局变量可以标记为unnamed_addr,这表示地址不重要,只有内容。如果常量具有相同的初始值设定项,则可以将此类标记的常量与其他常量合并。请注意,具有有效地址的常量可以与unnamed_addr常量合并,其结果是具有有效地址的常量。

如果给定了local_unnamed_addr属性,则已知该地址在模块内不重要。

全局变量可以声明为驻留在特定于目标的编号地址空间中。对于支持它们的目标,地址空间可能会影响优化的执行方式和/或用于访问变量的目标指令。默认地址空间为零。地址空间限定符必须位于任何其他属性之前。

LLVM允许为全局指定显式节。如果目标支持它,它将向指定的部分发出全局消息。此外,如果目标有必要的支持,全局可以放置在COMDAT中。

外部声明可以指定显式节。部分信息保留在LLVM IR中,用于使用此信息的目标。将节信息附加到外部声明是断言其定义位于指定节中。如果定义位于不同的部分,则行为未定义。

默认情况下,全局初始值设定项是通过假设在模块中定义的全局变量在全局初始值设定项开始之前没有从其初始值进行修改来优化的。即使对于可能从模块外部访问的变量(包括具有外部链接或出现在@llvm.used或dllexported变量中的变量),也是如此。这个假设可以通过标记externally_initialized的变量来抑制。             

可以为全局指定显式对齐,该全局必须是2的幂。如果不存在,或者如果对齐设置为零,则目标会将全局对齐设置为任何方便的方式。如果指定了显式对齐,则强制全局完全具有该对齐。如果全局具有分配的部分,则不允许目标和优化器过度对齐全局。在这种情况下,额外的对齐方式是可以观察到的:例如,代码可以假定全局数据在其区域中密集分布,并尝试作为数组对其进行迭代,对齐填充将中断此迭代。最大对齐方式为1<<29。             

全局还可以有一个dll存储类、一个可选的运行时抢占说明符、一个可选的全局属性和一个附加元数据的可选列表。             

变量和别名可以有一个线程本地存储模型。

@<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)*

例如,下面用初始值设定项、节和对齐方式在编号的地址空间中定义全局:

@G = addrspace(5) constant float 1.0, section "foo", align 4

下面的示例只是声明一个全局变量

@G = external global i32

下面的示例只是声明一个全局变量

@G = thread_local(initialexec) global i32 0, align 4

函数

LLVM函数定义包括“define”关键字、可选linkage type、可选runtime preemption specifier、可选visibility style、可选DLL storage class、可选calling convention、可选unnamed_addr属性、返回类型、返回类型的可选parameter attribute、函数名、a(可能为空)参数列表(每个参数都具有可选的参数属性)、可选的函数属性、可选的地址空间、可选的节、可选的对齐方式、可选的comdat、可选的垃圾收集器名称、可选的prefix、可选的prologue、可选的personality、可选的附加metadata列表、左大括号、基本块列表和右大括号。             

llvm函数声明包括“declare”关键字、可选linkage type、可选visibility style、可选DLL storage class、可选calling convention、可选unnamed_addr或local_unnamed_addr属性、可选地址空间、返回类型、返回类型的可选参数属性、函数名称、可能为空的参数列表、可选的对齐方式、可选的garbage collector name、可选的prefix和可选的prologue。             

函数定义包含基本块列表,形成函数的CFG(控制流图)。每个基本块可以选择从一个标签开始(给基本块一个符号表条目),包含一个指令列表,并以一个终止符指令结束(例如分支或函数返回)。如果未提供显式标签,则使用与未命名临时性相同的计数器中的下一个值,为块分配隐式编号标签(请参见上文)。例如,如果函数项块没有显式标签,它将被分配标签“%0”,则该块中第一个未命名的临时块将为“%1”等。

函数中的第一个基本块有两种特殊的方式:在函数入口立即执行,不允许有前置基本块(即函数入口块不能有任何分支)。因为块不能有前置任务,所以它也不能有任何phi节点。              LLVM允许为函数指定显式节。如果目标支持它,它将向指定的部分发出函数。此外,该函数可以放置在COMDAT中。

可以为函数指定显式对齐方式。如果不存在,或者如果对齐设置为零,则目标会将函数的对齐设置为任何方便的方式。如果指定了显式对齐,则强制函数至少具有如此多的对齐。所有对齐必须是2的幂。

如果给定了unnamed_addr属性,则地址已知不重要,可以合并两个相同的函数。              如果给定了local_unnamed_addr属性,则已知该地址在模块内不重要。

如果未给定显式地址空间,则它将默认为datalayout字符串中的程序地址空间。

例如:

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)* { ... }

参数列表是一个逗号分隔的参数序列,其中每个参数的格式如下:

Syntax:

<type> [parameter Attrs] [name]

别名

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

别名具有全局值或常量表达式的名称和别名。

别名可以具有可选的链接类型、可选的运行时抢占说明符、可选的可见性样式、可选的dll存储类和可选的tls model。

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

链接必须是private, internal, linkonce, weak, linkonce_odr, weak_odr, external的一种。请注意,某些系统链接器可能无法正确处理除去别名的弱符号。             

别名有unnamed_addr保证与别名表达式具有相同的地址。unnamed_addr只能保证指向相同的内容。             

如果给定了local_unnamed_addr属性,则已知该地址在模块内不重要。

由于别名只是第二个名称,因此应用了一些限制,其中一些限制只能在生成对象文件时检查:              定义别名的表达式必须在组装时可计算。因为它只是一个名称,所以不能使用任何重新定位。              表达式中的任何别名都不能是弱的,因为对象文件中不能表示被重写的中间别名的可能性。              表达式中没有全局值可以是声明,因为这需要重新定位,这是不可能的。

IFUNCS             

ifunc和别名一样,不会创建任何新数据或func。它们只是动态链接器在运行时通过调用解析程序函数解析的新符号。

ifuncs有一个名称和一个解析器,它是动态链接器调用的函数,返回与该名称关联的另一个函数的地址。

ifunc可以具有可选的链接类型和可选的可见性样式。

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

Comdats

COMDAT IR提供对COFF和ELF对象文件COMDAT功能的访问。

COMDAT具有表示COMDAT键的名称。如果链接器选择此键而不是其他键,则指定此键的所有全局对象将只在最终对象文件中结束。别名放置在其别名计算到的同一个COMDAT中(如果有)。

COMDAT有一种选择类型,用于提供有关链接器应如何在两个不同对象文件中的键之间进行选择的输入。

$<Name> = comdat SelectionKind

选择类型必须是以下类型之一:

any

链接器可以选择任何COMDAT密钥,选择是任意的

exactmatch

链接器可以选择任何COMDAT键,但各节必须包含相同的数据。             

largest 

链接器将选择包含最大COMDAT键的部分。             

noduplicates 

链接器要求只存在具有此COMDAT键的节。             

samesize 

链接器可以选择任何COMDAT键,但各节必须包含相同数量的数据。             

请注意,mach-o平台不支持comdats,elf和webassembly只支持将any作为选择类型。              以下是COMDAT组的一个示例,其中只有当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对象文件中,这将创建一个带有选择类型image_comdat_select_maximum的COMDAT节,其中包含@foo符号的内容;另一个带有选择类型image_comdat_select_associative的COMDAT节,其中与第一个COMDAT节关联,并包含@bar符号的内容。             

对全局对象的属性有一些限制。当以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)

从目标文件角度来看,这要求用相同的名称建立两个部分。This is necessary because both globals belong to different COMDAT groups and COMDATs, at the object file level, are represented by sections.

 

Note that certain IR constructs like global variables and functions may create COMDATs in the object file in addition to any which are specified using COMDAT IR. This arises when the code generator is configured to emit globals in individual sections (e.g. when -data-sections or -function-sections is supplied to llc).

命名元数据             

命名元数据是元数据的集合。元数据节点(而不是元数据字符串)是命名元数据的唯一有效操作数。             

命名元数据表示为带有元数据前缀的字符串。元数据名称的规则与标识符的规则相同,但不允许使用带引号的名称。“\xx“类型转义仍然有效,允许任何字符作为名称的一部分。              Syntax:

; Some unnamed metadata nodes, which are referenced by the named metadata.

!0 = !{!"zero"}

!1 = !{!"one"}

!2 = !{!"two"}

; A named metadata.

!name = !{!0, !1, !2}

参数属性             

函数类型的返回类型和每个参数可以有一组与其关联的参数属性。参数属性用于传递有关函数结果或参数的附加信息。参数属性被认为是函数的一部分,而不是函数类型,因此具有不同参数属性的函数可以具有相同的函数类型。             

参数属性是跟随指定类型的简单关键字。如果需要多个参数属性,则它们是空间分隔的。例如:

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指针参数有效。它通常用于按值传递结构和数组,但在指向标量的指针上也有效。副本被认为属于调用方而不是被调用方(例如,只读函数不应写入byval参数)。这不是返回值的有效属性。

ByVal属性还支持使用Align属性指定对齐方式。它指示要形成的堆栈槽的对齐方式,以及指定给调用站点的指针的已知对齐方式。如果没有指定对齐方式,那么代码生成器将做出特定于目标的假设。

inalloca

inalloca参数属性允许调用者获取传出堆栈参数的地址。inalloca参数必须是指向alloca指令生成的堆栈内存的指针。alloca或参数分配也必须用inalloca关键字标记。只有最后一个参数可以有inalloca属性,并且该参数保证在内存中传递。             

参数分配最多只能由调用使用一次,因为调用可能会取消分配它。inalloca属性不能与其他影响参数存储的属性一起使用,如inreg、nest、sret或byval。inalloca属性还禁用llvm隐式降低大型聚合返回值,这意味着前端作者必须使用sret指针降低它们。             

当到达调用站点时,参数分配必须是当前最新的堆栈分配,或者行为未定义。可以在参数分配之后及其调用站点之前分配额外的堆栈空间,但必须使用llvm.stackrestore清除它。              有关如何使用该属性的详细信息,请参阅inalloca属性的设计和使用。             

sret            

这表示指针参数指定结构的地址,该结构是源程序中函数的返回值。调用方必须保证此指针是有效的:对结构的加载和存储可能由被调用方假定不陷阱和正确对齐。这不是返回值的有效属性。

align<n>

这表示优化程序可能会假定指针值具有指定的对齐方式。

注意,当与byval属性组合时,这个属性具有附加的语义。

noalias

这表示在函数执行期间,通过基于参数或返回值的指针值访问的对象也不会通过不基于参数或返回值的指针值访问。返回值的属性还具有下面描述的附加语义。呼叫者与被呼叫者分担确保满足这些要求的责任。有关更多详细信息,请参见别名分析中的noalias响应讨论。

注意,noalias的这个定义有意类似于c99中函数参数的restrict的定义。

对于函数返回值,c99的限制没有意义,而llvm的noalias是。此外,当用于函数参数时,noalias属性返回值的语义比属性的语义更强。对于函数返回值,noalias属性指示该函数的作用类似于系统内存分配函数,从存储中为调用方可访问的任何其他对象返回一个指向已分配存储不连接的指针。 

nocapture

这表示被调用方不会复制比被调用方本身寿命长的指针。这不是返回值的有效属性。在易失性操作中使用的地址被认为是被捕获的。

nest

这表明指针参数可以使用蹦床内部函数来删除。这不是返回值的有效属性,只能应用于一个参数。

returned

这表示函数始终将参数作为返回值返回。这是在生成调用者、允许值传播、尾调用优化以及在某些情况下省略寄存器保存和恢复时使用的优化器和代码生成器的提示;在生成被调用者时不检查或强制执行。参数和函数返回类型必须是bitcast指令的有效操作数。这不是返回值的有效属性,只能应用于一个参数。

nonnull

这表示参数或返回指针不为空。此属性只能应用于指针类型的参数。这不是由llvm检查或强制执行的;如果参数或返回指针为空,则行为未定义。

dereferenceable(<n>)

这表示参数或返回指针不可引用。此属性只能应用于指针类型的参数。可取消引用的指针可以从推测中加载,而不存在捕获风险。必须在括号中提供已知可取消引用的字节数。字节数小于指针类型的大小是合法的。非空属性并不意味着可取消引用(考虑一个指针指向数组末尾的一个元素),但是dereferenceable(<n>)在addrspace(0)(默认地址空间)中并不意味着非空。

dereferenceable_or_null(<n>)

这表示参数或返回值并非同时为非空和不可取消引用(最多为<n>字节)。所有标记有dereferenceable_or_null(<n>)的非空指针都是dereferenceable(<n>)。对于地址空间0,dereferenceable_or_null(<n>)意味着指针恰好是dereferenceable((<n>)或“空值”之一,而在其他地址空间中,dereferenceable_or_null(<n>)意味着指针至少是dereferenceable(<n>)或“空值”(即,它可以同时为空值和dereferenceable(<n>)。此属性只能应用于指针类型的参数。

swiftself

这表示参数是self/context参数。这不是返回值的有效属性,只能应用于一个参数。              swifterror            

此属性用于建模和优化快速错误处理。它可以应用于具有指针到指针类型或指针大小alloca的参数。在调用站点,与swifterrror参数对应的实际参数必须来自调用方的swifterrror alloca或swifterrror参数。swifterror值(参数或alloca)只能从中加载和存储,或用作swifterror参数。这不是返回值的有效属性,只能应用于一个参数。             

这些约束允许调用约定通过在调用边界处将它们与特定寄存器关联,而不是将它们放在内存中来优化对swifter变量的访问。由于这确实改变了调用约定,因此对参数使用swifterrror属性的函数与不使用该属性的函数不兼容。             

这些约束还允许llvm假定swifterrror参数不会对函数中可见的任何其他内存进行别名,并且作为参数传递的swifterrror alloca不会转义。

垃圾收集器策略名称

每个函数都可以指定一个垃圾收集器策略名称,它只是一个字符串:

define void @f() gc "name" { ... }

支持的name值包括内置到llvm的值和加载插件提供的值。指定GC策略将导致编译器更改其输出,以支持指定的垃圾收集算法。请注意,LLVM本身不包含垃圾收集器,此功能仅限于生成可以与外部提供的收集器进行互操作的计算机代码。

前缀数据             

前缀数据是与一个函数相关联的数据,代码生成器将在该函数的入口点之前立即发出该函数。此功能的目的是允许前端将特定于语言的运行时元数据与特定函数相关联,并通过函数指针使其可用,同时仍允许调用函数指针。

为了访问一个给定函数的数据,程序可以将函数指针重传给指向常量类型和取消引用索引-1的指针。这意味着红外符号刚好指向前缀数据的末尾。例如,以一个用单个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_externally链接具有相似的语义,因为优化器可以使用数据,但不会在对象文件中发出。

序幕数据

prologue属性允许在函数体之前插入任意代码(编码为字节)。这可用于启用功能热修补和仪表。             

为了维护普通函数调用的语义,序言数据必须具有特定的格式。具体地说,它必须以一个字节序列开始,该字节序列解码为一系列对模块目标有效的机器指令,这些指令将控制传输到序言数据之后的点,而不执行任何其他可见操作。这允许inliner和其他传递对函数定义的语义进行推理,而不需要对prologue数据进行推理。显然,这使得序言数据的格式高度依赖于目标。             

x86体系结构的有效序言数据的一个小例子是i8 144,它编码nop指令:

define void @f() prologue i8 144 { ... }

一般来说,序言数据可以通过对跳过元数据的相对分支指令进行编码来形成,如本例中的x86_64体系结构的有效序言数据,其中前两个字节编码jmp.+10:

%0 = type <{ i8, i8, i8* }>

define void @f() prologue %0 <{ i8 235, i8 8, i8* @md}> { ... }

函数可以有序言数据,但没有正文。这与available_externally链接具有相似的语义,因为优化器可以使用数据,但不会在对象文件中发出。

个性函数

个性属性允许函数指定用于异常处理的函数。

属性组

属性组是由IR中的对象引用的属性组。它们对于保持.ll文件的可读性很重要,因为许多函数将使用相同的属性集。在对应于单个.c文件的.ll文件的退化情况下,单个属性组将捕获用于生成该文件的重要命令行标志。             

属性组是模块级对象。要使用属性组,对象引用属性组的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 { ... }

函数属性

函数属性设置为传递有关函数的附加信息。函数属性被认为是函数的一部分,而不是函数类型,因此具有不同函数属性的函数可以具有相同的函数类型。             

函数属性是跟随指定类型的简单关键字。如果需要多个属性,则它们是空间分隔的。例如:

define void @f() noinline { ... }

define void @f() alwaysinline { ... }

define void @f() alwaysinline optsize { ... }

define void @f() optsize { ... }

alignstack(<n>)

此属性表示,在发出序言和尾声时,后端应强制对齐堆栈指针。在括号中指定所需的对齐方式,该对齐方式必须是2的幂。

allocsize(<eltsizeparam>[,<numelsparam>]) 

此属性表示带注释的函数将始终返回至少给定数量的字节(或空)。其参数为零索引参数编号;如果提供了一个参数,则假定在返回的指针处至少有callsite.args[eltsizeparam]字节可用。如果提供了两个,则假定callsite.args[eltsizeparam]*callsite.args[numelsparam]字节可用。引用的参数必须是整数类型。没有对返回的内存块的内容进行假设。

alwaysinline

此属性指示内联线应尽可能尝试将此函数内联到调用方中,忽略此调用方的任何活动的内联大小阈值。

builtin

这表示调用站点上的被调用函数应被识别为内置函数,即使函数的声明使用nobuiltin属性。对于使用nobuiltin属性声明的函数的直接调用,这仅在调用站点有效。

cold

此属性表示很少调用此函数。在计算边缘权重时,以冷函数调用为主的基本块也被认为是冷的;因此,给定的权重较低。

convergent

在一些并行执行模型中,存在不能使控制依赖于任何附加值的操作。我们称这种操作为convergent,并用这个属性标记它们。        

convergent属性可能出现在函数或调用/调用指令上。当它出现在函数上时,表示不应根据附加值使对此函数的调用成为控件。例如,内部llvm.nvvm.barrier0是convergent,因此不能根据附加值来控制对该内部函数的调用。             

当它出现在调用/调用中时,convergent属性指示我们应该像调用聚合函数一样对待调用。这在间接调用中特别有用;如果没有这一点,我们可以将此类调用视为目标是不收敛的。              当优化器可以证明函数不执行任何convergent操作时,它可以删除函数的收敛属性。类似地,如果优化器能够证明调用/调用不能调用convergent函数,那么优化器可以删除对调用/调用的收敛。

Inaccessiblememonly

此属性表示函数只能访问被编译模块无法访问的内存。这是一种较弱的readnone形式。如果函数读取或写入其他内存,则行为未定义。         

Inaccessiblemem_or_argmemonly

此属性表示函数只能访问被编译的模块无法访问的内存,或者由其指针参数指向的内存。这只是argmemonly的一种较弱的形式。如果函数读取或写入其他内存,则行为未定义。

inlinehint

此属性指示源代码包含提示内联该函数的提示(例如C/C++中的“内联”关键字)。这只是一个提示;它对inliner没有任何要求。  

jumptable

此属性表示函数应在代码生成时添加到跳转指令表中,并且所有引用此函数的地址都应替换为对相应跳转指令表函数指针的引用。注意,这会为原始函数创建一个新的指针,这意味着依赖于函数指针标识的代码可能会中断。因此,用JumpTable注释的任何函数也必须是unnamed_addr。

minsize

此属性建议优化过程和代码生成器过程做出选择,使此函数的代码大小尽可能小,并执行可能牺牲运行时性能的优化,以最小化生成的代码的大小。

naked       

此属性禁用函数的序言/尾声发射。这可能会产生非常系统特定的后果。

no-jump-tables

当此属性设置为true时,将禁用从开关事例降低生成的跳转表和查找表。

nobuiltin

这表示调用站点上的被调用方函数不被识别为内置函数。除非调用站点使用built in属性,否则llvm将保留原始调用,而不使用基于内置函数语义的等效代码替换它。这在调用站点、函数声明和定义上有效。 

noduplicate

此属性表示对函数的调用不能重复。对noduplicate函数的调用可以在其父函数中移动,但不能在其父函数中重复。             

包含noduplicate调用的函数可能仍然是内联候选函数,前提是该调用不能通过内联进行复制。这意味着函数具有内部链接,并且只有一个调用站点,所以原始调用在内联之后就死了。              Noimplicitfloa

此属性禁用隐式浮点指令。

noinline

此属性指示inliner在任何情况下都不应内联此函数。此属性不能与alwaysinline属性一起使用。

nonlazybind

此属性禁止对函数进行惰性符号绑定。如果在程序启动期间不调用函数,这可能会使对函数的调用更快,但会花费额外的程序启动时间。

noredzone

此属性表示代码生成器不应使用红色区域,即使目标特定的ABI通常允许使用红色区域。              indirect-tls-seg-refs

此属性表示代码生成器不应通过段寄存器使用直接TLS访问,即使目标特定的ABI通常允许这样做。

noreturn

此函数属性表示函数从不正常返回。如果函数确实动态返回,则会在运行时产生未定义的行为。 

norecurse

此函数属性表示该函数不会直接或间接地沿着任何可能的调用路径调用自身。如果函数确实重复出现,则会在运行时产生未定义的行为。

nounwind

此函数属性指示函数从不引发异常。如果函数确实引发异常,则其运行时行为未定义。但是,标记为nounwind的函数可能仍然会捕获或生成异步异常。LLVM识别的用于处理异步异常(如SEH)的异常处理方案仍将提供其实现定义的语义。

…………………………………………

全局属性

属性可以设置为传递有关全局变量的附加信息。与函数属性不同,全局变量上的属性被分组为单个属性组。

bundles操作

bundles操作是标记的SSA值集,可以与某些LLVM指令相关联(当前仅调用和调用)。在某种程度上,它们类似于元数据,但是删除它们是不正确的,并且会改变程序语义。

Syntax:

operand bundle set ::= '[' operand bundle (, operand bundle )* ']'

operand bundle ::= tag '(' [ bundle operand ] (, bundle operand )* ')'

bundle operand ::= SSA value

tag ::= string constant

bundles操作不是函数签名的一部分,给定函数可以从具有不同类型操作数束的多个位置调用。这反映了一个事实,即bundles操作在概念上是调用(或调用)的一部分,而不是被调用方被调度到的一部分。

bundles操作是一种通用机制,旨在支持托管语言的运行时自省功能。虽然bundles操作的确切语义取决于bundle标记,但bundle操作的存在对程序语义的影响有一定的限制。这些限制被描述为“未知”bundle操作的语义。只要bundle操作的行为在这些限制内是可描述的,LLVM就不需要对操作数束有特殊的了解,以避免对包含它的程序进行错误编译。             

在控制权转移到被调用方或被调用方之前,未知操作数束的束操作数以未知方式转义。

使用操作数绑定的调用和调用在进入和退出时对堆有未知的读/写影响(即使调用目标为readnone或readonly),除非它们被特定于调用站点的属性覆盖。

调用站点上的操作数绑定无法更改被调用函数的实现。程序间优化只要考虑到前两个属性,就可以正常工作。

下面描述了更具体的操作数束类型。

 

 

 

 

 

 

 

 

 

 

 

 

 

发布了43 篇原创文章 · 获赞 23 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhang14916/article/details/89554081