【问题标题】:Why does this LLVM IR code produce unexpected results?为什么这个 LLVM IR 代码会产生意想不到的结果?
【发布时间】:2019-03-27 04:01:06
【问题描述】:

我真的很沮丧,因为这个问题已经困扰我好几天了,所以我很感激每一个可能的帮助。

我目前正在制作自己的编程语言,目前正在尝试实现枚举和匹配语句,以将值与枚举案例匹配并运行相应的语句,但我在这里和那里得到了意想不到的结果和段错误。

这是我的语言的一段代码,运行 (lli) 但会产生意想不到的结果有时(出于某种原因打印 1,而不是 3):

class Node {
    fld value: int;
    fld next: OptionalNode;

    new(_value: int, _next: OptionalNode) {
        value = _value;
        next = _next;
    }
}

enum OptionalNode {
    val nil;
    val some(Node);
}

fun main(): int {
    var s: OptionalNode = OptionalNode.some(new Node(3, OptionalNode.nil));

    match s {
        OptionalNode.some(n) => print n.value;
    }

    var r: int = 0;

    ret r;
}

这是我的编译器生成的相应 LLVM IR:

; ModuleID = 'test.bc'
source_filename = "test"

%test.Node = type { i32, %test.OptionalNode }
%test.OptionalNode = type { i8, [8 x i8] }
%test.OptionalNode.nil = type { i8 }
%test.OptionalNode.some = type { i8, %test.Node* }

@str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1

declare i32 @printf(i8*, ...)

define void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %this, i32 %_value, %test.OptionalNode %_next) {
entry:
  %arg0 = alloca %test.Node*, align 8
  store %test.Node* %this, %test.Node** %arg0
  %arg1 = alloca i32, align 4
  store i32 %_value, i32* %arg1
  %arg2 = alloca %test.OptionalNode, align 16
  store %test.OptionalNode %_next, %test.OptionalNode* %arg2
  %ldarg1 = load i32, i32* %arg1
  %tmpld_cls = load %test.Node*, %test.Node** %arg0
  %tmpfld = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  store i32 %ldarg1, i32* %tmpfld
  %ldarg2 = load %test.OptionalNode, %test.OptionalNode* %arg2
  %tmpld_cls1 = load %test.Node*, %test.Node** %arg0
  %tmpfld2 = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls1, i32 0, i32 1
  store %test.OptionalNode %ldarg2, %test.OptionalNode* %tmpfld2
  ret void
}

define i32 @"test.main$v"() {
entry:
  %s = alloca %test.OptionalNode, align 16
  %enm = alloca %test.OptionalNode
  %0 = bitcast %test.OptionalNode* %enm to %test.OptionalNode.nil*
  %1 = getelementptr inbounds %test.OptionalNode.nil, %test.OptionalNode.nil* %0, i32 0, i32 0
  store i8 0, i8* %1
  %2 = load %test.OptionalNode, %test.OptionalNode* %enm
  %tmpalloc = alloca %test.Node
  call void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %tmpalloc, i32 3, %test.OptionalNode %2)
  %enm1 = alloca %test.OptionalNode
  %3 = bitcast %test.OptionalNode* %enm1 to %test.OptionalNode.some*
  %4 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 0
  store i8 1, i8* %4
  %5 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 1
  store %test.Node* %tmpalloc, %test.Node** %5
  %6 = load %test.OptionalNode, %test.OptionalNode* %enm1
  store %test.OptionalNode %6, %test.OptionalNode* %s
  %7 = getelementptr inbounds %test.OptionalNode, %test.OptionalNode* %s, i32 0, i32 0
  %8 = load i8, i8* %7
  switch i8 %8, label %match_end [
    i8 1, label %case1
  ]

case1:                                            ; preds = %entry
  %n = alloca %test.Node*, align 8
  %9 = bitcast %test.OptionalNode* %s to %test.OptionalNode.some*
  %10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
  %11 = load %test.Node*, %test.Node** %10
  store %test.Node* %11, %test.Node** %n
  %tmpld_cls = load %test.Node*, %test.Node** %n
  %tmpgetfldgep = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  %tmpgetfldld = load i32, i32* %tmpgetfldgep
  %print_i = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @str, i32 0, i32 0), i32 %tmpgetfldld)
  br label %match_end

match_end:                                        ; preds = %case1, %entry
  %r = alloca i32, align 4
  store i32 0, i32* %r
  %tmploadlocal = load i32, i32* %r
  ret i32 %tmploadlocal
}

define i32 @main() {
entry:
  %call = tail call i32 @"test.main$v"()
  ret i32 %call
}

现在,正如我所说,它可以完全编译并运行,但由于某种原因,它有时会打印出 1 而不是 3,我完全不理解 。我不知道如何调试 llvm ir 代码并使用 opt 应用 debugify pass 会产生错误的源代码行(所有不同的偏移量),这也使得 NO SENSE (我使用的是 llvm 8 btw 但 llvm 6.0.我之前使用的 1 显示了相同的结果)。

然后,如果我在 match 语句之前将 r 变量的定义上移,突然我得到一个段错误,由于我之前提到的偏移 ir 源代码行,我无法确定它的位置。

这是相应的代码和 ir:

class Node {
    fld value: int;
    fld next: OptionalNode;

    new(_value: int, _next: OptionalNode) {
        value = _value;
        next = _next;
    }
}

enum OptionalNode {
    val nil;
    val some(Node);
}

fun main(): int {
    var s: OptionalNode = OptionalNode.some(new Node(3, OptionalNode.nil));

    var r: int = 0;

    match s {
        OptionalNode.some(n) => print n.value;
    }

    ret r;
}
; ModuleID = 'test.bc'
source_filename = "test"

%test.Node = type { i32, %test.OptionalNode }
%test.OptionalNode = type { i8, [8 x i8] }
%test.OptionalNode.nil = type { i8 }
%test.OptionalNode.some = type { i8, %test.Node* }

@str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1

declare i32 @printf(i8*, ...)

define void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %this, i32 %_value, %test.OptionalNode %_next) {
entry:
  %arg0 = alloca %test.Node*, align 8
  store %test.Node* %this, %test.Node** %arg0
  %arg1 = alloca i32, align 4
  store i32 %_value, i32* %arg1
  %arg2 = alloca %test.OptionalNode, align 16
  store %test.OptionalNode %_next, %test.OptionalNode* %arg2
  %ldarg1 = load i32, i32* %arg1
  %tmpld_cls = load %test.Node*, %test.Node** %arg0
  %tmpfld = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  store i32 %ldarg1, i32* %tmpfld
  %ldarg2 = load %test.OptionalNode, %test.OptionalNode* %arg2
  %tmpld_cls1 = load %test.Node*, %test.Node** %arg0
  %tmpfld2 = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls1, i32 0, i32 1
  store %test.OptionalNode %ldarg2, %test.OptionalNode* %tmpfld2
  ret void
}

define i32 @"test.main$v"() {
entry:
  %s = alloca %test.OptionalNode, align 16
  %enm = alloca %test.OptionalNode
  %0 = bitcast %test.OptionalNode* %enm to %test.OptionalNode.nil*
  %1 = getelementptr inbounds %test.OptionalNode.nil, %test.OptionalNode.nil* %0, i32 0, i32 0
  store i8 0, i8* %1
  %2 = load %test.OptionalNode, %test.OptionalNode* %enm
  %tmpalloc = alloca %test.Node
  call void @"test.Node.!ctor$[test.Node]i[test.OptionalNode]"(%test.Node* %tmpalloc, i32 3, %test.OptionalNode %2)
  %enm1 = alloca %test.OptionalNode
  %3 = bitcast %test.OptionalNode* %enm1 to %test.OptionalNode.some*
  %4 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 0
  store i8 1, i8* %4
  %5 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %3, i32 0, i32 1
  store %test.Node* %tmpalloc, %test.Node** %5
  %6 = load %test.OptionalNode, %test.OptionalNode* %enm1
  store %test.OptionalNode %6, %test.OptionalNode* %s
  %r = alloca i32, align 4
  store i32 0, i32* %r
  %7 = getelementptr inbounds %test.OptionalNode, %test.OptionalNode* %s, i32 0, i32 0
  %8 = load i8, i8* %7
  switch i8 %8, label %match_end [
    i8 1, label %case1
  ]

case1:                                            ; preds = %entry
  %n = alloca %test.Node*, align 8
  %9 = bitcast %test.OptionalNode* %s to %test.OptionalNode.some*
  %10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
  %11 = load %test.Node*, %test.Node** %10
  store %test.Node* %11, %test.Node** %n
  %tmpld_cls = load %test.Node*, %test.Node** %n
  %tmpgetfldgep = getelementptr inbounds %test.Node, %test.Node* %tmpld_cls, i32 0, i32 0
  %tmpgetfldld = load i32, i32* %tmpgetfldgep
  %print_i = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @str, i32 0, i32 0), i32 %tmpgetfldld)
  br label %match_end

match_end:                                        ; preds = %case1, %entry
  %tmploadlocal = load i32, i32* %r
  ret i32 %tmploadlocal
}

define i32 @main() {
entry:
  %call = tail call i32 @"test.main$v"()
  ret i32 %call
}

我知道这类问题真的很糟糕,我可能只是把我的代码扔到这里就违反了一些规则,但是如果有人愿意牺牲一些时间来帮助一些非常沮丧和接近放弃的人,我真的很感激。

【问题讨论】:

    标签: compiler-construction llvm llvm-ir


    【解决方案1】:

    这看起来确实很棘手。我想我已经回答了你的问题。

    当您尝试打印 %tmpgetfldld 时会导致段错误。如果您尝试使用 clang 编译然后执行它,您将不会收到段错误。这并不是说这是 lli 的错,因为即使这样你也会得到错误的输出,因为你正在访问无效的内存空间。让我解释一下这是怎么发生的。

    %tmpgetfldld(无效)是一个i32,原来是从%n指向的内存地址中提取出来的,上面3行:

    %tmpld_cls = load %test.Node*, %test.Node** %n
    

    如果%tmpgetfldld的值无效,则表示%11,存储到%n的人无效。原因是这条指令:

    %9 = bitcast %test.OptionalNode* %s to %test.OptionalNode.some*
    

    在程序开始时,您分配给指针 %s 的大小等于 %test.OptionalNode 对象的大小,即 9 个字节(1 个字节和另外 8 个字节用于数组)。然后将 %s 的位广播分配给注册 %9 以键入 %test.OptionalNode.some。这种类型的总大小为 1 + 4 + 1 + 8*1 = 14 个字节。此时您的程序还没有任何问题,并且 %9 指向 %s 所做的相同地址,但您只将其视为 %test.OptionalNode .一些。但是,在那个内存空间中,您已经分配了 9 个字节,现在通过“getelementptr”或“extractvalue”指令,您可以访问 14 个字节。在第 9 个字节之后读取会导致段错误。事实上,通过这些说明:

    %10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
    %11 = load %test.Node*, %test.Node** %10
    

    你得到一个指向字节 1 到 13 的指针(从索引 0 开始计数)。然后这个指针被存储在下面并再次加载,只有当你尝试访问该值时才会得到段错误,这发生在访问 %tmpgetfldld 时。

    要解决段错误,您需要以某种方式警告编译器,在分配 %s 或任何其他 %test.OptionalNode 时,您至少有 9 个字节,但您可能需要更多字节,例如,如果您比特转换为更大尺寸的结构。实际上,这正是 LLVM 处理虚拟类和多态性的方式,当子类具有可变大小的成员,但仍必须以某种方式比特转换为父类时。因此,如果您将 %test.OptionalNode 结构声明更改为此,您可以解决段错误:

    %test.OptionalNode = type { i8, [8 x i8], i8(...)** }
    

    最后一种类型是函数指针,表示您期望 i8 的可变数量(字节)。也可以在这里查看:LLVM what does i32 (...)** mean in type define?

    如果您进行此更改,您将摆脱段错误,但您会注意到您还没有完全解决您的问题。有时你可能会得到一个 3 作为输出,有时你可能会得到其他东西,这是一种未定义的行为。这是因为,即使您声明了 i8(...)** 来解释位转换结构类型的额外字节,两种结构类型之间公共内存区域中的数据类型也没有很好地对齐。您会注意到它们的区别从第二个字节开始:在 %test.OptionalNode 中,一个 i8 数组开始,而在 %test.OptionalNode.some 中,有一个 i32,然后是 i8,然后是相同的 i8 数组。要解决这个问题,您必须将结构定义更改为:

    %test.OptionalNode = type { i8, [8 x i8], i8(...)** }
    %test.OptionalNode.some = type { i8, [8 x i8], %test.Node* }
    

    或者说:

    %test.OptionalNode = type { i8, i8(...)** }
    %test.OptionalNode.some = type { i8, %test.Node* }
    

    取决于您的设计是否需要 [8 x i8] 数组。现在,您的输出始终为 3,您的问题就消失了。我认为这个解决方案也涵盖了您之前的问题 (How to fix segmentation fault in genrated llvm bytecode?)。

    抱歉,答案很长。希望对你有帮助。

    【讨论】:

    • 天啊,非常感谢!!!!我还没有尝试过,但它似乎很有意义!我对数组的推理是,在将枚举类型转换为 llvm 时,我会找出每个枚举大小写/值的大小,然后将最大的元素数作为基类型中字节数组的元素数(test.OptionalNode )。我认为这会抵消访问未分配内存的问题......
    • 我很高兴这是有道理的。我将粘贴指向您其他问题的链接,以将其合并到此答案:)
    猜你喜欢
    • 1970-01-01
    • 2012-03-13
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 2020-04-15
    • 1970-01-01
    • 2011-06-21
    • 2011-07-06
    相关资源
    最近更新 更多