I am trying to get some C++ functions to run on the browser using WebSssembly. I am following this tutorial. I would like to know:
emcc
in a way that avoids memory leaks?Adding exception catching functionality (DISABLE_EXCEPTION_CATCHING=0
) seems to increase the file size too much.
Any help will be greatly appreciated.
The sample C++ code is as follows:
// 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
It is built with the command:
emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.cc
It is tested with a webpage:
<!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>
How to detect (at JS side) an 'uncaught exception' coming from C++ code?
You have to catch the exceptions using try-catch
blocks (tutorial).
Example:
try {
console.log( fib(14) );
}
catch ( e ) {
console.error( e );
}
But, as of now, it is propagated as a pointer so you will see some number in the console:
5249672
If you want to get a proper error message in JS then you have to write a binding in your C++ code:
#include <emscripten/bind.h>
std::string getExceptionMessage(int eptr)
{
return reinterpret_cast<std::exception*>(eptr)->what();
}
EMSCRIPTEN_BINDINGS(getExceptionMessageBinding)
{
emscripten::function("getExceptionMessage", &getExceptionMessage);
};
This would be exposed in the JS code through the Module
object. You can use it in JS code like this:
try {
console.log( fib(14) );
}
catch ( e ) {
console.error( Module.getExceptionMessage(e) );
}
Output (exception is thrown):
input out of range
Here's the GitHub issue where this has been discussed and suggested.
I've compiled this code with exceptions enabled with C++11 and bindings like this:
~/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
Here's another similar GitHub issue discussing another approach to this.
Adding exception catching functionality (
DISABLE_EXCEPTION_CATCHING=0
) seems to increase the file size too much.
If your concern is the increased size of the output files, you can disable exception handling altogether and resort to error checking with invalid values or error codes returning by the function(s) e.g. -1 if the input is invalid.
However, here's an observation:
The file sizes of the previous build were:
110K - a.out.js
187K - a.out.wasm
The exception handling and RTTI for bindings were part of this.
I stripped the code and used inline JS using EM_ASM to throw a JS Error in the following code snippet:
#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;
}
}
Compiled with exceptions disabled:
$ ./em++ -std=c++11 -Os
-fno-exceptions
-s WASM=1
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
fib1.cc
Here's the HTML file (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>
Console Output (exception caught):
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)
And, the file sizes were:
15K - a.out.js
246 - a.out.wasm (bytes)
Throwing a JS Error still works with exceptions disabled and the generated file sizes are far less. You might want to explore this more. Maybe, create some classes inherited from Error with extended functionality. However, the exceptions that are thrown from the standard APIs such as std::vector::at()
won't work and cause termination. So, you need to be considerate about those while disabling exceptions.
How to reset/restart WebAssembly Module generated by
emcc
in a way that avoids memory leaks?
As of now, there is no such API to reset/restart a module. The module itself is automatically garbage collected when it is no longer referenced. You don't have to care about the memory leak in this case. The JS runtime is responsible for this.
But, the C++ object created by the JS code should be destroyed using Module.destroy
if it is managing resources (memory, file handles, etc.). The Garbage Collector (GC) won't call the destructor when it collects the object which would result in a memory/resource leak. Calling Module.destory
will invoke the destructor and there won't be any memory leaks. Right now, your question doesn't have such an object. So, be mindful when you do and call Module.destory
when required.
As for the allocations/deallocations in your C++ code, you yourself are responsible for the deallocation of the resources that you allocate. Here are a few points that might help you in this regard:
Avoid Undefined Behavior.
Follow the rule of three/five/zero religiously.
Use RAII-based C++ standard library facilities for automatic memory management such as std::unique_ptr
/ std::shared_ptr
along with std::make_unique
/ std::make_shared
.
Look for STL containers such as std::vector
, std::map
, etc. for storing and managing collections rather than resorting to writing your own. The standard stuff is well-tested hence fewer worries about bugs.
Always refer to the documentation of the APIs that you plan to use. Verify if an API allocates resources that you might have to deallocate a certain way after use.
Here's a thread on loading a WASM module: Loading WebAssembly modules efficiently
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With