javabytecodejava-bytecode-asmbytecode-manipulationjvm-bytecode

Java ASM: Bad local variable type (dload) Type top (current frame, locals[5]) is not assignable to double


I am trying to generate Java bytecode using the Java Asm library (I am basically trying to create yet another JVM programming language)

Here is the code I am compiling

float f = 2f
float f2 = new Float(2f)
Float f3 = f2
println(f2)

double d = 2d
double d2 = new Double(2d)
Double d3 = d2
println(d2)

As you can see the syntax is very similar to Java. println is just a call to System.out.println.

When I use my compiler, it generates the following code (got this output using javap)

Code:
       0: ldc           #14                 // float 2.0f
       2: fstore_1
       3: new           #16                 // class java/lang/Float
       6: dup
       7: ldc           #14                 // float 2.0f
       9: invokespecial #19                 // Method java/lang/Float."<init>":(F)V
      12: invokevirtual #23                 // Method java/lang/Float.floatValue:()F
      15: fstore_2
      16: fload_2
      17: invokestatic  #27                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
      20: astore_3
      21: getstatic     #33                 // Field java/lang/System.out:Ljava/io/PrintStream;
      24: fload_2
      25: invokevirtual #38                 // Method java/io/PrintStream.println:(F)V
      28: ldc2_w        #39                 // double 2.0d
      31: dstore        4
      33: new           #42                 // class java/lang/Double
      36: dup
      37: ldc2_w        #39                 // double 2.0d
      40: invokespecial #45                 // Method java/lang/Double."<init>":(D)V
      43: invokevirtual #49                 // Method java/lang/Double.doubleValue:()D
      46: dstore        5
      48: dload         5
      50: invokestatic  #52                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      53: astore        6
      55: getstatic     #33                 // Field java/lang/System.out:Ljava/io/PrintStream;
      58: dload         5
      60: invokevirtual #54                 // Method java/io/PrintStream.println:(D)V
      63: aconst_null
      64: areturn

But when I execute this code I get the following error

Exception in thread "main" java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    Test.run()Ljava/lang/Object; @58: dload
  Reason:
    Type top (current frame, locals[5]) is not assignable to double
  Current Frame:
    bci: @58
    flags: { }
    locals: { 'Test', float, float, 'java/lang/Float', top, top, 'java/lang/Double' }
    stack: { 'java/io/PrintStream' }

I've used the COMPUTE_MAXS and COMPUTE_FRAMES when instantiating my ClassWriter

I've been looking at this bytecode several times (and even asked ChatGPT lol) but couldn't find what is wrong. From what I see, I've stored a primitive double in variable 5, and then I want to load it to invoke println(double). I don't understand why Java is telling me this error. It worked for the first part using float, but doesn't with doubles. I've got the same problem with long type, but not with int

Can someone help me find what I missed?


Solution

  • You violate JVMS §2.6.1:

    2.6.1. Local Variables

    Each frame (§2.6) contains an array of variables known as its local variables. The length of the local variable array of a frame is determined at compile-time and supplied in the binary representation of a class or interface along with the code for the method associated with the frame (§4.7.3).

    A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress. A pair of local variables can hold a value of type long or double.

    Local variables are addressed by indexing. The index of the first local variable is zero. An integer is considered to be an index into the local variable array if and only if that integer is between zero and one less than the size of the local variable array.

    A value of type long or type double occupies two consecutive local variables. Such a value may only be addressed using the lesser index. For example, a value of type double stored in the local variable array at index n actually occupies the local variables with indices n and n+1; however, the local variable at index n+1 cannot be loaded from. It can be stored into. However, doing so invalidates the contents of local variable n.

    The Java Virtual Machine does not require n to be even. In intuitive terms, values of types long and double need not be 64-bit aligned in the local variables array. Implementors are free to decide the appropriate way to represent such values using the two local variables reserved for the value.

    The Java Virtual Machine uses local variables to pass parameters on method invocation. On class method invocation, any parameters are passed in consecutive local variables starting from local variable 0. On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language). Any parameters are subsequently passed in consecutive local variables starting from local variable 1.

    (Emphasis mine)

    You store a double in slot 5 (which occupies the slots 5 and 6), then you store something in slot 6, thereby invalidating the variable in slot 5.