Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EH] Use LLVM implementation for new Wasm EH #23469

Merged
merged 20 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ See docs/process.md for more on how version tagging works.

4.0.2 (in development)
----------------------
- The standard Wasm EH, enabled by `-sWASM_LEGACY_EXCEPTIONS=0`, now uses the
LLVM backend implementation rather than the previously used Binaryen
translator
(https://github.com/WebAssembly/binaryen/blob/main/src/passes/TranslateEH.cpp).
(#23469) No specific action from the user is required.
- Added support for compiling AVX2 intrinsics, 256-bit wide intrinsic is emulated
on top of 128-bit Wasm SIMD instruction set. (#23035). Pass `-msimd128 -mavx2`
to enable targeting AVX2.
Expand Down
5 changes: 5 additions & 0 deletions embuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
MINIMAL_TASKS = [
'libcompiler_rt',
'libcompiler_rt-legacysjlj',
'libcompiler_rt-wasmsjlj',
'libcompiler_rt-ww',
'libc',
'libc-debug',
Expand All @@ -40,13 +41,16 @@
'libc_optz-debug',
'libc++abi',
'libc++abi-legacyexcept',
'libc++abi-wasmexcept',
'libc++abi-noexcept',
'libc++abi-debug',
'libc++abi-debug-legacyexcept',
'libc++abi-debug-wasmexcept',
'libc++abi-debug-noexcept',
'libc++abi-debug-ww-noexcept',
'libc++',
'libc++-legacyexcept',
'libc++-wasmexcept',
'libc++-noexcept',
'libc++-ww-noexcept',
'libal',
Expand Down Expand Up @@ -80,6 +84,7 @@
'crt1_proxy_main',
'crtbegin',
'libunwind-legacyexcept',
'libunwind-wasmexcept',
'libnoexit',
'sqlite3',
'sqlite3-mt',
Expand Down
2 changes: 2 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,8 @@ https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-
If false, emit instructions for the standardized exception handling proposal:
https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md

.. note:: Applicable during both linking and compilation
dschuff marked this conversation as resolved.
Show resolved Hide resolved

Default value: true

.. _nodejs_catch_exit:
Expand Down
2 changes: 1 addition & 1 deletion src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ var EXCEPTION_STACK_TRACES = false;
// https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md
// If false, emit instructions for the standardized exception handling proposal:
// https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md
// [link]
// [compile+link]
var WASM_LEGACY_EXCEPTIONS = true;

// Emscripten throws an ExitStatus exception to unwind when exit() is called.
Expand Down
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_cxx_except_wasm.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
144652
144531
3 changes: 3 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3535,7 +3535,10 @@ def test_embind_tsgen_jspi(self):
'legacy': [1]
})
def test_embind_tsgen_exceptions(self, legacy):
if not legacy and shared.get_node_version(config.NODE_JS)[0] < 22:
self.skipTest('Node version needs to be 22 or greater to run tsgen with exnref')
self.set_setting('WASM_LEGACY_EXCEPTIONS', legacy)

# Check that when Wasm exceptions and assertions are enabled bindings still generate.
self.run_process([EMXX, test_file('other/embind_tsgen.cpp'),
'-lembind', '-fwasm-exceptions', '-sASSERTIONS',
Expand Down
10 changes: 10 additions & 0 deletions tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ def llvm_backend_args():
elif settings.SUPPORT_LONGJMP == 'wasm':
args += ['-wasm-enable-sjlj']

if settings.WASM_EXCEPTIONS:
if settings.WASM_LEGACY_EXCEPTIONS:
args += ['-wasm-use-legacy-eh']
else:
args += ['-wasm-use-legacy-eh=0']

# better (smaller, sometimes faster) codegen, see binaryen#1054
# and https://bugs.llvm.org/show_bug.cgi?id=39488
args += ['-disable-lsr']
Expand Down Expand Up @@ -277,6 +283,10 @@ def link_lld(args, target, external_symbols=None):

if settings.WASM_EXCEPTIONS:
cmd += ['-mllvm', '-wasm-enable-eh']
if settings.WASM_LEGACY_EXCEPTIONS:
cmd += ['-mllvm', '-wasm-use-legacy-eh']
else:
cmd += ['-mllvm', '-wasm-use-legacy-eh=0']
if settings.WASM_EXCEPTIONS or settings.SUPPORT_LONGJMP == 'wasm':
cmd += ['-mllvm', '-exception-model=wasm']

Expand Down
4 changes: 0 additions & 4 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,10 +431,6 @@ def check_human_readable_list(items):
extras = settings.BINARYEN_EXTRA_PASSES.split(',')
passes += [('--' + p) if p[0] != '-' else p for p in extras if p]

# Run the translator to the standardized EH instructions.
if not settings.WASM_LEGACY_EXCEPTIONS:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would be worthwhile to keep some way to activate the translator path for a while. Just in case, e.g. someone finds a bug, they can try the other pathway?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what would be a good option for enabling this... Adding one more possible value to WASM_LEGACY_EXCEPTIONS (which sounds weird because LEGACY_EXCEPTIONS option sounds like it should be on or off and not about what kind of non-leagcy exception we choose) or adding another option?

Also I don't have any idea how widely this feature has been adopted in the first place. I haven't received a single bug reports about this feature over the last year... So I guess this is at least not widely used, if ever used anywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm sure it's the case that nobody has been using it. I think Firefox is the only runtime that supports it so far, so nobody would really have a good reason to, when everything supports legacy exceptions.
As for options, I don't think it matters much. I'm imagining this would basically be an undocumented debugging option that we'd eventually get rid of once we're confident that LLVM and Binaryen's exnref support both work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's only useful for us Emscripten developers and not much for users. Having an internal option for testing for us sounds fine, but I'm a little hesitant because we already have a messy soup of options for EH/SjLj.. 🥲

Anyway can that be a followup?

Copy link
Member Author

@aheejin aheejin Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, come to think of it, I don't think it will be very useful after all... We, or more likely I, will be the only one who will run that option (for debugging, in case llvm implementation has bugs or something), and I still can do that with wasm-opt --translate-to-exnref on the legacy Wasm EH result.. It's only one command and it's gonna be needed only when debugging, so I don't feel that's too inconvenient. And I'd like to avoid adding one more option to the soup of EH/SjLj options...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's a good point that if it's a one-command translation that would be easy enough. Is the wasm/JS interface 100% compatible?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(even if not, if we decide to do it, a followup is fine)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's a good point that if it's a one-command translation that would be easy enough. Is the wasm/JS interface 100% compatible?

Yeah I don't see why not. I checked with a simple test case, replacing only the wasm with the translated binary, and it seems to work.

passes += ['--emit-exnref']

# If we are going to run metadce then that means we will be running binaryen
# tools after the main invocation, whose flags are determined here
# (specifically we will run metadce and possibly also wasm-opt for import/
Expand Down
1 change: 1 addition & 0 deletions tools/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
'INLINING_LIMIT',
'DISABLE_EXCEPTION_CATCHING',
'DISABLE_EXCEPTION_THROWING',
'WASM_LEGACY_EXCEPTIONS',
'MAIN_MODULE',
'SIDE_MODULE',
'RELOCATABLE',
Expand Down
8 changes: 5 additions & 3 deletions tools/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,13 @@ def node_reference_types_flags(nodejs):

def node_exception_flags(nodejs):
node_version = get_node_version(nodejs)
# Exception handling was enabled by default in node v17.
# Legacy exception handling was enabled by default in node v17.
if node_version and node_version < (17, 0, 0):
return ['--experimental-wasm-eh']
else:
return []
# Standard exception handling was supported behind flag in node v22.
if node_version and node_version >= (22, 0, 0) and not settings.WASM_LEGACY_EXCEPTIONS:
return ['--experimental-wasm-exnref']
return []


def node_pthread_flags(nodejs):
Expand Down
40 changes: 31 additions & 9 deletions tools/system_libs.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,10 +775,12 @@ class Exceptions(IntEnum):
- EMSCRIPTEN: Emscripten provides exception handling capability using JS
emulation. This causes code size increase and performance degradation.
- WASM_LEGACY: Wasm native exception handling support (legacy)
- WASM: Wasm native exception handling support
"""
NONE = auto()
EMSCRIPTEN = auto()
WASM_LEGACY = auto()
WASM = auto()


class ExceptionLibrary(Library):
Expand All @@ -793,7 +795,9 @@ def get_cflags(self):
elif self.eh_mode == Exceptions.EMSCRIPTEN:
cflags += ['-sDISABLE_EXCEPTION_CATCHING=0']
elif self.eh_mode == Exceptions.WASM_LEGACY:
cflags += ['-fwasm-exceptions']
cflags += ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS']
elif self.eh_mode == Exceptions.WASM:
cflags += ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS=0']

return cflags

Expand All @@ -805,19 +809,25 @@ def get_base_name(self):
name += '-noexcept'
elif self.eh_mode == Exceptions.WASM_LEGACY:
name += '-legacyexcept'
elif self.eh_mode == Exceptions.WASM:
name += '-wasmexcept'
return name

@classmethod
def variations(cls):
combos = super().variations()
return ([dict(eh_mode=Exceptions.NONE, **combo) for combo in combos] +
[dict(eh_mode=Exceptions.EMSCRIPTEN, **combo) for combo in combos] +
[dict(eh_mode=Exceptions.WASM_LEGACY, **combo) for combo in combos])
[dict(eh_mode=Exceptions.WASM_LEGACY, **combo) for combo in combos] +
[dict(eh_mode=Exceptions.WASM, **combo) for combo in combos])

@classmethod
def get_default_variation(cls, **kwargs):
if settings.WASM_EXCEPTIONS:
eh_mode = Exceptions.WASM_LEGACY
if settings.WASM_LEGACY_EXCEPTIONS:
eh_mode = Exceptions.WASM_LEGACY
else:
eh_mode = Exceptions.WASM
elif settings.DISABLE_EXCEPTION_CATCHING == 1:
eh_mode = Exceptions.NONE
else:
Expand All @@ -838,6 +848,12 @@ def get_cflags(self):
'-sDISABLE_EXCEPTION_THROWING=0']
elif self.eh_mode == Exceptions.WASM_LEGACY:
cflags += ['-sSUPPORT_LONGJMP=wasm',
'-sWASM_LEGACY_EXCEPTIONS',
'-sDISABLE_EXCEPTION_THROWING',
'-D__WASM_SJLJ__']
elif self.eh_mode == Exceptions.WASM:
cflags += ['-sSUPPORT_LONGJMP=wasm',
'-sWASM_LEGACY_EXCEPTIONS=0',
'-sDISABLE_EXCEPTION_THROWING',
'-D__WASM_SJLJ__']
return cflags
Expand All @@ -848,18 +864,24 @@ def get_base_name(self):
# suffixes. Change the default to wasm exception later.
if self.eh_mode == Exceptions.WASM_LEGACY:
name += '-legacysjlj'
elif self.eh_mode == Exceptions.WASM:
name += '-wasmsjlj'
return name

@classmethod
def variations(cls):
combos = super().variations()
return ([dict(eh_mode=Exceptions.EMSCRIPTEN, **combo) for combo in combos] +
[dict(eh_mode=Exceptions.WASM_LEGACY, **combo) for combo in combos])
[dict(eh_mode=Exceptions.WASM_LEGACY, **combo) for combo in combos] +
[dict(eh_mode=Exceptions.WASM, **combo) for combo in combos])

@classmethod
def get_default_variation(cls, **kwargs):
if settings.SUPPORT_LONGJMP == 'wasm':
eh_mode = Exceptions.WASM_LEGACY
if settings.WASM_LEGACY_EXCEPTIONS:
eh_mode = Exceptions.WASM_LEGACY
else:
eh_mode = Exceptions.WASM
else:
eh_mode = Exceptions.EMSCRIPTEN
return super().get_default_variation(eh_mode=eh_mode, **kwargs)
Expand Down Expand Up @@ -1600,7 +1622,7 @@ def get_files(self):
filenames += ['cxa_noexception.cpp']
elif self.eh_mode == Exceptions.EMSCRIPTEN:
filenames += ['cxa_exception_emscripten.cpp']
elif self.eh_mode == Exceptions.WASM_LEGACY:
elif self.eh_mode in (Exceptions.WASM_LEGACY, Exceptions.WASM):
filenames += [
'cxa_exception_storage.cpp',
'cxa_exception.cpp',
Expand Down Expand Up @@ -1654,7 +1676,7 @@ class libcxx(ExceptionLibrary, MTLibrary):

def get_cflags(self):
cflags = super().get_cflags()
if self.eh_mode == Exceptions.WASM_LEGACY:
if self.eh_mode in (Exceptions.WASM_LEGACY, Exceptions.WASM):
cflags.append('-D__WASM_EXCEPTIONS__')
return cflags

Expand All @@ -1677,7 +1699,7 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)

def can_use(self):
return super().can_use() and self.eh_mode == Exceptions.WASM_LEGACY
return super().can_use() and self.eh_mode in (Exceptions.WASM_LEGACY, Exceptions.WASM)

def get_cflags(self):
cflags = super().get_cflags()
Expand All @@ -1688,7 +1710,7 @@ def get_cflags(self):
cflags.append('-D_LIBUNWIND_HAS_NO_EXCEPTIONS')
elif self.eh_mode == Exceptions.EMSCRIPTEN:
cflags.append('-D__EMSCRIPTEN_EXCEPTIONS__')
elif self.eh_mode == Exceptions.WASM_LEGACY:
elif self.eh_mode in (Exceptions.WASM_LEGACY, Exceptions.WASM):
cflags.append('-D__WASM_EXCEPTIONS__')
return cflags

Expand Down
1 change: 1 addition & 0 deletions tools/webassembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Type(IntEnum):
V128 = 0x7b # -0x5
FUNCREF = 0x70 # -0x10
EXTERNREF = 0x6f # -0x11
EXNREF = 0x69 # -0x17
VOID = 0x40 # -0x40


Expand Down