Binding a single function symbol with [foreign]

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Fri Sep 7 07:52:27 UTC 2018



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