Calling wasm-compiled Functions from C++

327 views
Skip to first unread message

Steven Johnson

unread,
Feb 25, 2019, 8:12:50 PM2/25/19
to v8-users
I'm experimenting with embedding V8 in a test app in order to load, compile, and run wasm bytecode that has been generated by LLVM (using V8 7.4.x and recent trunk versions of LLVM)

Loading and running seems simple enough (via WasmModuleObject::DeserializeOrCompile), but calling the resulting functions from C++ seems tricky, because, well, it's not clear that there's a public V8 API for actually looking at what's in the resulting WasmModuleObject; in JavaScript, you can apparently do something like
        
        let module = new WebAssembly.Module(buffer)
        let imports = { ... map of funcs that are declared as 'import' by the module, if any ... }
        let instance = new WebAssembly.Instance(module, imports);
        let myfunc = instance.exports["myfunc"];
        myfunc(...);

but a similar API doesn't seem to be surfaced for C++, at least not in the public API.

I've tried replicating the JavaScript calls from C++, but there's a big problem: it appears that the 'imports' argument is required to be v8::internal::Handle<v8::internal::JSReceiver>, which (AFAICT) there isn't a clean way to create and manipulate directly from the C++ API, at least certainly not from the public API:

        Local<WasmModuleObject> module = WasmModuleObject::DeserializeOrCompile(
            isolate, {nullptr, 0}, {source, source_len}).ToLocalChecked();

        v8::internal::Handle<v8::internal::JSReceiver> imports = ???;  // Oh well
        Local<Value> args[2] = { module, imports };

        Local<Object> module_instance_exports = context->Global()
            ->Get(context, String::NewFromUtf8(isolate, "WebAssembly")).ToLocalChecked().As<Object>()
            ->Get(context, String::NewFromUtf8(isolate, "Instance")).ToLocalChecked().As<Object>()
            ->CallAsConstructor(context, 2, args).ToLocalChecked().As<Object>()
            ->Get(context, String::NewFromUtf8(isolate, "exports")).ToLocalChecked().As<Object>()
            ;

I suppose I could accomplish this by adding some extra JS-only wrapper code that does the necessary magic, but that's going to add another layer of indirection that surely shouldn't be necessary here (since the code in question here is otherwise free of JavaScript).

The thing that makes this extra-fun is that apparently LLVM injects a couple of symbols into the import table of every bit of wasm it generates ("__linear_memory" and "__indirect_function_table")... which don't appear to be referenced in any code in V8 that I can find, so their purpose is a bit of a mystery to me. (Perhaps holdovers from older implementations of wasm?) In any event, I'm almost certainly going to have other imports I need to fill in here (for glue functions), so figuring out how to to accomplish this is probably essential to my task.

So...

(1) Is there an explicit API to access/call wasm-compiled functions from C++? Is so, where may I find it? If not, um, shouldn't we have one?
(2) If there isn't a C++ API for this (and/or isn't going to be one anytime in the very near future), is the above approach the most reasonable workaround for now?
(3) If the above mimic-JS-from-C++ approach is reasonable, is there a clean way to specify the imports table? 
(4) Finally, just out of curiosity, anyone know what the story is with "__linear_memory" and "__indirect_function_table"?





J Decker

unread,
Feb 25, 2019, 8:28:56 PM2/25/19
to v8-u...@googlegroups.com
I'm not familiar with these, but do know about HEAP8, HEAP32, etc. :)  The code I get generated does not have either of those symbols; it might be to support some C++ structure you have... All the code I compiled was actually C. The basic C library support all works as expected (fopen for instance).  
 

Was far as I know, the WASM code is basically self contained and doesn't immediately provide interface with javascript.

I used WASM and compiled a GSM codec (audio).  
THis is the script I used to build it...
There's several options to export functions for JS to call.  The batch file includes
`-s EXPORTED_FUNCTIONS="['_gsm_create','_gsm_decode','_gsm_encode']"`


The readme here shows basically how to use _gsm_create, _gsm_decode and _gsm_encode.

To get JS arrays/strings into the WASM code you need to malloc in the wasm heap, and copy the data to the buffers; primitive values (numbers/strings) pass through simply enough.

And yes, there is very bad support for inter-oping the other direction; The other thing I was playing with WASMing is a JSOX(JSON variant) parser, but creating the objects on the .parse() side of things is very hairy.

But most of these issues would be Emscripten; since WASM has to interop with other JS engines, and not just V8.




--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to the Google Groups "v8-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to v8-users+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Steven Johnson

unread,
Feb 25, 2019, 8:49:36 PM2/25/19
to v8-u...@googlegroups.com
On Mon, Feb 25, 2019 at 5:28 PM J Decker <d3c...@gmail.com> wrote:
I'm not familiar with these, but do know about HEAP8, HEAP32, etc. :)  The code I get generated does not have either of those symbols; it might be to support some C++ structure you have... All the code I compiled was actually C. The basic C library support all works as expected (fopen for instance).  

The code being compiled into wasm in this case isn't C (or C++), but Halide, which interfaces directly with LLVM. Emscripten is not involved at all (and I'd prefer to keep it that way, if possible).


The readme here shows basically how to use _gsm_create, _gsm_decode and _gsm_encode.

Thanks for the suggestion, but this is specifically what I'm hoping to *avoid* (ie, writing JavaScript adaptor code just to be able to allow C++ and wasm-generated code to interoperate).

Ben Noordhuis

unread,
Feb 26, 2019, 2:45:30 PM2/26/19
to v8-users
A JSReceiver is (for the sake of this discussion) just a v8::Object.
This is the answer to your third question. :-)

As to (2), consulting context->Global() is reasonable and idiomatic.

And a propos (4), http://andrewsweeney.net/post/llvm-to-wasm/ contains
links to explanations but in a nutshell, one is the Memory object,
whereas the other is to support C semantics that don't map 1-to-1 to
WASM.

Steven Johnson

unread,
Feb 26, 2019, 2:52:25 PM2/26/19
to v8-u...@googlegroups.com
Thanks for the tips. 

I've also been informed (off-list) that I've made a really dumb mistake: the LLVM output here is really .o (rather than an executable-ready .wasm) and needs to go thru a linker, which is why I'm seeing the mysterious __linear_memory, etc. symbols. (This is a good thing for my purposes, as having a linker step will address some other issues on my plate._

Sorry for the noise.



Reply all
Reply to author
Forward
0 new messages