Add Wasm SjLj support (#14976)
This adds SjLj handling support using Wasm EH instructions: https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/Exceptions.md This does not yet support mixing of EH and SjLj. These are SjLj tests that I couldn't attach `@with_both_sjlj_handling` and the reasons: - `test_dlfcn_longjmp`: Uses NodeRAWFS - `test_longjmp_throw`: Mixes EH and SjLj - `test_exceptions_longjmp1`: Mixes EH and SjLj - `test_exceptions_longjmp2`: Mixes EH and SjLj - `test_exceptions_longjmp3`: Mixes EH and SjLj - `test_longjmp`: This currently uses `@also_with_standalone_wasm`, and it looks a test can't take two decorators when both of them take arguments, which is the case for `@also_with_standalone_wasm` and `@with_both_sjlj_handling`.
This commit is contained in:
Родитель
71c33cff21
Коммит
9ecfa19d67
23
emcc.py
23
emcc.py
|
@ -1369,6 +1369,20 @@ def phase_setup(options, state, newargs, settings_map):
|
|||
if settings.DISABLE_EXCEPTION_THROWING and not settings.DISABLE_EXCEPTION_CATCHING:
|
||||
exit_with_error("DISABLE_EXCEPTION_THROWING was set (probably from -fno-exceptions) but is not compatible with enabling exception catching (DISABLE_EXCEPTION_CATCHING=0). If you don't want exceptions, set DISABLE_EXCEPTION_CATCHING to 1; if you do want exceptions, don't link with -fno-exceptions")
|
||||
|
||||
# SUPPORT_LONGJMP=1 means the default SjLj handling mechanism, currently
|
||||
# 'emscripten'
|
||||
if settings.SUPPORT_LONGJMP == 1:
|
||||
settings.SUPPORT_LONGJMP = 'emscripten'
|
||||
|
||||
# Wasm SjLj cannot be used with Emscripten EH
|
||||
if settings.SUPPORT_LONGJMP == 'wasm':
|
||||
if not settings.DISABLE_EXCEPTION_CATCHING:
|
||||
exit_with_error('SUPPORT_LONGJMP=wasm cannot be used with DISABLE_EXCEPTION_CATCHING=0')
|
||||
if not settings.DISABLE_EXCEPTION_THROWING:
|
||||
exit_with_error('SUPPORT_LONGJMP=wasm cannot be used with DISABLE_EXCEPTION_THROWING=0')
|
||||
if settings.EXCEPTION_HANDLING:
|
||||
exit_with_error('Wasm SjLj is not supported with Wasm exceptions yet')
|
||||
|
||||
return (newargs, input_files)
|
||||
|
||||
|
||||
|
@ -2361,8 +2375,13 @@ def phase_linker_setup(options, state, newargs, settings_map):
|
|||
if settings.WASMFS:
|
||||
settings.LINK_AS_CXX = True
|
||||
|
||||
if settings.RELOCATABLE and settings.EXCEPTION_HANDLING:
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__cpp_exception')
|
||||
# Export tag objects which are likely needed by the native code, but which are
|
||||
# currently not reported in the metadata of wasm-emscripten-finalize
|
||||
if settings.RELOCATABLE:
|
||||
if settings.EXCEPTION_HANDLING:
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__cpp_exception')
|
||||
if settings.SUPPORT_LONGJMP == 'wasm':
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__c_longjmp')
|
||||
|
||||
return target, wasm_target
|
||||
|
||||
|
|
|
@ -511,6 +511,8 @@ def add_standard_wasm_imports(send_items_map):
|
|||
send_items_map['__indirect_function_table'] = 'wasmTable'
|
||||
if settings.EXCEPTION_HANDLING:
|
||||
send_items_map['__cpp_exception'] = '___cpp_exception'
|
||||
if settings.SUPPORT_LONGJMP == 'wasm':
|
||||
send_items_map['__c_longjmp'] = '___c_longjmp'
|
||||
|
||||
if settings.MAYBE_WASM2JS or settings.AUTODEBUG or settings.LINKABLE:
|
||||
# legalization of i64 support code may require these in some modes
|
||||
|
|
|
@ -3721,6 +3721,10 @@ LibraryManager.library = {
|
|||
__cpp_exception: "new WebAssembly.Tag({'parameters': ['{{{ POINTER_TYPE }}}']})",
|
||||
__cpp_exception__import: true,
|
||||
#endif
|
||||
#if SUPPORT_LONGJMP == 'wasm'
|
||||
__c_longjmp: "new WebAssembly.Tag({'parameters': ['{{{ POINTER_TYPE }}}']})",
|
||||
__c_longjmp_import: true,
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ var LibraryDylink = {
|
|||
// TODO: find a way to mark these in the binary or avoid exporting them.
|
||||
return [
|
||||
'__cpp_exception',
|
||||
'__c_longjmp',
|
||||
'__wasm_apply_data_relocs',
|
||||
'__dso_handle',
|
||||
'__tls_size',
|
||||
|
|
|
@ -697,7 +697,7 @@ function makeAbortWrapper(original) {
|
|||
if (
|
||||
ABORT // rethrow exception if abort() was called in the original function call above
|
||||
|| abortWrapperDepth > 1 // rethrow exceptions not caught at the top level if exception catching is enabled; rethrow from exceptions from within callMain
|
||||
#if SUPPORT_LONGJMP
|
||||
#if SUPPORT_LONGJMP == 'emscripten'
|
||||
|| e === 'longjmp' // rethrow longjmp if enabled
|
||||
#endif
|
||||
) {
|
||||
|
|
|
@ -1762,10 +1762,16 @@ var MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION = 0;
|
|||
// [link]
|
||||
var USES_DYNAMIC_ALLOC = 1;
|
||||
|
||||
// If true, compiler supports setjmp() and longjmp(). If false, these APIs are
|
||||
// not available. If you are using C++ exceptions, but do not need
|
||||
// setjmp()+longjmp() API, then you can set this to 0 to save a little bit of
|
||||
// code size and performance when catching exceptions.
|
||||
// If set to 'emscripten' or 'wasm', compiler supports setjmp() and longjmp().
|
||||
// If set to 0, these APIs are not available. If you are using C++ exceptions,
|
||||
// but do not need setjmp()+longjmp() API, then you can set this to 0 to save a
|
||||
// little bit of code size and performance when catching exceptions.
|
||||
//
|
||||
// 'emscripten': (default) Emscripten setjmp/longjmp handling using JavaScript
|
||||
// 'wasm': setjmp/longjmp handling using Wasm EH instructions (experimental)
|
||||
// 0: No setjmp/longjmp handling
|
||||
// 1: Default setjmp/longjmp/handling. Currently 'emscripten'.
|
||||
//
|
||||
// [compile+link] - at compile time this enables the transformations needed for
|
||||
// longjmp support at codegen time, while at link it allows linking in the
|
||||
// library support.
|
||||
|
|
|
@ -66,3 +66,24 @@ void emscripten_longjmp(uintptr_t env, int val) {
|
|||
setThrew(env, val);
|
||||
_emscripten_throw_longjmp();
|
||||
}
|
||||
|
||||
#ifdef __USING_WASM_SJLJ__
|
||||
|
||||
struct __WasmLongjmpArgs {
|
||||
void *env;
|
||||
int val;
|
||||
};
|
||||
|
||||
// FIXME Make this thread local?
|
||||
struct __WasmLongjmpArgs __wasm_longjmp_args;
|
||||
|
||||
// Wasm EH allows us to throw and catch multiple values, but that requires
|
||||
// multivalue support in the toolchain, whch is not reliable at the time.
|
||||
// TODO Consider switching to throwing two values at the same time later.
|
||||
void __wasm_longjmp(void *env, int val) {
|
||||
__wasm_longjmp_args.env = env;
|
||||
__wasm_longjmp_args.val = val;
|
||||
__builtin_wasm_throw(1, &__wasm_longjmp_args);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -121,6 +121,31 @@ def with_both_exception_handling(f):
|
|||
return metafunc
|
||||
|
||||
|
||||
# Tests setjmp/longjmp handling in Emscripten SjLJ mode, and if possible, new
|
||||
# Wasm SjLj mode
|
||||
def with_both_sjlj_handling(f):
|
||||
assert callable(f)
|
||||
|
||||
def metafunc(self, native_sjlj):
|
||||
if native_sjlj:
|
||||
# Wasm SjLj is currently supported only in Wasm backend and V8
|
||||
if not self.is_wasm():
|
||||
self.skipTest('wasm2js does not support Wasm SjLj')
|
||||
self.require_v8()
|
||||
self.set_setting('SUPPORT_LONGJMP', 'wasm')
|
||||
# These are for Emscripten EH/SjLj
|
||||
self.set_setting('DISABLE_EXCEPTION_THROWING')
|
||||
self.v8_args.append('--experimental-wasm-eh')
|
||||
f(self)
|
||||
else:
|
||||
self.set_setting('SUPPORT_LONGJMP', 'emscripten')
|
||||
f(self)
|
||||
|
||||
metafunc._parameterize = {'': (False,),
|
||||
'wasm_sjlj': (True,)}
|
||||
return metafunc
|
||||
|
||||
|
||||
def no_wasm2js(note=''):
|
||||
assert not callable(note)
|
||||
|
||||
|
@ -964,31 +989,39 @@ base align: 0, 0, 0, 0'''])
|
|||
def test_longjmp(self):
|
||||
self.do_core_test('test_longjmp.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp2(self):
|
||||
self.do_core_test('test_longjmp2.c')
|
||||
|
||||
@needs_dylink
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp2_main_module(self):
|
||||
# Test for binaryen regression:
|
||||
# https://github.com/WebAssembly/binaryen/issues/2180
|
||||
self.set_setting('MAIN_MODULE')
|
||||
self.do_core_test('test_longjmp2.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp3(self):
|
||||
self.do_core_test('test_longjmp3.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp4(self):
|
||||
self.do_core_test('test_longjmp4.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp_funcptr(self):
|
||||
self.do_core_test('test_longjmp_funcptr.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp_repeat(self):
|
||||
self.do_core_test('test_longjmp_repeat.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp_stacked(self):
|
||||
self.do_core_test('test_longjmp_stacked.c', assert_returncode=NON_ZERO)
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp_exc(self):
|
||||
self.do_core_test('test_longjmp_exc.c', assert_returncode=NON_ZERO)
|
||||
|
||||
|
@ -998,16 +1031,20 @@ base align: 0, 0, 0, 0'''])
|
|||
self.set_setting('DISABLE_EXCEPTION_CATCHING', disable_throw)
|
||||
self.do_core_test('test_longjmp_throw.cpp')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp_unwind(self):
|
||||
self.do_core_test('test_longjmp_unwind.c', assert_returncode=NON_ZERO)
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_longjmp_i64(self):
|
||||
self.emcc_args += ['-g']
|
||||
self.do_core_test('test_longjmp_i64.c', assert_returncode=NON_ZERO)
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_siglongjmp(self):
|
||||
self.do_core_test('test_siglongjmp.c')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_setjmp_many(self):
|
||||
src = r'''
|
||||
#include <stdio.h>
|
||||
|
@ -1024,6 +1061,7 @@ base align: 0, 0, 0, 0'''])
|
|||
print('NUM=%d' % num)
|
||||
self.do_run(src.replace('NUM', str(num)), '0\n' * num)
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_setjmp_many_2(self):
|
||||
src = r'''
|
||||
#include <setjmp.h>
|
||||
|
@ -1052,6 +1090,7 @@ int main()
|
|||
|
||||
self.do_run(src, r'''d is at 24''')
|
||||
|
||||
@with_both_sjlj_handling
|
||||
def test_setjmp_noleak(self):
|
||||
self.do_runf(test_file('core/test_setjmp_noleak.c'), 'ok.')
|
||||
|
||||
|
|
|
@ -245,9 +245,12 @@ def llvm_backend_args():
|
|||
allowed = ','.join(settings.EXCEPTION_CATCHING_ALLOWED)
|
||||
args += ['-emscripten-cxx-exceptions-allowed=' + allowed]
|
||||
|
||||
if settings.SUPPORT_LONGJMP:
|
||||
# asm.js-style setjmp/longjmp handling
|
||||
# asm.js-style setjmp/longjmp handling
|
||||
if settings.SUPPORT_LONGJMP == 'emscripten':
|
||||
args += ['-enable-emscripten-sjlj']
|
||||
# setjmp/longjmp handling using Wasm EH
|
||||
elif settings.SUPPORT_LONGJMP == 'wasm':
|
||||
args += ['-wasm-enable-sjlj']
|
||||
|
||||
# better (smaller, sometimes faster) codegen, see binaryen#1054
|
||||
# and https://bugs.llvm.org/show_bug.cgi?id=39488
|
||||
|
@ -376,10 +379,10 @@ def link_lld(args, target, external_symbols=None):
|
|||
for a in llvm_backend_args():
|
||||
cmd += ['-mllvm', a]
|
||||
|
||||
# Wasm exception handling. This is a CodeGen option for the LLVM backend, so
|
||||
# wasm-ld needs to take this for the LTO mode.
|
||||
if settings.EXCEPTION_HANDLING:
|
||||
cmd += ['-mllvm', '-exception-model=wasm', '-mllvm', '-wasm-enable-eh']
|
||||
cmd += ['-mllvm', '-wasm-enable-eh']
|
||||
if settings.EXCEPTION_HANDLING or settings.SUPPORT_LONGJMP == 'wasm':
|
||||
cmd += ['-mllvm', '-exception-model=wasm']
|
||||
|
||||
# For relocatable output (generating an object file) we don't pass any of the
|
||||
# normal linker flags that are used when building and exectuable
|
||||
|
|
|
@ -615,6 +615,42 @@ class NoExceptLibrary(Library):
|
|||
return super().get_default_variation(eh_mode=eh_mode, **kwargs)
|
||||
|
||||
|
||||
class SjLjLibrary(Library):
|
||||
def __init__(self, **kwargs):
|
||||
# Whether we use Wasm EH instructions for SjLj support
|
||||
self.is_wasm = kwargs.pop('is_wasm')
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def get_cflags(self):
|
||||
cflags = super().get_cflags()
|
||||
if self.is_wasm:
|
||||
# DISABLE_EXCEPTION_THROWING=0 is the default, which is for Emscripten
|
||||
# EH/SjLj, so we should reverse it.
|
||||
cflags += ['-s', 'SUPPORT_LONGJMP=wasm',
|
||||
'-s', 'DISABLE_EXCEPTION_THROWING=1',
|
||||
'-D__USING_WASM_SJLJ__']
|
||||
else:
|
||||
cflags += ['-s', 'SUPPORT_LONGJMP=emscripten']
|
||||
return cflags
|
||||
|
||||
def get_base_name(self):
|
||||
name = super().get_base_name()
|
||||
# TODO Currently emscripten-based SjLj is the default mode, thus no
|
||||
# suffixes. Change the default to wasm exception later.
|
||||
if self.is_wasm:
|
||||
name += '-wasm-sjlj'
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
def vary_on(cls):
|
||||
return super().vary_on() + ['is_wasm']
|
||||
|
||||
@classmethod
|
||||
def get_default_variation(cls, **kwargs):
|
||||
is_wasm = settings.SUPPORT_LONGJMP == 'wasm'
|
||||
return super().get_default_variation(is_wasm=is_wasm, **kwargs)
|
||||
|
||||
|
||||
class MuslInternalLibrary(Library):
|
||||
includes = ['system/lib/libc/musl/src/internal']
|
||||
|
||||
|
@ -650,7 +686,8 @@ class AsanInstrumentedLibrary(Library):
|
|||
return super().get_default_variation(is_asan=settings.USE_ASAN, **kwargs)
|
||||
|
||||
|
||||
class libcompiler_rt(MTLibrary):
|
||||
# Subclass of SjLjLibrary because emscripten_setjmp.c uses SjLj support
|
||||
class libcompiler_rt(MTLibrary, SjLjLibrary):
|
||||
name = 'libcompiler_rt'
|
||||
# compiler_rt files can't currently be part of LTO although we are hoping to remove this
|
||||
# restriction soon: https://reviews.llvm.org/D71738
|
||||
|
|
Загрузка…
Ссылка в новой задаче