Как обнаружить сбой и сбросить / перезапустить модуль веб-сборки? - PullRequest
2 голосов
/ 03 апреля 2020

Я пытаюсь заставить некоторые функции C ++ запускаться в браузере с помощью WebSssembly. Я следую этому учебнику . Я хотел бы знать:

  1. Как обнаружить (на JS стороне) «неперехваченное исключение» из кода C ++?
  2. Как сбросить / перезапустить модуль WebAssembly, сгенерированный emcc таким образом, чтобы избежать утечек памяти?

Добавление функции перехвата исключений (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

Он построен с помощью команды:

emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.cc

Он протестирован с веб-страницей:

<!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>

1 Ответ

1 голос
/ 11 апреля 2020

Как обнаружить (со стороны JS) «необнаруженное исключение», исходящее из кода C ++?

Вы должны перехватывать исключения, используя блоки try-catch () tutorial ).

Пример:

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

Но на данный момент он распространяется как указатель, поэтому в консоли вы увидите некоторое число:

5249672

Если вы хотите получить правильное сообщение об ошибке в 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);
};

Это будет показано в Код JS через объект Module. Вы можете использовать его в JS коде так:

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

Вывод (исключение выдается):

input out of range

Вот проблема 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

Вот еще одна похожая проблема GitHub , в которой обсуждается другой подход.


Добавление функции перехвата исключений (DISABLE_EXCEPTION_CATCHING=0), похоже, слишком сильно увеличивает размер файла.

Если вас беспокоит увеличение размера выходных файлов, вы можете полностью отключить обработку исключений и прибегнуть к проверке ошибок с недопустимыми значениями или кодами ошибок, возвращаемыми функцией (ями), например, -1, если ввод Неправильно.

Однако, вот замечание:

Размеры файлов предыдущей сборки были:

110K - a.out.js
187K - a.out.wasm

Обработка исключений и RTTI для привязок были частью этого.

Я снял код и использовал inline 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;
}

}

Скомпилировано с отключенными исключениями:

$ ./em++ -std=c++11 -Os
  -fno-exceptions 
  -s WASM=1
  -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
  fib1.cc

Вот 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>

Вывод на консоль (исключение обнаружено):

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)

И размеры файлов были:

15K - a.out.js
246 - a.out.wasm (bytes)

Бросок JS Ошибка по-прежнему работает с отключенными исключениями, а размеры сгенерированных файлов намного меньше. Возможно, вы захотите изучить это больше. Возможно, создайте несколько классов, унаследованных от Error с расширенной функциональностью. Однако исключения, которые вызываются из стандартных API, таких как std::vector::at(), не будут работать и вызывать прекращение. Таким образом, вы должны учитывать это при отключении исключений.


Как выполнить сброс / перезапуск модуля WebAssembly, сгенерированного emcc, таким образом, чтобы избежать утечек памяти?

На данный момент нет такого API для сброса / перезапуска модуля. Сам модуль автоматически собирает мусор, когда на него больше нет ссылок. Вам не нужно заботиться об утечке памяти в этом случае. За это отвечает среда выполнения JS.

Но объект C ++, созданный кодом JS, должен быть уничтожен с использованием Module.destroy, если он управляет ресурсами (памятью, файлом ручки, et c.). Сборщик мусора (G C) не будет вызывать деструктор, когда собирает объект, что может привести к утечке памяти / ресурсов. Вызов Module.destory вызовет деструктор и утечек памяти не будет. Прямо сейчас у вашего вопроса нет такого объекта. Поэтому будьте внимательны, когда вы это делаете, и при необходимости вызывайте Module.destory.

Что касается выделения / освобождения в вашем коде C ++, вы сами несете ответственность за освобождение ресурсов, которые вы выделить. Вот несколько моментов, которые могут вам в этом помочь:

  • Избегать Неопределенное поведение .

  • Следуйте правило трех / пяти / нуля религиозно.

  • Использование стандартных библиотек C ++ на основе RAII для автоматизированного c управления памятью, такого как std::unique_ptr / std::shared_ptr вместе с std::make_unique / std::make_shared.

  • Найдите контейнеры STL, такие как std::vector, std::map и др. c. для хранения и управления коллекциями, а не прибегать к написанию своих собственных. Стандартные компоненты хорошо протестированы, поэтому меньше проблем с ошибками.

  • Всегда обращайтесь к документации API, которые вы планируете использовать. Убедитесь, что API выделяет ресурсы, которые вам, возможно, придется освободить определенным образом после использования.


Ниже приведена информация о загрузке модуля WASM: Эффективная загрузка модулей WebAssembly

...