on usability
Jorn Vernee
jbvernee at xs4all.nl
Wed Sep 26 09:39:00 UTC 2018
The Swift example looks cool and I can say 2 things about that:
1.) Swift seems to have properties (i.e. syntactic sugar for getters and
setter), so It's much easier to inject some code that accesses an
underlying C struct instead of a backing field. I think at least for the
time being, the Java equivalent would be getters and setters. But tbh I
don't see that much usability problems with that, since it's more or
less the same amount of characters to write: `myStruct.x = 10` vs.
`myStruct.x$set(10)` (especially considering auto-completion). It just
doesn't look as fancy.
2.) I guess jextract could generate an equivalent of the 2 `Init()`
functions as well for generated structs, and that would be part of the
'civilisation layer' Maurizio mentioned. With current tech, it would
probably either create and leak a scope internally, or you'd have to
pass a scope. Maybe a long term solution could be something like using a
default scope that is managed by the garbage collector. The generated
struct would not have a tight life-cycle (GC lazily collects objects),
but it would be easier to use.
The Graal native access stuff uses truffle, i.e. it is baked into the
interpreter. But the truffle interpreter is built to be very
customizeable, so I think doing the same with the Hotspot interpreter
would be far more difficult. But either way, the way Graal maps a C
struct to an interface in that example looks pretty similar to what
panama is doing to me.
I think panama is making sane choices, and focusing on capability before
usability. panama is just not as far along as the other projects you
mention, and I think more usability (jextract civilization layer) and
better performance (linkToNative backend) are yet to come.
Jorn
Samuel Audet schreef op 2018-09-26 03:14:
> 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