on usability

Samuel Audet samuel.audet at gmail.com
Mon Oct 1 05:29:18 UTC 2018


Hi, Jorn, Maurizio,

I'm not talking about syntactic sugar, I'm fine with Java requiring 
getters/setters and having an init() call somewhere. What I'm not OK 
with is, just to call a native function, we have to call something like 
Libraries.bind() every single time with the name of the class and the 
path to the library, or save it somewhere in a field, and then what do 
we do with it? How do we manage it across classes and modules? Why can't 
the framework do that automatically for us?

This is just one example of the usability problems that Panama isn't 
solving over JNI, that yes Swift or GraalVM is solving, not with 
wrappers, but at compile time as Maurizio points out. Panama could do 
the same with an init() function if it decided not to go with vanilla 
interfaces since the factory method pattern prevents this: You'll need 
wrappers.

Another usability problem is code that is hard to read, like for the 
layouts, yes, which are neither easy to read nor write, I'm glad 
Maurizio agrees, but there are no proposal to fix this because we're 
committed to this approach as it's supposed to make things more 
general... but they still won't work for C++ templates or computational 
graphs! So, maybe everyone will want to use Truffle anyway?

BTW, Swift does support offsetof(), here's the proposal status:
https://github.com/apple/swift-evolution/blob/master/proposals/0210-key-path-offset.md
"Status: Implemented (Swift 4.2)": Open source at work!

BTW2, the example from GraalVM that I gave isn't from Truffle, it's from 
the "Substrate VM" codebase: It's completely unrelated.

JNI is already very capable. As Maurizio points out in his reply, the 
whole point of Panama is not to improve the capabilities of JNI, it's to 
improve the *usability* and the *performance*, but after over 4 years of 
work, I still do not see how it will be able to fulfill either goals! 
Let's maybe work on "linkToNative" and get this shipped already to let 
others work on things like jextract? That might be worth it, but it's 
not happening. Priorities are elsewhere, although it's not clear to me 
where exactly.

You see, in my opinion, the problem with Panama vs others like GraalVM, 
LLVM, or Swift is that the link with the community is missing. Decisions 
are made purely on an internal basis with no communication with the 
outside. If you Jorn like where Panama is going on a technical basis, 
that's great, but I'm not, and probably others are not either, but there 
is no forum to have a discussion about this.

In any case, the Java community is a bit wider than OpenJDK, so maybe 
one day Truffle will get integrated into the JDK before Panama gets 
anywhere, or Substrate VM will "replace" HotSpot, since AOT is pretty 
much required for platforms like iOS, Google might become interested in 
using it for Android too, and everyone's already bundling the JRE with 
their desktop apps these days anyway.

Samuel

On 09/26/2018 06:39 PM, Jorn Vernee wrote:
> 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