如何检测故障并重置/重启 webassembly 模块?

Axe*_*hra 5 javascript c++ webassembly

我正在尝试使用 WebSssembly 在浏览器上运行一些 C++ 函数。我正在关注本教程。我想知道:

  1. 如何检测(在 JS 端)来自 C++ 代码的“未捕获异常”?
  2. 如何以emcc一种避免内存泄漏的方式重置/重新启动由生成的 WebAssembly 模块?

添加异常捕获功能 ( DISABLE_EXCEPTION_CATCHING=0) 似乎会过多地增加文件大小。

任何帮助将不胜感激。


示例 C++ 代码如下:

// C++ source code (fib.cc)

#include <stdexcept>
#include <emscripten.h>

extern "C" {

EMSCRIPTEN_KEEPALIVE
int fib(int n) {
  if (n > 12) {
    throw std::out_of_range("input out of range");
  }
  int i, t, a = 0, b = 1;
  for (i = 0; i < n; i++) {
    t = a + b;
    a = b;
    b = t;
  }
  return b;
}

// >>
// other functions with allocations/deallocations

} // end of extern C
Run Code Online (Sandbox Code Playgroud)

它是使用以下命令构建的:

emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.cc
Run Code Online (Sandbox Code Playgroud)

它是用网页测试的:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WASM Test Page</title>
</head>
<body>

<script src="a.out.js"></script>
<script>
"use strict";

Module.onRuntimeInitialized = _ => {
  const fib = Module.cwrap('fib', 'number', ['number']);
  console.log(fib(10));
  console.log(fib(14)); // causes exception
};

</script>

</body>
</html>
Run Code Online (Sandbox Code Playgroud)

Aze*_*eem 5

如何(在 JS 端)检测来自 C++ 代码的“未捕获的异常”?

您必须使用try-catch块捕获异常(教程)。

例子:

try {
    console.log( fib(14) );
}
catch ( e ) {
    console.error( e );
}
Run Code Online (Sandbox Code Playgroud)

但是,到目前为止,它作为指针传播,因此您将在控制台中看到一些数字:

5249672
Run Code Online (Sandbox Code Playgroud)

如果您想在 JS 中获得正确的错误消息,那么您必须在 C++ 代码中编写绑定:

#include <emscripten/bind.h>

std::string getExceptionMessage(int eptr)
{
    return reinterpret_cast<std::exception*>(eptr)->what();
}

EMSCRIPTEN_BINDINGS(getExceptionMessageBinding)
{
    emscripten::function("getExceptionMessage", &getExceptionMessage);
};
Run Code Online (Sandbox Code Playgroud)

这将通过对象在 JS 代码中公开Module。您可以在 JS 代码中使用它,如下所示:

try {
    console.log( fib(14) );
}
catch ( e ) {
    console.error( Module.getExceptionMessage(e) );
}
Run Code Online (Sandbox Code Playgroud)

输出(抛出异常):

input out of range
Run Code Online (Sandbox Code Playgroud)

这是 GitHub问题,对此进行了讨论和建议

我编译了这段代码,并使用 C++11 启用了异常并进行了如下绑定:

~/emsdk/upstream/emscripten$ ./em++ -std=c++11 -Os -fexceptions --bind 
                             -s WASM=1
                             -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
                             -s DISABLE_EXCEPTION_CATCHING=0
                             fib.cc
Run Code Online (Sandbox Code Playgroud)

这是另一个类似的 GitHub问题,讨论了另一种方法。


添加异常捕获功能(DISABLE_EXCEPTION_CATCHING=0)似乎会导致文件大小增加太多。

如果您担心输出文件的大小增加,则可以完全禁用异常处理,并利用函数返回的无效值或错误代码进行错误检查,例如,如果输入无效,则返回 -1。

然而,这里有一个观察结果:

先前构建的文件大小为:

110K - a.out.js
187K - a.out.wasm
Run Code Online (Sandbox Code Playgroud)

绑定的异常处理和 RTTI 是其中的一部分。

我剥离了代码并使用内联 JS 使用EM_ASM在以下代码片段中抛出 JS 错误:

#include <emscripten.h>

extern "C" {

EMSCRIPTEN_KEEPALIVE
int fib(int n) {
    if (n > 12) {
        EM_ASM(
            throw Error("out_of_range");    // JS error with EM_ASM
        );
    }

    int a {0}, b {1};
    for ( int i {0}; i < n; ++i ) {
        const auto t = a + b;
        a = b;
        b = t;
    }
    return b;
}

}
Run Code Online (Sandbox Code Playgroud)

编译时禁用异常:

$ ./em++ -std=c++11 -Os
  -fno-exceptions 
  -s WASM=1
  -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
  fib1.cc
Run Code Online (Sandbox Code Playgroud)

这是 HTML 文件 ( fib1.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WASM Test Page</title>
</head>
<body>

<script src="a.out.js"></script>
<script>
"use strict";

Module.onRuntimeInitialized = _ => {
    const fib = Module.cwrap('fib', 'number', ['number']);

    try {
        console.log(fib(10));
        console.log(fib(14));
    }
    catch ( e ) {
        console.error(e);
     }
};

</script>

</body>
</html>
Run Code Online (Sandbox Code Playgroud)

控制台输出(捕获异常):

89
fib1.html:21 Error: out_of_range
    at Array.ASM_CONSTS (a.out.js:1)
    at _emscripten_asm_const_i (a.out.js:1)
    at wasm-function[1]:0x6b
    at Module._fib (a.out.js:1)
    at Object.Module.onRuntimeInitialized (fib1.html:18)
    at doRun (a.out.js:1)
    at run (a.out.js:1)
    at runCaller (a.out.js:1)
    at removeRunDependency (a.out.js:1)
    at receiveInstance (a.out.js:1)
Run Code Online (Sandbox Code Playgroud)

并且,文件大小为:

15K - a.out.js
246 - a.out.wasm (bytes)
Run Code Online (Sandbox Code Playgroud)

在禁用异常的情况下抛出 JS 错误仍然有效,并且生成的文件大小要小得多。您可能想进一步探索这一点。也许,创建一些继承自 Error 并具有扩展功能的类。但是,从标准 API 抛出的异常(例如)std::vector::at()将不起作用并导致终止。因此,您在禁用异常时需要考虑这些因素。


如何重置/重新启动 WebAssembly 模块生成以emcc避免内存泄漏?

截至目前,还没有这样的 API 来重置/重启模块。当模块本身不再被引用时,它会自动被垃圾回收。在这种情况下你不必关心内存泄漏。JS 运行时负责此操作。

但是,如果 JS 代码创建的 C++ 对象Module.destroy正在管理资源(内存、文件句柄等),则应该使用它来销毁它。垃圾收集器 (GC) 在收集对象时不会调用析构函数,这会导致内存/资源泄漏。调用Module.destory会调用析构函数,不会有任何内存泄漏。现在,你的问题没有这样的对象。所以,打电话时要小心Module.destory在需要时致电。

至于 C++ 代码中的分配/释放,您自己负责释放所分配的资源。在这方面,以下几点可能会对您有所帮助:


这是有关加载 WASM 模块的线程:高效加载 WebAssembly 模块