[foreign] structs passing: windows vs. SystemV

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Dec 5 11:42:47 UTC 2018


Hi,
I'm coming back to this topic, as this is a source of issue when 
considering Windows port. More specifically, there is a difference in 
how structs are passed as parameters to native functions under the two ABIs:

*SystemV*: structs are recursively classified and decomposed into their 
fields - the fields are then passed separately in registers (integer/fp) 
or, if there's no more register space on stack.

*Windows*: structs up to 64 bits are passes in (integer) registers; 
bigger aggregates are passes in register, but _as pointers_ (to some 
storage that is caller-allocated); if there's no space on registers, 
again stack is used.

So, in essence, Windows ABI is unable to deal with structs bigger than 
64bits - and has to resort to an indirection. This is a kind of a new 
beast that we have not encountered before in SystemV-land: on the one 
hand we are using a register, on the other hand we're using the register 
to store a pointer - an indirection to the thing we wanna pass.

There are several ways to deal with this:

1) We could introduce a new recipe class - e.g. distinguish indirect 
struct pointers from normal INTEGER_ARGUMENT classes - UniversalInvoker 
can now disambiguate the two cases.

2) We could treat this as an ABI quirk - and add some custom code to the 
UniversalInvoker under WIndows, so that one strategy or the other is 
used depending on the struct size

3) We could do *nothing* - that is, *never* pass a struct bigger than 64 
bits on the stack (e.g. give an exception when that happens on Windows); 
if the user wants to pass such structs, it will have to allocate a 
Pointer and pass that instead

I believe (2) is our plan of records; which makes sense, given the 
SystemABI API we are working on will make it easy to sweep these 
differences under the 'ABI rug'.

But (3) isn't as crazy as it sounds: after all, that's how we deal with 
arrays in native function signatures too - we don't support them and we 
ask the programmer to 'lower' them onto pointers (which is what the 
language does anyway). Also, if you have some automated tool generating 
all your API (like jextract), such tool could be made aware of the sharp 
edge, and always insert pointers when handling big structs in functions 
on Windows.

Of course that would also introduce differences in the extracted API 
between Windows and Linux/Max, so not all is rosy. But I thought it was 
worth at least considering this option, given that, with it, the 
existing ABI machinery can be reused almost in full.

Approach (1) seems to be the most consistent IMHO - we already have a 
CallingSequence::returnsInMemory() predicate, which is used to tell 
whether we need to allocate the return value onto some temporary storage 
(a pointer to which is returned in the first integer register). But 
we're missing its argument equivalent simply because SystemV doesn't 
happen to need it. For that reason, I think that generalizing over that 
concept so that every value passed in register can be an indirection 
could be an useful addition to UniversalInvoker.

Maurizio





More information about the panama-dev mailing list