I've been playing with this for the past week or so, i.e. Java JNI bindings to Wasmtime. Thought I'd share here, if there are other folks interested. It's very early stages, but I've at least gotten up to calling Java methods from WASM via the Java Wasmtime bindings. There's still a lot to do. But it's really exciting to see this working. Nice work all on all the interfaces, Wasmtime has been a pleasure to work with. https://github.com/bluejekyll/wasmtime-java
Nice! Would love to add this to the readme when it's ready and we may want to move into the org if you'd like as well
Yes. I’d definitely love to do that. I’m doing my best to do things correctly. I’m a little nervous about the threading model right now, but am happy with the memory management.
I'd be happy to help out with any questions in the wasmtime arena if need be!
Although I'm much less knowledgeable in the JNI arena...
Right now I have some concerns about dropping across threads. Java has a new Cleaner facility (deprecates finalize methods), and I think it will potentially cleanup objects on a separate thread. This could mean that !Send and !Sync types (e.g. Store) might get dropped by a different thread. Given that the wasmtime types don’t implement Drop, I think this is ok, but I wonder if there are other concerns there?
@Benjamin Fry nah that'll be the cause of many segfaults, they all need to be dropped on the same thread. I had to deal with this with the Go bindings as well, which specifically queued up items for destruction into a shared structure and then the "main item" occasionally gc'd it. That's not a great solution in Go though and I'd ideally like to do better, but I wasn't able to find a way in Go to force things to deallocate on the same thread.
Thanks, @Alex Crichton, that is exactly the type of thing I was worried about.
I'm a little tempted to spawn a thread in Rust to manage all of the Wasmtime components, but that would add a substantial amount of state to manage between threads...
I got end-to-end tests working! Still need to figure up the threading and cleanup, but this is in pretty decent shape for POC and outline.
@Alex Crichton taking you up on your offer, is there a standard for array and string conversion into WASM that I can follow? It's not 100% clear to me right now how to pass byte arrays and strings into wasmtime. I'm assuming this is mostly standardized because wasm-bindgen does something on the WASM interface level. Maybe you could point me at where this happens in the Go bindings?
@Benjamin Fry do you mean at the
C API layer?
or in wasm itself?
The "C" api layer, though I'm not using the C api, I'm going straight from Java -> JNI -> Rust -> Wasmtime... it's easier I think than using the C API.
ah ok, so wasm-bindgen is not really related to wasmtime in that it's all meant for the web
otherwise do you mean like passing wasm bytes to wasm_module_new
?
or
getting the name from wasm_importtype_module
or something like that?
No, the Func
interface.
so like you want to call a wasm program and give it a string?
Or a byte array, yes. I understand this needs to be done with a Memory or Table?
ah
ok
er
er sry learning a new keyboard layout...
but anyway you'll be using Memory
you'll at this point basically need to do what wasm-bindgen does by hand
right... that's what I figured.
which means you need to manually copy the bytes into Memory
and then call the wasm function with the pointer
currently wasmtime doesn't do anything to help you
although this is the whole schtick of interface types
What are "interface" types?
https://github.com/webassembly/interface-types
still a wasm proposal, but basically makes strings and such first-class types
Ah, that looks nice.
you can think of that proposal sort of like wasm-bindgen standardized
In the mean time, is there a common pattern for laying out the function parameters that are associated with the Memory being passed in?
That is, I believe I have to use a Linker to associate the memory, and then pass in the length of the Memory?
I guess I'm looking for naming conventions and parameter ordering/placement.
@Benjamin Fry hm I'm not
sure that I understand
what do you mean by Memory
as a parameter?
b/c that should largely be handled during instantiation, not while you're calling functions
yes. but the function definitions need to take offset and length right?
they
and they might be mixed with other parameters?
true, yeah, but there's no real convention about that since it's more
of an ABI thing with the wasm blob
any ordering of arguments is basically as easy as all the others, so I think s-to answer your question there's no real conventions or anything like that
I see. Do you forsee wasm-bindgen as ever being used for non-js areas?
because I would expect it expects the orders and naming of the memory to be done in some consistent way?
wasm-bindgen literally as is probably not, but spiritually yes (that's interface types effectively)
wasm-bindgen doesn't really deal with naming or ordering conventions, it just reflects what you wrote in the source
so if the first argument is a string then the first two abi arguments are the pointer and the length, but that moves around if you move the argument
That's exactly what i was looking for... {pointer, length} in that order, based on where the String/byte array appear as arguments in the original language.
Thank you!
oh
oh lol sorry if that was a roundabout way to answer your question
I'm sure mostly from my lack of familiarity with this space.
Thanks for helping me grok it.
@Alex Crichton I think, unless I'm doing something wrong, I need to have access to a dynamic version of Func::wrap, that would allow for a list of WasmTy parameters. Is there a reason that implementation doesn't exist? I think I need it to be able to build more complex types for passing via memory regions with Caller, but the compile time bindings of the existing Func::wrap impl is very restrictive.
@Benjamin Fry I think that's Func::new
?
I'm using that now, but it feels like I don't have access to some of the dynamic type conversion options that we get with the WasmTy implementations (I want a custom version of that for Slices, like what wasm-bindgen has).
Maybe I'm over-thinking this.
hm that's true
although the conversions are in theory relatively trivial
we may need to implement a few more things though
So I'm hoping to use wasm_bindgen for the wasm bindings. It appears that wasm_bindgen only produces 32bit arguments (which makes sense for wasm32 target), but wasmtime appears to directly support 64bit values? This implies that I have to detect if the module is wasm_bindgen generated and then only use 32bit arguments? Am I misunderstanding the lay of the land?
I'll share what I have when I get it working, then if you have time maybe you could review what I've done?
yeah
yeah this is where wasm-bindgen is very web focused
which only some browsers implementin the 64 bit integers in js for now
but yeah can help with review!
Ok, do you think it's worth "detecting" if the module is wasm_bindgen, and if so, only drop to 32bit impls in that case? or is there another more general purpose way to detect that?
hm maybe? tbh fusing wasm-bindgen and wasmtime is gonna be painful no matter what you do
it's just not really built for it
Yeah, I'm starting to get that... ok
different question then, is there a different macro tool like wasm-bindgen that is better to use in this case?
unfortunately not currently, no
or not that I know of
got it. I did notice some discussion on the wasm-bindgen repo in this area, so i can follow that.
Ugh, I'm so close on this, at least of passing Strings from WASM->Rust->Java, but I have an exception, so need to take a detour to unwrap the chain of exceptions.
Now I have full Java exception stack traces being reported back from the WASM calls to Java Funcs. That's really helpful.
@Alex Crichton Hopefully this is my last pestering question. I have byte array's being passed from WASM to Java now. I'm going to work on the inverse next. I'm curious about something related to Memory usage. Is the general best practice to export allocation methods in the WASM module, and get pointers back from those functions? Or is there another best practice for finding an offset into Memory for that call? I've been reading a lot of the documentation and looking at implementations, and I'm quite confused.
for now, exposing allocation functions is probably best.
eventually interface types will handle all of this for you
Thanks, @fitzgen (he/him), that confirms what I was thinking would be needed.
@Benjamin Fry the witx tooling might work for your use case, depending on the details. See this excellent blog post by @Radu M for an overview
Yeah, I'm considering that for generating bindings and having a common API that's defined... right now I'm just trying to support Rust in WASM, but the witx stuff looks really useful for when we expand to other languages...
Even for just Rust it might be a good approach, because it's intended to approximate Interface Types, and will evolve in that direction more and more
True... I haven't looked at using it yet. Do you have an opinion on the ease of adoption and usage of witx?
I'm guessing that differs across different languages and their support for witx
yeah, I doubt there's a general answer to be had here — though I'd like to have one, too! :smile:
Thanks for all your help @fitzgen (he/him) and @Alex Crichton. I just got byte arrays as both parameters into WASM and returns working all the way back to Java (using ByteBuffer for the Java type). There's some deallocations that I need to insert as everything is just allocating right now, but it's all working. Now just need to do some cleanup.
FYI, I have this in fairly decent shape at least for POC work. Not sure what the rest of the group thinks about where it lives. I have test automation in place for Linux and macOS, but Windows not yet due to a lack of desire on my part at the moment. In terms of work left, I have something in place at least for guaranteeing that Store's aren't shared across threads on the Java side, but it might need to be better for some of the other types. In terms of GC, all the Java types are auto-closeable, and memory is freed in the close of the Java object, so this should help ensure that all cleanup in the Rust impls happens on the same thread, but since I haven't used this in anger yet, I'm sure there are issues with that model.
Is there any mature binding in Java for wasmtime? I'm trying to build some proof of concept to replace statics sdks with something more dynamic using webassembly from Java, python, go, node and rust
Last updated: Dec 23 2024 at 12:05 UTC