-
Notifications
You must be signed in to change notification settings - Fork 313
C library for Pkl #1026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
C library for Pkl #1026
Conversation
80a0c54
to
3e26bbb
Compare
1226e49
to
4d07f34
Compare
01d886a
to
a34e1ce
Compare
ec1199c
to
3db68c1
Compare
if (Objects.equals(System.getenv("PKL_DEBUG"), "1")) { | ||
System.err.println("[libpkl] " + msg); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work? What's the preferred approach to take here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to test this locally (set the env var, run unit tests and see what happens)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work so far! I left some comments, but it's great to see this already working.
libpkl/src/main/c/libpkl.h
Outdated
typedef void (*PklMessageResponseHandler)(int length, char* message); | ||
|
||
int pkl_init(PklMessageResponseHandler handler); | ||
|
||
int pkl_send_message(int length, char* message); | ||
|
||
int pkl_close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add doxygen-style doc comments to these methods?
Also: let's add a void pointer here (per our convos with some C folks); we'd just keep a reference to this pointer and pass it back when calling PklMessageResponseHandler
.
typedef void (*PklMessageResponseHandler)(int length, char* message); | |
int pkl_init(PklMessageResponseHandler handler); | |
int pkl_send_message(int length, char* message); | |
int pkl_close(); | |
typedef void (*PklMessageResponseHandler)(int length, char *message, void *handlerContext); | |
int pkl_init(PklMessageResponseHandler handler, void *handlerContext); | |
int pkl_send_message(int length, char *message); | |
int pkl_close(); |
BTW: it seems like putting the asterisk next to the name of the variable is more idiomatic in C
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you expect handlerContext
to be provided back to the user in int pkl_init(PklMessageResponseHandler handler, void *handlerContext);
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd just pass it back every time we call handler
, which also accepts void *handlerContext
.
libpkl/src/main/c/libpkl.c
Outdated
#include <pthread.h> | ||
|
||
#include <graal_isolate.h> | ||
#include <libpkl-internal-macos-aarch64.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to address this before merging into a feature branch, but, this file should be platform-independent.
I think this should be:
#include <pkl_internal.h>
The platform-specific header/library would then be provided as a linker option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is almost there. I don't know if there's a way to get native-image
to produce a dynamic library prefixed with lib
whilst the .h
file it creates doesn't have the lib
prefix. Otherwise we'll have to rename it ourselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a big deal if it does have a lib
prefix, I guess. That's an internal file anyway; users would import pkl.h
and not libpkl_internal.h
private static MessageCallbackFunctionPointer cb; | ||
|
||
@CEntryPoint(name = "pkl_internal_init", builtin = CEntryPoint.Builtin.CREATE_ISOLATE) | ||
static native IsolateThread pklInternalInit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason we are creating our own method, instead of graal_create_isolate
from the Native Image C API?
https://www.graalvm.org/latest/reference-manual/native-image/native-code-interoperability/C-API/
I think we can just handle this in our pkl_init:
graal_isolatethread_t *isolatethread = NULL;
graal_isolate_t *isolate = NULL;
int pkl_init(PklMessageResponseHandler handler) {
// etc
if (graal_create_isolate(NULL, &isolate, &isolatethread) != 0) {
return -1
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it would be consistent with how all of the pkl-internal
bits are exposed through LibPkl.java
. This would now be one case where the management of Graal-specific resources is handled out-of-band in the .c
file. If you'd prefer me to do the above let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the benefit of doing it this way is, especially since there already is a C method that does the same thing.
But, they have a tutorial where they do it this way too: https://www.graalvm.org/21.3/reference-manual/native-image/ImplementingNativeMethodsInJavaWithSVM/
So, I guess let's just keep this? We can ask about this in their Slack too.
val unpacker = MessagePack.newDefaultUnpacker(response.result) | ||
val value = unpacker.unpackValue() | ||
assertThat(value.isArrayValue) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can address this in a follow-up PR, but: I think let's move org.pkl.server.AbstractServerTest
into pkl-commons-test.
import org.pkl.server.ServerMessagePackDecoder | ||
import org.pkl.server.ServerMessagePackEncoder | ||
|
||
class JNATestClient : ILibPklLibrary.PklMessageResponseHandler, Iterable<Message?>, AutoCloseable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is mixing quite a few concerns.
- Does this need to implement
Iterable
? I don't see where that's being used - Should separate the callback into a different object
I think this can just have an init()
that calls into ILibPklLibrary
:
private val messageResponseHandler: PklMessageResponseHandler = object : PklMessageResponseHandler {
override fun invoke(length: Int, message: Pointer) {
val receivedBytes = message.getByteArray(0, length)
val message = decode(receivedBytes)
assertThat(message).isNotNull
incoming.add(message!!)
}
}
fun init() {
assertThat(ILibPklLibrary.INSTANCE.pkl_init(messageResponseHandler)).isEqualTo(0)
}
override fun close() {
incoming.clear()
assertThat(ILibPklLibrary.INSTANCE.pkl_close()).isEqualTo(0)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to implement Iterable? I don't see where that's being used
By making it Iterable
in JNATest.kt
I can write assertions like the following: assertThat(client).hasSize(1)
.
libpkl/src/main/c/pkl.h
Outdated
* @return -1 on failure. | ||
* @return 0 on success. | ||
*/ | ||
int pkl_send_message(int length, char *message, void *handlerContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void *handlerContext
should be on pkl_init
, not pkl_send_message
.
cc16920
to
e3598b0
Compare
This introduces native C bindings for Pkl. Dynamic Library --------------- Using `org.graalvm.nativeimage` and the `native-image` binary we produce a dynamic library (for each OS/Arch variant) that provides a way to initialise itself with a `graal_isolatethread_t`. Methods are annotated with `@CEntryPoint` and exported. This change results in an architecture and OS-specific directory being created which now produces the headers for our shared library functions: ``` ❯ ll libpkl/build/libs/macos-aarch64/ graal_isolate_dynamic.h graal_isolate.h libpkl-internal_dynamic.h libpkl-internal-macos-aarch64_dynamic.h libpkl-internal-macos-aarch64.dylib libpkl-internal-macos-aarch64.h libpkl-internal.dylib libpkl-internal.h ``` `libpkl` -------- The produced `libpkl` dynamic library wraps the GraalVM C interface into something that is future-friendly for the needs of a Pkl integrator. It exports an interface which aligns with SPICE-0015[1]. ``` ❯ ll libpkl/build/libs/macos-aarch64/ graal_isolate_dynamic.h graal_isolate.h libpkl-internal_dynamic.h libpkl-internal-macos-aarch64_dynamic.h libpkl-internal-macos-aarch64.dylib libpkl-internal-macos-aarch64.h libpkl-internal.dylib libpkl-internal.h libpkl.dylib <--- this is new libpkl.h <--- this is new ``` JNA --- Testing of the produced `libpkl` dynamic library is done using Java Native Access[2] for ease. We provide an `interface` in Kotlin which JNA transposes against the `libpkl` dynamic library discoverable at the path that is discoverable with `jna.library.path`. Load in `projects.pklCommonsCli` to deal with `UnsupportedFeatureException` --------------------------------------------------------------------------- This is to deal with the following error: ``` Caused by: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a started Thread in the image heap. Thread name: main. Threads running in the image generator are no longer running at image runtime. If these objects should not be stored in the image heap, you can use '--trace-object-instantiation=java.lang.Thread' ``` [1] apple/pkl-evolution#16
This allows a user to provide a pointer, and have it be passed through and back to their `PklMessageResponseHandler` handler for them track as part of their own bookkeeping.
a61fc37
to
e4ca22c
Compare
This introduces native C bindings for Pkl.
Dynamic Library
Using
org.graalvm.nativeimage
and thenative-image
binary weproduce a dynamic library (for each OS/Arch variant) that provides a
way to initialise itself with a
graal_isolatethread_t
.Methods are annotated with
@CEntryPoint
and exported.This change results in an architecture and OS-specific directory being
created which now produces the headers for our shared library
functions:
libpkl
The produced
libpkl
dynamic library wraps the GraalVM C interfaceinto something that is future-friendly for the needs of a Pkl
integrator. It exports an interface which aligns with SPICE-0015[1].
JNA
Testing of the produced
libpkl
dynamic library is done using JavaNative Access[2] for ease. We provide an
interface
in Kotlin whichJNA transposes against the
libpkl
dynamic library discoverable atthe path that is discoverable with
jna.library.path
.Load in
projects.pklCommonsCli
to deal withUnsupportedFeatureException
This is to deal with the following error:
[1] apple/pkl-evolution#16