[foreign] structs passing: windows vs. SystemV
Jorn Vernee
jbvernee at xs4all.nl
Wed Dec 5 13:49:53 UTC 2018
> 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
This is interesting, but doing that might make some people angry. In C
the compiler always takes care of allocating the copy when passing
larger than 64-bit structs, so it's reasonable to expect the same when
using panama. (FWIW, it's not just smaller than 64-bit structs, it's
exactly 8,16,32 or 64 bits; i.e. 24-bit structs are also passed in
memory)
On the other hand it might offer some user-space opportunity for
optimization where they elide the copy since they don't need the old
value any more, but then again, that might also be a source of bugs;
where users naïvely pass a pointer to their struct to a native function
(without allocating a copy) and expect to get by-value semantics, and
end up shooting themselves in the foot.
> 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
This is currently what I'm using, and I feel that it's the most straight
forward way to implement it. We can make the unbox-/box-value methods of
UniversalNativeInvoker something that is implemented separately by every
ABI. The only thing that would have to be added is a Scope argument
which is closed after the call, so that unboxValue can use it to
allocate a copy (in the current prototype I'm leaking the copy).
This approach also gives extra flexibility for other ports if they need
to do something special for boxing and unboxing.
> 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.
This could work I think, but it requires special casing in the
UniversalNativeInvoker code which SysV has no need for. There's also
some assertions being made in the current unboxing code which do not
work for Windows [1]; the maximum number of bindings per argument is
always 1. This conflicting assumption also makes me feel like 2. is a
more solid approach, at least for the moment. Maybe if other ABIs use
this form of argument passing as well we could look again at moving it
to shared code.
Jorn
[1] :
http://hg.openjdk.java.net/panama/dev/file/62c3c062d504/src/java.base/share/classes/jdk/internal/foreign/invokers/UniversalNativeInvoker.java#l173
Maurizio Cimadamore schreef op 2018-12-05 12:42:
> 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