From d90d7de10e2208905b1cdf29817f89c3ed68cbcd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 11 Sep 2025 21:04:04 -0500 Subject: [PATCH] Rewrite setjmp/longjmp docs (#562) * Provide flags for emitting the standard exception-handling instructions. * More further explain what flags are doing. * Show a sample program that can be compiled both ways. * Delegate running in a runtime to various runtimes, so remove the "run the program" documentation here. * Remove `wasm-opt` documentation since it's now documented how to emit the standard instructions directly with LLVM. --- SetjmpLongjmp.md | 131 ++++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 48 deletions(-) diff --git a/SetjmpLongjmp.md b/SetjmpLongjmp.md index 9034bd2..364a061 100644 --- a/SetjmpLongjmp.md +++ b/SetjmpLongjmp.md @@ -3,80 +3,115 @@ WASI-SDK provides basic setjmp/longjmp support. Note that it's still under active development and may change in -future versions. +future versions. The tl;dr; version of this document is to pass these flags to +the C compiler: -## Build an application - -To build an application using setjmp/longjmp, you need two things: +``` +-mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false +``` -* Enable the necessary LLVM translation (`-mllvm -wasm-enable-sjlj`) +## Implementation Primitives + +Support for `setjmp` and `longjmp` is built on top of the +[exception-handling](https://github.com/WebAssembly/exception-handling) +WebAssembly proposal. This proposal is now [phase +5](https://github.com/WebAssembly/proposals) and becoming part of the official +specification. Note, however, that the exception-handling proposal has a long +history and has a "legacy" version which shipped in browsers as well. This means +that there are two different, but similar, sets of instructions that can be +emitted to support `setjmp` and `longjmp`. Clang 20 and later (wasi-sdk-26 and +later) is capable of emitting both at this time via `-mllvm +-wasm-use-legacy-eh={false,true}` compiler flags. + +Another important point is that exception-handling only provides structured +control flow primitives for exceptions. This means it is not possible to purely +define `setjmp` in C as otherwise it must be a function that returns twice. This +means that support for `setjmp` and `longjmp` in WebAssembly relies on a +compiler pass to transform invocations of `setjmp` at a compiler IR level. This +means that the `setjmp` symbol is not defined in wasi-libc, for example, but +instead primitives used to implement `setjmp`, in conjunction with LLVM, are +found in wasi-libc. -* Link the setjmp library (`-lsetjmp`) +## Build an application -### Example without LTO +To build an application using setjmp/longjmp, you need three sets of compiler +flags: -```shell -clang -Os -mllvm -wasm-enable-sjlj -o your_app.legacy.wasm your_app.c -lsetjmp -``` +1. `-mllvm -wasm-enable-sjlj`: Enable LLVM compiler pass which replaces calls to + `setjmp` and `longjmp` with a different implementation that wasi-libc + implements and hooks into. +2. `-lsetjmp`: Link the setjmp library that wasi-libc provides which contains + these hooks that LLVM uses. +2. `-mllvm -wasm-use-legacy-eh=false`: Specify which version of the + exception-handling instructions will be emitted. Note that if this is omitted + it currently defaults to `true` meaning that the legacy instructions are + emitted, not the standard instructions. -### Example with LTO +In short, these flags are required to use `setjmp`/`longjmp` -```shell -clang -Os -flto=full -mllvm -wasm-enable-sjlj -Wl,-mllvm,-wasm-enable-sjlj -o your_app.legacy.wasm your_app.c -lsetjmp +``` +-mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false ``` -## Run an application +### Examples -To run the application built as in the previous section, -you need to use a runtime with [exception handling proposal] support. +This source code: -Unfortunately, there are two incompatible versions of -[exception handling proposal], which is commonly implemented by runtimes. +```c +#include +#include +#include +#include -* The latest version with `exnref` +static jmp_buf env; -* The legacy [phase3] version +static bool test_if_longjmp(void(*f)(void)) { + if (setjmp(env)) + return true; + f(); + return false; +} -### Example with the latest exception handling proposal +static void do_not_longjmp() { +} -By default, the current version of WASI-SDK produces the legacy -"phase3" version of [exception handling proposal] instructions. +static void do_longjmp() { + longjmp(env, 1); +} -You can tell the llvm to produce the latest version of proposal by -specifying `-mllvm -wasm-use-legacy-eh=false`. This is expected -to be the default in a future version. +int main() { + bool longjmped = test_if_longjmp(do_not_longjmp); + assert(!longjmped); + longjmped = test_if_longjmp(do_longjmp); + assert(longjmped); + return 0; +} +``` -Alternatively, you can use binaryen `wasm-opt` command to convert -existing modules from the legacy "phase3" version to the "exnref" version. +can be compiled using the standard set of instructions as: ```shell -wasm-opt --translate-to-exnref -all -o your_app.wasm your_app.legacy.wasm +clang -Os -o test.wasm test.c \ + -mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false ``` -Then you can run it with a runtime supporting the "exnref" version of -the proposal. -[toywasm] is an example of such runtimes. +and then `test.wasm` can be executed in a WebAssembly runtime supporting WASI. + +You can also compile for the legacy exceptions proposal with: ```shell -toywasm --wasi your_app.wasm +clang -Os -o test.wasm test.c \ + -mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=true ``` -(You may need to enable the support with `-D TOYWASM_ENABLE_WASM_EXCEPTION_HANDLING=ON`.) - -### Example with the legacy phase3 exception handling proposal -If your runtime supports the legacy [phase3] version of -[exception handling proposal], which is the same version as what WASI-SDK -currently produces by default, you can run the produced module as it is. +and then `test.wasm` can be executed in a WebAssembly runtime supporting the +legacy WebAssembly instructions. -For example, the classic interpreter of [wasm-micro-runtime] is -one of such runtimes. +Note that when compiling with LTO you'll need to pass `-mllvm` flags to the +linker in addition to Clang itself, such as: ```shell -iwasm your_app.legacy.wasm +clang -Os -flto=full -o test.wasm test.c \ + -mllvm -wasm-enable-sjlj -lsetjmp -mllvm -wasm-use-legacy-eh=false \ + -Wl,-mllvm,-wasm-enable-sjlj,-mllvm,-wasm-use-legacy-eh=false ``` -(You may need to enable the support with `-D WAMR_BUILD_EXCE_HANDLING=1 -D WAMR_BUILD_FAST_INTERP=0`.) - -[exception handling proposal]: https://github.com/WebAssembly/exception-handling/ -[phase3]: https://github.com/WebAssembly/exception-handling/tree/main/proposals/exception-handling/legacy -[toywasm]: https://github.com/yamt/toywasm -[wasm-micro-runtime]: https://github.com/bytecodealliance/wasm-micro-runtime