Search code examples
javacompiler-constructionjvmbytecode

Automatically filling StackMapTable with Krakatau


I'm using Krakatau to generate bytecodes from Jasmin syntax. My Jasmin code is created from a direct translation of a intermediate code in the form of Three Address Code(TAC). My problem is that I can't tell for sure, only looking at the TAC, where I should position my stack directives when translating jump statements.

Krakatau documentation on its assembler says the following:

The content of a StackMapTable attribute is automatically filled in based on the stack directives in the enclosing code attribute. If this attribute’s contents are nonempty and the attribute isn’t specified explicitly, one will be added implicitly.

But it also says:

Krakatau will not calculate a new stack map for you from bytecode that does not have any stack information. If you want to do this, you should try using ASM.

I'm confused about which kind of directives, and where should I add them to my translation so the assembler knows how to add the attributes implicitly. Any help on this would be appreciated.

For instance, I have this code written in a syntax similar to Java(but not the same, so I need to use another compiler for it):

What I get from the front end of my compiler is the TAC on the left and my translator generate the Jasmin code on the right(I'm removing the headers and footers and only leaving the bytecode itself, missing the return instruction):

When I try to run it, I get something like:

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)

I know that happens because it is expecting a .stack append Double Double Object [D after the line with the label L23, but, as I said before, this example is simple, and I cannot always infer the position of such directives. It would be nice if Krakatau could infer then for me with some directives appended at the beginning of the enclosing code attribute.


Solution

  • The easiest approach is to avoid the need for stack maps entirely. Stack maps are only required if you want to use version 51.0+ features (i.e. invokedynamic). If you aren't using invokedynamic, you can just set the classfile version to 50 or lower, and you don't need a stack map at all. In fact, Krakatau defaults the version to 49.0 if you don't explicitly specify one, so you don't need to do anything there.

    If you are using invokedynamic, things get much tricker, because you have to generate stack maps. The basic rule is that you need a stack map entry whenever an instruction is reachable from anywhere other than the preceding instruction. (I think you also need entries for dead code, but I haven't checked).

    As for actually generating the entries, there are several different types of stack frames, but you don't have to worry about that. The easiest approach is to just use a full frame every time. This involves listing out the current type for every live value in the local variable ("register") slots and on the operand stack, so you'll have to track those.

    Yes, it is a pain to calculate and generate all that type information, but you'll have to blame Oracle for that, not me. Alternatively, you could try to use ASM to generate the stack maps for you, as suggested in the documentation.