Graal: Incorrect results for Math.scalb()

Created on 17 Dec 2018  路  7Comments  路  Source: oracle/graal

We implemented a Java bytecode generator that creates random class files, which we also tested on GraalVM.
Over the last couple of weeks we implemented a test suite that resembles
CompileTheWorld from the Graal compiler test suite. Utilizing the compiler API, we repeatedly generate random class files and load them using a custom class loader. Then we run those files with the HotSpot interpreter and compare this result to a run with the Graal compiler.

In a number of test runs we managed to find a bug that seems to stem from field default values
as well as the java.lang.Math.scalb method. As the generated class files are usually fairly complex,
we narrowed it down to the following class file:

  public class Bug
    minor version: 0
    major version: 52
    flags: ACC_PUBLIC
  Constant pool:
     #1 = Utf8               Bug
     #2 = Class              #1             // Bug
     #3 = Utf8               java/lang/Object
     #4 = Class              #3             // java/lang/Object
     #5 = Utf8               a
     #6 = Utf8               I
     #7 = Utf8               main
     #8 = Utf8               ([Ljava/lang/String;)V
     #9 = NameAndType        #5:#6          // a:I
    #10 = Fieldref           #2.#9          // Bug.a:I
    #11 = Utf8               java/lang/System
    #12 = Class              #11            // java/lang/System
    #13 = Utf8               out
    #14 = Utf8               Ljava/io/PrintStream;
    #15 = NameAndType        #13:#14        // out:Ljava/io/PrintStream;
    #16 = Fieldref           #12.#15        // java/lang/System.out:Ljava/io/PrintStream;
    #17 = Utf8               java/lang/Math
    #18 = Class              #17            // java/lang/Math
    #19 = Utf8               scalb
    #20 = Utf8               (FI)F
    #21 = NameAndType        #19:#20        // scalb:(FI)F
    #22 = Methodref          #18.#21        // java/lang/Math.scalb:(FI)F
    #23 = Utf8               java/io/PrintStream
    #24 = Class              #23            // java/io/PrintStream
    #25 = Utf8               println
    #26 = Utf8               (F)V
    #27 = NameAndType        #25:#26        // println:(F)V
    #28 = Methodref          #24.#27        // java/io/PrintStream.println:(F)V
    #29 = Utf8               Code
    #30 = Utf8               StackMapTable
  {
    static int a;
      descriptor: I
      flags: ACC_STATIC

    public static void main(java.lang.String[]);
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=3, locals=2, args_size=1
           0: sipush        -279
           3: istore_1
           4: getstatic     #10                 // Field a:I
           7: ifle          21
          10: getstatic     #10                 // Field a:I
          13: ifge          17
          16: nop
          17: iconst_1
          18: goto          22
          21: iconst_0
          22: ifeq          27
          25: iconst_0
          26: istore_1
          27: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
          30: iconst_m1
          31: i2f
          32: iload_1
          33: invokestatic  #22                 // Method java/lang/Math.scalb:(FI)F
          36: invokevirtual #28                 // Method java/io/PrintStream.println:(F)V
          39: return
        StackMapTable: number_of_entries = 4
          frame_type = 252 /* append */
            offset_delta = 17
            locals = [ int ]
          frame_type = 3 /* same */
          frame_type = 64 /* same_locals_1_stack_item */
            stack = [ int ]
          frame_type = 4 /* same */
  }

When run with the interpreter, this results in the output of -0.0 while execution of code compiled by Graal yields -1.0.

We could not further minimize the bytecode. We managed to recreate the class in ASM to perform manual corrections, but even the removal of the nop (bci 16) results in the test executing correctly.

Just for comparison, the decompiled source code would be something like:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

public class Bug {
    static int a;

    public static void main(String[] var0) {
        short var1 = -279;
        boolean var10000;
        if (a > 0) {
            if (a < 0) {
                ;
            }

            var10000 = true;
        } else {
            var10000 = false;
        }

        if (var10000) {
            var1 = 0;
        }

        System.out.println(Math.scalb((float)-1, var1));
    }
}

Interestingly, this source code also does not reproduce the bug when compiling it again.

We used the following version of the LabsJDK on a Fedora release 28 machine:

java version "1.8.0_192"
Java(TM) SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12-jvmci-0.53, mixed mode)
bug compiler

All 7 comments

Hi @skloibi, thanks for the report, could you please post your Bug.class ?

Hi, thank you for the fast response.

Yeah sure, sorry that I forgot that in the original report: IncorrectResultsForMathScalb.class

If it is useful, I also uploaded the ASM generator that reproduces the exact same file: IncorrectResultsForMathScalb.java

I can't reproduce it, could you please post detailed steps on how to reproduce, if possible including the test harness you are using. I've tried warming it up, forcing compilation... without success.

Okay, the steps that we perform in our test suite are as follows:
We generate the class file (or use an existing like the provided one) and load it using a custom class loader.
Then we get the "expected" result by invoking the "main" Method of the class via reflection:

Class<?> generatedClass = ...;
generatedClass.getMethod("main", String[].class).invoke(null, (Object) new String[0]);

The result is simply all the content that is written to STDOUT by the class (the generator produces print statements).

We then invalidate the class (and the class loader - to not influence consecutive runs) and reload it.
We compile each method of the class using a stripped down variant of the CompileTheWorld code in https://github.com/oracle/graal/blob/master/compiler/src/org.graalvm.compiler.hotspot.test/src/org/graalvm/compiler/hotspot/test/CompileTheWorld.java#L591-L631. It uses the same functions to get the InstalledCode but skips some of the metrics as they are not needed for our purpose and also compiles only this class.

Execution is done by running InstalledCode#executeVarargs on the compiled main method.

I have refactored the test suite and uploaded it: Graal Generator Tests.

You can build it by running mx build on the cloned repository and run it with mx graal_generator_tests <classfile> - e.g. mx graal_generator_tests IncorrectResultsForMathScalb.class.

Thanks for the detailed reproducer!
Your test uncovered rogue canonicalizations in IfNode. The bug is completely unrelated to Math.scalb, it triggers the right pattern at the right moment (cold method). A fix with some additional improvements is already up in our internal system.

Was this page helpful?
0 / 5 - 0 ratings