Compiler construction 为什么这个LLVM IR代码会产生意外的结果?
我真的很沮丧,因为这个问题已经困扰了我好几天了,所以我非常感谢所有可能的帮助 我目前正在开发自己的编程语言,并且正在尝试实现enum和match语句,这些语句将值与enum案例相匹配,并运行相应的语句,但是我在这里和那里得到了意外的结果和SEGFULTS 下面是我的语言的一段代码,它运行(lli),但有时会产生意外的结果(出于某种原因打印1,而不是3): 这是我的编译器生成的相应LLVM IR:Compiler construction 为什么这个LLVM IR代码会产生意外的结果?,compiler-construction,llvm,llvm-ir,Compiler Construction,Llvm,Llvm Ir,我真的很沮丧,因为这个问题已经困扰了我好几天了,所以我非常感谢所有可能的帮助 我目前正在开发自己的编程语言,并且正在尝试实现enum和match语句,这些语句将值与enum案例相匹配,并运行相应的语句,但是我在这里和那里得到了意外的结果和SEGFULTS 下面是我的语言的一段代码,它运行(lli),但有时会产生意外的结果(出于某种原因打印1,而不是3): 这是我的编译器生成的相应LLVM IR: ; ModuleID = 'test.bc' source_filename = "test" %
; 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过程会产生错误的源代码行(所有偏移量都在变化),这也没有任何意义(顺便说一句,我使用的是llvm 8,但我之前使用的llvm 6.0.1显示了相同的结果)
然后,如果我将r变量的定义向上移动到match语句之前,我会突然得到一个segfault,由于我前面提到的偏移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;
}
我知道这类问题真的很糟糕,我可能因为把代码扔到这里而违反了一些规则,但如果有人愿意牺牲一些时间来帮助一些真正沮丧和接近放弃的家伙,我会非常感激。这看起来确实很棘手。我想我已经找到了你问题的答案 当您尝试打印%tmpgetfldld时,会导致SEGFULT故障。如果您尝试使用clang编译,然后执行它,您将不会得到segfault。这并不是说这是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个字节)。然后将%s的比特广播分配给register%9以键入%test.OptionalNode.some。此类型的总大小为1+4+1+8*1=14字节。在程序的这一点上,还没有任何错误,%9指向与%s相同的地址,但您只将其视为一个%test.OptionalNode.some。然而,在那个内存空间中,您分配了9个字节,现在通过“getelementptr”或“extractvalue”指令,您可以访问14个字节。在第9个字节后读取将导致segfault。事实上,通过这些指示:
%10 = getelementptr inbounds %test.OptionalNode.some, %test.OptionalNode.some* %9, i32 0, i32 1
%11 = load %test.Node*, %test.Node** %10
您将得到一个指向字节1到13的指针(从索引0开始计数)。然后,该指针存储在下面并再次加载,只有当您尝试访问该值时,才会获得SEGFULT,这在访问%tmpgetfldld时发生
要解决segfault,您需要以某种方式警告编译器,在分配%s或任何其他%test.OptionalNode时,您至少有9个字节,但如果您将字节转换为更大的结构,则可能需要更多字节。实际上,这正是LLVM处理虚拟类和多态性的方式,当子类具有可变大小的成员,但仍然必须以某种方式位转换到父类时。因此,如果将%test.OptionalNode结构声明更改为此,则可以解决segfault:
%test.OptionalNode = type { i8, [8 x i8], i8(...)** }
最后一种类型是一个函数指针,指示您期望可变的i8(字节)数。请在此查看:
如果你做了这个改变,你就摆脱了segfault,但是你会注意到你还没有完全解决你的问题。有时候你可能会得到一个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,您的问题消失了。我认为这个解决方案也涵盖了您前面的问题()
对不起,回答得太长了。我希望这会有帮助。天哪,非常感谢您!!!!我还没有试过,但它似乎有很大的意义!我对数组的推理是,在将枚举类型转换为llvm时,我会找出每个枚举大小写/值的大小,然后将最大值作为基类型(test.OptionalNode)中字节数组的元素数。我认为这将抵消访问未分配内存的问题…我很高兴这是有意义的。我将粘贴到您的其他问题的链接,以将其合并到此答案:)
%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* }