-
Notifications
You must be signed in to change notification settings - Fork 3
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
Chasm cannot run wasm generated from Java code via TeaVM #81
Comments
Hey @mipastgt I took a quick a look and can see the module depends on these imports: (import "teavm" "takeStackTrace" (func (;0;) (type 24)))
(import "teavm" "decorateException" (func (;1;) (type 40)))
(import "teavmJso" "stringFromCharCode" (func (;2;) (type 25)))
(import "teavmJso" "createClass" (func (;3;) (type 30)))
(import "teavmJso" "defineFunction" (func (;4;) (type 32)))
(import "teavmJso" "defineStaticMethod" (func (;5;) (type 33)))
(import "teavmJso" "emptyString" (func (;6;) (type 39)))
(import "teavmJso" "concatStrings" (func (;7;) (type 42)))
(import "teavmJso" "unwrapInt" (func (;8;) (type 43)))
(import "teavmJso" "wrapInt" (func (;9;) (type 25))) You would need to provide implementations of these using chasms host functions api, I'd have a chat with the guys working on teavm as they will understand better why these are needed |
Yes, that's basically what I learned from the author of TeaVM too. |
@mipastgt Are we okay to close this? Chasm can actually run wasm code generated by teavm the issue here is that those wasm files have imports which are not being provided. Tbh I'm surprised the imports are present at all given the simplicity of your code, ideally these imports are only lowered into the wasm binary if the code is dependant on something from teavms runtime. |
I would say that Chasm does not clearly reports what's wrong. @mipastgt posted the following output:
which is not enough to understand what's going on. I'd expect at least function name.
Just FYI. Real-world code would use more runtime functions, for example, to represent WeakRefs (which are missing in Wasm GC) or to print something to console, etc. Currently, optimizer is good enough to produce 2-3kb for minimal module, so I simply did not want put extra efforts into making this, say 50 bytes. Also, as for slightly unusual structure of imports/exports. TeaVM used to produce JS code, so the main goal behind Wasm GC BE was to allow to migrate as seamlessly as possible existing JS-targeted code base into Wasm GC. For this purpose, it was important to interact with JS rather than with other Wasm modules or to behave like Wasm. Never expected someone would use it in browserless runtime. |
Maybe lets make a new issue for improving the error messaging for function imports as I agree this could be more helpful than it currently is In terms of running teamvm wasm binaries its not going to currently work with the approach that exports a global, the only way to invoke a function currently is for it to be exported. Chasms embedding api is largely a mirror of the spec suggested in the official spec https://webassembly.github.io/spec/core/appendix/embedding.html @konsoletyper I appreciate you're targeting browsers and have probably arrived at the current solution for performance reasons, its interesting to me that V8 lets you invoke an unexported function through an extern ref. I imagine you'll get more and more requests around this subject as Java is popular and you currently have a means of transforming it to webassembly. If its possible (I haven't looked into TeaVm), making the function exportable and removing the teavm runtime imports (when not needed) would allow these binaries to run on popular wasm engines |
We can close this. Thanks for having a look into it. |
Not exactly. First, TeaVM-generated Wasm module imports |
Is this related to the error messages produced by GraalWasm |
@mipastgt you should ask this question GraalmWasm and Chicory team. But I suppose they simply don't support Wasm GC proposal, that's it. |
That's possible because I don't see a clear statement anywhere that they do. For me it is a bit diffult to understand all this because I simply know nothing about the internal workings of Wasm. I just want to use it :-) |
@mipastgt what you need is to:
And of course, you have to learn WebAssembly a little bit in order to use it. Not entire spec with all instructions and all single letter of spec, but of what Wasm module exists, what is Wasm heap, how Wasm functions interact with outer world, what is embedding, etc. |
@mipastgt I haven't tried this myself but you might be able to include your java sources in a kotlin multiplatform project and have the kotlin wasm compiler create your binary, this would produce a binary chasm can work with similar to how the rust and c compilers you used previously worked |
@CharlieTap no, this would not work. Kotlin multiplatform does not support Java. TeaVM does support mixed Java + Kotlin projects though. BTW, what is the difficulty in introducing function that takes |
This does indeed not work as @konsoletyper pointed out. If that would work I'd be happy and wouldn't have to care about any embeddable Wasm runtimes :-) I could then just compile my Kotlin multiplatform project to native code, e.g., for iOS, or to Wasm for the browser and run it directly on the JVM for desktop and Android. |
@mipastgt with TeaVM you have option to compile to C code and then compile this C to native binary. |
Yes, actually I have already tried that and considered that as a further option for iOS and other native targets. |
@CharlieTap although I don't know your code base, it took me 15 minutes to write a simple proof-of-concept: Subject: [PATCH] call function
---
Index: executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/function/HostFunctionCall.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/function/HostFunctionCall.kt b/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/function/HostFunctionCall.kt
--- a/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/function/HostFunctionCall.kt (revision 806390c7b5bdbcf356d802b2452cde2ca7e91824)
+++ b/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/function/HostFunctionCall.kt (date 1738157567135)
@@ -26,6 +26,7 @@
}.asReversed()
val functionContext = HostFunctionContext(
+ context,
context.config,
store,
frame.instance,
Index: executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/callFunctionRef.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/callFunctionRef.kt b/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/callFunctionRef.kt
new file mode 100644
--- /dev/null (date 1738158902645)
+++ b/executor/invoker/src/commonMain/kotlin/io/github/charlietap/chasm/executor/invoker/callFunctionRef.kt (date 1738158902645)
@@ -0,0 +1,79 @@
+package io.github.charlietap.chasm.executor.invoker
+
+import io.github.charlietap.chasm.executor.invoker.instruction.InstructionBlockExecutor
+import io.github.charlietap.chasm.executor.invoker.instruction.admin.FrameInstructionExecutor
+import io.github.charlietap.chasm.executor.runtime.execution.ExecutionContext
+import io.github.charlietap.chasm.executor.runtime.ext.function
+import io.github.charlietap.chasm.executor.runtime.ext.toExecutionValue
+import io.github.charlietap.chasm.executor.runtime.instance.FunctionInstance
+import io.github.charlietap.chasm.executor.runtime.instance.HostFunctionContext
+import io.github.charlietap.chasm.executor.runtime.stack.ActivationFrame
+import io.github.charlietap.chasm.executor.runtime.stack.ControlStack
+import io.github.charlietap.chasm.executor.runtime.stack.FrameStackDepths
+import io.github.charlietap.chasm.executor.runtime.stack.LabelStackDepths
+import io.github.charlietap.chasm.executor.runtime.value.ExecutionValue
+import io.github.charlietap.chasm.executor.runtime.value.ReferenceValue
+
+fun ExecutionContext.callFunctionRef(
+ function: ReferenceValue.Function,
+ vararg params: ExecutionValue,
+): List<ExecutionValue> {
+ return when (val functionInstance = store.function(function.address)) {
+ is FunctionInstance.HostFunction -> {
+ val functionContext = HostFunctionContext(
+ this,
+ this.config,
+ store,
+ cstack.peekFrame().instance,
+ )
+ functionContext.instance
+ functionInstance.function.invoke(functionContext, params.toList())
+ }
+ is FunctionInstance.WasmFunction -> {
+ val cstack = cstack
+ val vstack = vstack
+ val params = functionInstance.functionType.params.types.size
+ val results = functionInstance.functionType.results.types.size
+
+ val valuesDepth = vstack.depth() - params
+ vstack.push(functionInstance.function.locals)
+
+ val depths = FrameStackDepths(
+ handlers = cstack.handlersDepth(),
+ instructions = cstack.instructionsDepth(),
+ labels = cstack.labelsDepth(),
+ values = valuesDepth,
+ )
+ val frame = ActivationFrame(
+ arity = results,
+ depths = depths,
+ instance = functionInstance.module,
+ previousFramePointer = vstack.framePointer,
+ )
+
+ cstack.push(frame)
+ cstack.push { FrameInstructionExecutor(this) }
+
+ val labelDepths = LabelStackDepths(
+ instructions = cstack.instructionsDepth(),
+ labels = cstack.labelsDepth(),
+ values = vstack.depth(),
+ )
+
+ val label = ControlStack.Entry.Label(
+ arity = results,
+ depths = labelDepths,
+ continuation = null,
+ )
+
+ vstack.framePointer = valuesDepth
+ InstructionBlockExecutor(cstack, label, functionInstance.function.body.instructions, null)
+ List(functionInstance.functionType.results.types.size) { vstack.pop() }
+ .asReversed()
+ .zip(functionInstance.functionType.results.types)
+ .map { (encodedValue, type) ->
+ encodedValue.toExecutionValue(type)
+ }
+ }
+ }
+}
Index: executor/runtime-internal/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/instance/HostFunctionContext.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/executor/runtime-internal/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/instance/HostFunctionContext.kt b/executor/runtime-internal/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/instance/HostFunctionContext.kt
--- a/executor/runtime-internal/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/instance/HostFunctionContext.kt (revision 806390c7b5bdbcf356d802b2452cde2ca7e91824)
+++ b/executor/runtime-internal/src/commonMain/kotlin/io/github/charlietap/chasm/executor/runtime/instance/HostFunctionContext.kt (date 1738157567126)
@@ -1,9 +1,11 @@
package io.github.charlietap.chasm.executor.runtime.instance
import io.github.charlietap.chasm.config.RuntimeConfig
+import io.github.charlietap.chasm.executor.runtime.execution.ExecutionContext
import io.github.charlietap.chasm.executor.runtime.store.Store
data class HostFunctionContext(
+ internal val context: ExecutionContext,
val config: RuntimeConfig,
val store: Store,
val instance: ModuleInstance, |
I can certainly add an api to invoke a function by reference, this is actually a suggestion in the wasm spec: I'm dubious about allowing any function reference to called however, wasm modules have tons of functions and the majority of which are not intended to be called from the host directly. For this to respect the sandboxing it needs to invoke exported functions only otherwise whats the point of having the concept of exports. My usual reference for things like this is wasmtime as its maintained by bytecode alliance who specify webassembly, if you try to get a reference to a non exported function it fails: let instance = linker.instantiate(&mut store, &module)?;
let func = instance.get_typed_func::<i32, i32>(&mut store, "internal_func"); // ❌ Will fail if not exported Anyway I think the suggestion to create a function that allows invoking (exported) functions by reference is a good one. I'll be doing a release on the weekend so can include it in that, you'll still need to create the teavm runtime functions using chasms host functions api and mark the function as exported to make it work however. |
It's exactly what function reference proposal was for
Sure, but wasm module won't pass all these tons of functions to host functions. Moreover, spec does not allow to take
No-no-no, I did not mean that host should take any function from the module directly. Instead, host provides a function, wasm module imports it, takes function reference with |
Clearly we're talking about different things here Chasm supports functions references, period. Any wasm program you generate with function references will work, but teavm does not generate standalone wasm binaries, it requires a collection of imports to work which is effectively a lightweight runtime. This is like generating a wasi wasm binary It sounds like you want a way from a host function to call another function by reference, rather than an extension to the embedding api you need an extension to the host functions api. This isn't a problem from a sandboxing perspective as that function is executing as part of the runtime. This again would be a nice addition to the api and would allow host functions to do more than they can currently... However, what I don't understand, is currently the wasm binaries you produce have no functions exported, so how would this process even start? Even if you get the function ref through the global, the embedding api will not let you invoke a function which is not exported that WOULD break sandboxing. So either you're going to have to export a function, I can't really see why you wouldn't want to do this anyway, its the standard way of interacting with a wasm binary |
It has globals exported. Wasm module fills these exports on initialization.
Embedding API allows to invoke a function ref as soon as embedder's function receives this ref, no matter how we actually got it. Wasm spec allows to take references to functions if they are either exported or have corresponding
Because what's being exported is not a Wasm function ref (which is opaque object from JS's standpoint), but actual JS function. This function is generated by the function
And so on. The reason is compatibility. Once a user took their Java/Kotlin/Scala/etc codebase and compiled it into JS, whey would take exactly same codebase, change backend to Wasm GC and get exactly the same result that behaves 100% same as previously did JS code. |
I'll update the host function api to allow calling functions by reference because it sounds like that will do the trick, it will also be a nice upgrade to that api |
I wonder why I am not able to even get a primitive Java function, compiled via TeaVM to Wasm,
running on Chasm, whereas the same tests work without problems for an equivalent C function compiled via Emscripten.
I have asked the same but more detailed question on the TeaVM issue tracker and got several answers from the author of TeaVM which might explain this. But I still wanted to forward this to you too in case there is also a problem with Chasm that you might want to look into.
One problem, for example, is the fact that you don't get the name of missing imports.
konsoletyper/teavm#1003
The text was updated successfully, but these errors were encountered: