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:
Heejin Ahn 2021-11-01 16:33:21 -07:00 коммит произвёл GitHub
Родитель 71c33cff21
Коммит 9ecfa19d67
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 145 добавлений и 13 удалений

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

39
tests/test_core.py поставляемый
Просмотреть файл

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