Compilation 如何使用LLVM将基于堆栈的虚拟机字节码转换为SSA格式

Compilation 如何使用LLVM将基于堆栈的虚拟机字节码转换为SSA格式,compilation,llvm,disassembly,llvm-ir,ssa,Compilation,Llvm,Disassembly,Llvm Ir,Ssa,关于如何将SSA表示转换为堆栈机器,有许多问题,但我对相反的问题感兴趣 问题 考虑一个具有条件/无条件跳转的基于堆栈的VM,其中每个操作码都有固定数量的它使用和生成的堆栈元素 LLVM框架中是否有从字节码输出重构SSA表单的工具/方法。这本质上是一种反汇编形式。LLVM本身没有工具,但它只是一种工具。我做到了。有些地方很难,但有些地方也很难。我将回答而不是评论,来漫谈一下最困难的部分 堆栈通常是无类型的;堆栈顶部的值具有类型,但“堆栈顶部”没有。LLVMValue总是有一个类型,当代码包含循环时

关于如何将SSA表示转换为堆栈机器,有许多问题,但我对相反的问题感兴趣

问题

考虑一个具有条件/无条件跳转的基于堆栈的VM,其中每个操作码都有固定数量的它使用和生成的堆栈元素


LLVM框架中是否有从字节码输出重构SSA表单的工具/方法。这本质上是一种反汇编形式。

LLVM本身没有工具,但它只是一种工具。我做到了。有些地方很难,但有些地方也很难。我将回答而不是评论,来漫谈一下最困难的部分

堆栈通常是无类型的;堆栈顶部的值具有类型,但“堆栈顶部”没有。LLVM
Value
总是有一个类型,当代码包含循环时,这两个系统会发生冲突。考虑这个代码:

int a = b();
while(a<10)
    a++;
(很抱歉出现语法错误,我还没有写太多的IR。)

您可能会看到,除了φ之外的每条指令都可以从堆栈语言中的一条指令生成。可能堆栈语言中的一条指令会导致多条IR指令,或导致没有IR指令,例如dup或push常量零,这些指令只会修改堆栈

phi不同,它表示该点的堆栈

条目
b1
末尾的堆栈计算进入块
b1
的堆栈。您可以在每个基本块的开始处为堆栈上的每个值生成一个phi节点;挑战在于每个phi节点的类型取决于前面块末尾堆栈上的类型。在这种情况下,
entry
末端的堆栈有一个条目,
a1
,而
b1
末端的堆栈有一个条目,
a2
。因此,
stack.0.b1
的类型取决于
a2
的类型,后者又取决于
stack.0.b1
。您需要认真考虑这一点,特别是如果您的语言包含隐式类型提升或强制转换(i32到i64、字符串到对象等)


(我本来可以从一个类似ruby的类型系统和代码开始,而不是像c一样;我认为最后的问题是相同的,只是您的解决方案不同。)

非常感谢您的解释,我有一个具体的问题:我有一个可工作的非类型化实现,但它并不令人满意。挑战在于虚拟机的跳转位置取决于进入该节点的路径(即CFG的循环只运行一次)。我不知道如何在LLVM中表示这一点。有没有办法让phi条件有效地“展望”直接节点之外的情况?我不确定我是否理解这个问题。。。如果我这样做了,那么您可以使用一个或多个i1phi节点来实现这一点。我可能会遇到问题,并会尝试更改问题以避免解决方案,但这是可以做到的:
phii1[%b1,1],%b2,0]
。现在查看该phi的值将告诉您是否执行了块b1。如果您必须使用多个PHI链来传播它,那么您也可以回过头来写入alloca的内存,llvm将变成必要的PHI。天啊,这太难看了。哦,我明白你的意思了。这里的问题是,有时路径可能是b0->b1->b2->b1->b3。现在,从b1跳转的条件是b0是上一个节点,或者b2是上一个节点,但是在任何情况下都将访问b0!因此,它不可能是b0和b2之间的简单φ条件。也许我最好的办法是告诉输出此字节码的编译器作者删除这些路径相关的跳转目的地(这是一种相对较新的编程语言)。它们几乎不比动态跳转好,而且是不必要的。
p1=phi i1[%b1,1],%b2,0]
在块b3中,然后
p2=phi i1[%b3,%p1],%b4,0]
将信息从b3传播到它的后续。。。但我承认,如果你在午饭前读到这篇文章,可能会破坏你的食欲,这是一个非常丑陋的恶作剧。
entry:
  %a1 = call @b();
  br label %b1
b1:
  %stack.0.b1 = phi i32 [%entry, %a1], [%b1, %a2]
  %a2 = add i32 1, %stack.0.b1
  %done = icmp ult i32 %stack.0.b1, 10
  br i1 %done, label %b1, label %b2
b2: