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: | diff --git a/Makefile b/Makefile index f7dde2740..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 @@ -92,6 +94,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 +126,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 +432,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 +498,6 @@ MUSL_OMIT_HEADERS += \ "netdb.h" \ "resolv.h" \ "pty.h" \ - "setjmp.h" \ "ulimit.h" \ "sys/xattr.h" \ "wordexp.h" \ @@ -529,6 +533,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 +548,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 +578,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 +596,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 +627,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 +673,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 \ @@ -727,11 +740,15 @@ LIBC_SO = \ $(SYSROOT_LIB)/libwasi-emulated-getpid.so \ $(SYSROOT_LIB)/libwasi-emulated-signal.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 \ @@ -740,6 +757,12 @@ libc: include_dirs \ $(SYSROOT_LIB)/libwasi-emulated-getpid.a \ $(SYSROOT_LIB)/libwasi-emulated-signal.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 # @@ -799,8 +822,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..b57999ed6 100644 --- a/libc-top-half/musl/include/setjmp.h +++ b/libc-top-half/musl/include/setjmp.h @@ -7,7 +7,12 @@ extern "C" { #include -#ifdef __wasilibc_unmodified_upstream /* WASI has no setjmp */ +#ifndef __wasilibc_unmodified_upstream +/* 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 { @@ -40,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 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 */ +}