RFR: 8350896: Integer/Long.compress gets wrong type from CompressBitsNode::Value [v2]

Emanuel Peter epeter at openjdk.org
Mon May 5 08:15:47 UTC 2025


On Mon, 5 May 2025 06:49:29 GMT, Jatin Bhateja <jbhateja at openjdk.org> wrote:

>> Hi @eme64 , I have addressed and responded to your comments, please verify.
>
>> @jatin-bhateja Thanks for the updates! I think I now understand everything except this, so we are making good progress 😊
>> 
>> ```
>>       // For upper bound estimation of result value range with a constant input we
>>       // pessimistically pick max_int value to prevent incorrect constant folding
>>       // in case input equals above estimated lower bound.
>>       hi = src_type->hi_as_long() == lo ? hi : src_type->hi_as_long();
>>       hi = result_bit_width < mask_bit_width ? (1L << result_bit_width) - 1 : hi;
>> ```
>> 
>> Can you please explain it with an example, and walk me through the steps to the incorrect constant folding?
> 
> Let's assume the following
>  - The input was a constant value Integer.MIN_VALUE, hence ideal type TypeInt will have both _lo and _hi set to MIN_VALUE,
>  - Currently, _lo value of result value range flip b/w 0 or MIN_VALUE,  lets take that to be MIN_VALUE in our case.
> 
> Earlier _hi value of the result value range was set to _hi value of the source value range.
> 
> `      hi = mask_max_bw < max_bw ? (1L << mask_max_bw) - 1 : src_type->hi_as_long();
> `
> 
> If the result bit width was less than the maximum bit width of the integral type, in that case both _hi and _lo values were being set to MIN_VALUE resulting into a constant value.

@jatin-bhateja Thanks for explaining the case with `MIN_VALUE`. I suppose the same could happen if `lo = 0` and `src_type->_hi = 0`?

I would still like to see a argument/proof of line
`hi = src_type->hi_as_long() == lo ? hi : src_type->hi_as_long();`

Let's assume `mask_type = [min_int, max_int] = int`.
`lo = min_int`, `hi = max_int` before the line in question.
Now lets assume `src_type = [min_int+1, -1]`. So now the line in question sets `hi = src_type->hi_as_long() = -1`.
And the line below does not change it, because `result_bit_width < mask_bit_width`.
So we now have a remaining range `lo = min_int, hi = -1`, i.e. we return a type `[min_int, -1]`.
But imagine at runtime we have `mask_type = 0`, then obviously the result is `0`, which is outside the bounds!

Here the counter-example:

public class Test {
    public static int test(int src, int mask) {
        // src_type = [min_int + 1, -1]
        src = Math.max(Integer.MIN_VALUE + 1, Math.min(src, -1));
        int result = Integer.compress(src, mask);
        // The type is now calculated to be #int:<=-1
        // Hence, the test below must always be true.
        // But at runtime we only pass in mask = 0, so result should be 0.
        if (result < 0) {
            throw new RuntimeException("woopsies " + result);
        }
        return result;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10_000; i++) {
            test(0, 0);
        }
    }
}

Running it with: `./java -Xbatch -XX:CompileCommand=compileonly,Test::test -XX:+PrintIdeal Test.java`:

CompileCommand: compileonly Test.test bool compileonly = true
AFTER: print_ideal
  0  Root  === 0 42  [[ 0 1 3 23 24 37 ]] inner 
  1  Con  === 0  [[ ]]  #top
  3  Start  === 3 0  [[ 3 5 6 7 8 9 10 11 ]]  #{0:control, 1:abIO, 2:memory, 3:rawptr:BotPTR, 4:return_address, 5:int, 6:int}
  5  Parm  === 3  [[ 38 ]] Control !jvms: Test::test @ bci:-1 (line 4)
  6  Parm  === 3  [[ 38 ]] I_O !jvms: Test::test @ bci:-1 (line 4)
  7  Parm  === 3  [[ 38 ]] Memory  Memory: @BotPTR *+bot, idx=Bot; !jvms: Test::test @ bci:-1 (line 4)
  8  Parm  === 3  [[ 38 42 ]] FramePtr !jvms: Test::test @ bci:-1 (line 4)
  9  Parm  === 3  [[ 38 ]] ReturnAdr !jvms: Test::test @ bci:-1 (line 4)
 10  Parm  === 3  [[ 25 ]] Parm0: int !jvms: Test::test @ bci:-1 (line 4)
 11  Parm  === 3  [[ 27 ]] Parm1: int !jvms: Test::test @ bci:-1 (line 4)
 23  ConI  === 0  [[ 26 ]]  #int:min+1
 24  ConI  === 0  [[ 25 ]]  #int:-1
 25  MinI  === _ 10 24  [[ 26 ]]  !jvms: Test::test @ bci:4 (line 4)
 26  MaxI  === _ 23 25  [[ 27 ]]  !jvms: Test::test @ bci:7 (line 4)
 27  CompressBits  === _ 26 11  [[ 38 ]]  #int:<=-1:www !jvms: Test::test @ bci:13 (line 5)
 37  ConI  === 0  [[ 38 ]]  #int:22
 38  CallStaticJava  === 5 6 7 8 9 (37 1 1 27 ) [[ 39 ]] # Static uncommon_trap(reason='unloaded' action='reinterpret' index='22' debug_id='0')  void ( int ) C=0.000100 Test::test @ bci:21 (line 10) !jvms: Test::test @ bci:21 (line 10)
 39  Proj  === 38  [[ 42 ]] #0 !jvms: Test::test @ bci:21 (line 10)
 42  Halt  === 39 1 1 8 1  [[ 0 ]]  !jvms: Test::test @ bci:21 (line 10)
Exception in thread "main" java.lang.RuntimeException: woopsies 0
	at Test.test(Test.java:10)
	at Test.main(Test.java:17)

You can see the the exception is wrongly thrown, and you can see the wrong type of the `CompressBits`.

If I run it in intepreter only, I do not see the exception: `./java -Xbatch -XX:CompileCommand=compileonly,Test::test -XX:+PrintIdeal -Xint Test.java`

---------------------------

We got this code wrong before, and now again. How can we gain confidence that it will be correct on the next attempt?

My Opinion?
I really want to see a solid **proof** of this code. Because it is so easy to get these things wrong. And it seems our tests are also not good enough to catch this. So we obviously **need better tests** too.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/23947#issuecomment-2850233348


More information about the hotspot-compiler-dev mailing list