on usability

Samuel Audet samuel.audet at gmail.com
Wed Oct 3 03:57:07 UTC 2018


After discussing offline with John and Maurizio, I'd like to make a few 
additions to this thread.

Although we need wrappers to make the interfaces more usable, those 
wrappers can eventually be generated at runtime, just like lambda 
expressions actually create classes dynamically. If we can make this 
part of the plan, that sounds good. Then we'd just need to make it all 
work to manage the native libraries, maybe using JavaCPP as reference 
since I'm not aware of any other serious attempt at packaging native 
libraries for multiple platforms.

As for the layout definition language, I've learned that the idea comes 
from similar features found on other platforms such as Python's struct 
module, which does get used by most popular serialization methods out 
there (Protocol Buffers, FlatBuffers, Avro, etc):
	https://docs.python.org/2/library/struct.html

But I still think it's an overly complicated way of representing a 
"struct"... Couldn't we just list the member variables with methods or 
properties that access memory directly as with Swift or Substrate VM? 
Maybe that could even be part of the value types from Valhalla? We could 
then leave the layouts out of the interfaces for native code. What do 
you think John?

As for C++ templates, we could have say std::map<std::string, MyObject>. 
How could we map this with Panama layouts without any calls to native 
functions? It's basically just data, but the structure's definition is 
implementation dependent. Could be red-black trees, a hash table, an 
entirely custom method, etc, so it's not something that I think we can 
support with Panama layouts without actually executing the C++ bits.

In any case, what I'm most interested in is the performance. JNI is 
already pretty fast, and tools such as JNR or JavaCPP have shown that 
it's possible to make it user friendly enough without any additional 
overhead. (Although given the popularity of JNA that's not something 
people typically care about all that much). Anyway, I'll be waiting for 
a version of "linkToNative" that we can start testing, try to modify 
JavaCPP to target the new API and classes, although I'm starting to get 
the feeling that the leap isn't going to be big enough for people to 
want to make the switch. After all, everyone will have to be supporting 
old versions of the JDK and Android for years to come...

Samuel


On 10/01/2018 02:29 PM, Samuel Audet wrote:
> 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