From 8850318610e31aaed0af7bf58b346ed06cb67ccf Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Fri, 15 Mar 2024 13:26:15 +0900 Subject: [PATCH 1/4] 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: https://github.com/llvm/llvm-project/pull/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: https://github.com/emscripten-core/emscripten/pull/21502 An older version of this PR, which doesn't require LLVM changes: https://github.com/WebAssembly/wasi-libc/pull/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. --- Makefile | 25 ++++-- libc-top-half/musl/arch/wasm32/bits/setjmp.h | 1 + libc-top-half/musl/include/setjmp.h | 8 +- libc-top-half/musl/src/setjmp/wasm32/rt.c | 83 ++++++++++++++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 libc-top-half/musl/arch/wasm32/bits/setjmp.h create mode 100644 libc-top-half/musl/src/setjmp/wasm32/rt.c diff --git a/Makefile b/Makefile index f7dde2740..d93484945 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,7 @@ LIBC_BOTTOM_HALF_OMIT_SOURCES := \ $(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip2.c LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES)) # Omit p2-specific headers from include-all.c test. +# for exception-handling. INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h endif @@ -123,6 +124,7 @@ LIBWASI_EMULATED_SIGNAL_MUSL_SOURCES = \ $(LIBC_TOP_HALF_MUSL_SRC_DIR)/signal/psignal.c \ $(LIBC_TOP_HALF_MUSL_SRC_DIR)/string/strsignal.c LIBDL_SOURCES = $(LIBC_TOP_HALF_MUSL_SRC_DIR)/misc/dl.c +LIBSETJMP_SOURCES = $(LIBC_TOP_HALF_MUSL_SRC_DIR)/setjmp/wasm32/rt.c LIBC_BOTTOM_HALF_CRT_SOURCES = $(wildcard $(LIBC_BOTTOM_HALF_DIR)/crt/*.c) LIBC_TOP_HALF_DIR = libc-top-half LIBC_TOP_HALF_MUSL_DIR = $(LIBC_TOP_HALF_DIR)/musl @@ -428,6 +430,7 @@ LIBWASI_EMULATED_GETPID_OBJS = $(call objs,$(LIBWASI_EMULATED_GETPID_SOURCES)) LIBWASI_EMULATED_SIGNAL_OBJS = $(call objs,$(LIBWASI_EMULATED_SIGNAL_SOURCES)) LIBWASI_EMULATED_SIGNAL_MUSL_OBJS = $(call objs,$(LIBWASI_EMULATED_SIGNAL_MUSL_SOURCES)) LIBDL_OBJS = $(call objs,$(LIBDL_SOURCES)) +LIBSETJMP_OBJS = $(call objs,$(LIBSETJMP_SOURCES)) LIBC_BOTTOM_HALF_CRT_OBJS = $(call objs,$(LIBC_BOTTOM_HALF_CRT_SOURCES)) # These variables describe the locations of various files and @@ -493,7 +496,6 @@ MUSL_OMIT_HEADERS += \ "netdb.h" \ "resolv.h" \ "pty.h" \ - "setjmp.h" \ "ulimit.h" \ "sys/xattr.h" \ "wordexp.h" \ @@ -529,6 +531,7 @@ LIBWASI_EMULATED_GETPID_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_GETP LIBWASI_EMULATED_SIGNAL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_SIGNAL_OBJS)) LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS)) LIBDL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBDL_OBJS)) +LIBSETJMP_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBSETJMP_OBJS)) BULK_MEMORY_SO_OBJS = $(patsubst %.o,%.pic.o,$(BULK_MEMORY_OBJS)) DLMALLOC_SO_OBJS = $(patsubst %.o,%.pic.o,$(DLMALLOC_OBJS)) LIBC_BOTTOM_HALF_ALL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBC_BOTTOM_HALF_ALL_OBJS)) @@ -543,6 +546,7 @@ PIC_OBJS = \ $(LIBWASI_EMULATED_SIGNAL_SO_OBJS) \ $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS) \ $(LIBDL_SO_OBJS) \ + $(LIBSETJMP_SO_OBJS) \ $(BULK_MEMORY_SO_OBJS) \ $(DLMALLOC_SO_OBJS) \ $(LIBC_BOTTOM_HALF_ALL_SO_OBJS) \ @@ -572,6 +576,8 @@ $(OBJDIR)/libwasi-emulated-signal.so.a: $(LIBWASI_EMULATED_SIGNAL_SO_OBJS) $(LIB $(OBJDIR)/libdl.so.a: $(LIBDL_SO_OBJS) +$(OBJDIR)/libsetjmp.so.a: $(LIBSETJMP_SO_OBJS) + $(SYSROOT_LIB)/libc.a: $(LIBC_OBJS) $(SYSROOT_LIB)/libc-printscan-long-double.a: $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS) @@ -588,6 +594,8 @@ $(SYSROOT_LIB)/libwasi-emulated-signal.a: $(LIBWASI_EMULATED_SIGNAL_OBJS) $(LIBW $(SYSROOT_LIB)/libdl.a: $(LIBDL_OBJS) +$(SYSROOT_LIB)/libsetjmp.a: $(LIBSETJMP_OBJS) + %.a: @mkdir -p "$(@D)" # On Windows, the commandline for the ar invocation got too long, so it needs to be split up. @@ -617,6 +625,9 @@ $(BULK_MEMORY_OBJS) $(BULK_MEMORY_SO_OBJS): CFLAGS += \ $(BULK_MEMORY_OBJS) $(BULK_MEMORY_SO_OBJS): CFLAGS += \ -DBULK_MEMORY_THRESHOLD=$(BULK_MEMORY_THRESHOLD) +$(LIBSETJMP_OBJS) $(LIBSETJMP_SO_OBJS): CFLAGS += \ + -mllvm -wasm-enable-sjlj + $(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS): CFLAGS += \ -D_WASI_EMULATED_SIGNAL @@ -660,7 +671,7 @@ startup_files $(LIBC_BOTTOM_HALF_ALL_OBJS) $(LIBC_BOTTOM_HALF_ALL_SO_OBJS): CFLA -I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/include \ -I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal -$(LIBC_TOP_HALF_ALL_OBJS) $(LIBC_TOP_HALF_ALL_SO_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_SO_OBJS) $(MUSL_PRINTSCAN_NO_FLOATING_POINT_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS) $(LIBDL_OBJS) $(LIBDL_SO_OBJS): CFLAGS += \ +$(LIBC_TOP_HALF_ALL_OBJS) $(LIBC_TOP_HALF_ALL_SO_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_SO_OBJS) $(MUSL_PRINTSCAN_NO_FLOATING_POINT_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS) $(LIBDL_OBJS) $(LIBDL_SO_OBJS) $(LIBSETJMP_OBJS) $(LIBSETJMP_SO_OBJS): CFLAGS += \ -I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/include \ -I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal \ -I$(LIBC_TOP_HALF_MUSL_DIR)/arch/wasm32 \ @@ -726,7 +737,8 @@ LIBC_SO = \ $(SYSROOT_LIB)/libwasi-emulated-process-clocks.so \ $(SYSROOT_LIB)/libwasi-emulated-getpid.so \ $(SYSROOT_LIB)/libwasi-emulated-signal.so \ - $(SYSROOT_LIB)/libdl.so + $(SYSROOT_LIB)/libdl.so \ + $(SYSROOT_LIB)/libsetjmp.so endif libc_so: include_dirs $(LIBC_SO) @@ -739,7 +751,8 @@ libc: include_dirs \ $(SYSROOT_LIB)/libwasi-emulated-process-clocks.a \ $(SYSROOT_LIB)/libwasi-emulated-getpid.a \ $(SYSROOT_LIB)/libwasi-emulated-signal.a \ - $(SYSROOT_LIB)/libdl.a + $(SYSROOT_LIB)/libdl.a \ + $(SYSROOT_LIB)/libsetjmp.a finish: startup_files libc # @@ -799,8 +812,10 @@ check-symbols: startup_files libc # # Generate a test file that includes all public C header files. # + # setjmp.h is excluded because it requires a different compiler option + # cd "$(SYSROOT_INC)" && \ - for header in $$(find . -type f -not -name mman.h -not -name signal.h -not -name times.h -not -name resource.h $(INCLUDE_ALL_CLAUSES) |grep -v /bits/ |grep -v /c++/); do \ + for header in $$(find . -type f -not -name mman.h -not -name signal.h -not -name times.h -not -name resource.h -not -name setjmp.h $(INCLUDE_ALL_CLAUSES) |grep -v /bits/ |grep -v /c++/); do \ echo '#include <'$$header'>' | sed 's/\.\///' ; \ done |LC_ALL=C sort >$(SYSROOT_SHARE)/include-all.c ; \ cd - >/dev/null diff --git a/libc-top-half/musl/arch/wasm32/bits/setjmp.h b/libc-top-half/musl/arch/wasm32/bits/setjmp.h new file mode 100644 index 000000000..63973a800 --- /dev/null +++ b/libc-top-half/musl/arch/wasm32/bits/setjmp.h @@ -0,0 +1 @@ +typedef unsigned long __jmp_buf[8]; diff --git a/libc-top-half/musl/include/setjmp.h b/libc-top-half/musl/include/setjmp.h index f505f8e4f..5f90c97b8 100644 --- a/libc-top-half/musl/include/setjmp.h +++ b/libc-top-half/musl/include/setjmp.h @@ -7,8 +7,12 @@ extern "C" { #include -#ifdef __wasilibc_unmodified_upstream /* WASI has no setjmp */ -#include +/* WASI has no setjmp */ +#ifndef __wasilibc_unmodified_upstream +#if !defined(__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 `-mllvm -wasm-enable-sjlj` and use an engine that implements the Exception handling proposal. +#endif +#endif typedef struct __jmp_buf_tag { __jmp_buf __jb; diff --git a/libc-top-half/musl/src/setjmp/wasm32/rt.c b/libc-top-half/musl/src/setjmp/wasm32/rt.c new file mode 100644 index 000000000..24e4e3361 --- /dev/null +++ b/libc-top-half/musl/src/setjmp/wasm32/rt.c @@ -0,0 +1,83 @@ +/* + * a runtime implementation for + * https://github.com/llvm/llvm-project/pull/84137 + * https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit + */ + +#include +#include + +/* + * function prototypes + */ +void __wasm_setjmp(void *env, uint32_t label, void *func_invocation_id); +uint32_t __wasm_setjmp_test(void *env, void *func_invocation_id); +void __wasm_longjmp(void *env, int val); + +/* + * jmp_buf should have large enough size and alignment to contain + * this structure. + */ +struct jmp_buf_impl { + void *func_invocation_id; + uint32_t label; + + /* + * this is a temorary storage used by the communication between + * __wasm_sjlj_longjmp and WebAssemblyLowerEmscriptenEHSjL-generated + * logic. + * ideally, this can be replaced with multivalue. + */ + struct arg { + void *env; + int val; + } arg; +}; + +void +__wasm_setjmp(void *env, uint32_t label, void *func_invocation_id) +{ + struct jmp_buf_impl *jb = env; + if (label == 0) { /* ABI contract */ + __builtin_trap(); + } + if (func_invocation_id == NULL) { /* sanity check */ + __builtin_trap(); + } + 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; + if (jb->label == 0) { /* ABI contract */ + __builtin_trap(); + } + if (func_invocation_id == NULL) { /* sanity check */ + __builtin_trap(); + } + if (jb->func_invocation_id == func_invocation_id) { + return jb->label; + } + return 0; +} + +void +__wasm_longjmp(void *env, int val) +{ + struct jmp_buf_impl *jb = env; + struct arg *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; + } + arg->env = env; + arg->val = val; + __builtin_wasm_throw(1, arg); /* 1 == C_LONGJMP */ +} From ef6ef314d0cb8f308ecf9c04549eff36557c4b27 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Fri, 15 Mar 2024 16:15:00 +0900 Subject: [PATCH 2/4] Make libsetjmp build optional --- Makefile | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d93484945..32d621e3b 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,8 @@ WASI_SNAPSHOT ?= p1 MALLOC_IMPL ?= dlmalloc # yes or no BUILD_LIBC_TOP_HALF ?= yes +# yes or no +BUILD_LIBSETJMP ?= yes # The directory where we will store intermediate artifacts. OBJDIR ?= build/$(TARGET_TRIPLE) # The directory where we store files and tools for generating WASIp2 bindings @@ -737,13 +739,16 @@ LIBC_SO = \ $(SYSROOT_LIB)/libwasi-emulated-process-clocks.so \ $(SYSROOT_LIB)/libwasi-emulated-getpid.so \ $(SYSROOT_LIB)/libwasi-emulated-signal.so \ - $(SYSROOT_LIB)/libdl.so \ + $(SYSROOT_LIB)/libdl.so +ifeq ($(BUILD_LIBSETJMP),yes) +LIBC_SO += \ $(SYSROOT_LIB)/libsetjmp.so endif +endif libc_so: include_dirs $(LIBC_SO) -libc: include_dirs \ +STATIC_LIBS = \ $(SYSROOT_LIB)/libc.a \ $(SYSROOT_LIB)/libc-printscan-long-double.a \ $(SYSROOT_LIB)/libc-printscan-no-floating-point.a \ @@ -751,8 +756,13 @@ libc: include_dirs \ $(SYSROOT_LIB)/libwasi-emulated-process-clocks.a \ $(SYSROOT_LIB)/libwasi-emulated-getpid.a \ $(SYSROOT_LIB)/libwasi-emulated-signal.a \ - $(SYSROOT_LIB)/libdl.a \ - $(SYSROOT_LIB)/libsetjmp.a + $(SYSROOT_LIB)/libdl.a +ifeq ($(BUILD_LIBSETJMP),yes) +STATIC_LIBS += \ + $(SYSROOT_LIB)/libsetjmp.a +endif + +libc: include_dirs $(STATIC_LIBS) finish: startup_files libc # From aeb2ee895286a6423e2fdcbc4ff250982b5e78ca Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Fri, 15 Mar 2024 16:15:18 +0900 Subject: [PATCH 3/4] CI: Disable libsetjmp for old LLVM --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d35c4d827..e906e5e6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,6 +82,12 @@ jobs: echo "NM=$CLANG_DIR/llvm-nm" >> $GITHUB_ENV if: matrix.os == 'ubuntu-latest' + - name: Disable libsetjmp for old LLVM + shell: bash + run: | + echo "BUILD_LIBSETJMP=no" >> $GITHUB_ENV + if: matrix.clang_version == '10.0.0' + - name: Build libc shell: bash run: | From 703209f7b8308825d659815e49745f6143863177 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Sat, 16 Mar 2024 13:07:27 +0900 Subject: [PATCH 4/4] libc-top-half/musl/include/setjmp.h: fix a rebase botch --- libc-top-half/musl/include/setjmp.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libc-top-half/musl/include/setjmp.h b/libc-top-half/musl/include/setjmp.h index 5f90c97b8..b57999ed6 100644 --- a/libc-top-half/musl/include/setjmp.h +++ b/libc-top-half/musl/include/setjmp.h @@ -7,12 +7,13 @@ extern "C" { #include -/* WASI has no setjmp */ #ifndef __wasilibc_unmodified_upstream -#if !defined(__wasm_exception_handling) +/* WASI has no setjmp */ +#if !defined(__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 `-mllvm -wasm-enable-sjlj` and use an engine that implements the Exception handling proposal. #endif #endif +#include typedef struct __jmp_buf_tag { __jmp_buf __jb; @@ -44,9 +45,6 @@ int setjmp (jmp_buf) __setjmp_attr; _Noreturn void longjmp (jmp_buf, int); #define setjmp setjmp -#else -#warning setjmp is not yet implemented for WASI -#endif #undef __setjmp_attr