Skip to content
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
6 changes: 6 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ See docs/process.md for more on how version tagging works.

3.1.32 (in development)
-----------------------
- In Wasm exception mode (`-fwasm-exceptions`), when
`EXCEPTION_STACK_TRACES` is enabled, uncaught exceptions will display stack
traces. This defaults to true when `ASSERTIONS` is enabled. This option is
Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I think this should be "This is set to true when ASSERTIONS is enabled", because we print exception stack traces even when -sASSERTIONS -sEXCEPTION_STACK_TRACES=0. See #18642 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

If we ban ASSERTIONS=0 && EXCEPTION_TRACES=1 as suggested in #18642 (comment), I think the current text is OK.

mainly for the users who want only exceptions' stack traces without turning
`ASSERTIONS` on. This option currently works only for Wasm exceptions
(-fwasm-exceptions). (#18642)

3.1.31 - 01/26/23
-----------------
Expand Down
10 changes: 7 additions & 3 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2785,9 +2785,13 @@ def get_full_import_name(name):
if settings.WASM_EXCEPTIONS:
settings.REQUIRED_EXPORTS += ['__trap']

# When ASSERTIONS is set, we include stack traces in Wasm exception objects
# using the JS API, which needs this C++ tag exported.
if settings.ASSERTIONS and settings.WASM_EXCEPTIONS:
# When ASSERTIONS or EXCEPTION_STACK_TRACES is set, we include stack traces in
# Wasm exception objects using the JS API, which needs this C++ tag exported.
if settings.ASSERTIONS:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would have thought ASSERTIONS and EXCEPTION_STACK_TRACES could now be considered completely authogonal, and the -sASSERTIONS=1 -sEXCEPTION_STACK_TRACES=0 would "just work". But if that is too tricky or annoying implement when we should do this here to avoid any confusion:

if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES:
   exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSSERTIONS are enabled')

My guess is that adding this error and testing for it might be more complex than just supporting that config?

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'm not sure who would want ASSERTIONS but not exception stack traces. Because ASSERTIONS builds libc++abi in debug mode anyway, it wouldn't save any code size. I don't think it will be difficult or tricky, but I wouldn't want one more combination to care for, especially when I'm not sure who would want that... I like the exiting with error you suggested better.

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 banned the combination and added a test for that. (I tested all four combinations)

if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES:
exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled')
default_setting('EXCEPTION_STACK_TRACES', 1)
if settings.EXCEPTION_STACK_TRACES and settings.WASM_EXCEPTIONS:
settings.EXPORTED_FUNCTIONS += ['___cpp_exception']
settings.EXPORT_EXCEPTION_HANDLING_HELPERS = True

Expand Down
2 changes: 1 addition & 1 deletion src/library_exceptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ var LibraryExceptions = {
#endif
},

#if ASSERTIONS
#if EXCEPTION_STACK_TRACES
// Throw a WebAssembly.Exception object with the C++ tag with a stack trace
// embedded. WebAssembly.Exception is a JS object representing a Wasm
// exception, provided by Wasm JS API:
Expand Down
7 changes: 7 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,13 @@ var EXCEPTION_CATCHING_ALLOWED = [];
// example usage.
var EXPORT_EXCEPTION_HANDLING_HELPERS = false;

// When this is enabled, exceptions will contain stack traces and uncaught
// exceptions will display stack traces upon exiting. This defaults to true when
// ASSERTIONS is enabled. This option is for users who want exceptions' stack
// traces but do not want other overheads ASSERTIONS can incur. This currently
// works only for Wasm exceptions (-fwasm-exceptions).
var EXCEPTION_STACK_TRACES = false;

// Internal: Tracks whether Emscripten should link in exception throwing (C++
// 'throw') support library. This does not need to be set directly, but pass
// -fno-exceptions to the build disable exceptions support. (This is basically
Expand Down
2 changes: 1 addition & 1 deletion src/shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ var read_,
function logExceptionOnExit(e) {
if (e instanceof ExitStatus) return;
let toLog = e;
#if ASSERTIONS
#if EXCEPTION_STACK_TRACES
if (e && typeof e == 'object' && e.stack) {
toLog = [e, e.stack];
}
Expand Down
29 changes: 26 additions & 3 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -8062,7 +8062,12 @@ def test_wasm_nope(self):
out = self.run_js('a.out.js', assert_returncode=NON_ZERO)
self.assertContained('no native wasm support detected', out)

@requires_wasm_eh
# FIXME Node v18.13 (LTS as of Jan 2023) has not yet implemented the new
# optional 'traceStack' option in WebAssembly.Exception constructor
# (https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception)
# and embeds stack traces unconditionally. Change this back to
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm note sure its useful but we now have ways to checking the node version used in testing (looks for shared.check_node_version in test/common.py) and ways of running them (test-node-compat in `.circleci/config.yml``). Maybe not worth the effort to make that work for this case?

Copy link
Member Author

Choose a reason for hiding this comment

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

Our CI is fine without this, because we use node v14, which doesn't have Wasm EH support at all, and your logic will make sure we fall back to v8. But if we upgrade our node to a version such as v18, this will start to fail on our CI, and who will do the update can be confused by the failures, so I made this changes.

Another thing we can do is we leave @requires_wasm_eh as is and leave this comment for future reference, because our CI falls back to v8 and has no problem currently, and deal with the error when we update the node, but the person who's doing the update will still be confused.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We now also run some tests of the very latest node tip of tree (see test-node-compat).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just FYI in case you want to run some wasm EH under node too... we can now.

Copy link
Member Author

@aheejin aheejin Feb 1, 2023

Choose a reason for hiding this comment

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

I tested with several node versions:

  • Node 14.18.2 (Our default): No support of Wasm EH
  • Node 17.5: Hasn't implemented traceStack, so always embeds stack traces. Prints stack traces when thrown and uncaught.
  • Node 18.13 (LTS as of now): Same with 17.5
  • Node 19.5 (Current as of now): Has implemented traceStack. But now it doesn't show stack traces when exceptions are thrown and uncaught... Not sure why.

Given this a little confusing situation, I'm tempted to use only V8 for this test for now...

# @requires_wasm_eh if this issue is fixed later.
@requires_v8
def test_wasm_exceptions_stack_trace_and_message(self):
src = r'''
#include <stdexcept>
Expand Down Expand Up @@ -8098,12 +8103,30 @@ def test_wasm_exceptions_stack_trace_and_message(self):
'at foo',
'at main']

# We attach stack traces to exception objects only when ASSERTIONS is set
self.set_setting('ASSERTIONS')
# Stack traces are enabled when either of ASSERTIONS or
# EXCEPTION_STACK_TRACES is enabled. You can't disable
# EXCEPTION_STACK_TRACES when ASSERTIONS is enabled.

# Prints stack traces
self.set_setting('ASSERTIONS', 1)
self.set_setting('EXCEPTION_STACK_TRACES', 1)
self.do_run(src, emcc_args=emcc_args, assert_all=True,
assert_returncode=NON_ZERO, expected_output=stack_trace_checks)

# Prints stack traces
self.set_setting('ASSERTIONS', 0)
self.set_setting('EXCEPTION_STACK_TRACES', 1)
self.do_run(src, emcc_args=emcc_args, assert_all=True,
assert_returncode=NON_ZERO, expected_output=stack_trace_checks)

# Not allowed
create_file('src.cpp', src)
err = self.expect_fail([EMCC, 'src.cpp', '-sASSERTIONS=1', '-sEXCEPTION_STACK_TRACES=0'])
self.assertContained('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled', err)

# Doesn't print stack traces
self.set_setting('ASSERTIONS', 0)
self.set_setting('EXCEPTION_STACK_TRACES', 0)
err = self.do_run(src, emcc_args=emcc_args, assert_returncode=NON_ZERO)
for check in stack_trace_checks:
self.assertNotContained(check, err)
Expand Down
9 changes: 9 additions & 0 deletions tools/system_libs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,15 @@ class libcxxabi(NoExceptLibrary, MTLibrary, DebugLibrary):
]
includes = ['system/lib/libcxx/src']

def __init__(self, **kwargs):
super().__init__(**kwargs)
# TODO EXCEPTION_STACK_TRACES currently requires the debug version of
# libc++abi, causing the debug version of libc++abi to be linked, which
# increases code size. libc++abi is not a big library to begin with, but if
# this becomes a problem, consider making EXCEPTION_STACK_TRACES work with
# the non-debug version of libc++abi.
self.is_debug |= settings.EXCEPTION_STACK_TRACES
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can also revert this I think?

Copy link
Member Author

@aheejin aheejin Feb 1, 2023

Choose a reason for hiding this comment

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

I don't think so? The point of this PR is to display stack traces when ASSERTIONS is not set but EXXCEPTION_STACK_TRACES is set, and

if settings.ASSERTIONS:
  default_setting('EXCEPTION_STACK_TRACES', 1)

This doesn't make ASSERTIONS set.
Related: https://github.com/emscripten-core/emscripten/pull/18642/files#r1092668280

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, yes I agree then. Can you add a comment here maybe" "EXCEPTION_STACK_TRACES currently requires the debug version of libc++" ... BTW why is that? Why do we need the debug version here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because of this:

#elif __USING_WASM_EXCEPTIONS__
#ifdef NDEBUG
_Unwind_RaiseException(&exception_header->unwindHeader);
#else
// In debug mode, call a JS library function to use WebAssembly.Exception JS
// API, which enables us to include stack traces
__throw_exception_with_stack_trace(&exception_header->unwindHeader);
#endif

We could've come up with a separate preprocessor define keyword probably, but I thought this was simpler. Also enabling the debug version of libc++abi had its own advantages other than the stack traces, for example, it shows abort error messages, which the release version doesn't.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we instead remove __throw_exception_with_stack_trace and have _Unwind_RaiseException modify its behaviour based on EXCEPTION_STACK_TRACES in JS code?

Or is _Unwind_RaiseException maybe not a JS function here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added the comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Could we instead remove __throw_exception_with_stack_trace and have _Unwind_RaiseException modify its behaviour based on EXCEPTION_STACK_TRACES in JS code?

Or is _Unwind_RaiseException maybe not a JS function here?

_Unwind_RaiseException is a function in libunwind:

/// Called by __cxa_throw.
_LIBUNWIND_EXPORT _Unwind_Reason_Code
_Unwind_RaiseException(_Unwind_Exception *exception_object) {
_LIBUNWIND_TRACE_API("_Unwind_RaiseException(exception_object=%p)",
(void *)exception_object);
__builtin_wasm_throw(0, exception_object);
}

We can move the differing behavior from libc++abi to libunwind, but given that libunwind is meant to be a low-level library and our Unwind-wasm.c is mostly a stub to call wasm instruction builtins, I feel libc++abi is a better place for calling out to JS. Also I like having the debug version of libc++abi for other reasons, such as users can see the abort error messages now, without which the errors could be very confusing and there was no way to build libc++abi in debug mode, even when ASSERTIONS was on.

Copy link
Member

Choose a reason for hiding this comment

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

Using the debug version will affect NDEBUG. Maybe for libcxxabi that's not significant, though? But I think it's worth measuring code size just to make sure.

(If that ends up an issue, we could maybe have a separate library for the function that calls out to JS, and link it first so it overrides part of libcxxabi, but hopefully that's not necessary.)

Copy link
Member Author

Choose a reason for hiding this comment

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

We already use the debug version of libc++abi when ASSERTIONS is set. This was enabled in #17979. Having a debug version of libc++abi also has other advantages, such as, it shows abort error messages, which the release version doesn't. I don't think people would mind much about the small code increase when they set EXCEPTION_STACK_TRACES to true..? We also discussed whether enabling the debug version of libc++abi was worth it last year in our team chat and decided it was.

If we want to avoid the code size increase for libc++abi when ASSERTIONS is not set but EXCEPTION_STACK_TRACES is set, I think we need to come up with a separate preprocessor keyword for the code snippet included in https://github.com/emscripten-core/emscripten/pull/18642/files#r1093681153.

FWIW, the code sizes for wasm32-emscripten in my emscripten cache are:

aheejin@aheejin:~/emscripten/cache/sysroot/lib/wasm32-emscripten$ ls libc++abi* -al
-rw-r----- 1 aheejin primarygroup  935626 Feb  1 13:33 libc++abi.a
-rw-r----- 1 aheejin primarygroup 1295312 Feb  1 13:33 libc++abi-debug.a
-rw-r----- 1 aheejin primarygroup 1376762 Feb  1 13:34 libc++abi-debug-except.a
-rw-r----- 1 aheejin primarygroup 1310656 Feb  1 13:33 libc++abi-debug-mt.a
-rw-r----- 1 aheejin primarygroup 1396908 Feb  1 13:34 libc++abi-debug-mt-except.a
-rw-r----- 1 aheejin primarygroup 1304380 Feb  1 13:33 libc++abi-debug-mt-noexcept.a
-rw-r----- 1 aheejin primarygroup 1289086 Feb  1 13:33 libc++abi-debug-noexcept.a
-rw-r----- 1 aheejin primarygroup 1310656 Feb  1 13:33 libc++abi-debug-ww.a
-rw-r----- 1 aheejin primarygroup 1396908 Feb  1 13:34 libc++abi-debug-ww-except.a
-rw-r----- 1 aheejin primarygroup 1304380 Feb  1 13:33 libc++abi-debug-ww-noexcept.a
-rw-r----- 1 aheejin primarygroup 1016138 Feb  1 13:33 libc++abi-except.a
-rw-r----- 1 aheejin primarygroup  951196 Feb  1 13:33 libc++abi-mt.a
-rw-r----- 1 aheejin primarygroup 1036278 Feb  1 13:34 libc++abi-mt-except.a
-rw-r----- 1 aheejin primarygroup  946236 Feb  1 13:33 libc++abi-mt-noexcept.a
-rw-r----- 1 aheejin primarygroup  930950 Feb  1 13:33 libc++abi-noexcept.a
-rw-r----- 1 aheejin primarygroup  951196 Feb  1 13:33 libc++abi-ww.a
-rw-r----- 1 aheejin primarygroup 1036278 Feb  1 13:34 libc++abi-ww-except.a
-rw-r----- 1 aheejin primarygroup  946236 Feb  1 13:33 libc++abi-ww-noexcept.a

Copy link
Member

Choose a reason for hiding this comment

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

It might be fine to increase the code size here. But some of the motivation for this new option was to get stack traces without the extra overhead and size of all of ASSERTIONS. Anyhow, this is still a big improvement over ASSERTIONS so I'd be ok with it with a TODO to investigate further optimizations in system_libs.py, if we find the need for that later. (From the .a files the debug version does look larger, but that might not matter after linking or in comparison to other libraries I guess.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Added the TODO.


def get_cflags(self):
cflags = super().get_cflags()
if not self.is_mt and not self.is_ww:
Expand Down