[foreign-abi] On invokers

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue Sep 24 10:02:28 UTC 2019


On 24/09/2019 10:42, Jorn Vernee wrote:
> Yes, you're right that this is not the ultimate solution. It's 
> 'programmable' in the sense that what was previously hard-coded inside 
> the VM code is now passed in dynamically from Java through the API, 
> and defined by a particular ABIDescriptor.
Thanks, that matches my understanding of the code.
>
> These changes are meant to clean up some of the existing technological 
> debt we have in the UNI API. The 'programmability' is merely improved 
> to be able to describe the quirks in the existing ABIs using more 
> general, and hopefully broadly applicable concepts.
>
> I noticed that there is a mismatch between a C-level function 
> descriptor and an ABI-level function descriptor, for instance in the 
> case of meta-arguments. The current problem is that we give an 
> un-altered descriptor to UNI to work with, and then expect the invoker 
> to work out the mapping to an ABI-level call, which is inherently 
> ABI-specific. This patch adds another layer in between; it's the job 
> of the ABI implementation to adapt from the C-level descriptor to the 
> ABI-level descriptor. We need to extend the invoker API to provided 
> the right primitives for the ABI impl to do that. The API supports 
> creating a MethodHandle for an ABI-level call, which is then adapted 
> to C-level by the particular ABI impl.
>
> If more programmability is needed in the future this can be added. As 
> you say, likely in the areas of Bindings and ABIDescriptor + stub 
> generation :). This is just intended to be the next iteration in the 
> process.
Sure, don't let my comments detract from anything you have done which is 
great - I was just connecting it to some of the comments that were 
raised in the past - re. a desire to have a very low level ABI to allow 
user to _create_ new ABIs from scratch [1]. After seeing this work, I'm 
more skeptical that such an approach is really feasible.
>
> If you want to do a SysV port that would be much appreciated :). In 
> principle everything in the `programmable` package, except for 
> CallArranger is a part of the API, though there should be no need to 
> interact directly with BindingInterpreter and BufferLayout. So, if you 
> want to do a port the only thing you should have to do is add a SysV 
> version of CallArranger, (which I based on CallingSequenceBuilderImpl, 
> so that's probably a good place to start) and then call that from 
> SysVx64ABI.
>
> The things to do are;
>
> 1.) Create a SysV ABIDescriptor instance (make sure to add RAX as an 
> input storage). The x86_64Architecture class has the `abiFor` method 
> to do this
>
> 2.) Add a method that takes a C-level FunctionDescriptor + MethodType 
> and creates an ABI-level MethodHandle for this using the invoker API, 
> and then adapts this back to a C-level MethodHandle. You have to add 
> meta-arguments manually (see CallArranger::arrangeDowncall for an 
> example with in-memory return).
>
> 3.) When generating bindings, map storage indices to VMStorage 
> instances using the ABIDescriptor instance you created + 
> StorageClasses defined in x86_64Architecture (to get the right 
> VMStorage[] index).
>
> 4.) Add additional needed Binding operations when generating the 
> bindings, to replace things handled by the box-/unbox-Value methods 
> (this was needed for Windows, but maybe not for SysV).

Yep - this seems to match what I understood when looking around (hence 
my question on generality). I'll give this a short (probably in the 
later part of the week).

Thanks!
Maurizio

>
> Cheers,
> Jorn
>
> On 23/09/2019 22:56, Maurizio Cimadamore wrote:
>> 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