Skip to content

Conversation

@yamt
Copy link
Contributor

@yamt yamt commented Jan 31, 2024

Basically copy-and-paste from:
https://github.com/yamt/garbage/tree/master/wasm/longjmp

While it seems working well, it's disabled by default because:

  • It might be controversial if it's a good idea to use this emscripten API for WASI as well.

  • LLVM produces bytecode for an old version of the EH proposal.

  • The EH proposal is not widely implemeted in runtimes yet. Maybe it isn't a problem for libc.a. But for libc.so, it would be a disaster for runtimes w/o EH.

Tested with binaryen --translate-eh-old-to-new and toywasm:

  • build wasi-libc with BUILD_SJLJ=yes
  • build your app with "-mllvm -wasm-enable-sjlj"
  • apply "wasm-opt --translate-eh-old-to-new"
  • run with "toywasm --wasi"

Besides that, for libc.so, your embedder needs to provide "env:__c_longjmp".

@yamt
Copy link
Contributor Author

yamt commented Jan 31, 2024

the ci failure looks unrelated. i guess it's adapter/wasmtime mismatch.

Caused by:
    0: import `wasi:cli/[email protected]` has the wrong type
    1: instance export `get-arguments` has the wrong type
    2: expected func found nothing
make: *** [Makefile:185: /home/runner/work/wasi-libc/wasi-libc/test/build/functional/argv.wasm.err] Error 1
Error: Process completed with exit code 2.

@yamt
Copy link
Contributor Author

yamt commented Jan 31, 2024

the ci failure looks unrelated. i guess it's adapter/wasmtime mismatch.

Caused by:
    0: import `wasi:cli/[email protected]` has the wrong type
    1: instance export `get-arguments` has the wrong type
    2: expected func found nothing
make: *** [Makefile:185: /home/runner/work/wasi-libc/wasi-libc/test/build/functional/argv.wasm.err] Error 1
Error: Process completed with exit code 2.

#468

@whitequark
Copy link
Contributor

This seems quite burdensome to use--I'm not sure how much value it really has to have it upstream in this form.

yamt added a commit to yamt/toywasm that referenced this pull request Jan 31, 2024
@sbc100
Copy link
Member

sbc100 commented Jan 31, 2024

I'm a little confused because it seems like we are talking using Wasm EH here which is great.

However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

@aheejin
Copy link
Member

aheejin commented Jan 31, 2024

@sbc100

I'm a little confused because it seems like we are talking using Wasm EH here which is great.

However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

Despite the pass name WebAssemblyLowerEmscriptenEHSjLj, that pass is also responsible for some of Wasm SjLj transformation. The pass was created only for Emscripten EH/SjLj and Wasm SjLj support was added years later to that pass, because it shares a lot of code with Emscripten SjLj support. Between Emscripten SjLj & Wasm SjLj, while the underlying mechanism of longjmping is different (JS exception vs. Wasm throw), they share the same bookeeping functions for the jumping table (saveSetjmp and testSetjmp).

https://github.com/llvm/llvm-project/blob/cf2533e75ec4360da460bb577e0a4e64f2d8997f/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L187-L266

@aheejin
Copy link
Member

aheejin commented Jan 31, 2024

  • apply "wasm-opt --translate-eh-old-to-new"

As you may have noticed, the new Wasm EH proposal that was recently changed (Oct 2023) is still not yet fully supported in the tools. LLVM currently emits the old format. But all major browsers will support the old format for the foreseeable future. V8 supports the old format by default, and the new format under the option --experimental-wasm-exnref. I heard SpiderMonkey is also beta-testing the new format.

The toolchain now has a translator in Binaryen (and you are using that here). While this translator seems working it is still very new and has not been tested and used much yet, so it can still have some bugs. And actually the name of the translator option changed yesterday to --translate-to-new-eh 😅 WebAssembly/binaryen#6259

@sbc100
Copy link
Member

sbc100 commented Jan 31, 2024

@sbc100

I'm a little confused because it seems like we are talking using Wasm EH here which is great.
However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

Despite the pass name WebAssemblyLowerEmscriptenEHSjLj, that pass is also responsible for some of Wasm SjLj transformation. The pass was created only for Emscripten EH/SjLj and Wasm SjLj support was added years later to that pass, because it shares a lot of code with Emscripten SjLj support. Between Emscripten SjLj & Wasm SjLj, while the underlying mechanism of longjmping is different (JS exception vs. Wasm throw), they share the same bookeeping functions for the jumping table (saveSetjmp and testSetjmp).

https://github.com/llvm/llvm-project/blob/cf2533e75ec4360da460bb577e0a4e64f2d8997f/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L187-L266

Ah I see. I wonder we can rename that file or move some code to avoid that confusion?

@aheejin
Copy link
Member

aheejin commented Jan 31, 2024

@sbc100

I'm a little confused because it seems like we are talking using Wasm EH here which is great.
However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

Despite the pass name WebAssemblyLowerEmscriptenEHSjLj, that pass is also responsible for some of Wasm SjLj transformation. The pass was created only for Emscripten EH/SjLj and Wasm SjLj support was added years later to that pass, because it shares a lot of code with Emscripten SjLj support. Between Emscripten SjLj & Wasm SjLj, while the underlying mechanism of longjmping is different (JS exception vs. Wasm throw), they share the same bookeeping functions for the jumping table (saveSetjmp and testSetjmp).
https://github.com/llvm/llvm-project/blob/cf2533e75ec4360da460bb577e0a4e64f2d8997f/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L187-L266

Ah I see. I wonder we can rename that file or move some code to avoid that confusion?

Do you have any suggestions? So basically what it does is

  • Emscripten EH (O)
  • Wasm EH (X)
  • Emscripten SjLj (O)
  • Wasm SjLj (O)

@yamt
Copy link
Contributor Author

yamt commented Feb 1, 2024

This seems quite burdensome to use--I'm not sure how much value it really has to have it upstream in this form.

having a working baseline would help further development at least.

@yamt
Copy link
Contributor Author

yamt commented Feb 1, 2024

I'm a little confused because it seems like we are talking using Wasm EH here which is great.

However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

in the commit message ("It might be controversial if it's a good idea to use this emscripten API for WASI as well") i meant emscripten-looking apis like saveSetjmp getTempRet0 __wasm_longjmp.
also, i feel the direct use of malloc might be controversial.

@sbc100
Copy link
Member

sbc100 commented Feb 1, 2024

I'm a little confused because it seems like we are talking using Wasm EH here which is great.
However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

in the commit message ("It might be controversial if it's a good idea to use this emscripten API for WASI as well") i meant emscripten-looking apis like saveSetjmp getTempRet0 __wasm_longjmp. also, i feel the direct use of malloc might be controversial.

If its true (as I understand that it is) that those APIs are not emscripten-specific, but are part of upstream llvm support for Wasm EH, perhaps they should be renamed to match the pattern for other llvm builtins? @aheejin WDYT?

yamt added a commit to yamt/toywasm that referenced this pull request Feb 1, 2024
@aheejin
Copy link
Member

aheejin commented Feb 2, 2024

I'm a little confused because it seems like we are talking using Wasm EH here which is great.
However, if we are using Wasm EH here, why the reference to Emscripten's EH (the thing that emscripten used/uses prior to Wasm EH being available)? I though that llvm has separate support for Emscripten EH and Wasm EH, but maybe I'm misunderstanding?

in the commit message ("It might be controversial if it's a good idea to use this emscripten API for WASI as well") i meant emscripten-looking apis like saveSetjmp getTempRet0 __wasm_longjmp. also, i feel the direct use of malloc might be controversial.

If its true (as I understand that it is) that those APIs are not emscripten-specific, but are part of upstream llvm support for Wasm EH, perhaps they should be renamed to match the pattern for other llvm builtins? @aheejin WDYT?

If we want to upstream them, I agree we would need better names. I haven't upstreamed anything on emscripten's custom compiler-rt, but do you think we should? Apparently Wasm EH (and Emscripten EH too)'s SjLj support is not usable without those functions. Looking at LLVM's compiler-rt directory structure, I'm not sure where that would fit though.. https://github.com/llvm/llvm-project/tree/main/compiler-rt/lib

@whitequark
Copy link
Contributor

@aheejin I would definitely put it under builtins/wasm/. There's plenty of similar stuff there already.

Basically copy-and-paste from:
https://github.com/yamt/garbage/tree/master/wasm/longjmp

While it seems working well, it's disabled by default because:

* It might be controversial if it's a good idea to use this emscripten
  API for WASI as well.

* LLVM produces bytecode for an old version of the EH proposal.

* The EH proposal is not widely implemeted in runtimes yet.
  Maybe it isn't a problem for libc.a. But for libc.so, it would be
  a disaster for runtimes w/o EH.

Tested with `binaryen --translate-eh-old-to-new` and toywasm:

* build wasi-libc with BUILD_SJLJ=yes
* build your app with "-mllvm -wasm-enable-sjlj"
* apply "wasm-opt --translate-eh-old-to-new"
* run with "toywasm --wasi"

Besides that, for libc.so, your embedder needs to provide
"env:__c_longjmp".
@yamt
Copy link
Contributor Author

yamt commented Feb 2, 2024

rebased after #468

@sunfishcode
Copy link
Member

The overall idea here looks good. C's setjmp is entirely dynamic rather than being tied to any lexical structure, so we need to do essentially the same thing Emscripten is doing.

Emscripten's particular ABI here unfortunately uses names with no prefixes. getTempRet0 in particular seems plausible to collide with user code. I know it's much less convenient if we need to make a change in LLVM and then wait for a release here, but especially now that we're in the territory of dynamic libraries, ABIs like this may be hard to fix later.

also, i feel the direct use of malloc might be controversial.

I don't think it's a show stopper, but I also don't fully understand how the ABI works and why it's necessary to have a thread-local table rather than storing all needed info in the jmp_buf. Could you briefly explain how the table works?

@yamt
Copy link
Contributor Author

yamt commented Feb 7, 2024

micropython's exception, which is using setjmp/longjmp, seems working with this.

in https://github.com/micropython/micropython/tree/master/ports/embed

CC="/opt/wasi-sdk-21.0/bin/clang --sysroot=${HOME}/git/wasi-libc/sysroot" CFLAGS="-mllvm -wasm-enable-sjlj -DMICROPY_GCREGS_SETJMP" make
hello world! [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]eol
iter 00000000
iter 00000001
iter 00000002
iter 00000003
iter 00000004
iter 00000005
iter 00000006
iter 00000007
iter 00000008
iter 00000009
caught exception ZeroDivisionError('divide by zero',)
run GC collect
finish
Assertion failed: ATB_GET_KIND(area, block) == AT_HEAD (micropython_embed/py/gc.c: gc_free: 924)
Error: [trap] unreachable executed (4): unreachable at 028287
2024-02-07 17:16:21 (1707293781.040613000): [10d6f1600] instance_execute_func failed with -1
2024-02-07 17:16:21 (1707293781.041091000): [10d6f1600] invoke failed with -1

(i haven't investigated the assertion failure because i don't expect the GC work. we don't have a way to scan some of GC roots like locals.)

@yamt
Copy link
Contributor Author

yamt commented Feb 7, 2024

The overall idea here looks good. C's setjmp is entirely dynamic rather than being tied to any lexical structure, so we need to do essentially the same thing Emscripten is doing.

Emscripten's particular ABI here unfortunately uses names with no prefixes. getTempRet0 in particular seems plausible to collide with user code. I know it's much less convenient if we need to make a change in LLVM and then wait for a release here, but especially now that we're in the territory of dynamic libraries, ABIs like this may be hard to fix later.

also, i feel the direct use of malloc might be controversial.

I don't think it's a show stopper, but I also don't fully understand how the ABI works and why it's necessary to have a thread-local table rather than storing all needed info in the jmp_buf. Could you briefly explain how the table works?

my understanding is that the table is used to track which invocation of the setjmp-calling functions jmp_buf belong to. if the last setjmp call on the buffer is not by the current invocation of the function, it rethrows the exception.

it might be possible to do it without the tables. eg. save the frame pointer in jmp_buf.

@sunfishcode
Copy link
Member

it might be possible to do it without the tables. eg. save the frame pointer in jmp_buf.

If that works, would it want a different ABI, or a different contract between compiled code and libc?

@aheejin
Copy link
Member

aheejin commented Feb 7, 2024

I think the table is necessary. Correct me if I'm mistaken.

I'm not aware of how exactly setjmp and longjmp are implemented in the native platforms, but I can imagine they would store the PC where we should resume the execution and other machine registers.

But we don't have access to PCs in Wasm and everything has to be taken care of in user space. So basically what we do in https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp is, for every function that has setjmp, maintain a table, or a map, from "setjmp ID" to "label (= resuming destination)". Here the setjmp ID is a number we assign to each setjmp, and the label (destination) is also a number we assign to each callsite of setjmp (to figure out where we should return to after longjmping for this setjmp). saveSetjmp saves an entry to this map, and testSetjmp takes a setjmp ID to figure out which BB should we jump to after longjmping. This is explained in more detail here: https://github.com/llvm/llvm-project/blob/ff8c865838b46d0202963b816fbed50aaf96a7f4/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L130-L185
(The mechanism is the same for Wasm SjLj. What's different is Wasm SjLj use Wasm throw instruction for longjmping, whereas Emscripten SjLj throws a JS exception.)

So we need to keep this map that maps setjmp IDs to destinations somewhere, and I'm not sure individual jmp_bufs can contain this info (jmp_bufs can contain the pointer to this map but the map has to live somewhere else).

That's why the current library code does mallocing and reallocing. Not sure if we need a thread local struct for this as in this PR.

https://github.com/emscripten-core/emscripten/blob/main/system/lib/compiler-rt/emscripten_setjmp.c is the library functions Emscripten is currently using, which I think is very similar to what this PR has here.

@whitequark
Copy link
Contributor

whitequark commented Feb 8, 2024

@aheejin Each setjmp allocates exactly one entry in the table, which is live exactly until the function containing setjmp ends, right? Could the lowering pass perform an alloca (which, if I understand it right, will be allocated in the linear memory in this case) which would be a tuple of (id, label, next) and pass it in, so that the runtime can maintain a linked list of these tuples on stack?

This would require the setjmp-ing function to deallocate the memory on (normal or exceptional) return, but in exchange, no realloc is needed and no trap could happen, which in my view is a significant advantage, especially as one of the major uses of setjmp is error handling (such as out of memory errors).

@aheejin
Copy link
Member

aheejin commented Feb 8, 2024

@whitequark Not sure if I understand. How can I allocate a linked list on a stack using alloca? Do you mean I preserve some portion of linear memory for that? Then how can I reallocate when the table grows out of it?

@yamt
Copy link
Contributor Author

yamt commented Feb 9, 2024

it might be possible to do it without the tables. eg. save the frame pointer in jmp_buf.

If that works, would it want a different ABI, or a different contract between compiled code and libc?

it would be a different ABI for sure, because the current ABI makes the user function allocate the table by calling malloc directly.

@yamt
Copy link
Contributor Author

yamt commented Feb 9, 2024

it might be possible to do it without the tables. eg. save the frame pointer in jmp_buf.

If that works, would it want a different ABI, or a different contract between compiled code and libc?

it would be a different ABI for sure, because the current ABI makes the user function allocate the table by calling malloc directly.

i experimented a bit with the approach and wrote a POC implementation.
llvm change: https://github.com/yamt/llvm-project/commits/wasm-sjlj-alt/
runtime: https://github.com/yamt/garbage/blob/wasm-sjlj-alt/wasm/longjmp/rt.c
although it still takes "table" pointer, it merely uses it as an identifier of function invocation and never dereference it. a frame pointer (or alloca with minimum size) should work as well.

@whitequark
Copy link
Contributor

How can I allocate a linked list on a stack using alloca?

@aheejin What I meant is that each entry of the linked list would be its own alloca; there is a global pointer to the head, and each alloca points to the previous one. However, it seems that @yamt has a better understanding of the problem domain here than I do, and their solution would make me happy if it was merged, so I think I'll yield to them.

@yamt
Copy link
Contributor Author

yamt commented Feb 14, 2024

a few questions:

  • is the approach w/o malloc (poc: Experimental setjmp/longjmp support #467 (comment)) good enough to pursuit further? if so, i guess it needs an llvm option to enable. (probably similarly to the current -mllvm -wasm-enable-sjlj) does anyone here have a suggestion of the option to enable the new approach? i feel the relevant clang/llvm options are already very confusing and i'm a bit hesitate to add another.
  • why doesn't llvm use setjmp/longjmp builtins for wasm? WebAssemblyLowerEmscriptenEHSjLj.cpp seems to find setjmp/longjmp calls by looking at function names. i suppose it can be simpler (and probably safer) if they are builtins. historical reasons?

@sunfishcode
Copy link
Member

@yamt

a few questions:

* is the approach w/o malloc (poc: [Experimental setjmp/longjmp support #467 (comment)](https://github.com/WebAssembly/wasi-libc/pull/467#issuecomment-1935431141)) good enough to pursuit further? if so, i guess it needs an llvm option to enable. (probably similarly to the current `-mllvm -wasm-enable-sjlj`) does anyone here have a suggestion of the option to enable the new approach? i feel the relevant clang/llvm options are already very confusing and i'm a bit hesitate to add another.

Would you mind writing up a short document describing how this approach works? That'd help us all understand it, and, since this will be part of the C ABI and relevant to compilers for other languages doing C FFI, it'd be good to have some documentation for it.

* why doesn't llvm use setjmp/longjmp builtins for wasm? WebAssemblyLowerEmscriptenEHSjLj.cpp seems to find setjmp/longjmp calls by looking at function names. i suppose it can be simpler (and probably safer) if they are builtins. historical reasons?

C defines setjmp to be a function, but it's not possible to define setjmp as a function that calls __builtin_setjmp because the wrapper would have its own call frame. So LLVM has to recognize "setjmp" calls.

@yamt
Copy link
Contributor Author

yamt commented Feb 15, 2024

@yamt

a few questions:

* is the approach w/o malloc (poc: [Experimental setjmp/longjmp support #467 (comment)](https://github.com/WebAssembly/wasi-libc/pull/467#issuecomment-1935431141)) good enough to pursuit further? if so, i guess it needs an llvm option to enable. (probably similarly to the current `-mllvm -wasm-enable-sjlj`) does anyone here have a suggestion of the option to enable the new approach? i feel the relevant clang/llvm options are already very confusing and i'm a bit hesitate to add another.

Would you mind writing up a short document describing how this approach works? That'd help us all understand it, and, since this will be part of the C ABI and relevant to compilers for other languages doing C FFI, it'd be good to have some documentation for it.

https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit?usp=sharing

* why doesn't llvm use setjmp/longjmp builtins for wasm? WebAssemblyLowerEmscriptenEHSjLj.cpp seems to find setjmp/longjmp calls by looking at function names. i suppose it can be simpler (and probably safer) if they are builtins. historical reasons?

C defines setjmp to be a function, but it's not possible to define setjmp as a function that calls __builtin_setjmp because the wrapper would have its own call frame. So LLVM has to recognize "setjmp" calls.

i don't understand.
surely LLVM has to recognize "setjmp". my point is that for LLVM it's simpler to recognize __builtin_setjmp than non-builtin "setjmp".

@sunfishcode
Copy link
Member

@yamt

a few questions:

* is the approach w/o malloc (poc: [Experimental setjmp/longjmp support #467 (comment)](https://github.com/WebAssembly/wasi-libc/pull/467#issuecomment-1935431141)) good enough to pursuit further? if so, i guess it needs an llvm option to enable. (probably similarly to the current `-mllvm -wasm-enable-sjlj`) does anyone here have a suggestion of the option to enable the new approach? i feel the relevant clang/llvm options are already very confusing and i'm a bit hesitate to add another.

Would you mind writing up a short document describing how this approach works? That'd help us all understand it, and, since this will be part of the C ABI and relevant to compilers for other languages doing C FFI, it'd be good to have some documentation for it.

https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit?usp=sharing

Thanks! If I understand correctly, the magic here is that there's a "setjmp id" which is an alloca address so it dynamically identifies a call frame, and it's stored in both a local variable and the jmp_buf, so the local variable gets restored by the throw, and it can be compared against the value tored in the jmp_buf. Is that an accurate description?

* why doesn't llvm use setjmp/longjmp builtins for wasm? WebAssemblyLowerEmscriptenEHSjLj.cpp seems to find setjmp/longjmp calls by looking at function names. i suppose it can be simpler (and probably safer) if they are builtins. historical reasons?

C defines setjmp to be a function, but it's not possible to define setjmp as a function that calls __builtin_setjmp because the wrapper would have its own call frame. So LLVM has to recognize "setjmp" calls.

i don't understand. surely LLVM has to recognize "setjmp". my point is that for LLVM it's simpler to recognize __builtin_setjmp than non-builtin "setjmp".

I'm not clear on what you're asking here. LLVM has to recognize at least "setjmp" because that's what users typically write. It seems like it probably could be made to recognize __builtin_setmp too, though that's less commonly used.

@yamt
Copy link
Contributor Author

yamt commented Feb 16, 2024

@yamt

a few questions:

* is the approach w/o malloc (poc: [Experimental setjmp/longjmp support #467 (comment)](https://github.com/WebAssembly/wasi-libc/pull/467#issuecomment-1935431141)) good enough to pursuit further? if so, i guess it needs an llvm option to enable. (probably similarly to the current `-mllvm -wasm-enable-sjlj`) does anyone here have a suggestion of the option to enable the new approach? i feel the relevant clang/llvm options are already very confusing and i'm a bit hesitate to add another.

Would you mind writing up a short document describing how this approach works? That'd help us all understand it, and, since this will be part of the C ABI and relevant to compilers for other languages doing C FFI, it'd be good to have some documentation for it.

https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit?usp=sharing

Thanks! If I understand correctly, the magic here is that there's a "setjmp id" which is an alloca address so it dynamically identifies a call frame, and it's stored in both a local variable and the jmp_buf, so the local variable gets restored by the throw, and it can be compared against the value tored in the jmp_buf. Is that an accurate description?

yes.

* why doesn't llvm use setjmp/longjmp builtins for wasm? WebAssemblyLowerEmscriptenEHSjLj.cpp seems to find setjmp/longjmp calls by looking at function names. i suppose it can be simpler (and probably safer) if they are builtins. historical reasons?

C defines setjmp to be a function, but it's not possible to define setjmp as a function that calls __builtin_setjmp because the wrapper would have its own call frame. So LLVM has to recognize "setjmp" calls.

i don't understand. surely LLVM has to recognize "setjmp". my point is that for LLVM it's simpler to recognize __builtin_setjmp than non-builtin "setjmp".

I'm not clear on what you're asking here. LLVM has to recognize at least "setjmp" because that's what users typically write. It seems like it probably could be made to recognize __builtin_setmp too, though that's less commonly used.

i suppose libc can have "#define setjmp __builtin_setjmp".

@yamt
Copy link
Contributor Author

yamt commented Mar 6, 2024

it might be possible to do it without the tables. eg. save the frame pointer in jmp_buf.

If that works, would it want a different ABI, or a different contract between compiled code and libc?

it would be a different ABI for sure, because the current ABI makes the user function allocate the table by calling malloc directly.

i experimented a bit with the approach and wrote a POC implementation. llvm change: https://github.com/yamt/llvm-project/commits/wasm-sjlj-alt/ runtime: https://github.com/yamt/garbage/blob/wasm-sjlj-alt/wasm/longjmp/rt.c although it still takes "table" pointer, it merely uses it as an identifier of function invocation and never dereference it. a frame pointer (or alloca with minimum size) should work as well.

i wrote a bit more complete implementation:
llvm/llvm-project#84137

@whitequark
Copy link
Contributor

Thank you @yamt! It's been a while since I had my LLVM reviewer hat on, but the changes look good to me.

@yamt
Copy link
Contributor Author

yamt commented Mar 7, 2024

The EH proposal is not widely implemeted in runtimes yet. Maybe it isn't a problem for libc.a. But for libc.so, it would be a disaster for runtimes w/o EH.

i'm thinking about providing a separate library (say, libsetjmp.so/a) to workaround this.
how do you think?

@sunfishcode
Copy link
Member

The EH proposal is not widely implemeted in runtimes yet. Maybe it isn't a problem for libc.a. But for libc.so, it would be a disaster for runtimes w/o EH.

i'm thinking about providing a separate library (say, libsetjmp.so/a) to workaround this. how do you think?

Wasi-libc doesn't currently have any setjmp/longjmp support, so code using them doesn't work at all, no matter the engine. A setjmp/longjmp that only runs in engines with EH support would not be a regression for anyone, so I don't think a separate library is necessary.

That said, EH is indeed only phase-3 and not supported in major WASI runtimes yet, so it'd be good to put something like this in setjmp.h:

#ifndef __wasm_exception_handling__
#error Setjmp/longjmp support requires Exception handling support, which is [not yet standardized](https://github.com/WebAssembly/proposals?tab=readme-ov-file#phase-3---implementation-phase-cg--wg). To enable it, compile with `-mexception-handling` and use an engine that implements the Exception handling proposal.
#endif

@yamt
Copy link
Contributor Author

yamt commented Mar 7, 2024

The EH proposal is not widely implemeted in runtimes yet. Maybe it isn't a problem for libc.a. But for libc.so, it would be a disaster for runtimes w/o EH.

i'm thinking about providing a separate library (say, libsetjmp.so/a) to workaround this. how do you think?

Wasi-libc doesn't currently have any setjmp/longjmp support, so code using them doesn't work at all, no matter the engine. A setjmp/longjmp that only runs in engines with EH support would not be a regression for anyone, so I don't think a separate library is necessary.

if we put it in libc.so, runtimes w/o EH support won't be able to load it at all. it's a regression.

@sunfishcode
Copy link
Member

Ah, good point. Making it a separate library makes sense then.

@yamt yamt marked this pull request as draft March 15, 2024 03:53
yamt added a commit to yamt/wasi-libc that referenced this pull request Mar 15, 2024
Add setjmp/longjump support based on Wasm EH proposal.

It's provided as a separate library (libsetjmp) from libc so that
runtimes w/o EH support can still load libc.so.

To use this setjmp/longjmp implementation, an application should
be compiled with `-mllvm -wasm-enable-sjlj` and linked with `-lsetjmp`.
(You need an LLVM with the change mentioned below.)

Also, you need a runtime with EH support to run such an application.

If you want to use the latest EH instructions, you can use
`binaryen --translate-eh-old-to-new` on your application.

Note: You don't need to translate libsetjmp.a/so to the new EH.
While LLVM currently produces bytecode for an old version of the EH
proposal, luckily for us, the bytecode used in this library (ie. the tag
definition and the "throw" instruction) is compatible with the latest
version of the proposal.

The runtime logic is basically copy-and-paste from:
    https://github.com/yamt/garbage/tree/wasm-sjlj-alt2/wasm/longjmp

The corresponding LLVM change:
    llvm/llvm-project#84137
    (Note: you need this change to build setjmp/longjmp using code.
    otoh, you don't need this to build libsetjmp.)

A similar change for emscripten:
    emscripten-core/emscripten#21502

An older version of this PR, which doesn't require LLVM changes:
    WebAssembly#467

Discussion:
    https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit

An example to use the latest EH instructions:
```
clang -mllvm -wasm-enable-sjlj -o your_app.wasm your_app.c -lsetjmp
wasm-opt --translate-eh-old-to-new -o your_app.wasm your_app.wasm
toywasm --wasi your_app.wasm
```
Note: use toywasm built with `-DTOYWASM_ENABLE_WASM_EXCEPTION_HANDLING=ON`.

An example to use the older EH instructions, which LLVM currently produces:
```
clang -mllvm -wasm-enable-sjlj -o your_app.wasm your_app.c -lsetjmp
iwasm your_app.wasm
```
Note: use wasm-micro-runtime built with `-DWASM_ENABLE_EXCE_HANDLING=1`.
Note: as of writing this, only the classic interpreter supports EH.
@yamt yamt mentioned this pull request Mar 15, 2024
yamt added a commit to yamt/wasi-libc that referenced this pull request Mar 15, 2024
Add setjmp/longjump support based on Wasm EH proposal.

It's provided as a separate library (libsetjmp) from libc so that
runtimes w/o EH support can still load libc.so.

To use this setjmp/longjmp implementation, an application should
be compiled with `-mllvm -wasm-enable-sjlj` and linked with `-lsetjmp`.
(You need an LLVM with the change mentioned below.)

Also, you need a runtime with EH support to run such an application.

If you want to use the latest EH instructions, you can use
`binaryen --translate-eh-old-to-new` on your application.

Note: You don't need to translate libsetjmp.a/so to the new EH.
While LLVM currently produces bytecode for an old version of the EH
proposal, luckily for us, the bytecode used in this library (ie. the tag
definition and the "throw" instruction) is compatible with the latest
version of the proposal.

The runtime logic is basically copy-and-paste from:
    https://github.com/yamt/garbage/tree/wasm-sjlj-alt2/wasm/longjmp

The corresponding LLVM change:
    llvm/llvm-project#84137
    (Note: you need this change to build setjmp/longjmp using code.
    otoh, you don't need this to build libsetjmp.)

A similar change for emscripten:
    emscripten-core/emscripten#21502

An older version of this PR, which doesn't require LLVM changes:
    WebAssembly#467

Discussion:
    https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit

An example to use the latest EH instructions:
```
clang -mllvm -wasm-enable-sjlj -o your_app.wasm your_app.c -lsetjmp
wasm-opt --translate-eh-old-to-new -o your_app.wasm your_app.wasm
toywasm --wasi your_app.wasm
```
Note: use toywasm built with `-DTOYWASM_ENABLE_WASM_EXCEPTION_HANDLING=ON`.

An example to use the older EH instructions, which LLVM currently produces:
```
clang -mllvm -wasm-enable-sjlj -o your_app.wasm your_app.c -lsetjmp
iwasm your_app.wasm
```
Note: use wasm-micro-runtime built with `-DWAMR_BUILD_EXCE_HANDLING=1`.
Note: as of writing this, only the classic interpreter supports EH.
sunfishcode pushed a commit that referenced this pull request Apr 1, 2024
* Add libsetjmp.a/so

Add setjmp/longjump support based on Wasm EH proposal.

It's provided as a separate library (libsetjmp) from libc so that
runtimes w/o EH support can still load libc.so.

To use this setjmp/longjmp implementation, an application should
be compiled with `-mllvm -wasm-enable-sjlj` and linked with `-lsetjmp`.
(You need an LLVM with the change mentioned below.)

Also, you need a runtime with EH support to run such an application.

If you want to use the latest EH instructions, you can use
`binaryen --translate-eh-old-to-new` on your application.

Note: You don't need to translate libsetjmp.a/so to the new EH.
While LLVM currently produces bytecode for an old version of the EH
proposal, luckily for us, the bytecode used in this library (ie. the tag
definition and the "throw" instruction) is compatible with the latest
version of the proposal.

The runtime logic is basically copy-and-paste from:
    https://github.com/yamt/garbage/tree/wasm-sjlj-alt2/wasm/longjmp

The corresponding LLVM change:
    llvm/llvm-project#84137
    (Note: you need this change to build setjmp/longjmp using code.
    otoh, you don't need this to build libsetjmp.)

A similar change for emscripten:
    emscripten-core/emscripten#21502

An older version of this PR, which doesn't require LLVM changes:
    #467

Discussion:
    https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit

An example to use the latest EH instructions:
```
clang -mllvm -wasm-enable-sjlj -o your_app.wasm your_app.c -lsetjmp
wasm-opt --translate-eh-old-to-new -o your_app.wasm your_app.wasm
toywasm --wasi your_app.wasm
```
Note: use toywasm built with `-DTOYWASM_ENABLE_WASM_EXCEPTION_HANDLING=ON`.

An example to use the older EH instructions, which LLVM currently produces:
```
clang -mllvm -wasm-enable-sjlj -o your_app.wasm your_app.c -lsetjmp
iwasm your_app.wasm
```
Note: use wasm-micro-runtime built with `-DWAMR_BUILD_EXCE_HANDLING=1`.
Note: as of writing this, only the classic interpreter supports EH.

* Make libsetjmp build optional

* CI: Disable libsetjmp for old LLVM

* libc-top-half/musl/include/setjmp.h: fix a rebase botch
@sunfishcode
Copy link
Member

Closing as this is now subsumed by #483.

@sunfishcode sunfishcode closed this Apr 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants