[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