[foreign-abi] On invokers

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Sep 23 20:56:23 UTC 2019


Hi Jorn,
this is a very solid piece of work, and I like how it makes it a lot 
saner to reason about what's going on in the universal invoker - the 
entire logic is now more scrutable, so well done!

I think that, in order to understand better how things are glued, I'd 
like to try to port SysV and see how that goes; from that perspective, I 
think it's not immediately clear which parts of the Java code are meant 
to be truly reusable across ABIs/platforms and which one are fixed.

For instance: the ABIDescriptor class looks very general - and it is 
probably enough to cover the ABIs we have now. But, in a way, I see with 
it the same problems I see in the current universal invoker - that is, 
the ABIDescriptor class has to be an 'union' of all the things used in 
all possible ABIs. That is, a descriptor has to have input storage, 
output storage, (arguably very general) then more exotic info such as 
shadow space etc. The fact that - e.g. when using SysV we'd be forced to 
use an ABIDescriptor that speaks about 'shadow space' (ok we can set it 
to zero, no big deal) feels wrong in a way, for all the same reason as 
to why it was wrong in the old universal invoker to speak about x87 
registers (which are pretty SysV specific).

Let's imagine to add a very weird ABI which requires a more complex 
stack alignment scheme (e.g. stack alignment not constant across all 
args). How would we encode such an hypothetical ABI using the classes 
provided? Am I right that it would not be possible to do that using the 
out of the box ABIDescriptor, but that we would have to extend the 
ABIDescriptor to cover the requirements of the exotic ABI (e.g. have an 
extra array for the alignments for each arguments), and then leave those 
bits unused in most cases.

The same goes, I think, for the set of operation supported by bindings 
and the binding interpreter, which are, I think, supposed to be shared 
across ABIs/platform. For now we have moves, buffer copy - which are, 
again, probably ok to cover the waterfront (especially since moves 
involve arbitrary VM storage, which makes them quite flexible and 
general); at the same time if a new operation is required by some exotic 
API (e.g. set overflow flag), maybe we need to add a new binding - which 
would mean updating the single shared binding interpreter (and then 
maybe start thinking about which interaction the new binding operation 
could have with the existing ones, even though, maybe some combinations 
are not even possible, by ABI design).

In other words, while I can see this being much more general and 
maintainable than what we have now, I'm not sure I would still call it 
"pluggable" or "programmable", if you get what I'm saying. That is, at 
the end of the day the capability of ProgrammableInvoker are fixed by 
two factors: (i) the ABI descriptor (which seems fixed) and (ii) the set 
of supported binding operations (again fixed). So, even assuming we had 
an ultra-low-level API to allow an advanced developer to define his/her 
own ABI, I could still see ways in which certain ABIs could not be 
modeled w/o going deeper into the API internals (e.g. tweak ABI 
descriptor or bindings) or the VM.

And that's ok - as long as we're honest about the goal here: we're not 
after a single API to rule'em'all (which I think, as attractive as it 
is, it might be a siren song) - we're after a way to put some method 
into the ABI madness, so that less work will be required when new ABIs 
will need to be defined.

Or, did I take a wrong turn somewhere when going through the code?

Maurizio


On 23/09/2019 13:32, Jorn Vernee wrote:
> Here is a webrev version of the changes as well: 
> http://cr.openjdk.java.net/~jvernee/prog-back/webrev.00/
>
> Jorn
>
> On 23/09/2019 13:43, Jorn Vernee wrote:
>> Hi,
>>
>> I've been looking into the current set of invokers we have on the 
>> foreign-abi branch for the past few weeks. There is still work to be 
>> done in this area, both in terms of performance, and in terms of 
>> programmability. In this email I will focus on the latter.
>>
>> The UniversalNativeInvoker (UNI) API is currently the most 
>> programmble invoker that we have, so if we want to increase the 
>> programmability of our backend to cover more and more ABIs, this 
>> seems like a good place to start. UNI goes a ways in being 
>> programmable with the CallingSequence, ShuffleRecipe and 
>> ArgumentBinding APIs, being able to select in which registers to pass 
>> values, but there are still some aspects that could be polished:
>>
>> 1.) If you look into the VM code that processes the shuffle recipe, 
>> you'll notice that the eventual argument buffer that's being fed to 
>> the stub has a fixed set of registers it can work with on a given 
>> platform [1], namely the ones that are used by the C ABI. This works 
>> when we have only one ABI (C), but for different ABIs we'd probably 
>> want a different set of registers. We can change the stub generation 
>> code to take an 'ABIDescriptor' from which we derive the stub and 
>> argument buffer layout instead. This will also provide a place to put 
>> other ABI details that need to be customized, like stack alignment, 
>> and argument shadow space (Windows), as well as a set of volatile 
>> registers, which will be a super set of the argument registers. We 
>> would end up generating 1 generic downcall stub for each ABI. Also, 
>> note that we would need to create architecture definitions on the 
>> Java side to be able to specify the ABIDescriptors there (since ABIs 
>> are defined in terms of architecture).
>>
>> 2.) There is a need to pass meta arguments to a function sometimes. 
>> For instance, we need to pass in a pointer to a return buffer for 
>> in-memory-returns, and e.g. on SysV we need to pass in the number of 
>> float arguments in RAX (or rather AL) for variadic functions. The 
>> former is handled automatically by CallingSequenceBuilder, and the 
>> latter is hard-coded in the VM code. Since these are both ABI 
>> details, I believe they should be handled by the ABI implementations. 
>> Ideally we'd have an invoker API that let's us say: "add a Java 
>> argument with this carrier type, and this MemoryLayout, and then 
>> shuffle it into this register.", and then the ABI implementation can 
>> handle the further adaptation from the ABI-level signature (e.g. an 
>> additional MemoryAddress passed in as first argument), to the C-level 
>> signature (allocate a buffer as first argument and also return it). 
>> This is mostly a refactoring move in UNI::invoke and 
>> CallingSequenceBuilder that removes the handling for in memory 
>> returns, and replaces it with a more general way of passing those 
>> kinds of arguments.
>>
>> 3.) The unboxing/boxing is currently handled by calling into the 
>> various ABI implementations. We can make this code shared by 
>> extending the current ArgumentBinding 'recipe' to include other 
>> operations, besides moving from a pointer to a register, that cover 
>> the things that are currently handled by the ABI boxing/unboxing 
>> implementations. The various CallingSequenceBuilder implementations 
>> can then specify these additional binding operations when generating 
>> bindings. This means that we only need one shared piece of code that 
>> interprets this 'binding recipe'. The other advantage of doing this 
>> is that we would eventually be able to use these binding recipes + 
>> ABIDescriptor to generate a specialized stub for a particular call site.
>>
>> 4.) We are currently shuffling the arguments for a down call into a 
>> long[], and then in the VM we shuffle the arguments from the long[] 
>> into an argument buffer (ShuffleDowncallContext). We can merge these 
>> steps together, by directly shuffling the arguments into an argument 
>> buffer on the Java side (since we have an off-heap API). This 
>> decreases the overall complexity of the invoker implementation 
>> significantly, since we can drop all the code relating to shuffle 
>> recipes.
>>
>> I've been experimenting with these ideas, and have a prototype for 
>> downcalls on Windows [2]. For this I copied the relevant UNI classes 
>> to a separate `programmable` package and made the relevant changes 
>> there, since some of the code was shared with UniversalUpcallHandler. 
>> I've also preemptively removed the old UNI code (for x86) to show 
>> roughly how much code would be removed by switching to the new 
>> invoker API. I want to continue the experiment for upcalls as well, 
>> after which more old code could be removed; namely Argument, 
>> ArgumentBinding, CallingSequence (old), CallingSeqeunceBuilder (old), 
>> Storage, StorageClass, SharedUtils (mostly) and UniversalAdapter.
>>
>> How do these ideas sound? I'm mostly interested if this is flexible 
>> enough to support AArch64 and SysV. After the upcall support, I can 
>> look into porting the other 2 ABIs as well.
>>
>> Thanks,
>> Jorn
>>
>> [1] : 
>> https://github.com/openjdk/panama/blob/foreign-abi/src/hotspot/cpu/x86/universalNativeInvoker_x86.cpp#L73
>> [2] : 
>> https://github.com/openjdk/panama/compare/foreign-abi...JornVernee:prog-back-no-old
>>


More information about the panama-dev mailing list