-
Enhancement
-
Resolution: Unresolved
-
P4
-
23
We have seen a production case where one the of the virtual call receivers got folded into a constant null, and then C2 started doing uncommon traps to get to the exception path.
Simplest way to reproduce follows:
```
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class NullReceiver {
static final Object CONSTANT_OBJ = null;
@Benchmark
public String test() {
try {
return CONSTANT_OBJ.toString();
} catch (NullPointerException e) {
return "Boo";
}
}
}
```
It produces the attached flamegraph: null-receiver-uncommon-trap.
`-XX:+TraceDeoptimization` on current mainline produces a stream of "deopt" events with reason=null_check action=none:
```
UNCOMMON TRAP method=org.openjdk.NullReceiver.test()Ljava/lang/String; bci=3 pc=0x00000001148506e8, relative_pc=0x0000000000000068, debug_id=0 compiler=c2 compile_id=727 (@0x00000001148506e8) thread=26115 reason=null_check action=none unloaded_class_index=-1 debug_id=0
DEOPT PACKING thread=0x00000001259ada10 vframeArray=0x000000015800c410
Compiled frame (sp=0x0000000171959f40 unextended sp=0x0000000171959f40, fp=0x0000000171959ff0, real_fp=0x0000000171959f60, pc=0x00000001148506e8)
nmethod6032 727 ! 4 org.openjdk.NullReceiver::test (11 bytes)
Virtual frames (innermost/newest first):
VFrame 0 (0x00000001258bec20) - org.openjdk.NullReceiver.test()Ljava/lang/String; - invokevirtual @ bci=3
```
I think it is from here:
https://github.com/openjdk/jdk/blob/3f1d9c441ea98910d9483e133bccfac784db393d/src/hotspot/share/opto/callGenerator.cpp#L238-L240
Arguably, calling virtual methods on known-null receiver is an antipattern, which we can avoid by checking for receiver null-ness explicitly. But it can also be the result of optimizations, which lets compiler know about receiver null-const-ness. It might be good if we can avoid doing this uncommon-trap dance and "just" throw the exception directly, as long as it is easy to do.
Simplest way to reproduce follows:
```
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class NullReceiver {
static final Object CONSTANT_OBJ = null;
@Benchmark
public String test() {
try {
return CONSTANT_OBJ.toString();
} catch (NullPointerException e) {
return "Boo";
}
}
}
```
It produces the attached flamegraph: null-receiver-uncommon-trap.
`-XX:+TraceDeoptimization` on current mainline produces a stream of "deopt" events with reason=null_check action=none:
```
UNCOMMON TRAP method=org.openjdk.NullReceiver.test()Ljava/lang/String; bci=3 pc=0x00000001148506e8, relative_pc=0x0000000000000068, debug_id=0 compiler=c2 compile_id=727 (@0x00000001148506e8) thread=26115 reason=null_check action=none unloaded_class_index=-1 debug_id=0
DEOPT PACKING thread=0x00000001259ada10 vframeArray=0x000000015800c410
Compiled frame (sp=0x0000000171959f40 unextended sp=0x0000000171959f40, fp=0x0000000171959ff0, real_fp=0x0000000171959f60, pc=0x00000001148506e8)
nmethod6032 727 ! 4 org.openjdk.NullReceiver::test (11 bytes)
Virtual frames (innermost/newest first):
VFrame 0 (0x00000001258bec20) - org.openjdk.NullReceiver.test()Ljava/lang/String; - invokevirtual @ bci=3
```
I think it is from here:
https://github.com/openjdk/jdk/blob/3f1d9c441ea98910d9483e133bccfac784db393d/src/hotspot/share/opto/callGenerator.cpp#L238-L240
Arguably, calling virtual methods on known-null receiver is an antipattern, which we can avoid by checking for receiver null-ness explicitly. But it can also be the result of optimizations, which lets compiler know about receiver null-const-ness. It might be good if we can avoid doing this uncommon-trap dance and "just" throw the exception directly, as long as it is easy to do.