Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call webassembly from embedded v8 without JS

I would like to use webassembly directly from my embedded v8 without the detour via JavaScript. I used the provided hello-world example and the WasmModuleObjectBuilderStreaming class from v8.h. However, I am stuck at how to extract the add function. Help would be appreciated.

#include <include/v8.h>

#include <include/libplatform/libplatform.h>

#include <stdlib.h>
#include <unistd.h>

using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Promise;
using v8::WasmModuleObjectBuilderStreaming;

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);

  WasmModuleObjectBuilderStreaming stream(isolate);

  // Use the v8 API to generate a WebAssembly module.
  //
  // |bytes| contains the binary format for the following module:
  //
  //     (func (export "add") (param i32 i32) (result i32)
  //       get_local 0
  //       get_local 1
  //       i32.add)
  //
  // taken from: https://github.com/v8/v8/blob/master/samples/hello-world.cc#L66
  std::vector<uint8_t> wasmbin {
          0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
          0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
          0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01,
          0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b
  };

  // write bytes and finish
  stream.OnBytesReceived(wasmbin.data(), wasmbin.size());
  stream.Finish();
  Local<Promise> promise = stream.GetPromise();

  // TODO: Get exports, extract `add` & call `add`
}

Build setup:

Follow the instruction in Run the example from the official Getting started with embedding V8. Save the code to sample/wasm.cc and execute following commands:

$ g++ -I. -O2 -Iinclude samples/wasm.cc -o wasm  -lv8_monolith -Lout.gn/x64.release.sample/obj/ -pthread -std=c++17`
$ ./wasm`

Solution:

Thanks @liliscent, I adapted my example accordingly. Because we all like, working code:

#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.
  //
  // |bytes| contains the binary format for the following module: //
  //     (func (export "add") (param i32 i32) (result i32)
  //       get_local 0
  //       get_local 1
  //       i32.add)
  //
  // taken from: https://github.com/v8/v8/blob/master/samples/hello-world.cc#L66
  std::vector<uint8_t> wasmbin {
          0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
          0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
          0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01,
          0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 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, 88)
  args_type add_args{Int32::New(isolate, 77), Int32::New(isolate, 88)};
  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 + 88 = %d\n", adder_res->Value());
  return 0;
}
like image 354
mhk Avatar asked Dec 25 '18 22:12

mhk


People also ask

Does WebAssembly require JavaScript?

WebAssembly does not replace JavaScript; in fact, some JavaScript code is required to load WebAssembly modules. WebAssembly runs in all major browsers and in all platforms. Developers can reasonably assume WebAssembly support is anywhere JavaScript is available.

Can V8 run WebAssembly?

For experimentation, V8 and Chrome can be configured to compile WebAssembly code only with Liftoff or only with TurboFan. It is even possible to experiment with lazy compilation, where functions only get compiled when they get called for the first time.

Does WebAssembly run faster than JavaScript?

In one recent study, a developer discovered Wasm is faster than JavaScript across three desktop browsers on desktop computers and smartphones. Wasm is 1.15-1.67 times faster than JavaScript on Google Chrome on a desktop. Wasm is 1.95-11.71 times faster than JavaScript on Firefox on a desktop.

Can JavaScript be compiled to WebAssembly?

Yes, it is totally possible to call JavaScript-functions from inside your running WebAssembly functions!


1 Answers

You can construct a WebAssembly module directly from C++ via v8::WasmCompiledModule class (it will be renamed to v8::WasmModuleObject in next version):

    Local<WasmCompiledModule> module = WasmCompiledModule::DeserializeOrCompile(isolate,
            WasmCompiledModule::BufferReference(0, 0),
            WasmCompiledModule::BufferReference(wasmbin.data(), wasmbin.size())
        ).ToLocalChecked();

But AFAIK, v8 doesn't expose its webassembly api directly, you have to get them from JS global context. The following code creates a module instance, and gets the exports of the instance:

    using args_type = Local<Value>[];

    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, args_type{module})
        .ToLocalChecked().As<Object>()
        ->Get(context, String::NewFromUtf8(isolate, "exports"))
        .ToLocalChecked().As<Object>()
        ;

Then you can get the add function from exports object and call it:

    Local<Int32> adder_res = module_instance_exports
        ->Get(context, String::NewFromUtf8(isolate, "add"))
        .ToLocalChecked().As<Function>()
        ->Call(context, context->Global(), 2, args_type{Int32::New(isolate, 77), Int32::New(isolate, 88)})
        .ToLocalChecked().As<Int32>();

    std::cout << "77 + 88 = " << adder_res->Value() << "\n";
like image 137
llllllllll Avatar answered Sep 30 '22 00:09

llllllllll