Hi!
Thanks for trying out the Valhalla EA release and providing feedback.
A few questions:
- Could you please share the full file/benchmark?
- Did you try to build the latest JDK version right from the Valhalla repo to
see if that resolves the issue?
- Are you sure the allocations happen on return?
To answer your questions:
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?
InlineTypeReturnedAsFields only affects *non-inlined* callees, i.e. actual
calls in the generated code. Scalarization of the return value is then
possible, if the returned value object fits into the registers available for
return.
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?
That should not be required.
I can have a closer look next week if you could share a reproducer.
Best regards,
Tobias
On 4/26/26 15:06, Veriktig wrote:
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?