Binding a single function symbol with [foreign]
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Fri Sep 7 12:59:01 UTC 2018
On 07/09/18 13:20, Jorn Vernee wrote:
> Yes, I think that would work. As an API point, in that case you could
> add an overload that also takes the `LayoutType` to cast to, and
> appends a call to `cast` to the bound method handle i.e.:
>
> MethodHandle bind(MethodType mt, Function f) {...}
>
> MethodHandle bindWithCast(MethodType mt, Function f, LayoutType<?>
> lt) {
> MethodHandle bound = bind(mt, f);
> bound = collectArguments(insertArguments(MH_PointerCast, 1,
> lt), 0, bound);
> return bound;
> }
>
> And if the overload without the `LayoutType` is used while the method
> type returns a `Pointer` throw an IAE?
This is a good idea to allow for more user-friendly behavior.
>
> I guess the `LayoutType` of a returned `Pointer` would be
> `LayoutType.ofVoid` before the cast? Or is there a generic fall-back
> carrier type? Maybe a direct `ByteBuffer` that wraps the returned
> pointer, and uses spill-to-heap for non-pointer returns?
LayoutType.ofVoid is what I was thinking.
>
> Though the solution I was thinking of yesterday was to use a more
> informative type instead of `MethodType`, that also carries the
> generic type information. Something using
> `java.lang.reflect.ParameterizedType` maybe? In that case the right
> `LayoutType` for the returned `Pointer` could be immediately derived
> from that, without the need for the extra cast?
This is probably the wrong path - there's no API to construct
java.lang.reflect.Type(s) in a succint way, so asking something like
that to users would feel like a step backwards to me.
Maurizio
>
> Jorn
>
> Maurizio Cimadamore schreef op 2018-09-07 09:52:
>> On 06/09/18 23:41, Maurizio Cimadamore wrote:
>>> Of course you can infer a 'default' carrier information from a
>>> layout, but in the general case you can't - think of structs
>>> (modeled as classes) and function pointers (modeled as functional
>>> interfaces) - how do you know whether something is a Foo or a Bar,
>>> if both Foo and Bar are structs with same fields?
>> Pulling more on this string as I kept thinking about this: adding
>> carrier info will help to distinguish a toplevel Foo from a toplevel
>> Bar - e.g. let's make this more concrete:
>>
>>
>> @NativeStruct("[ i32(get=x) ](foo)")
>> interface Foo extends Struct<Foo> {
>> int x();
>> }
>>
>> @NativeStruct("[ i32(get=y) ](bar)")
>> interface Bar extends Struct<Bar> {
>> int y();
>> }
>>
>> These two struct declarations are isomorphic - you can use one in
>> place of another; since in a .so/.dynlib file there's no info about
>> struct names (well in the absence of debugging symbol which I assume
>> to be the norm), if you want to dynamically bind to a native function
>> whose descriptor is:
>>
>> ( [ i32 ])v
>>
>> What should the binder do? E.g. what should be the Java type
>> associated with the argument of this function? Here's where the
>> MethodType is handy - as it could help the inference process
>> understanding whether we want our method handle to accept a Foo or a
>> Bar thingie.
>>
>> But, I realized, this trick doesn't have a lot of mileage: the next
>> example up is with pointers - so let's assume the function descriptor
>> is now changed to:
>>
>> ( u64: [ i32 ] ) v
>>
>> Ok, now we have a pointer to a thingie with one 32-bit signed int
>> inside. Is that Pointer<Foo> or Pointer<Bar> ? This time,
>> unfortunately, the MethodType won't help - class types in method types
>> are erased, so the method type for this will be something like
>>
>> (Pointer)void
>>
>> Which is not enough for our inference process to resolve the ambiguity.
>>
>> I believe the next best thing here is for our process to end up with
>> some Pointer<?> - that is, a pointer whose layout info is composed of
>> the following info:
>>
>> - a layout (namely, "[ i32 ]")
>> - no carrier info
>>
>> Note that, since there's no carrier, attempting to call get() on such
>> a Pointer will fail with exception. Now, since the pointer here is in
>> an argument position, it's not a big deal - e.g. the binder doesn't
>> need to produce one, so we're fine (e.g. the pointer will never be
>> dereferenced). But if the pointer was in a return position, as in:
>>
>> ( ) u64: [ i32 ]
>>
>> This would now be an issue, as the returned Pointer<?> will not
>> support any dereference operation:
>>
>> Pointer<?> p = mh.invoke();
>> p.get(); //exception!
>>
>> So the right way to go about this would be to cast the result to the
>> right LayoutType - e.g.
>>
>> Pointer<?> p = mh.invoke();
>> p = p.cast(NativeTypes.INT32);
>> int x = p.get(); //ok!
>>
>> In other words, I think the invocation game can always be decomposed
>> into two stages:
>>
>> 1) the first step, works only on erased types and produces types that
>> are correct *up to the generic layer*
>> 2) the second step, is a fixup step that takes info from source
>> signatures (or in other, more explicit ways, as above) and turns
>> generic carriers 'right' (typically with a cast)
>>
>> Back to your original question of providing binding to separate method
>> handle symbol, I now think the answer is yes, *provided* that what you
>> mean is (1) - e.g. that you are willing to make up for losses of
>> generic type info resulting from the lack of source information that
>> directs the binding process.
>>
>> Does this help?
>>
>> Cheers
>> Maurizio
More information about the panama-dev
mailing list