Call webassembly with import statement from embedded v8 ( without JS )
Following the thread Call webassembly from embedded v8 without JS I was able to call a WebAssembly code directly from c++. My problem started when I tried to run a more "complex" code ( see attached code ) that includes an import statement. When trying to run this code I get a v8 error WebAssembly.Instance(): Imports argument must be present and must be an object. I dag into the v8 code and found that this error happens when the module's import_table is empty ( v8/src/wasm/module-instantiate.cc#276 ). I think I need to provide the implementation of the imported function but I couldn't figure out how to do it.
#include <include/v8.h>
#include <include/libplatform/libplatform.h>
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Promise;
using v8::WasmModuleObjectBuilderStreaming;
using v8::WasmCompiledModule;
using v8::Context;
using v8::Local;
using v8::Value;
using v8::String;
using v8::Object;
using v8::Function;
using v8::Int32;
using args_type = Local<Value>[];
int main(int argc, char* argv[]) {
v8::V8::InitializeICUDefaultLocation(argv[0]);
v8::V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
Isolate* isolate = Isolate::New(create_params);
Isolate::Scope isolate_scope(isolate);
HandleScope scope(isolate);
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
WasmModuleObjectBuilderStreaming stream(isolate);
// Use the v8 API to generate a WebAssembly module.
// compiled from the following c code
//
// #include <stdlib.h>
// int add(int x, int y) {
// return x + rand();
// }
//
// produce the following wasm code
//
// (module
// (type $FUNCSIG$i(func(result i32)))
// (import "env" "rand" (func $rand(result i32)))
// (table 0 anyfunc)
// (memory $0 1)
// (export "memory" (memory $0))
// (export "add" (func $add))
// (func $add(; 1;) (param $0 i32) (param $1 i32) (result i32)
// (i32.add
// (call $rand)
// (get_local $0)
// )
// )
// )
//
// binary representation of the above code
std::vector<uint8_t> wasmbin{
0x00 ,0x61 ,0x73 ,0x6d ,0x01 ,0x00 ,0x00 ,0x00 ,0x01 ,0x8b ,0x80 ,0x80 ,0x80 ,0x00 ,0x02 ,0x60 ,0x00 ,0x01 ,
0x7f ,0x60 ,0x02 ,0x7f ,0x7f ,0x01 ,0x7f ,0x02 ,0x8c ,0x80 ,0x80 ,0x80 ,0x00 ,0x01 ,0x03 ,0x65 ,0x6e ,0x76 ,
0x04 ,0x72 ,0x61 ,0x6e ,0x64 ,0x00 ,0x00 ,0x03 ,0x82 ,0x80 ,0x80 ,0x80 ,0x00 ,0x01 ,0x01 ,0x04 ,0x84 ,0x80 ,
0x80 ,0x80 ,0x00 ,0x01 ,0x70 ,0x00 ,0x00 ,0x05 ,0x83 ,0x80 ,0x80 ,0x80 ,0x00 ,0x01 ,0x00 ,0x01 ,0x06 ,0x81 ,
0x80 ,0x80 ,0x80 ,0x00 ,0x00 ,0x07 ,0x90 ,0x80 ,0x80 ,0x80 ,0x00 ,0x02 ,0x06 ,0x6d ,0x65 ,0x6d ,0x6f ,0x72 ,
0x79 ,0x02 ,0x00 ,0x03 ,0x61 ,0x64 ,0x64 ,0x00 ,0x01 ,0x0a ,0x8d ,0x80 ,0x80 ,0x80 ,0x00 ,0x01 ,0x87 ,0x80 ,
0x80 ,0x80 ,0x00 ,0x00 ,0x10 ,0x00 ,0x20 ,0x00 ,0x6a ,0x0b
};
// same as calling:
// let module = new WebAssembly.Module(bytes);
Local<WasmCompiledModule> module = WasmCompiledModule::DeserializeOrCompile(isolate,
WasmCompiledModule::BufferReference(0, 0),
WasmCompiledModule::BufferReference(wasmbin.data(), wasmbin.size())
).ToLocalChecked();
// same as calling:
// let module_instance_exports = new WebAssembly.Instance(module).exports;
args_type instance_args{module};
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, 1, instance_args)
.ToLocalChecked().As<Object>()
->Get(context, String::NewFromUtf8(isolate, "exports"))
.ToLocalChecked().As<Object>()
;
// same as calling:
// module_instance_exports.add(77, 0)
args_type add_args{Int32::New(isolate, 77), Int32::New(isolate, 0)};
Local<Int32> adder_res = module_instance_exports
->Get(context, String::NewFromUtf8(isolate, "add"))
.ToLocalChecked().As<Function>()
->Call(context, context->Global(), 2, add_args)
.ToLocalChecked().As<Int32>();
printf("77 + rand() = %d\n", adder_res->Value());
return 0;
}
Any help will be welcomed.
I would recommend to use the WebAssembly C/C++ API, which V8 implements in the form of a library called "libwee8". The documentation for that is currently here. That should be the easiest way to run WebAssembly modules from C++ without involving JavaScript. As an added bonus, it makes it easy to switch the underlying implementation, if for whatever reason in the future life of your product V8 won't be the right solution for a given use-case.
That said, if you prefer to do everything by yourself using the (JavaScript-focused!) traditional V8 API, then you can look at the prototype implementation that maps the Wasm C++ API onto the (slightly modified, see other files in that directory) V8 API here: https://github.com/WebAssembly/wasm-c-api/blob/master/src/wasm-v8.cc. Be aware that due to the indirections required, this will be quite a bit slower than using libwee8
, but if you're reinventing the wheel for educational purposes, that might not matter to you ;-)
Specifically, in https://github.com/WebAssembly/wasm-c-api/blob/master/src/wasm-v8.cc#L2106 you can see how the instantiation call takes an imports
object as its second argument. As the error message you got informs you, it must be present (and it must be a JavaScript object), even when it is empty.