LLVM IR 常量

简单常量

  • 布尔常量: 两个字符串truefalse都是该i1类型的有效常量。
  • 整型常量: 标准整数(如4)是整数类型的常量。负数可能与整数类型一起使用。
  • 浮点常量: 浮点常量使用标准十进制表示法(例如123.421),指数表示法(例如1.23421e+2)或更精确的十六进制表示法。汇编器需要浮点常量的精确十进制值。例如,汇编程序接受1.25,但拒绝1.3,因为1.3是二进制中的重复小数。浮点常量必须具有浮点类型。
  • 空指针常量: 标识符’null‘被识别为空指针常量,并且必须是指针类型。
  • 令牌(Token)常量: 标识符’none’被识别为空的标记常量,并且必须是标记类型。

常量的一个非直观符号是浮点常量的十六进制形式。例如,形式为double 0x432ff973cafa8000等同于(但难以阅读)double 4.5e+15。唯一需要十六进制浮点常量(以及它们由反汇编程序生成的唯一时间)是必须发出浮点常量但不能用合理数目的十进制浮点数表示的浮点常量数字。例如,NaN'sinfinities和其他特殊值以IEEE十六进制格式表示,因此汇编和反汇编不会导致常量中的任何位发生更改。

当使用十六进制形式时,half,float和double类型的常量使用上面显示的16位数字形式表示(与IEEE754表示符合double);
然而,half和float值必须分别精确表示为IEEE 754的半精度和单精度。十六进制格式总是用于长双,并有三种形式的长双。x86使用的80位格式表示0xK后跟20个十六进制数字。PowerPC使用的128位格式(两个相邻的双精度)0xM由32个十六进制数字表示。IEEE 128位格式0xL由32个十六进制数字表示。长双打只有在你的目标上的长双重格式匹配时才有效。IEEE16位格式(半精度)由表示0xH后跟4个十六进制数字。所有的十六进制格式都是big-endian(左边的符号位)。

注意:没有x86_mmx类型的常量。

复杂常量(Complex Constants)

复杂常量是简单常量和较小复常量的(可能递归)组合。

  • 结构常数:
    结构常量用类似于结构类型定义的符号表示(逗号分隔的元素列表,用大括号({})括起来)。例如:“{ i32 4, float 17.0, i32* @G },其中“@G被声明为“@G = external global i32。结构常量必须具有结构类型,并且元素的数量和类型必须与该类型指定的类型匹配。
  • 数组常量:
    数组常量用类似于数组类型定义的符号表示(逗号分隔的元素列表,用方括号([])括起来)。例如:[ i32 42, i32 11, i32 74 ]。数组常量必须具有数组类型,并且元素的数量和类型必须与该类型指定的数量和类型相匹配。作为一种特殊情况,字符数组常量也可以用前缀表示为双引号字符串。例如:“c"Hello World\0A\00"
  • 矢量(Vector)常量:
    向量常量用类似于向量类型定义的符号表示(逗号分隔的元素列表,由小于/大于(<>))围绕)。例如:< i32 42, i32 11, i32 74, i32 100 >。向量常量必须具有向量类型,并且元素的数量和类型必须与该类型指定的类型匹配。
  • 零初始化:
    字符串zeroinitializer可用于将零值初始化为任何类型的零,包括标量和聚合类型。这通常用于避免必须打印大型零初始化器(例如,用于大型数组),并且始终完全等同于使用显式零初始化器。
  • 元数据节点
    元数据节点是一个没有类型的常量元组。例如:!{!0, !{!2, !0}, !"test"}。元数据可以引用常量值,例如:!{!0, i32 0, i8* @global, i64 (i64)* @function, !"str"}。与其他类型化的常量不同,它们被解释为指令流的一部分,元数据是附加附加信息的地方,例如调试信息。

全局变量和函数的地址

全局变量和函数]的地址总是隐式有效(链接时间)的常量。当使用全局标识符并且总是有指针类型时,这些常量被明确引用。例如,以下是合法的LLVM文件:

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

未定义的值

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

未定义的值非常有用,因为它们向编译器指出,无论使用什么值,该程序都已定义良好。这为编译器提供了更多的优化自由度。下面是一些有效的(可能令人惊讶的)转换的例子(在伪IR中):

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

这是安全的,因为所有的输出位都受undef位的影响。任何输出位都可以有一个零或一个依赖的输入位。

  %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是安全的。同样,假设可以设置undefor操作的所有位是安全的,允许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 = sdiv undef, %X
%B = sdiv %X, undef
Safe:
%A = 0
b: unreachable

这些示例显示了未定义的值和未定义的行为之间的关键区别。一个未定义的值(如undef)允许有一个任意的位模式。这意味着%A操作可以不断折叠为0,因为undef可能为零,并且零除以任何值为零。但是,在第二个例子中,我们可以做一个更积极的假设:因为undef允许它是一个任意值,我们可以假设它可能为零。由于被零除以具有未定义的行为,我们被允许假设该操作根本不执行。这允许我们删除分割和所有代码。由于未定义的操作“不可能发生”,因此优化器可以假定它发生在死代码中。

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

存储的未定义的值可以被假设为不具有任何影响;
我们可以假设这个值被恰好与已经存在的相匹配的位覆盖。然而,一个存储到一个未定义的位置可能破坏任意的内存,因此,它具有未定义行为。

毒药值(Poison Values)

毒性(Poison)值与undef值相似,但它们也表示这样的事实,即不能引起副作用的指令或常量表达式已经检测到导致未定义行为的条件。

目前在IR中无法表示毒物值;
它们只存在于某些操作的调用,如带有nsw标志的add操作。

毒药值行为是根据值依赖来定义的:

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

Poison值具有与undef值相同的行为,另外的效果是任何依赖poison值的指令都具有未定义的行为。

基本块的地址

Syntax: blockaddress(@function, %block)

blockaddress常数计算在指定函数指定的基本块的地址,并总是有一个i8*类型。取出输入块的地址是非法的。

当用作indirectbr指令的操作指令时,或者用于与空值进行比较时,该值仅具有已定义的行为。标签地址之间的指针相等测试会导致未定义的行为

但是,再次,与null进行比较是可以的,并且没有标签等于空指针。只要这些位未被检查,这可以作为不透明的指针大小值传递。ptrtoint只要原始值在indirectbr指令之前重新构成,就允许和计算这些值。

最后,有些目标可能会在使用该值作为内联程序集的操作数时提供定义的语义,但这是目标特定的。

常量表达式

常量表达式用于允许涉及其他常量的表达式用作常量。常量表达式可以是任何第一类类型,并且可能涉及没有副作用的任何LLVM操作(例如,不支持加载和调用)。以下是常量表达式的语法:

  • trunc (CST to TYPE):
    对常量执行trunc操作
  • zext (CST to TYPE):
    对常量执行zext操作
  • sext (CST to TYPE):
    对常量执行sext操作
  • fptrunc (CST to TYPE):
    将浮点常量截断为另一个浮点类型。CST的大小必须大于TYPE的大小。这两种类型都必须是浮点型。
  • fpext (CST to TYPE):
    浮点将常量扩展为另一种类型。CST的大小必须小于或等于TYPE的大小。这两种类型都必须是浮点型。
  • fptoui (CST to TYPE):
    将浮点常量转换为相应的无符号整数常量。TYPE必须是标量或向量整数类型。CST必须是标量或向量浮点类型。CST和TYPE都必须是标量,或者具有相同数量元素的向量。如果该值不适合整数类型,则结果未定义。
  • fptosi (CST to TYPE):
    将浮点常量转换为相应的有符号整数常量。TYPE必须是标量或向量整数类型。CST必须是标量或向量浮点类型。CST和TYPE都必须是标量,或者具有相同数量元素的向量。如果该值不适合整数类型,则结果未定义。
  • uitofp (CST to TYPE):
    将无符号整数常量转换为相应的浮点常量。TYPE必须是标量或向量浮点类型。CST必须是标量或向量整数类型。CST和TYPE都必须是标量,或者具有相同数量元素的向量。如果该值不适合浮点类型,则结果未定义。
  • sitofp (CST to TYPE):
    将有符号整数常量转换为相应的浮点常量。TYPE必须是标量或向量浮点类型。CST必须是标量或向量整数类型。CST和TYPE都必须是标量,或者具有相同数量元素的向量。如果该值不适合浮点类型,则结果未定义。
  • ptrtoint (CST to TYPE):
    对常量执行ptrtoint操作
  • inttoptr (CST to TYPE):
    对常量执行inttoptr操作。这个真的很危险!
  • bitcast (CST to TYPE):
    将常数CST转换为另一个TYPE。操作数的限制与bitcast指令的限制相同。
  • addrspacecast (CST to TYPE):
    将指针CST的常量指针或常量向量转换为另一个地址空间中的另一个TYPE。操作数的约束与addrspacecast指令的约束相同。
  • getelementptr (TY, CSTPTR, IDX0, IDX1, ...), getelementptr inbounds (TY, CSTPTR, IDX0, IDX1, ...):
    对常量执行getelementptr操作。与getelementptr 指令一样,索引列表可能有一个或多个索引,这些索引对于“指向TY的指针”类型是有意义的。
  • select (COND, VAL1, VAL2):
    对常量执行选择操作
  • icmp COND (VAL1, VAL2):
    对常量执行icmp操作
  • fcmp COND (VAL1, VAL2):
    对常量执行fcmp操作
  • extractelement (VAL, IDX):
    对常量执行extractelement操作
  • insertelement (VAL, ELT, IDX):
    对常量执行insertelement操作
  • shufflevector (VEC1, VEC2, IDXMASK):
    对常量执行shufflevector操作
  • extractvalue (VAL, IDX0, IDX1, ...):
    对常量执行extractvalue操作。索引列表的解释方式与“getelementptr”操作中的索引类似。至少必须指定一个索引值。
  • insertvalue (VAL, ELT, IDX0, IDX1, ...):
    对常量执行insertvalue操作。索引列表的解释方式与“getelementptr”操作中的索引类似。至少必须指定一个索引值。
  • OPCODE (LHS, RHS):
    执行LHS和RHS常量的指定操作。操作码可以是任何二进制按位二进制操作。操作数的限制与相应指令的限制相同(例如,不允许对浮点值进行按位操作)。