Short feedback on the linker (CLinker)

Johannes Kuhn info at j-kuhn.de
Mon Dec 21 13:08:15 UTC 2020



On 21-Dec-20 12:32, Maurizio Cimadamore wrote:
> Hi Johannes,
> thanks a lot for the feedback, it's always great to get a pair of fresh 
> eyes looking at how things are coming along!
> 
> On 21/12/2020 10:52, Johannes Kuhn wrote:
>> So, I played a bit around with the CLinker, to see what is possible 
>> and what is hard. I used a self-build JDK from the openjdk/jdk 
>> repository.
>>
>> To do this, I picked 3 libraries I had installed on my system:
>> * JNI
>> * Tcl
>> * Win32 api
>>
>> JNI:
>> ----
>> This is a bad idea, but someone will inevitably try it, so why 
>> shouldn't that someone be me.
>>
>> Entry point is `JNI_GetCreatedJavaVMs`.
>> Works so far, but the returned handles by JNI are immediately invalid. 
>> (Somewhat expected, as the native call is over.)
>> Interesting is that env->FindClass can throw NoClassDefFoundError, 
>> which I could catch.
> Interesting experiment, this is definitively an area which could use 
> some polishing. While a JNI library is a shared library, its 
> requirements are so specific that I think perhaps we should try harder 
> to ban JNI library loading from Panama - that said, as always with 
> shared library, there's not much we can do other than looking for 
> special JNI names (OnLoad) - which might or might not be present. But 
> seems an area where some warning triggered by an heuristic (a la 
> JNI:check) might be helpful in avoiding mistakes.

The problem is a bit bigger: Any native code could use JNI during the 
call. In my experiment, I obtained the entry point with

     LibraryLookup.ofDefault().lookup("JNI_GetCreatedJavaVMs")

There is not much you could do to guard against this - as this call 
might be down in the native code. Or just use `GetProcAddress` from 
win32 if you forbid looking up "JNI_*". Or it's linux equivalent.

The better option is: What guarantees do you make if someone does that?
A sentence in the spec that says "Using JNI using a downcall is not 
supported, it may or may not work." could help.

>>
>> What seems to be missing is the ability to use a dynamic address for a 
>> downcall to support vtables.
> Yes, as discussed in a separate thread, this was on the table, we just 
> didn't get enough time to get something in for 16 - but it's 
> definitively something we want to get to, and thanks for reminding us of 
> this important use case/generalization.

Yes, saw that thread. For now, I cache an internal "Functions" object 
that has final MethodHandle fields. It works, but is clumsy.

>>
>> Tcl:
>> ----
>> Works great so far.
>>
>> Tcl uses something called ClientData (just a typedef for void*) to 
>> pass application specific structures around. It doesn't try to 
>> dereference that pointer. The ClientData is only passed to upcalls.
>>
>> It might be nice to be able to get a "handle" for a java object - 
>> otherwise you have to implement the bookkeeping yourself.
>>
>> It would avoid creating new downcall handles just because you changed one
> We have in the past discussed ways to "pin" heap memory, so that heap 
> addresses could stay stable across a native call. This would be useful 
> to implement logic like the one currently implemented in JNI critical 
> sections, but I think it would always help in your case. That said, 
> pinning heap objects would need to be made an unsafe operation (e.g. 
> foreign restricted), given that it has the potential to undermine the 
> correct behavior of the garbage collector, if abused. But yes, obtaining 
> pointers from Java objects is something that is on our radar - it's 
> technically not too hard to do that - the safety implication are the 
> difficult part (as in many other areas of this project).

No objections to making it restricted.
There is just a few things to consider:
* The handle might have a long lifetime - possibly until the application 
exits.
* Tcl provides an upcall to "free" the data. In C, a function pointer to 
free is a good implementation (if the pointer came from malloc). This 
mode should be supported.

(This mode would be surprisingly easy to implement using JNI - with 
NewGlobalRef & DeleteGlobalRef.)

>>
>> Win32API:
>> ---------
>>
>> In the past, I often had problems with other FFI and the Win32 api:
>> Something was clearing the last error, so a downcall to some function, 
>> directly followed by an other downcall to GetLastError would result in 0.
>>
>> But with Panama, everything works fine - a call to GetLastError indeed 
>> returns the error code.
> Phew - Windows.h is a really big stress test for all things Panama - I'm 
> glad it worked out ok so far (although I wouldn't be surprised if more 
> issues would be discovered down the road :-) ).

The problem sometimes are "non-errors". For example in named pipes. To 
figure out if the connection comes from a remote machine, you call 
"GetNamedPipeClientComputerName" - if there is no error, then it's 
remote. If there is a specific error (don't remember which), then it's 
local.

Haven't played much with it, basically just "I get the right error if I 
pass NULL to some function".

>>
>> Conclusion:
>> -----------
>>
>> Impressive work so far. There are a few areas that could get some 
>> improvements (vtable support, handles for java objects).
> Thanks for the honest feedback - being the first incubation of the 
> linker API, we know we're not done - there's work to do in some of the 
> areas you describe, as well as some performance work to do on upcalls - 
> but I'm happy to hear that what we have seems to be working ok - at 
> least for the use cases you have tried >>
>> I also noticed that I tend to declare a few classes with final 
>> MemoryAddress as their sole instance field. I hope Project Valhalla 
>> will reduce the cost of those type-safe wrappers.
> 
> Yeah - I suppose you had to do that for structs - while we could 
> auto-generate these wrappers in jextract, for now our driving principle 
> for jextract has been one to try not to add any overhead in the 
> plumbings that are generated, so that clients could decide how to wire 
> things up (and how much to pay for the extra safety provided). I too 
> hope that Valhalla will make the cost of implementing such wrappers so 
> negligible that at some point we might just be able to flip a switch and 
> start generating struct wrappers by default. I've been doing some 
> experiments few months ago and, while things look definitively solid (to 
> the point I could even declare wrappers for primitive C types), we have 
> also observed some performance cliffs (some of which have been fixed). 
> So this is an area that we will keep monitoring going forward, as of 
> course we'd like to make jextract generated code more useful, if we can 
> do so at no performance cost.

Rarely structs. Mostly ordinary pointers.
Just to provide a type-safe, object oriented interface.
They have a few methods - they usually just call a MethodHandle with 
this.base as first argument. (And the usual allocation stuff for 
CStrings, out parameters, and error checking...)

- Johannes

> 
> 
> Cheers
> Maurizio
> 
>>
>> - Johannes

PS.: What is the best way to handle out parameters?
For now I allocate a new segment every time - is that fine?


More information about the panama-dev mailing list