编程语言
首页 > 编程语言> > LLVM一些编程语法语义特性

LLVM一些编程语法语义特性

作者:互联网

LLVM一些编程语法语义特性

High Level Structure

Module Structure

LLVM 程序由Module's组成,每个 's 是输入程序的一个翻译单元。每个模块由函数、全局变量和符号表条目组成。模块可以与 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”。

通常,模块由全局值列表组成(其中函数和全局变量都是全局值)。全局值由指向内存位置的指针(在这种情况下,指向字符数组的指针和指向函数的指针)表示。

命名元数据

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

  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}

参数属性

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

参数属性是遵循指定类型的简单关键字。如果需要多个参数属性,空格分隔。例如:

declare i32 @printf(i8* noalias nocapture, ...)
declare i32 @atoi(i8 zeroext)
declare signext i8 @returns_signed_char()

前缀数据

前缀数据是与函数关联的数据,代码生成器将在函数入口点之前立即发出数据。允许前端将特定于语言的运行时元数据与特定函数相关联,通过函数指针可用,允许调用函数指针。

要访问给定函数的数据,程序可以将函数指针位转换为指向常量类型的指针,取消引用索引 -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
define void @f() prologue i8 144 { ... }

序言数据

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

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

x86 架构的有效序言数据的一个简单示例,对指令进行编码:i8 144nop

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

 

; 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 { ... }

属性组

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

属性组是模块级对象。要使用属性组,对象引用属性组的 ID(例如#37)。一个对象可能引用多个属性组。在这种情况下,来自不同组的属性将合并。

下面是一个函数的属性组示例,该函数应该始终内联,堆栈对齐为 4,不应使用 SSE 指令:

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

去优化操作数包

去优化操作数包的特点是"deopt" 操作数包标签。这些操作数包代表了所连接的调用站点的替代“安全”延续,可以由合适的运行时使用,取消优化指定调用站点的编译帧。最多可以有一个"deopt"操作数束,附加到调用站点。去优化的确切细节超出了语言参考的范围,通常涉及将编译帧重写为一组解释帧。

从编译器的角度来看,去优化操作数包,所连接的调用点,至少连接到readonly。通读了所有指针类型的操作数(即使没有以其它方式转义)和整个可见堆。去优化操作数包不捕获操作数,除非在去优化期间,在这种情况下控制,不会返回到编译帧。

内联器知道如何通过具有去优化操作数包的调用进行内联。就像通过普通调用站点内联涉及组合正常和异常延续一样,通过具有去优化操作数包的调用站点,内联需要适当地组合“安全”去优化延续。内联程序通过将父级的去优化延续,添加到内联正文中的每个去优化延续之前,做到这一点。例如内联@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
}

在每个需要 a 的规范上<abi>:<pref>,指定 <pref>对齐方式是可选的。如果省略,前面的应该省略,并且<pref>等于<abi>。

在为给定目标构建数据布局时,LLVM 从一组默认规范开始,然后(可能)被datalayout关键字中的规范覆盖。此列表中给出了默认规格:

使用列表order指令

使用列表指令,对每个使用列表的内存顺序进行编码,允许重新创建顺序。<order-indexes>是分配给引用值的使用的索引的逗号分隔列表。引用值的使用列表,立即按这些索引排序。

使用列表指令,可能出现在函数作用域,或全局作用域。不是指令,对 IR 的语义没有影响。当处于函数作用域时,必须出现在最终基本块的终止符之后。

如果基本块的地址,通过blockaddress()表达式获取, uselistorder_bb则可用于从其函数范围之外重新排序使用列表。

Syntax:

 

uselistorder <ty> <value>, { <order-indexes> }

uselistorder_bb @function, %block { <order-indexes> }

Examples:

 

define void @foo(i32 %arg1, i32 %arg2) {

entry:

  ; ... instructions ...

bb:

  ; ... instructions ...

 

  ; At function scope.

  uselistorder i32 %arg1, { 1, 0, 2 }

  uselistorder label %bb, { 1, 0 }

}

 

; At global scope.

uselistorder i32* @global, { 1, 2, 0 }

uselistorder i32 7, { 1, 0 }

uselistorder i32 (i32) @bar, { 1, 0 }

uselistorder_bb @foo, %bb, { 5, 1, 3, 2, 0, 4 }

函数类型

概述:

 

函数类型可以被认为是一个函数签名。由一个返回类型和一个形参类型列表组成。函数类型的返回类型是 void 类型,或第一类类型——标签元数据类型除外。

句法:

<returntype> (<parameter list>)

...其中 ' ' 是逗号分隔的类型说明符列表。参数列表可以包括一个 type ,表明该函数采用可变数量的参数。可变参数函数可以使用可变参数处理内部函数访问参数。' ' 是除标签元数据之外的任何类型。<parameter list>...<returntype>

示例

i32 (i32)

函数取一个i32,返回一个i32

float (i16, i32 *) *

指针,以接受一个函数i16和一个指针 i32,返回float。

i32 (i8*, ...)

可变参数函数,至少一个指针到i8(在C炭),返回一个整数。这是printfLLVM 中的签名。

{i32, i32} (i32)

一个函数采用i32,返回一个包含两个值的结构i32

整数将占用的位数由N 值指定。

例示例

i1

一位整数。

i32

一个 32 位整数。

i1942652

一个超过 100 万位的非常大的整数。

浮点类型

类型

描述

half

16 位浮点值

float

32 位浮点值

double

64 位浮点值

fp128

128 位浮点值(112 位尾数)

x86_fp80

80 位浮点值 (X87)

ppc_fp128

128 位浮点值(两个 64 位)

指针类型

概述:

 

指针类型用于指定内存位置。指针通常用于引用内存中的对象。

指针类型可能有一个可选的地址空间属性,用于定义指向对象所在的编号地址空间。默认地址空间是数字零。非零地址空间的语义是特定于目标的。

请注意,LLVM 不允许指向 void ( void*) 的指针,也不允许指向标签 ( label*) 的指针。使用i8*来代替。

句法:

 
<类型>  *

例子

 

 

[4 x i32]*

指针阵列的4个i32值。

i32 (i32*) *

一个指向函数指针,接受一个i32*,返回一个i32。

i32 addrspace(5)*

指向i32位于地址空间#5值的指针

矢量类型

概述:

 

向量类型是表示元素向量的简单派生类型。当使用单个指令 (SIMD) ,并行操作多个原始数据时,将使用向量类型。向量类型需要大小(元素数量)和底层原始数据类型。向量类型被认为是一类的

< <# elements> x <elementtype> >

元素个数是一个大于0的常量整数值;elementtype 可以是任何整数、浮点数或指针类型。不允许大小为零的向量。

例子:

 

 

<4 x i32>

4 个 32 位整数值的向量。

<8 x float>

8 个 32 位浮点值的向量。

<2 x i64>

2 个 64 位整数值的向量。

<4 x i64*>

4 个指向 64 位整数值的指针的向量。

数数组类型

概述:

 

数组类型是一种非常简单的派生类型,在内存中按顺序排列元素。数组类型需要大小(元素数)和基础数据类型。

句法:

 

 [<# elements> x <elementtype>]

元素的数量是一个常量整数值;elementtype可以是具有大小的任何类型。

例子:

 

 

[40 x i32]

40 个 32 位整数值的数组。

[41 x i32]

41 个 32 位整数值的数组。

[4 x i8]

4 个 8 位整数值的数组。

多维数组的一些示例:

[3 x [4 x i32]]

32 位整数值的 3x4 数组。

[12 x [10 x float]]

12x10 单精度浮点值数组。

[2 x [3 x [4 x i16]]]

2x3x4 16 位整数值数组。

对超出静态类型所隐含的数组末尾的索引,没有限制(尽管在某些情况下对超出已分配对象范围的索引有限制)。在具有零长度数组类型的 LLVM 中,实现一维“可变大小数组”寻址。例如,LLVM 中“pascal 样式数组”的实现,可以使用类型“ ”。{ i32, [0 x float]}

结构类型

概述:

 

结构类型用于表示内存中数据成员的集合。结构的元素可以是具有大小的任何类型。

通过使用“ ”指令,获取指向字段的指针,可以使用“ load”和“ store”访问内存中的结构getelementptr。使用“ extractvalue”和“ insertvalue”指令,访问寄存器中的结构。

结构可以选择是“打包”结构,这表明结构的对齐是一个字节,元素之间没有填充。在非压缩结构中,字段类型之间的填充,按照模块中 DataLayout 字符串的定义插入,这是匹配底层代码生成器所期望的。

结构可以是“文字”或“已知”。文字结构是与其它类型(例如)内联定义的,标识的类型总是在顶层定义一个名称。文字类型因内容而唯一,永远不会是递归或不透明的,无法编写。已知类型可以是递归的,可以是不透明的,永远不会是唯一的。{i32, i32}*

句法:

 
% T1  =  type  {  <类型 列表>  }      ;  已知的 正常 结构体 类型
% T2  =  type  < {  <类型 列表>  } >    ;  已知的 压缩 结构 类型

例子:

 

 

{ i32, i32, i32 }

三个i32值的三元组

{ float, i32 (i32) * }

第一个元素是 float,第二个元素是指向一个函数指针,该函数接受一个i32,返回一个i32。

<{ i8, i32 }>

已知大小为 5 个字节的打包结构。

不透明结构类型

概述:

 

不透明结构类型,用于表示没有指定主体的命名结构类型。对应于(例如)前向声明结构的 C 概念。

句法:

 
% X  = 类型 不透明
% 52  = 类型 不透明

例子:

 

 

opaque

不透明类型。

全局变量和函数地址

全局变量和 函数的地址总是隐式有效(链接时)常量。这些常量在使用全局标识符时显式引用,始终具有 指针类型。例如,以下是一个合法的 LLVM 文件:

@X = global i32 17
@Y = global i32 42
@Z = global [2 x i32*] [ i32* @X, i32* @Y ]

未定义值

字符串 ' undef' 可用于任何需要常量的地方,指示该值的用户,可能会收到未指定的位模式。未定义的值可以是任何类型(除了“ label”或“ void”),可以在任何允许常量的地方使用。

未定义值很有用,向编译器表明无论使用什么值,程序都是定义良好的。这给了编译器更多的优化自由。以下是一些有效(在伪 IR 中)转换的示例:

  %A = add %X, undef
  %B = sub %X, undef
  %C = xor %X, undef
Safe:
  %A = undef
  %B = undef
  %C = undef

这是安全的,因为所有输出位都受 undef 位影响。任何输出位都可以有0,,1,具体取决于输入位。

%A = or %X, undef
  %B = and %X, undef
Safe:
  %A = -1
  %B = 0
Safe:
  %A = %X  ;; By choosing undef as 0
  %B = %X  ;; By choosing undef as -1
Unsafe:
  %A = undef
  %B = undef

这些逻辑运算的位,并不总是受输入影响。例如,如果%X有一个零位,那么and对于该位' ' 操作的输出,将始终为零,无论 ' undef'的相应位是什么。因此,优化或假设“ and”的结果 “ undef”是不安全的。但是,可以安全地假设 ' undef' 的所有位,都可以为 0,将 ' and'优化为 0。同样,可以安全地假设 ' undef' 操作数的所有位or,都可以设置,允许将“ or”设置为 -1。

  %A = select undef, %X, %Y
  %B = select undef, 42, %Y
  %C = select %X, %Y, undef
Safe:
  %A = %X     (or %Y)
  %B = 42     (or %Y)
  %C = %Y
Unsafe:
  %A = undef
  %B = undef

  %C = undef这组示例表明,未定义的“ select”(和条件分支)条件,可以采用任何一种方式,必须来自两个操作数之一。在%A例子中,如果%X和%Y是两个已知具有明显的低位,那么%A就必须有一个清除低位。但是,在%C示例中,优化器可以假设“ undef”操作数,可以与  %Y相同,从而select可以消除整个“ ”。

  %A = xor undef, undef 
 
  %B = undef 
  %C = xor %B, %B 
 
  %D = undef 
  %E = icmp slt %D, 4 
  %F = icmp gte %D, 4 
 
Safe: 
  %A = undef 
  %B = undef 
  %C = undef 
  %D = undef 
  %E = undef 
  %F = undef

此示例指出两个“ undef”操作数不一定相同。可能会让人们感到惊讶(并且也匹配 C 语义),假设“ X^X”始终为零,即使 X是未定义的。出于多种原因,情况并非如此,但简短的回答是undef“变量”,可以在 “有效范围”内,任意更改值。因为变量实际上没有有效范围。相反,该值是在需要时,从恰好在附近的任意寄存器逻辑读取的,因此该值不一定随时间保持一致。事实上,%A与 %C需要具有相同的语义,或核心LLVM概念,也不会执行“替换所有用途”。

  %A = fdiv undef, %X
  %B = fdiv %X, undef
Safe:
  %A = undef
b: unreachable

这些示例显示了未定义值 和未定义行为之间的关键区别。undef允许未定义的值(如“ ”)具有任意位模式。这意味着%A 操作可以常量折叠为“ undef”,因为“ undef”可能是 SNaN, fdiv(当前)未在 SNaN 上定义。在第二个例子中,可以做出更激进的假设:因为undef允许是任意值,可以假设可能为零。由于除以零具有未定义的行为,可以假设该操作根本不执行。删除除法和之后的所有代码。因为未定义的操作“不可能发生”,优化器可以假设发生在死代码中。

a:  store undef -> %X
b:  store %X -> undef
Safe:
a: <deleted>
b: unreachable

危险值

危险值类似于undef 值,也代表了这样一个事实,即不能引起副作用的指令,或常量表达式,仍然检测到导致未定义行为的条件。

目前没有办法在 IR 中表示危险值;当通过操作,如产生只存在附加与nsw标记。

危险值行为是根据值依赖定义的:

  • phi节点以外的值取决于操作数。
  • Phi节点依赖于与动态前驱基本块对应的操作数。
  • 函数参数取决于函数的动态调用者中相应的实际参数值。
  • 调用指令依赖于 动态地将控制返回给ret指令。
  • Invoke指令依赖于 ret resume,或异常抛出调用指令,动态地将控制权转移回。
  • 非易失性加载和存储,依赖于所有引用内存地址的最新存储,遵循 IR 中的顺序(包括内部函数隐含的加载和存储,例如 @llvm.memcpy。)
  • 具有外部可见副作用的指令,取决于最近的具有外部可见副作用的指令,遵循 IR 中的顺序。(包括易失性操作。)
  • 如果终止指令有多个后继指令,当控制转移到其中一个后继指令时,总是执行该指令,当控制转移到另一个后继指令时,可能不执行该指令,则指令控制依赖终止指令
  • 此外,如果终止符已将控制权转移到不同的后继指令,该指令还控制依赖于终止符指令,否则该指令所依赖的指令集将不同。
  • 依赖是可传递的。

危险值与undef值具有相同的行为,附加效果是任何依赖 危险值的指令,都具有未定义的行为。

例子:

 

entry:
  %poison = sub nuw i32 0, 1           ; Results in a poison value.
  %still_poison = and i32 %poison, 0   ; 0, but also poison.
  %poison_yet_again = getelementptr i32, i32* @h, i32 %still_poison
  store i32 0, i32* %poison_yet_again  ; memory at @h[0] is poisoned
 
  store i32 %poison, i32* @g           ; Poison value stored to memory.
  %poison2 = load i32, i32* @g         ; Poison value loaded back from memory.
 
  store volatile i32 %poison, i32* @g  ; External observation; undefined behavior.
 
  %narrowaddr = bitcast i32* @g to i16*
  %wideaddr = bitcast i32* @g to i64*
  %poison3 = load i16, i16* %narrowaddr ; Returns a poison value.
  %poison4 = load i64, i64* %wideaddr  ; Returns a poison value.
 
  %cmp = icmp slt i32 %poison, 0       ; Returns a poison value.
  br i1 %cmp, label %true, label %end  ; Branch to either destination.
 
true:
  store volatile i32 0, i32* @g        ; This is control-dependent on %cmp, so
                                       ; it has undefined behavior.
  br label %end
 
end:
  %p = phi i32 [ 0, %entry ], [ 1, %true ]
                                       ; Both edges into this PHI are
                                       ; control-dependent on %cmp, so this
                                       ; always results in a poison value.
 
  store volatile i32 0, i32* @g        ; This would depend on the store in %true
                                       ; if %cmp is true, or the store in %entry
                                       ; otherwise, so this is undefined behavior.
 
  br i1 %cmp, label %second_true, label %second_end
                                       ; The same branch again, but this time the
                                       ; true block doesn't have side effects.
 
second_true:
  ; No side effects!
  ret void
 
second_end:
  store volatile i32 0, i32* @g        ; This time, the instruction always depends
                                       ; on the store in %end. Also, it is
                                       ; control-equivalent to %end, so this is
                                       ; well-defined (ignoring earlier undefined
                                       ; behavior in this example).
支持的约束代码列表

通常,约束代码的行为方式,与在 GCC 中的行为方式相同。LLVM 的支持通常是在“按需”基础上实现的,支持 GCC 支持的 C 内联汇编代码。LLVM 和 GCC 之间的行为不匹配,表明 LLVM 中存在错误。

所有目标通常都支持一些约束代码:

其它约束是特定于目标的:

AArch64:

AMDGPU:

所有 ARM 模式:

ARM 和 ARM 的 Thumb2 模式:

ARM 的 Thumb1 模式:

Hexagon:

MSP430:

MIPS:

NVPTX:

PowerPC:

sparc:

SystemZ:

X86:

Core:

Asm 模板参数修饰符

在 asm 模板字符串中,可以在操作数引用上使用修饰符,例如“ ${0:n}”。

通常,修饰符的行为方式,与在 GCC 中的行为方式相同。LLVM 的支持通常是在“按需”基础上实现的,支持 GCC 支持的 C 内联汇编代码。LLVM 和 GCC 之间的行为不匹配,可能表明 LLVM 中存在错误。

目标独立:

AArch64:

AMDGPU:

ARM:

Hexagon:

MSP430:

没有额外的修饰符。

MIPS:

NVPTX:

PowerPC:

Sparc:

SystemZ:

SystemZ 仅实现n,支持任何其它与目标无关的修饰符。

X86:

 

 

参考链接:

https://releases.llvm.org/6.0.0/docs/LangRef.html#module-structure

 

 

 

 

标签:操作数,LLVM,i32,寄存器,编程,语义,undef,64,32
来源: https://www.cnblogs.com/wujianming-110117/p/15328335.html