jextract woes
Michael Zucchi
notzed at gmail.com
Tue Jan 28 03:37:54 UTC 2020
On 27/1/20 9:44 pm, Maurizio Cimadamore wrote:
>
>
> On 27/01/2020 01:52, Michael Zucchi wrote:
>>
>> Ok, using the platform clang headers - by default - I get 8000 lines
>> of this:
>>
>> WARNING: Using incubator modules: jdk.incubator.foreign,
>> jdk.incubator.jextract
>> java.lang.UnsupportedOperationException
>> at
>> jdk.incubator.jextract/jdk.incubator.jextract.tool.TypeTranslator.visitArray(TypeTranslator.java:98)
>> at
>> jdk.incubator.jextract/jdk.incubator.jextract.tool.TypeTranslator.visitArray(TypeTranslator.java:35)
>> at
>> jdk.incubator.jextract/jdk.internal.jextract.impl.TypeImpl$ArrayImpl.accept(TypeImpl.java:231)
> Noticed those too
>>
>>
>> But using --filter=cl.h I get something similar to my current
>> generator, only it's much much slower to create.
> Yeah - after some experimenting I found this filter to be optimal in
> the set of things that are generated. If you pull in platform.h, you
> get a lot of data type definitions which I'm not sure if they are
> useful - at least for the common examples.
The platform stuff isn't really needed, or has to be coded specifically
by java in other ways. extensions on the other hand (the 'interesting'
problem i had, see below).
>>
>> But it looks interesting and will try using it - although i'm
>> confused now about which panama branch should I be working against?
>> Or should i just run the tool from one and use it with the foreign-abi?
> foreign-jextract is, essentially, foreign-abi + the tool. So you can
> just stay on foreign-jextract. Every change pushed to foreign-abi is
> automatically propagated to foreign-jextract.
Thanks.
>
>> * +1 for outputting source rather than a jar.
>>
> This isn't set in stone, at least the source vs. classfile generation.
> Most notably, the fact that we're forced to generate sources, right
> know put too many constraints which affect performances. Basically,
> the generated class has a huge (and I mean huge) static initializer
> which is suboptimal for two reasons:
>
> * even if you are interested in calling 4-5 functions, you have to
> initialize _everything_
>
> * it is easy to go beyond the code size limit of the static initializer
>
> Both issues can be addressed by having direct classfile generation;
> if, in the future, Java will support something like 'lazy statics' we
> could consider emitting soucefiles again - but at this point of time,
> sourcefile generation is much less optimal.
>
Ok that's fair, I was under the impression that the java compiler did
better with constant initialisers so didn't realise it was an issue. I
wouldn't have spotted that union bug if I was wading through javap
output though.
Class file(s) would be easier to use than a jar though.
>> *
>>
>>
>> * Should all the calls catch Throwable and then assert? e.g.
>> something that calls c that calls java, can the java throw an
>> exception to be passed back up as one could with jni? This isn't
>> really something that can be automated though.
>>
> Perhaps we could catch checked exceptions, and leave runtime
> exceptions be?
I don't know i don't know. In zcl (because the generated method i
created just exports the Throwable) I do something like this:
try {
res = clSomeFunction();
... code might throw a checked thrownException
} catch (RuntimeException | thrownException | Error e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException();
}
But that was just the first approach i thought of. It only requires one
try block at least, if the clSomeFunction() caught some and threw some
you'd probably still need something similar.
Since you don't know which c functions might call java synchronously,
and all the java interfaces have no exceptions defined so shouldn't be
throwing any anyway, just asserting as you do is probably the right
approach and it doesn't need to be over complicated. There's plenty of
other places uncaught runtime exceptions sort of vanish (thread.run())
so it wouldn't be unusual.
>> Some simple ideas which I don't think are out of scope (when the time
>> comes to consider them):
>>
>> * option to disable get/setter generation and if so, optionally
>> varhandles (for libraries where many fields exist that aren't
>> public).
>> * option to put enum/#defines in another class
>> * option to provide files with specific list of functions / structs
>> / enum / defines to generate and not just a regex.
>>
> I think we need to do a better job with grouping things. And, the old
> jextract had more filtering options, more similar to what you
> describe, which could be resurrected - although it was also very complex.
I had to dive into the code this morning to find out whether --filter
was a regex or not (it doesn't) and I saw a lot of infrastructure there
for it. If you're not going to generate source some of this is going to
be required for it to be useful.
Also these outputs don't necessarily have to be from one run, it could
take multiple runs (but boy is clang so slow).
>> I guess It has the same problem I have with my generator - which is
>> both a benefit and a curse - there's just no type checking beyond
>> primitive types. When you write jni you have all the facilities of
>> the C language and the Java language at your disposal. Here you have
>> ... "every pointer is a 'void *'". It works in practice but it is
>> definitely error-prone. I don't have anything to suggest though
>> because either you have to put a big layer of common abstraction over
>> everything which is both a cost and a constraint (and gets cumbersome
>> real fast), or you make something tighter but application-specific
>> outside of jextract.
>>
> Big layer of abstraction was the route the old jextract went. While in
> some cases it was helpful, in other cases it was more an hindrance
> than anything else. In my (brief) experience converting OpenCL
> examples to Panama, I've seen exactly the same issues I had with the
> old jextract - that is, the added abstraction in the older jextract
> didn't really help solving some of the issues when using this API -
> actually it made it worse (IMHO). As you pointed out in another email,
> OpenCL is quite void* happy. And, in the old world, turning a void*
> into e.g. a Pointer<CL_Int> was not as easy as in C - and added
> overhead. Here, if you have memory address, you can dereference it any
> way you like, which is unsafe (e.g. you could attempt to dereference a
> string as a cl_int) but it's handy as well.
>>
>>
Yep exactly my experience with it. Things like Pointer<Pointer<>> are
nasty, especially when it's actually just a trivial address load.
Now onto the interesting problem. OpenCL extensions.
OpenCL extensions provide extra sets of calls and constants but there
are no entry points provided in libOpenCL.so. They need to be resolved
at runtime as they depend on the driver, aka 'platform'. OpenCL 1.0/1.1
has clGetExtensionFunctionAddress but opencl 1.2 deprecated it in favour
of clGetExtensionFunctionAddressForPlatform as they are really platform
specific. Vulkan follows the same idea but it covers the whole api
apart from the function resolution functions.
(incase you try jextract on cl_ext.h or cl_gl.h it and it seems to work,
libOpenCL can include extension entry point symbols, but it isn't
portable and it can't work for cl-platform-specific routines.
https://www.khronos.org/registry/OpenCL/sdk/1.2/docs/man/xhtml/clGetExtensionFunctionAddressForPlatform.html)
So there needs to be a way to instantiate a set of method handles whose
entry addresses are resolved based on runtime state. This isn't
possible now with jextract and also obviously conflicts with the static
method/handle route currently employed.
With the old jextract I could directly call Libraries.bind() with a
custom name resolver (more code than with the low level api would
require). With 'generate-abi' (my generator) I would just create a
concrete class with the constants/handles/calls and a factory method or
constructor which takes a name resolver. The jni code basically did
this but in c-land.
An easy low-level solution for jextract which maintains the current
static class design would be to add an option to the method
generation. Rather than method handles and methods which use them it
has methodhandle factory methods that take an address or address
resolution function*. The 'public' library interface would just use
this to get a set of method handles it cares about and then invoke them
directly in it's methods. This would force it to call invoke(exact)
directly but that isn't *much* worse than the untyped method calls as of
now and they are already exposed for such use anyway.
I will re-implement what i had with zcl/jni on the branch using this
type of approach (but with my generator) to see how it works.
Z
* For vulkan this is going to have be per-method since it everything a
big generated header file, or multiple runs of jextract with specific
methods filtered in.
I want to look at it vulkan at some point, it's got some additional
problems to tackle.
More information about the panama-dev
mailing list