From 97b7bccbf8f9535e896668a07e691a707ccaa4be Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 6 May 2022 00:34:18 -0700 Subject: [PATCH] [EH] Make abort() work with EH We used to implement `abort()` with throwing a JS exception. #9730 changed it to `RuntimeError`, because it is the class a trap becomes once it hits a JS frame. But this turned out to be not working, because Wasm EH currently does not assume all `RuntimeError`s are from traps; it decides whether a `RuntimeError` is from a trap or not based on a hidden field within the object. Relevant past discussion: https://github.com/WebAssembly/exception-handling/issues/89#issuecomment-559533953 So at the moment we don't have a way of throwing a trap from JS, which is inconvenient. I think we eventually want to make JS API for this, but for the moment, this PR resorts to a wasm function that traps. This at least makes calling `std::terminate` work. So far calling it exhausted the call stack and aborted. Fixes #16407. --- emcc.py | 3 +++ src/preamble.js | 11 ++++++++++- system/lib/compiler-rt/exception_builtins.S | 6 ++++++ tests/test_core.py | 14 ++++++++++++++ tools/system_libs.py | 3 ++- 5 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 system/lib/compiler-rt/exception_builtins.S diff --git a/emcc.py b/emcc.py index 37a8a25ce588c..87a8c3879e91f 100755 --- a/emcc.py +++ b/emcc.py @@ -2566,6 +2566,9 @@ def get_full_import_name(name): if settings.SUPPORT_LONGJMP == 'wasm': settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__c_longjmp') + if settings.EXCEPTION_HANDLING: + settings.REQUIRED_EXPORTS += ['__trap'] + return target, wasm_target diff --git a/src/preamble.js b/src/preamble.js index 2090af133dbb7..2b223b014739c 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -605,12 +605,20 @@ function abort(what) { // Use a wasm runtime error, because a JS error might be seen as a foreign // exception, which means we'd run destructors on it. We need the error to // simply make the program stop. + // FIXME This is not working now, because Wasm EH currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a trap from JS. TODO Make a JS API that + // allows this. // Suppress closure compiler warning here. Closure compiler's builtin extern // defintion for WebAssembly.RuntimeError claims it takes no arguments even // though it can. // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. - +#if EXCEPTION_HANDLING == 1 + // In the meantime, we resort to wasm code for trapping. + ___trap(); +#else /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what); @@ -621,6 +629,7 @@ function abort(what) { // in code paths apart from instantiation where an exception is expected // to be thrown when abort is called. throw e; +#endif } // {{MEM_INITIALIZER}} diff --git a/system/lib/compiler-rt/exception_builtins.S b/system/lib/compiler-rt/exception_builtins.S new file mode 100644 index 0000000000000..58fb769a4da06 --- /dev/null +++ b/system/lib/compiler-rt/exception_builtins.S @@ -0,0 +1,6 @@ +.globl __trap + +__trap: + .functype __trap() -> () + unreachable + end_function diff --git a/tests/test_core.py b/tests/test_core.py index 2a03fac6f32d7..5f06ab47787c5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1639,6 +1639,20 @@ class Polymorphic {virtual void member(){}}; } ''', 'exception caught: std::bad_typeid') + @with_both_eh_sjlj + def test_terminate_abort(self): + # std::terminate eventually calls abort(). We used ti implement abort() with + # throwing a JS exception, but this can be again caught by std::terminate's + # cleanup and cause an infinite loop. When Wasm EH is enabled, abort() is + # implemented by a trap now. + err = self.do_run(r''' +#include +int main() { + std::terminate(); +} +''', assert_returncode=NON_ZERO) + self.assertNotContained('Maximum call stack size exceeded', err) + def test_iostream_ctors(self): # iostream stuff must be globally constructed before user global # constructors, so iostream works in global constructors diff --git a/tools/system_libs.py b/tools/system_libs.py index 0eadc8ad41430..ecb1350521db8 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -708,7 +708,8 @@ class libcompiler_rt(MTLibrary, SjLjLibrary): 'stack_ops.S', 'stack_limits.S', 'emscripten_setjmp.c', - 'emscripten_exception_builtins.c' + 'emscripten_exception_builtins.c', + 'exception_builtins.S' ])