Skip to content

Add a setjmp/longjmp runtime for a new sjlj translation proposed in LLVM #21502

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

Merged
merged 4 commits into from
Mar 21, 2024
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,4 @@ a license to everyone to use it as detailed in LICENSE.)
* James Hu <[email protected]>
* Jerry Zhuang <[email protected]>
* Taisei Kon <[email protected]>
* YAMAMOTO Takashi <[email protected]>
53 changes: 40 additions & 13 deletions system/lib/compiler-rt/emscripten_setjmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* found in the LICENSE file.
*/

#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <setjmp.h>
Expand Down Expand Up @@ -73,33 +74,59 @@ void emscripten_longjmp(uintptr_t env, int val) {
#endif

#ifdef __USING_WASM_SJLJ__

struct __WasmLongjmpArgs {
void *env;
int val;
};

thread_local struct __WasmLongjmpArgs __wasm_longjmp_args;
Copy link
Member

Choose a reason for hiding this comment

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

That we don't need this thread_local struct is nice!


// llvm uses `1` for the __c_longjmp tag.
// See https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/CodeGen/WasmEHFuncInfo.h
#define C_LONGJMP 1
#endif

// jmp_buf should have large enough size and alignment to contain
// this structure.
struct jmp_buf_impl {
void* func_invocation_id;
uint32_t label;
#ifdef __USING_WASM_SJLJ__
struct __WasmLongjmpArgs arg;
#endif
};

void __wasm_setjmp(void* env, uint32_t label, void* func_invocation_id) {
struct jmp_buf_impl* jb = env;
assert(label != 0); // ABI contract
assert(func_invocation_id != NULL); // sanity check
jb->func_invocation_id = func_invocation_id;
jb->label = label;
}

uint32_t __wasm_setjmp_test(void* env, void* func_invocation_id) {
struct jmp_buf_impl* jb = env;
assert(jb->label != 0); // ABI contract
assert(func_invocation_id != NULL); // sanity check
if (jb->func_invocation_id == func_invocation_id) {
return jb->label;
}
return 0;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the corresponding LLVM change not also change the ABI for emscripten exceptions too? i.e. not just when USING_WASM_SJLJ? (i.e. should all this new code live outside the #if block?

Copy link
Member

@aheejin aheejin Mar 13, 2024

Choose a reason for hiding this comment

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

+1. These __wasm_setjmp and __wasm_setjmp_test can be used both for emscripten and Wasm SjLj. Actually, using different functions for the two of them will be messy and not allow us to delete the existing saveSetjmp and testSetjmp. This is currently within #ifdef __USING_WASM_SJLJ__. Only __wasm_longjmp is exclusive to Wasm SjLj.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does the corresponding LLVM change not also change the ABI for emscripten exceptions too? i.e. not just when USING_WASM_SJLJ? (i.e. should all this new code live outside the #if block?

currently, no.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1. These __wasm_setjmp and __wasm_setjmp_test can be used both for emscripten and Wasm SjLj. Actually, using different functions for the two of them will be messy and not allow us to delete the existing saveSetjmp and testSetjmp. This is currently within #ifdef __USING_WASM_SJLJ__. Only __wasm_longjmp is exclusive to Wasm SjLj.

maybe. i haven't looked at the emscrpiten sjlj closely. do you think it's enough to apply the same change mechanically?


#ifdef __USING_WASM_SJLJ__
// 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;
/*
 * C standard:
 * The longjmp function cannot cause the setjmp macro to return
 * the value 0; if val is 0, the setjmp macro returns the value 1.
 */
void __wasm_longjmp(void* env, int val) {
struct jmp_buf_impl* jb = env;
struct __WasmLongjmpArgs* arg = &jb->arg;
// C standard says:
// The longjmp function cannot cause the setjmp macro to return
// the value 0; if val is 0, the setjmp macro returns the value 1.
if (val == 0) {
val = 1;
}
Comment on lines +122 to 127
Copy link
Member

Choose a reason for hiding this comment

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

We need this for emscripten_longjmp too. Can you paste it there too?

__wasm_longjmp_args.val = val;
__builtin_wasm_throw(C_LONGJMP, &__wasm_longjmp_args);
arg->env = env;
arg->val = val;
__builtin_wasm_throw(C_LONGJMP, arg);
}

#endif