Java 使用Krakatau自动填充StackMapTable

Java 使用Krakatau自动填充StackMapTable,java,compiler-construction,jvm,bytecode,Java,Compiler Construction,Jvm,Bytecode,我使用Krakatau从Jasmin语法生成字节码。我的Jasmin代码是由三地址代码TAC形式的中间代码直接翻译而成的。我的问题是,我不能确定,只有 看看TAC,在翻译跳转语句时,我应该将堆栈指令放在哪里 Krakatau在其汇编程序上的文档说明如下: StackMapTable属性的内容将自动填充 基于封闭代码属性中的堆栈指令。如果这 属性的内容为非空且未指定属性 显式地,一个将被隐式地添加 但它也说: Krakatau不会从没有任何堆栈信息的字节码为您计算新的堆栈映射。如果您想这样做,您应

我使用Krakatau从Jasmin语法生成字节码。我的Jasmin代码是由三地址代码TAC形式的中间代码直接翻译而成的。我的问题是,我不能确定,只有 看看TAC,在翻译跳转语句时,我应该将堆栈指令放在哪里

Krakatau在其汇编程序上的文档说明如下:

StackMapTable属性的内容将自动填充 基于封闭代码属性中的堆栈指令。如果这 属性的内容为非空且未指定属性 显式地,一个将被隐式地添加

但它也说:

Krakatau不会从没有任何堆栈信息的字节码为您计算新的堆栈映射。如果您想这样做,您应该尝试使用ASM

我对哪种指令感到困惑,我应该在哪里将它们添加到翻译中,以便汇编器知道如何隐式添加属性。在此方面的任何帮助都将不胜感激

例如,我用类似于Java的语法编写了这段代码,但不同,因此我需要使用另一个编译器:

我从编译器前端得到的是左边的TAC,我的翻译器在右边生成Jasmin代码我删除了页眉和页脚,只留下字节码本身,缺少返回指令:

当我尝试运行它时,我会得到如下结果:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 24
Exception Details:
  Location:
    Main.main([Ljava/lang/String;)V @16: iflt
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: 1103 e8bc 073a 050f 4814 000a 4a27 2997
    0x0000010: 9b00 0819 0503 2752 b1

    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
    at java.lang.Class.getMethod0(Class.java:3018)
    at java.lang.Class.getMethod(Class.java:1784)
    at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)

我知道会发生这种情况,因为它需要一个.stack-append-Double对象[D在带有标签L23的行之后,但是,正如我前面所说的,这个例子很简单,我不能总是推断出这样的指令的位置。如果Krakatau可以通过在封闭代码属性的开头附加一些指令来推断,那就太好了。

最简单的方法是完全避免需要堆栈映射。堆栈映射仅在您希望使用51.0+版功能(即InvokedDynamic)时才需要。如果您不使用InvokedDynamic,您可以将类文件版本设置为50或更低,并且根本不需要堆栈映射。事实上,如果您没有显式指定,Krakatau将默认版本为49.0,因此您不需要在那里执行任何操作

如果使用invokedynamic,事情会变得非常棘手,因为您必须生成堆栈映射。基本规则是,只要可以从前面的指令以外的任何位置访问指令,您就需要堆栈映射条目。我认为您还需要死代码条目,但我没有检查

至于实际生成条目,有几种不同类型的堆栈帧,但您不必担心。最简单的方法是每次只使用一个完整的帧。这包括在局部变量寄存器插槽和操作数堆栈中列出每个活动值的当前类型,因此您必须跟踪它们嗯


是的,计算和生成所有类型信息是一件痛苦的事情,但你必须为此责怪Oracle,而不是我。或者,你可以尝试使用ASM为你生成堆栈映射,正如文档中所建议的那样。

你的问题太宽泛和抽象了,这使它脱离主题。请回答它并提供具体示例你正在努力完成的事情和你尝试过的备选方案的细节。@JimGarrison,更新了它。哦,我的上帝,你真的救了我的命,谢谢你。你的工具以及你在堆栈溢出上的回答对我帮助很大。我将向任何在本科课程上学习编译器课程的人提供我的代码,这样他们就可以将输出翻译为将龙之书的编译器直接从Java字节码中读取并运行。非常感谢。我不同意“计算并生成所有类型信息是一件痛苦的事情”。我无法想象任何人在不了解每个点的堆栈框架中应该包含哪些类型的情况下,如何生成正确的字节码。你所要做的就是显式声明你已经知道的内容。而这似乎常常被忽略,你只需要声明你以后要使用的内容,其他一切都可以n被声明为“top”,即不可用。对于以后要使用的元素,更明显的是,您应该知道它们的类型。