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