How to execute Kotlin WebAssembly function from JavaScript?
Asked Answered
B

3

8

My goal is to write a Kotlin library, compile it to WebAssembly and call its functions from JS. Since a few hours I try to get a simple hello world to work. The documentation on this topic is either non existent or well hidden.

This is my kotlin file:

@Used
public fun hello() {
    println("Hello world!")
}

fun main(args: Array<String>) {
    println("main() function executed!")
}

When I compile it to WebAssembly, I get a hello.wasm and hello.wasm.js file.

First I tried to use something like this to execute the function:

WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
    .then(obj => obj.instance.exports.hello());

Then I understood that I need to pass the imports from my hello.wasm.js file in the importObject parameter. So I guess that I need to use the hello.wasm.js file to correctly initialize my wasm program.

When I load my wasm like the following, I don't get any errors and the main() function is executed.

<script wasm="hello.wasm" src="hello.wasm.js"></script>

But how can I execute the hello() function from JavaScript? The only kotlin wasm examples I found are not calling specific functions but rendering something from the main() function.

Also any links to relevant documentation are very much appreciated.


UPDATE: I managed to execute the function, but I don't believe this is the correct way:

<script wasm="hello.wasm" src="hello.wasm.js"></script>
<script>
WebAssembly.instantiateStreaming(fetch('hello.wasm'), konan_dependencies)
        .then(obj => obj.instance.exports['kfun:hello$$ValueType']());
</script>

The problem is, that my wasm file is fetched two times if I do it like that.

Loading only the hello.wasm.js file without the wasm attribute gets me the following error:

Uncaught Error: Could not find the wasm attribute pointing to the WebAssembly binary.
    at Object.konan.moduleEntry (stats.wasm.js:433)
    at stats.wasm.js:532
Basque answered 10/1, 2019 at 23:5 Comment(1)
I ended up writing my library in Rust, the tooling and documentation is way better and everything works as expected.Basque
T
1

I recently did some research into this myself and from my understanding, your usecase is not really supported so far. What you are looking for is essentially a library, yet if you look into the documentation of Kotlin/Native it states:

The following binary kinds are supported (note that not all the kinds are available for all native platforms):

[...]

sharedLib - a shared native library - all native targets except wasm32

staticLib - a static native library - all native targets except wasm32

From my understanding, the difficulty lies withing data passing between Javascript and WebAssembly as it only supports ints or the rather roundabout way via linear memory.

Tillie answered 18/3, 2019 at 9:32 Comment(0)
S
0

Currently I enjoy working on a Kotlin WASM project and (nice for the researcher in me) there is no experience in our team. Sorry, I still have that same question mark in my head but got it quite far.

In my case, I found many examples for wasm, but it felt like I couldn't use them, because in Kotlin things were different... Actually this is not the case and things are just hidden in build processes which in our case is both a blessing and a curse. There is no docu, but kotlin does things for us!

For me the best strategy to fill that gap of information is to have a deeper look into the generated *wasm.js file. It might look scary and it is JavaScript, but it is actually quite easy to get an idea of what Kotlin is actually capable to do for you. The best: If you had a look on the Api references (https://developer.mozilla.org/en-US/docs/WebAssembly) or you just watched a tutorial but could not apply what you have seenn, chances are best that you will find these pieces of code in here!

Who ever reads this and wants to try things out: You should prepare a build setup that allows you to append things to the generated *.wasm.js file. The important parts for this in my setup:

    
val jsinterface by tasks.creating(Exec::class) {
    val kotlincExecutable = "${project.properties["konanHome"]}/bin/kotlinc"

    inputs.property("kotlincExecutable", kotlincExecutable)
    outputs.file(jsInterfaceKlibFileName)

    val ktFile = file(workingDir).resolve("src/wasm32Main/kotlin/js_interface/imported_js_funcs.kt")
    val jsStubFile = file(workingDir).resolve("src/wasm32Main/js/stub.js")

    executable(kotlincExecutable)
    args(
        "-include-binary", jsStubFile,
        "-produce", "library",
        "-o", jsInterfaceKlibFileName,
        "-target", "wasm32",
        ktFile
    )
}

val jsinterop by tasks.creating(Exec::class) {
    dependsOn(jsinterface) //inserts customized js functions on xxx.wasm.js
    //jsinterop -pkg kotlinx.interop.wasm.dom  -o build/klib/kotlinx.interop.wasm.dom-jsinterop.klib -target wasm32
    workingDir("./")
    executable("${project.properties["konanHome"]}/bin/jsinterop")
    args("-pkg", "kotlinx.interop.wasm.dom", "-o", jsinteropKlibFileName.toString(), "-target", "wasm32")
}

// generate jsinterop before native compile
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile::class).all {
    dependsOn(jsinterop)
}

How many interest is on that topic?

Shortstop answered 3/7, 2020 at 23:29 Comment(0)
G
-1

As far as I know you will need to export the function into a variable to keep using it, or either stay in the instance of the streaming.

So I think it should be something like this:

let helloFunc;

WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
.then(({instance}) => {
helloFunc = instance.exports.hello;
});

after that you can just use your variable as a function and call it like this:

helloFunc();

Otherwise if you don't need to export the function for later use you can just use it inside of the instance like this:

WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
.then(({instance}) => {
instance.exports.hello();
});

If you don't want to use the object deconstruction like I used, you can keep using your normal syntax with obj.instance.

Gametocyte answered 11/1, 2019 at 6:11 Comment(2)
It is also notable that sometimes the function name get's modified while the wasm compiling takes place. C++ for example likes to add something like '_Z' at the beginning of the function name. To analyze if that happens with kotlin too, you could assemble your WebAssembly code to WAT (WebAssembly Text Format) to check that.Gametocyte
I cannot execute the function like this because the wasm is not correctly initialized with all the konan imports. The *.wasm.js file contains functions like initiateAndRun(..) which initializes everything correctly but only runs the main function and doesn't return the instance. Since this is a generated file I really think I am missing something.Basque

© 2022 - 2024 — McMap. All rights reserved.