Implementation of -fexceptions and -fwasm-exceptions

78 views
Skip to first unread message

Stephan Bergmann

unread,
Aug 7, 2024, 3:31:48 AM8/7/24
to emscripte...@googlegroups.com
Is there, by chance, any documentation on how
-fexceptions/-fwasm-exceptions are actually implemented? What I'd like
to understand is why, apparently, a JS exception thrown across C++
frames causes automatic vars in those frames to get destroyed with
-fwasm-exceptions but not with -fexceptions:

> $ cat test.cc
> #include <iostream>
> #include <emscripten.h>
> #include <emscripten/bind.h>
> struct S { ~S() { std::cout << "destructor\n"; } };
> EM_JS(void, js4, (), { throw Error(); });
> void cpp3() { js4(); };
> void cpp2() {
> S s;
> cpp3();
> }
> EM_JS(void, js1, (), {
> try {
> Module.cpp2();
> } catch (e) {
> console.log("catch");
> }
> });
> EMSCRIPTEN_BINDINGS(test) { emscripten::function("cpp2", &cpp2); }
> int main() { js1(); }

> $ em++ -fwasm-exceptions -lembind test.cc
> $ node a.out.js
> destructor
> catch

> $ em++ -fexceptions -lembind test.cc
> $ node a.out.js
> catch

Heejin Ahn

unread,
Aug 7, 2024, 9:33:17 PM8/7/24
to emscripte...@googlegroups.com
It's a current limitation of Emscripten JS-based exception handling.

The details on why, just in case anyone is interested:
So every time we call a possibly-throwing function, we call out to a JS function (named 'invoke_SIG' where SIG is the signature string of the function) that calls the actual throwing function, and after catching the exception, sets some variables for future processing and returns to the original calling function. And the original calling function checks those variables, runs destructors if any, and does the necessary thing depending on what the exception's type and the calling code is.

The limitation of JS-based EH is, those 'invoke_SIG' functions are structured like this:
function invoke_v(index) {
  var sp = stackSave();
  try {
    getWasmTableEntry(index)();
  } catch(e) {
    stackRestore(sp);
    if (!(e instanceof EmscriptenEH)) throw e;
    _setThrew(1, 0);
  }
}

So basically, unless the exception is a C++ exception ('EmscriptenEH' in the code means that), the JS code just rethrows the exception to be caught by other functions in the call stack, preventing the function from returning to the original caller function.

I think it would be better to be documented in our exception docs; I'll work on it.

Stephan Bergmann

unread,
Aug 8, 2024, 2:14:04 AM8/8/24
to emscripte...@googlegroups.com
On 8/8/24 03:32, Heejin Ahn wrote:
> The details on why, just in case anyone is interested:

Thanks a lot for your response! That nicely explains why the JS `throw
'unwind'` from within `emscripten_set_main_loop` with
`simulate_infinite_loop = true` behaves the way it does.

Reply all
Reply to author
Forward
0 new messages