on usability (was Re: [foreign] RFR 8211060: Library.getDefault() does not work on Windows)

Samuel Audet samuel.audet at gmail.com
Wed Sep 26 01:14:49 UTC 2018


We can do a lot of wrapper magic in either Java or C++. JavaCPP already 
does for JNI what Jorn is describing we could do for Panama:
https://github.com/bytedeco/javacpp-presets/tree/master/systems#the-srcmainjavatestavxjava-source-file

If we consider JNI to be "legacy", it makes a lot of sense to try and do 
some acrobatics like that to support legacy systems, but why not make 
the new hotness actually _usable_? Take a look at how Swift does it:
https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_structs_and_unions_in_swift

Now that's what I call *usable*. Panama is very far from that level of 
usability. And it's not because the Java language is somehow 
handicapped. Check what the guys over at GraalVM are doing:
https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.tutorial/src/com/oracle/svm/tutorial/CInterfaceTutorial.java

It also features performance that's already apparently higher than JNI:
https://cornerwings.github.io/2018/07/graal-native-methods/

Why not do something user friendly like that in Panama's case as well? 
What's the rationale to make it all in the end as complicated to use as 
JNI? Maybe there's something I'm missing, so please point it out.

Samuel


On 09/24/2018 10:37 PM, Maurizio Cimadamore wrote:
> 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
>>>


More information about the panama-dev mailing list