On 27-jep401ea3+1-1 with --enable-preview and the default JIT flags
(InlineTypePassFieldsAsArgs=true, InlineTypeReturnedAsFields=true,
UseFieldFlattening=true), I'm seeing one specific case where a
value-class return allocates on every call.
The value class is a tagged-union, ~32 bytes:
public value class SimValue {
private final byte kindOrdinal;
private final byte signedAndFlags;
private final short width;
private final long a, b, c;
private final Object ref; // null for scalar kinds
public static SimValue bitwiseAnd(SimValue a, SimValue b) { /*
230 bytes */ }
public static SimValue ofTwoStateScalar(int w, long v, boolean s)
{ /* 85 bytes */ }
public long toLong() { /* 181 bytes */ }
// ...
}
// Hot loop:
for (int i = 0; i < arr.length; i++) {
SimValue r = SimValue.bitwiseAnd(arr[i], mask);
sum += r.toLong();
}
-XX:+PrintInlining -XX:+PrintCompilation on the loop:
1346 % 3 FlatteningBenchmark::arithReturnOp @ 12 (45 bytes)
@ 24 SimValue::bitwiseAnd (230 bytes) failed to
inline: callee is too large
1345 4 SimValue::bitwiseAnd (230 bytes) ; standalone
C2 compile
1346 % 3 arithReturnOp made not entrant: OSR invalidation of lower
level
@ 24 SimValue::bitwiseAnd (230 bytes) failed to
inline: already compiled into a big method
1349 % 4 arithReturnOp @ 12 (45 bytes)
@ 24 SimValue::bitwiseAnd (230 bytes) failed to
inline: already compiled into a big method
Steady-state JFR pins SimValue.bitwiseAnd at 95.95% of allocations;
the loop runs at ~120 Mops/sec vs. ~550 Mops/sec for an equivalent
read-only loop (arr[i].toLong()).
What works on the same JDK build:
- Field flattening (SimValue stored in an identity-class field) —
zero allocation.
- Array flattening (SimValue[]) — zero allocation.
- Cross-method mutation (holder.assign(srcHolder.value)) — zero
allocation (parameter side scalarizes).
So the question is specifically about the return side when the callee
is too large to inline:
1. Is InlineTypeReturnedAsFields only honored when the JIT inlines
the callee, or should a standalone-compiled callee also use a
scalarized calling convention so its return value can stay on the
caller's stack?
2. If scalarization requires inlining, is the right fix to split
bitwiseAnd into a tiny X/Z-free fast path that's small
enough to inline, with the existing 230-byte body kept as the cold
fallback? Anything I should be aware of about how that interacts with
the C2 inlining budget for value-class-returning methods?