on usability (was Re: [foreign] RFR 8211060: Library.getDefault() does not work on Windows)
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Sep 24 13:37:58 UTC 2018
Having a pre-extracted stdlib bundle is something we have considered -
quoting from [1]:
"Now, since most (all?) of the libraries out there are going to assume
the availability of some 'standard library', let's also assume that some
extracted artifact for such library is available and that jextract
always knows how to find it - this is the equivalent of java.base for
the module system, or java.lang for the Java import system. This
addresses the bootstrapping issue."
In time we'll get there, I don't see any real technical obstacles to get
to your 'optimal' snippet.
I think there are two aspects that I'd like to draw attention upon:
1) Magic does not come for free. E.g. it might "seem" that JNI has a
more direct approach to calling native methods (ease of use issues
aside). In reality it's just that the complexity of calling that native
method, marshalling arguments, unmarshalling returns, dealing with
native thread transitions and what's not has just been pushed under the
JVM rug. So, yes, you can "just" call getpid - but the burden is on the
VM. Now, the JNI support is already quite complex - I can't honestly
imagine a sane way for the VM to support any given invocation scheme
that a user might wish to see supported. This is why Panama is betting
on Java code + layouts to do the lifting: that way the VM interface can
be kept simple (which has significant payoffs - as the resulting code
can be optimized much more - see the linkToNative experimental results
in [2]).
2) As your example points out, while calling 'getpid' is something that
seems 'easy' enough - 'puts' is already some other beast. It takes a
pointer to some memory location where the string is stored. The JNI
approach is to pass the Java string as is, and then do the wiring in
native code. That is, there's no free lunch here - either you do the
adaptation in Java, or you do it in native code (**). Panama gives you a
rich enough API to do all such adaptations in Java, so that all native
calls are... just native calls (again this means more regularity which
means more performances). Having opaque native code snippets which do
argument adaptation is not very optimal (and optimizable) for the JVM.
With Panama you can create a good-looking API which internally uses
pointers/scopes and delegates to the right native method - all done in
Java. On top of that, our plans cover a so called 'civilization' layer
(see [3]), by which users will be able to customize what comes out of
jextract in order e.g. to tell that for 'puts' they really want a Java
String argument and not a Pointer<Byte>; again this will be done in a
more general way, so that the binder will be pointed at a pair of
functions which can be used to map the user provided data to and from
native code.
(**) for an example of how interfacing with standard libraries needs
some kind of wrapping, even in JNI - look at [4]; this file is
essentially a collection of system calls which are wrapped by some logic
(e.g. to check errno, ...). I claim that there is something
_fundamentally_ wrong with code like this, in that the native code is
mixing two concerns: (i) performing the required native call and (ii)
adjusting input/output/errors of the call in a way that is suitable to
the corresponding Java API. Why shouldn't the Java API itself be in
charge of doing (ii) ?
Maurizio
[1] -
http://mail.openjdk.java.net/pipermail/panama-dev/2018-August/002560.html
[2] -
http://mail.openjdk.java.net/pipermail/panama-dev/2018-September/002652.html
[3] -
http://mail.openjdk.java.net/pipermail/panama-dev/2018-April/001537.html
[4] -
http://hg.openjdk.java.net/jdk/jdk/file/tip/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c#l314
On 24/09/18 13:34, Jorn Vernee wrote:
> I agree with the usability point. In C++ it's as simple to call puts
> as doing:
>
> #include <stdio.h>
>
> int main() {
> puts("Hello World!");
> }
>
> And I think the optimal Java equivalent would be something like:
>
> import static org.openjdk.stdio.*;
>
> public class Main {
>
> public static void main(String[] args) {
> puts("Hello World!");
> }
>
> }
>
> This can be facilitated by creating a 'singleton facade' for the
> library interface like so:
>
> public class stdio {
>
> private static final stdioImpl lib = Libraries.bind(lookup(),
> stdioImpl.class);
>
> public static int puts (String message) {
> try(Scope scope = Scope.newNativeScope()) {
> Pointer<Byte> msg = scope.toCString(message);
> return lib.puts(msg);
> }
> }
>
> ...
> }
>
> Such a facade class could be shipped with the JDK or perhaps as an
> artifact on maven central, or maybe it could be an additional output
> of jextract.
>
> But there is only so much you can do automagically from the Java side.
> When working from C/C++ you have the compiler filling in the blanks.
> For instance, it automatically allocates storage for string literals.
> Java does that as well for Java string literals `String s = "Hello";`,
> but it can not do that for native strings, and you have to use the
> Scope API to do that manually. In some cases, like the above, you can
> write glue-code to make that automatic, but I think at some point
> things become too complex for that, and there will always be some
> usability barrier to interop.
>
> Jorn
>
> Samuel Audet schreef op 2018-09-24 13:38:
>> FWIW, I think the factory method pattern should be reconsidered
>> entirely. In C/C++, when we want to call say getpid(), we don't start
>> loading stuff up before calling getpid(), we call getpid()! Why not do
>> the same from Java? From a usability point of view, not loading stuff
>> manually works fine for JavaCPP...
>>
>> Now, I know you're going to start taking about interfaces and what
>> not. You said that you had plans to introduce an entirely new array
>> type just to make it more friendly with vector instructions and native
>> libraries. Why not start thinking about an "interface" that would be
>> friendly to native libraries as well? Why stop at arrays?
>>
>> Samuel
>>
>> On 09/24/2018 08:10 PM, Maurizio Cimadamore wrote:
>>> Hi Jorn,
>>> thanks for the patch. As mentioned last time we have two options
>>> here: one is to follow the approach forward in your patch; another
>>> would be to ditch Library.getDefault() entirely and adopt a more
>>> explicit approach - that is to always require 'implicit' libraries
>>> to be mentioned - either by jextract artifacts or by API points.
>>>
>>> A note on the latter - when you do:
>>>
>>> Libraries.bindRaw(lookup, Foo.class)
>>>
>>> the code lookup the @NativeHeader annotation on Foo.class and tries
>>> to extract required library names from there. Currently, we do not
>>> add library names for standard libraries such as "c" or "m" (math),
>>> which is what led us down the (slippery?) slope of having a
>>> Library.getDefault somewhere.
>>>
>>> Another option would be to have jextract to always generate the
>>> names of the libraries an artifact depends on, and then the API
>>> should throw an exception if a @NativeHeader is found with no
>>> libraries. More specifically, jextract should always add "c" to the
>>> set of used libraries in the @NativeHeader annotation (other
>>> libraries can be explicitly supplied using the -l flag).
>>>
>>> Note that I'm not saying "the binder should always add in 'clib'"
>>> for you, because that's kind of a lame assumption: it works
>>> obviously well for C but it doesn't make a lot of sense if you want
>>> to use Panama for other purposes/languages. Which seems to suggest
>>> that, as far as the binder is concerned, library dependencies should
>>> always be explicit.
>>>
>>> Thoughts?
>>>
>>> Maurizio
>>>
>>>
>>>
>>> On 24/09/18 11:52, Jorn Vernee wrote:
>>>> Hello,
>>>>
>>>> I'd like to contribute a patch that adds support for the default
>>>> library on windows.
>>>>
>>>> Bug: https://bugs.openjdk.java.net/browse/JDK-8211060
>>>> Diff:
>>>> https://gist.github.com/JornVernee/7d45780df082cbfb27417b437c7b13a8
>>>>
>>>> As mentioned before [1], this fixes 2 bugs:
>>>>
>>>> 1.) When no library was loaded ClassLoader#NativeLibrary#getFromClass
>>>> threw an NPE (at least on windows). That is fixed by returning
>>>> defaultLibrary.fromClass when the nativeLibraryContext is empty.
>>>>
>>>> 2.) The default library search was not working on windows. It was
>>>> using
>>>> a default handle, which works on unix (dlsym(RTLD_DEFAULT)), but
>>>> not on
>>>> windows (see https://stackoverflow.com/q/23437007). I have changed the
>>>> implementation from using a default handle to using a new native
>>>> function findEntryInProcess, which does the right thing for Windows
>>>> (iterate over all loaded modules), and does the same thing it used to
>>>> for unix. findEntry is now a Java method, and the original
>>>> findEntry is
>>>> renamed to findEntry0. The NativeLibrary implementation of findEntry
>>>> forwards to findEntry0, and the anonymous class of the default library
>>>> overrides to forward to findEntryInProcess.
>>>>
>>>> Please test this patch on unix as well, since I don't have the
>>>> ability to do so.
>>>>
>>>> Jorn
>>>>
>>>> [1] :
>>>> http://mail.openjdk.java.net/pipermail/panama-dev/2018-September/002644.html
>>>
More information about the panama-dev
mailing list