How to dispatch the same method call on different platforms?
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Sun Feb 18 11:36:58 UTC 2024
Hi,
Note that here the problem is not the downcall method handle, which
returns just a pointer, so that's portable.
The problem is that the pointer points to a struct whose layout might
vary depending on the platform.
The way one might attack this could be to wrap a bunch of "shared"
accessors for "passwd" inside a class, where the static initializer for
the class determines which platforms we're in, and sets up the layout
accordingly.
For that, I agree that some form of [1] is needed. Note that we
currently have this internal class:
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/jdk/internal/util/Architecture.java
We have started using this internally more and more, and adding stuff we
know the JDK needs. Ideally, this class will be added in some shape or
form to the public API, because I think that it could be useful to
regularize a lot of ad-hoc code that is written by 3rd parties (e.g.
using system properties and what not). I think that, realistically,
there will be small follow ups such as this: FFM is a relatively small
API, but one that comes with a relatively big conceptual shift. As some
layouts and method handles are now platform dependent, of course that
now puts more pressure on ways in which to detect said platform
differences (and adapt method/var handles as required).
P.S.
While what you say re. System::loadLibrary is true (e.g. it is not fully
aware of dynamic linker paths), FFM provides a different "blessed" way
of looking up libraries that is very well aware of what the dynamic
linker does. In fact, it's nothing but a thin wrapper around dlopen:
https://download.java.net/java/early_access/jdk22/docs/api/java.base/java/lang/foreign/SymbolLookup.html#libraryLookup(java.lang.String,java.lang.foreign.Arena)
On Linux, you can do `loaderLookup("libc.so.6")` and that works fine -
no custom logic is needed. The problem of shipping native libraries into
maven modules, on the other hand, is one that still requires manual
workarounds. But I believe that to be more a problem of the tool we're
"forced" to work with (Maven/Gradle): there's _no sane way_ to load a
shared library from a zip file (or from some random memory address).
Meaning that, to do that, you need to copy the library _somewhere_
before loading, which can come with its own set of issues (e.g. no write
access, not even to "/tmp"). A qualitatively better solution would be to
use jmods and custom JDK images (with jlink) which can deal with native
libraries just fine, but unfortunately these things are not supported by
the build tools we have today. This is another of these follow-up
activity that will likely fall out from the initial FFM addition.
Cheers
Maurizio
On 17/02/2024 06:53, tison wrote:
> For example, getpwnam returns passwd struct, but the struct layout is
> various on different platform, making the code snippet below unportable:
>
> final Linker linker = Linker.nativeLinker();
> final SymbolLookup lookup = linker.defaultLookup();
> final MemorySegment getpwnam =
> lookup.find("getpwnam").orElseThrow();
> final MethodHandle getpwnamFn =
> linker.downcallHandle(getpwnam,
> FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
> final StructLayout passwdLayout = MemoryLayout.structLayout(
> ValueLayout.ADDRESS.withName("pw_name"),
> ValueLayout.ADDRESS.withName("pw_passwd"),
> ValueLayout.JAVA_INT.withName("pw_uid"),
> ValueLayout.JAVA_INT.withName("pw_gid"),
> ValueLayout.JAVA_LONG.withName("pw_change"),
> ValueLayout.ADDRESS.withName("pw_class"),
> ValueLayout.ADDRESS.withName("pw_gecos"),
> ValueLayout.ADDRESS.withName("pw_dir"),
> ValueLayout.ADDRESS.withName("pw_shell"),
> ValueLayout.JAVA_LONG.withName("pw_expire"),
> ValueLayout.JAVA_INT.withName("pw_fields"));
> try (final Arena arena = Arena.ofConfined()) {
> final MemorySegment username = arena.allocateUtf8String(user);
> final MemorySegment passwd = ((MemorySegment)
> getpwnamFn.invoke(username)).reinterpret(Long.MAX_VALUE);
> final MemorySegment dir = passwd.get(
> ValueLayout.ADDRESS,
> passwdLayout.byteOffset(MemoryLayout.PathElement.groupElement("pw_dir")));
> System.out.println(dir.reinterpret(Long.MAX_VALUE).getUtf8String(0));
> }
>
> This code snippet only works on macOS because the layout differs on
> other platforms.
>
> In Rust, we can use #[target(os = ..)] to switch the manner, and in
> Java, perhaps we can use inheritance or interfaces, but it still lacks:
>
> * A unified way to determine current os, arch, toolchain, etc. I made
> [1] that can help but it's no more than another incomplete slang.
> * A compile-time dispatch decision. Maybe with static initialization
> block it can helps by generating 'static final'
> MethodHandle/VarHandle. I don't know.
>
> Best,
> tison.
>
> [1] github.com/tisonspieces/os-detector
> <http://github.com/tisonspieces/os-detector>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/panama-dev/attachments/20240218/ca492305/attachment-0001.htm>
More information about the panama-dev
mailing list