【译】LLVM 类型相等判断

本文探讨了在LLVM中如何判断类型等价的问题,尤其是在处理结构体类型时遇到的困难。由于LLVM在合并模块时可能导致类型重命名,简单的指针比较或布局检查无法准确判断类型是否相同。解决方案是通过建立类型树并将其转换为序列化的字符串,结合结构体ID来判断类型等价性,尤其是对于递归结构体和不明确类型结构体的情况。这种方法利用了树自动机和字母序列的概念,确保了类型等价性的正确判断。
摘要由CSDN通过智能技术生成

在 LLVM 的类型系统中,进行两个类型是否等价的判断,对于做代码分析而言是很重要的,本文将介绍遇到的难题以及解决的方法。

内容来源: https://lowlevelbits.org/type-equality-in-llvm/
原文标题:TYPE EQUALITY IN LLVM
作者:AlexDenisov
译者:流左沙
注:翻译较原文会有一定的精简、重排和添删,主要是为了提取重要内容以达到更好的理解,请以原文为准。

1、LLVM 类型系统的简述

关于 LLVM 类型系统的背景,可以看我之前写的一篇译文或者原文,类型系统在本文不再过多展开。

简单再概述一下:

  • LLVMContext 管理 LLVM‘s core infrastructure 的 core “global” data,包括类型和常量的表
  • 类型的实例会被分配到堆内存上(如 llvm::Type *type = new llvm::Type;)
  • 类型的比较是通过指针的比较来完成的
  • LLVM 类型分成三种:原始类型(primitive types)、派生类型(derived types)、提前声明类型(forward-declared types)

2、问题的产生

如果两个模块(modules)使用了有相同名字的相同结构体,那么他们被加载到同一个上下文(context,内容)时,LLVM 会重命名其中一个,来避免命名冲突。

; Module1
%struct.Point = type { i32, i32 }

; Module 2
%struct.Point = type { i32, i32 }

; same context
%struct.Point = type { i32, i32 }
%struct.Point.1 = type { i32, i32 }

如果想消除重复的数据类型,明显的解决方法是:判断有相似名字的结构体,它们的实现是否相同。

注:直接去掉 LLVM 给名字加上的数字后缀,然后判断名字是否相等是不可行的,因为名字冲突的两个结构体,有可能有不同的实现体。

而 llvm::StructType::isLayoutIdentical 判断类型等价的操作,是基于指针的。导致会出现下面代码中的判断情况。

; these two have identical layout 判断为相同
%Point = type { i32, i32 }
%Pair = type { i32, i32 }

; these two DO NOT have identical layout 判断为不同
%PointWrap = type { %Point }
%PairWrap = type { %Pair }

说明这种方式也是不可行的。

2.1、Primitive Types Equality 原始类型等价判断

在 LLVM 当中,类型都归 LLVMContext 管理。原始类型,如 int32floatdouble 是提前分配好,然后再重复使用。在 LLVMContext 的同一个上下文当中,你只能创建一种原始类型的一个实例。有了这个前提条件,很容易就能判断类型是否等价,直接比较它们的指针就够了。

但是如果是来自于不同的上下文,就不能这么比较了。比如,来自一个 LLVMContext 的 int32 类型,与来自另一个 LLVMContext 的 int32 类型。

2.2、Struct Types Equality 结构体类型等价判断

对于结构体类型,判断是否等价就更加复杂了。

; these two have identical layout 判断为相同
%Point = type { i32, i32 }
%Pair = type { i32, i32 }

; these two DO NOT have identical layout 判断为不同
%PointWrap = type { %Point }
%PairWrap = type { %Pair }

llvm::StructType::isLayoutIdentical通过指针来进行类型比较,只有结构体里的类型元素都相等,才返回 true。

2.3、IRLinker/llvm-link

LLVM 通过 IRLinker类来合并两个模块。LLVM 还提供了它的命令行工具 llvm-link。 IRLinker 运作正常,但它会丢掉一些重要信息。

下面是运行 IRLinker 的 IR 的例子:

%struct.Point = type { i32, i32 }
%struct.Tuple = type { i32, i32 }

然后变成

%struct.Point = type { i32, i32 }

另一个结构体被丢掉了(优化掉)了,因为它们有相同的实现体。但我们并不想丢失这个信息。

还有, IRLinker 还可能会引入源码级别没有出现的类型。

3、Solution 解决方法

解决方法的灵感来源是 Tree Automata(树自动机) 和 Ranked Alphabets(字母序列)。

字母序列(ranked alphabet) 包含有符号 F的有限集合, 以及一个算术函数 Arity(f), 而 f 属于 F的集合。

算术函数 Arity 表示符号 f 有多少个参数。符号可以是 constant(常量), unary(一元运算符), binary(二元), ternary(三元), or n-ary.

比如: abf(,)g()h(,,,,)

a 和 b 是常量, f(,) 是二元运算, g() 是一元运算, 还有 h(,,,,) 是 n 元。 这些符号的参数数量分别是 0, 0, 2, 1, 5。

3.1、construction 构造

给定字母表 abf(,)g() 我们可以构造以下这些树:

  • f(a, b)
  • g(b)
  • g(f(b, b))
  • f(g(a), f(f(a, a), b))
  • f(g(a), g(f(a, a)))
  • 等等

如果我们知道每个符号的参数数量,那么我们可以生成圆括号和逗号,来将一棵树序列化成一个字符串。这棵树以深度优先算法来构造。比如下面这些序列串:

  • fab
  • gb
  • gfbb
  • fgaffaab
  • fgagfaa

下面是更容易理解的对应的例子:

绿色箭头表示深度优先的遍历顺序。

所以我们可以将类型问题映射成 ranked alphabet/tree automaton 的概念。

3.2、Type Equality 类型等价判断

我们把每种类型视为一个符号,而它的参数数量则是类型的属性,这正是我们要比较的。然后,我们创建类型的树,把它转换成字符串序列。如果两种类型有相同的字符串序列,那么它们就是等价的。

比如:

  • i32i64i156: 符号是 I, 参数数量是 1,因为我们只在乎它的位宽 (32, 64, 156)
  • float: 符号是 F, 参数数量是 0, 所有 float 类型都是一样的
  • [16 x i32]: 符号是 A, 参数数量是 2, 我们只在乎数组长度和元素类型
  • i8*: 符号是 P, 参数数量是 1, 我们只在乎指针的类型
  • { i32, [16 x i8], i8* }: 符号是 S, 参数数量是 元素数量 + 2。我们要存储结构体的 ID 和元素数量

我们可以把这些类型转为序列化串:

  • i32 -> I(32) -> I32
  • i177 -> I(177) -> I177
  • [16 x i8*] -> A(16, P(I(8))) -> A16PI8
  • { i32, i8*, float } -> S(3, S0, I(32), P(I(8)), F) -> S3S0I32PI8F

注: S 的值分别是,元素数量 (3), 结构体 ID (S0), 以及所有它所包含的类型(递归地展开)。

3.3、Structural Equality 结构体等价判断

上面有个概念 struct ID。我们需要用它来判断递归类型的结构体是否等价:

%list = type { %list*, i32 }
%node = type { %node*, i32 }
%root = type { %node*, i32 }

上面的结构体都有相同的实现:一个指针 + 一个整型。但我们不会判断它们都是等价的,根据等价的定义,得出下面的结果:

list == node
root != node
root != list

原因很简单,因为 list 和 node 有相同的实现体和相同的递归结构,而 root 是其他的递归结构。

所以需要把递归结构也纳入判断之中,但我们不使用递归结构的名字。在构建树之前,我们给它们赋值一个符号名或者 ID。

所以 list 和 node 会被定义成 S(2, S0, P(S(2, S0, x, x), I(32)) ,而 S0 是结构体 ID。当已经生成过递归结构的类型时,为了使递归能够终止,我们使用了符号 x 来代替。

而 root 被定义成 S(2, S0, P(S(2, S1, P(S(2, S1, x, x), I(32), I(32))), I(32)) ,在这里 S0 和 S1 是结构体 ID。

3.4、Opaque Struct Equality 不明确类型结构体等价判断

注:LLVM 3.0 的 new type system 已经去掉了这个 opaque struct。

对于不明确类型结构体的等价判断,同样采用符号化的名字,如果不明确类型的原始名字是一样的,那么它们就会有相同的符号名。

%struct.A = type opaque
%struct.A.0 = type opaque
%struct.B = type opaque

%foo = type { %struct.A* }
%bar = type { %struct.A.0* }
%buzz = type { %struct.B* }

这里的例子中,不明确类型的原始名字是 A (%struct.A%struct.A.0) 和 B (%struct.B)。因此我们把 %struct.A 和 %struct.A.0 视为等价的,而 %struct.B 和 A是不等价的。即使这 3 个结构体都能指向相同的类型或者不同的类型。

参考

TYPE EQUALITY IN LLVM:https://lowlevelbits.org/type-equality-in-llvm/

llvm::LLVMContext:https://llvm.org/doxygen/classllvm_1_1LLVMContext.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值