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?


Reply via email to