javascriptc++v8webassemblyembedded-v8

WebAssembly.instantiate didn't call then nor catch in v8 embedded


I tried to ship WebAssembly feature in v8 7.2 for my Android Project. I have successfully imported v8 as a static library. But I came across an issue that WebAssembly didn't call either then nor catch callback. Here is my code below:

std::unique_ptr<v8::Platform> platform;
v8::Isolate *isolate;
v8::Persistent<v8::Context> persistentContext;
void runMain();
void runScript();
void _log(const v8::FunctionCallbackInfo<v8::Value>& info) {
  v8::String::Utf8Value utf(isolate, info[0].As<v8::String>());
  __android_log_print(ANDROID_LOG_DEBUG, "V8Native", "%s",*utf);
}

void JNICALL
Java_com_hustunique_v8demoapplication_MainActivity_initV8(JNIEnv *env, jobject /* this */) {
  // Initialize V8.
  v8::V8::InitializeICU();
  platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(&(*platform.get()));
  v8::V8::Initialize();
  runMain();
}

void runMain() {
  // Create a new Isolate and make it the current one.

  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  isolate = v8::Isolate::New(create_params);
//  isolate->Enter();
  v8::Isolate::Scope isolate_scope(isolate);
  v8::HandleScope scope(isolate);


  auto global_template = v8::ObjectTemplate::New(isolate);
  global_template->Set(v8::String::NewFromUtf8(isolate, "log"), v8::FunctionTemplate::New(isolate, _log));   // set log function here, as it is used in my sample javascript code
  // Enter the context for compiling and running the sample script.
  v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global_template);
  persistentContext.Reset(isolate, context);

  // Run the script to get the result.
  runScript();

}

void runScript() {
  // sample wasm javascript code here.
  const char *csource = R"(
    WebAssembly.instantiate(new Uint8Array([0,97,115,109,1,0,0,0,1,8,2,96,1,127,0,96,0,0,2,8,1,2,106,
      115,1,95,0,0,3,2,1,1,8,1,1,10,9,1,7,0,65,185,10,16,0,11]),
      {js:{_:console.log('Called from WebAssembly Hello world')}}).then(function(obj) {
        log('Called with instance ' + obj);
      }).catch(function(err) {
        log('Called with error ' + err);
      });
  )"; // should call my Hello World log and trigger the error or return the instance successfully

  v8::HandleScope handle_scope(isolate);
  auto ctx = persistentContext.Get(isolate);
  v8::Context::Scope context_scope(ctx);
  v8::TryCatch try_catch(isolate);
  v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, csource,
                                                         v8::NewStringType::kNormal).ToLocalChecked();

  v8::Local<v8::Script> script =
      v8::Script::Compile(ctx, source).ToLocalChecked();
  v8::Local<v8::Value> result;
  if (!script->Run(ctx).ToLocal(&result)) {
    ReportException(isolate, &try_catch); // report exception, ignore the implementation here
    return;
  }
  // Convert the result to an UTF8 string and print it.
  v8::String::Utf8Value utf8(isolate, result);
  __android_log_print(ANDROID_LOG_INFO, "V8Native", "%s\n", *utf8);

}

In the demo above, I got the output with Called from WebAssembly Hello world as excepted, but I couldn't get the error message nor the instance info.

I made a simple example on the website comparing with my demo above, here is the output in the website, which can be reproduced easily I think:

Called from WebAssembly Hello world
Called with error LinkError: WebAssembly.instantiate(): Import #0 module="js" function="_" error: function import requires a callable

It seems that in my demo, neither resolve nor reject was called from WebAssembly's returning promise. After checking the type of v8::Local<v8::Value> result in runScript method, v8 runtime confirms that it is a promise object.

I have tried several things here but none of them works:

  1. call v8::Isolate::RunMicroTasks(). Nothing happened
  2. cast result to v8::Local<v8::Promise> at the end of runScript method, then run with:
auto resolver = v8::Resolver::New(context)->toLocalChecked();
while (promise->State() == v8::PromiseState::kPending) {
    isolate->RunMicroTasks();
}
if (promise->State() == v8::PromiseState::kFullfilled) {
    resolver->Resolve(context, promise->Result());
}
if (promise->State() == v8::PromiseState::kRejected) {
    resolver->Reject(context, promise->Result());
}

This snippet didn't work either, besides, it stuck at the kPending status.

I searched for something like flush the promise queue, but didn't get any solutions. What am I missing here?


Solution

  • The WebAssembly's async compile API registers the ongoing work on one of v8::Platform's background threads, and eventually in the future, a task will be posted to the foreground thread to resolve the promise for the compilation.

    In order for this promise to be resolved you need to pump the message loop and run any pending micro-tasks:

    v8::platform::PumpMessageLoop(platform.get(), isolate);
    isolate->RunMicrotasks();
    

    It may be necessary to perform this pumping for a longer period depending on how long it takes to complete the compilation.