Limitations of the Calling Convention Optimization

Tobias Hartmann tobias.hartmann at oracle.com
Mon Oct 26 08:15:33 UTC 2020


Hi Rémi,

On 23.10.20 15:40, forax at univ-mlv.fr wrote:
> Technically, if you look one layer below, the register allocator of c1 is able to do register spilling, which is a kind of stack allocation with more constraints.
> Hence my proposal to have a special type to represent an inline object has a value that always end up on stack.

Sure, C1's register allocator is able to spill values on the stack. But "stack allocation" of an
object is much more than that. For example, the object needs to be "split" into its field values and
re-allocated on the heap if it escapes from the method. Also, compiled code needs to provide debug
information at safepoints such that the interpreter state can be re-constructed and objects can be
re-allocated on the heap to continue execution in the interpreter if deoptimization happens.

That's exactly what scalarization in C2 does. We keep the field values of an object in registers or
on the stack (if we run out of registers). However, my point is that it is not feasible to implement
scalarization in C1 due to technical limitations with its current design.

> yes, that's more of less what i'm trying to say, what is needed to put inline object on stack for c1 is a kind of restricted form of stack allocation.
> It's like stack allocation where you will never send the address of the inline object on stack.
> 
> So it's quite different from what Microsoft proposes for c2.

> If you never send the address of the stack or the address of a page that grows has the stack, there is no additional runtime check.

But isn't that exactly what we want to do? After all, the problem we are trying to solve here is
that we need a way to pass an inline type between C1 and C2 compiled code. And since C1 does not
support scalarization, we need to pass a pointer to a buffer. Currently that buffer is a heap
allocation and as I understood, you were proposing to use a stack allocation instead.

>> We can speculate but the question is how do we handle null if it still shows up?
>> The easiest way would be to just deoptimize but then performance will suck whenever null is passed.
> 
> yep,
> in the case of Maurizio, the interface acts as an opaque type because as a user, you can not directly access to the implementation of the interface,
> so it's safe to assume that if a user uses null it will hit a requireNonNull soon.

Right but that's not always the case. An interface might be used in a context where passing null is
frequent and therefore expected to be fast while inline types should still be fully optimized (i.e.
pollution by null should not have a negative effect on inline type performance).

>> But above code does not support a 'null' being passed. To handle null, we would
>> need to have a
>> second version of the same method where the method body handles null:
>>
>> void int foo(MyValue bar) {
>>    if (bar == null) return 42;
>>    return bar.x;
>> }
> 
> 
> I believe the code will be more like
> void int foo(MyInterface bar) {
>    requireNonNull(bar);
>    return bar.getX();
> }
> 
> so it worth to aggressively speculate that bar is never null and deopt if necessary.

Yes, in this case it would be fine to just deopt on null.

Best regards,
Tobias



More information about the valhalla-dev mailing list