-
Notifications
You must be signed in to change notification settings - Fork 343
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)
bioball
left a comment
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.nativeimageand thenative-imagebinary 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
@CEntryPointand exported.This change results in an architecture and OS-specific directory being
created which now produces the headers for our shared library
functions:
libpklThe produced
libpkldynamic 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
libpkldynamic library is done using JavaNative Access[2] for ease. We provide an
interfacein Kotlin whichJNA transposes against the
libpkldynamic library discoverable atthe path that is discoverable with
jna.library.path.Load in
projects.pklCommonsClito deal withUnsupportedFeatureExceptionThis is to deal with the following error:
[1] apple/pkl-evolution#16