LiquidPlayer / LiquidCore

Node.js virtual machine for Android and iOS
MIT License
1.01k stars 127 forks source link

Guide on how to work with WASM or WASI? #138

Closed bangonkali closed 4 years ago

bangonkali commented 4 years ago

This is not a bug but rather a request for guidance on how to get started with running WASM/WASI on V8 embedded in LiquidCore. I think the V8 version supports WASM. Any leads will do. I, of course, will put my investigation notes here also.

A short overview of the use case. There is a library of scripts that anyone can contribute to. We want to optimize these scripts by requiring them to be written in RUST/C++ -> WASM. They can be loaded in runtime from the cloud to be executed in the Phone at any time.

ericwlange commented 4 years ago

Hi @bangonkali

You should be able to use WASM on Android. I just verified that it works on latest version of LiquidCore (HEAD). I am not sure about the last release (0.6.2). I haven't checked, but you can try it.

See https://developer.mozilla.org/en-US/docs/WebAssembly

The WebAssembly API object exists and should be accessible (below untested). I have no practical experience with WASM, so you will have to experiment.

JSObject webAssembly = context.property("WebAssembly").toObject();

You can then get its methods and call them, e.g.:

byte sourceCode[] = ... // wherever your compiled code came from
JSFunction compile = webAssembly.property("compile").toFunction();
JSUint8Array byteArray = new JSUint8Array(context, sourceCode.length);
System.arraycopy(sourceCode, 0, byteArray, 0, byteArray.length);
JSArrayBuffer buffer = byteArray.buffer();
JSObject promise = compile.call(compile, buffer).toObject();
// etc ...
bangonkali commented 4 years ago

I will post my report here after I am able to test with the latest stable release.

bangonkali commented 4 years ago

Since I'm very new with the Microservice - (ie, had to ask for some advice previously). Please bear with me with this approach.

Sample Scenario

main.c

#define WASM_EXPORT __attribute__((visibility("default")))

WASM_EXPORT
int main() {
  return 42;
}

index.js

setInterval(function() {}, 1000)

LiquidCore.on( 'req_init', function(wasmBase64) {
    try {

        var byteArray = Buffer.from(wasmBase64, 'base64');

        WebAssembly.instantiate(byteArray).then(results => {
            instance = results.instance;
            var outcome = instance.exports.main();
            LiquidCore.emit( 'res_init', { message: `Response: ${outcome}` } );

        }).catch((err) => {
            LiquidCore.emit( 'log', { message: `Error: ${err}` } );

        });
    } catch (ex) {

        LiquidCore.emit( 'log', { message: `Error: ${ex}` } );
    }
})

LiquidCore.on( 'req_ping', function() {
    LiquidCore.emit( 'res_pong', { message: `End of lifecycle!` } );
    process.exit(0);
})

LiquidCore.emit( 'res_ready' )

Currently, I did not have the stack to compile C programs to WASM, so I utilized this site and downloaded the compiled code.

image

Based on the doc from here, I wrote the following Test Code for the Kotlin Android side:

        val res = URI("android.resource://package.name/raw/index")
        val wasmStream: InputStream = resources.openRawResource(R.raw.main)
        val wasmBytes: ByteArray = wasmStream.readBytes()
        val wasmBase64 = Base64.encodeToString(
            wasmBytes,
            Base64.DEFAULT or Base64.NO_WRAP
        )

        Log.d(TAG, "Wasm Loaded (Base 64): $wasmBase64")

        val logListener =
            EventListener { service, event, payload ->
                Log.d(TAG, "Event:$event ${payload.getString("message")}")
            }

        val readyListener =
            EventListener { service, event, payload ->
                Log.d(TAG, "Event:$event")
                service.emit("req_init", wasmBase64)
            }

        val initListener =
            EventListener { service, event, payload ->
                Log.d(TAG, "Event:$event ${payload.getString("message")}")
                service.emit("req_ping")
            }

        val pongListener =
            EventListener { service, event, payload ->
                Log.d(TAG, "Event:$event ${payload.getString("message")}")
            }

        val startListener =
            ServiceStartListener { service ->
                Log.d(TAG, "Microservice started.")
                service.addEventListener("log", logListener)
                service.addEventListener("res_ready", readyListener)
                service.addEventListener("res_init", initListener)
                service.addEventListener("res_pong", pongListener)
            }

        service = MicroService(
            activity,
            res,
            startListener
        )

        service.start()

Following were attached as res/raw: image

Thankfully, I had the following results:

2019-11-25 23:54:05.889 18604-18604/se.netzon.blocks D/DashboardFragment: Wasm Loaded (Base 64): AGFzbQEAAAABCAJgAABgAAF/AwQDAAEABAUBcAEBAQUDAQACBg8CfwFBgIgEC38AQYCIBAsHKAQGbWVtb3J5AgAGX3N0YXJ0AAAEbWFpbgABC19faGVhcF9iYXNlAwEKDAMCAAsEAEEqCwIACwALB2xpbmtpbmcDAQAAKQRuYW1lASIDAAZfc3RhcnQBBG1haW4CEV9fd2FzbV9jYWxsX2N0b3Jz
2019-11-25 23:54:06.217 18604-18685/se.netzon.blocks D/DashboardFragment: Microservice started.
2019-11-25 23:54:06.245 18604-18685/se.netzon.blocks D/DashboardFragment: Event:res_ready
2019-11-25 23:54:06.253 18604-18685/se.netzon.blocks D/DashboardFragment: Event:res_init Response: 42
2019-11-25 23:54:06.254 18604-18685/se.netzon.blocks D/DashboardFragment: Event:res_pong End of lifecycle!

With emphasis on:

Event:res_init Response: 42

Conclusion: WASM works on 'com.github.LiquidPlayer:LiquidCore:0.6.2'

Side note: I have been reading about WASI which is somewhat similar but focusing on the runtime only. Maybe in the future we can let go of Node altogether and just use WASI. But for now, this will do for my experiment.

Do you have any advice on how to get the context of a Microservice so that I can do like you did in your example above?

JSObject webAssembly = context.property("WebAssembly").toObject();

I will close this for now as I am able to achieve my primary objective with your guidance. Any advice will help very much though. Thanks!

ericwlange commented 4 years ago

Glad that it works! I am really interested in using WASM with LiquidCore, but I haven't had the opportunity to play with it. The bad news is that it won't work on iOS, at least not yet. JavaScriptCore does not seem to support it.

The simplest way to get the JSContext is to get it from the payload object in one of your listeners with payload.getContext().

It's not the cleanest solution. I hid the context intentionally for historical reasons, but those reasons are probably not that useful anymore. Ideally you would get the context from the Process object with service.getProcess().getContext(). But it would have to be added to the API.

bangonkali commented 4 years ago

The bad news is that it won't work on iOS, at least not yet.

Hopefully, they will support WASM soon. I believe Webkit already supports WASM.

The simplest way to get the JSContext is to get it from the payload object in one of your listeners with payload.getContext().

I will do this for now while I'll wait for service.getProcess().getContext(). Thanks!

aehlke commented 1 year ago

it's now supported in javascriptcore as of feb 2020