为什么此LLVM IR代码会产生意外结果?

时间:2019-03-24 20:53:28

标签: compiler-construction llvm llvm-ir

我真的很沮丧,因为这个问题困扰了我好几天,所以我会很感激。

我目前正在使用自己的编程语言,并且目前正在尝试实现将值与枚举大小写匹配的枚举和匹配语句,并运行相应的语句,但是到处都是意外的结果和段错误。

>

这是我的一种语言,可以运行(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进行debug传递会产生错误的源代码行(所有偏移量都不同),这也会导致无意义(我使用的是llvm 8 btw,但使用llvm 6.0。我之前使用的1显示了相同的结果。

然后,如果将r变量的定义上移到match语句之前,则突然出现段错误,由于前面提到的源代码行偏移,我无法精确定位该段错误。

这是相应的代码和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
}

我知道这类问题确实很糟糕,我可能只是通过将我的代码放到这里而破坏了一些规则,但是如果有人会牺牲一些时间来帮助一些真正沮丧并且接近放弃的家伙,我真的会很感激。

1 个答案:

答案 0 :(得分:1)

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

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

%tmpgetfldld (无效)是i32,它最初是从%n 所指向的内存地址中提取的,该地址位于上面3行:

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

如果%tmpgetfldld 的值无效,则意味着存储到%n %11 无效。原因是此指令:

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

在程序开始时,您已分配给指针%s 大小,该大小等于%test.OptionalNode 对象的大小,即9个字节(1个字节)和另外8个字节的数组)。然后,您指定要注册%9 %s 的位播,以键入%test.OptionalNode.some 。此类型的总大小为1 + 4 + 1 + 8 * 1 = 14字节。在程序的这一点上,没有什么错,并且%9 指向与%s 相同的地址,但是您仅将其视为%test.OptionalNode .some 。但是,在该内存空间中,您分配了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 结构声明更改为此,则可以解决segfault:

%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?)。

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