From 689b17ce578f71c5c868ff7a1723e77c3e250b7e Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Fri, 15 Mar 2024 13:26:15 +0900 Subject: [PATCH] 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 `-DWASM_ENABLE_EXCE_HANDLING=1`. Note: as of writing this, only the classic interpreter supports EH. --- Makefile | 24 ++++-- 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, 109 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..ab49cb771 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,9 @@ 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. -INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h +# setjmp.h is excluded because it requires a different compiler option +# for exception-handling. +INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h -not -name setjmp.h endif ifeq ($(WASI_SNAPSHOT), p2) @@ -123,6 +125,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 +431,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 +497,6 @@ MUSL_OMIT_HEADERS += \ "netdb.h" \ "resolv.h" \ "pty.h" \ - "setjmp.h" \ "ulimit.h" \ "sys/xattr.h" \ "wordexp.h" \ @@ -529,6 +532,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 +547,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 +577,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 +595,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 +626,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 +672,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 +738,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 +752,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 # 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 */ +}