Skip to content

Commit 1ab654e

Browse files
authored
Add libsetjmp.a/so (#483)
* 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
1 parent a2ed34e commit 1ab654e

File tree

5 files changed

+125
-8
lines changed

5 files changed

+125
-8
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ jobs:
8282
echo "NM=$CLANG_DIR/llvm-nm" >> $GITHUB_ENV
8383
if: matrix.os == 'ubuntu-latest'
8484

85+
- name: Disable libsetjmp for old LLVM
86+
shell: bash
87+
run: |
88+
echo "BUILD_LIBSETJMP=no" >> $GITHUB_ENV
89+
if: matrix.clang_version == '10.0.0'
90+
8591
- name: Build libc
8692
shell: bash
8793
run: |

Makefile

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ WASI_SNAPSHOT ?= p1
2121
MALLOC_IMPL ?= dlmalloc
2222
# yes or no
2323
BUILD_LIBC_TOP_HALF ?= yes
24+
# yes or no
25+
BUILD_LIBSETJMP ?= yes
2426
# The directory where we will store intermediate artifacts.
2527
OBJDIR ?= build/$(TARGET_TRIPLE)
2628
# The directory where we store files and tools for generating WASIp2 bindings
@@ -95,6 +97,7 @@ LIBC_BOTTOM_HALF_OMIT_SOURCES := \
9597
$(LIBC_BOTTOM_HALF_SOURCES)/poll-wasip2.c
9698
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
9799
# Omit p2-specific headers from include-all.c test.
100+
# for exception-handling.
98101
INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h
99102
endif
100103

@@ -128,6 +131,7 @@ LIBWASI_EMULATED_SIGNAL_MUSL_SOURCES = \
128131
$(LIBC_TOP_HALF_MUSL_SRC_DIR)/signal/psignal.c \
129132
$(LIBC_TOP_HALF_MUSL_SRC_DIR)/string/strsignal.c
130133
LIBDL_SOURCES = $(LIBC_TOP_HALF_MUSL_SRC_DIR)/misc/dl.c
134+
LIBSETJMP_SOURCES = $(LIBC_TOP_HALF_MUSL_SRC_DIR)/setjmp/wasm32/rt.c
131135
LIBC_BOTTOM_HALF_CRT_SOURCES = $(wildcard $(LIBC_BOTTOM_HALF_DIR)/crt/*.c)
132136
LIBC_TOP_HALF_DIR = libc-top-half
133137
LIBC_TOP_HALF_MUSL_DIR = $(LIBC_TOP_HALF_DIR)/musl
@@ -433,6 +437,7 @@ LIBWASI_EMULATED_GETPID_OBJS = $(call objs,$(LIBWASI_EMULATED_GETPID_SOURCES))
433437
LIBWASI_EMULATED_SIGNAL_OBJS = $(call objs,$(LIBWASI_EMULATED_SIGNAL_SOURCES))
434438
LIBWASI_EMULATED_SIGNAL_MUSL_OBJS = $(call objs,$(LIBWASI_EMULATED_SIGNAL_MUSL_SOURCES))
435439
LIBDL_OBJS = $(call objs,$(LIBDL_SOURCES))
440+
LIBSETJMP_OBJS = $(call objs,$(LIBSETJMP_SOURCES))
436441
LIBC_BOTTOM_HALF_CRT_OBJS = $(call objs,$(LIBC_BOTTOM_HALF_CRT_SOURCES))
437442

438443
# These variables describe the locations of various files and
@@ -498,7 +503,6 @@ MUSL_OMIT_HEADERS += \
498503
"netdb.h" \
499504
"resolv.h" \
500505
"pty.h" \
501-
"setjmp.h" \
502506
"ulimit.h" \
503507
"sys/xattr.h" \
504508
"wordexp.h" \
@@ -534,6 +538,7 @@ LIBWASI_EMULATED_GETPID_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_GETP
534538
LIBWASI_EMULATED_SIGNAL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_SIGNAL_OBJS))
535539
LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS))
536540
LIBDL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBDL_OBJS))
541+
LIBSETJMP_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBSETJMP_OBJS))
537542
BULK_MEMORY_SO_OBJS = $(patsubst %.o,%.pic.o,$(BULK_MEMORY_OBJS))
538543
DLMALLOC_SO_OBJS = $(patsubst %.o,%.pic.o,$(DLMALLOC_OBJS))
539544
LIBC_BOTTOM_HALF_ALL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBC_BOTTOM_HALF_ALL_OBJS))
@@ -548,6 +553,7 @@ PIC_OBJS = \
548553
$(LIBWASI_EMULATED_SIGNAL_SO_OBJS) \
549554
$(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS) \
550555
$(LIBDL_SO_OBJS) \
556+
$(LIBSETJMP_SO_OBJS) \
551557
$(BULK_MEMORY_SO_OBJS) \
552558
$(DLMALLOC_SO_OBJS) \
553559
$(LIBC_BOTTOM_HALF_ALL_SO_OBJS) \
@@ -577,6 +583,8 @@ $(OBJDIR)/libwasi-emulated-signal.so.a: $(LIBWASI_EMULATED_SIGNAL_SO_OBJS) $(LIB
577583

578584
$(OBJDIR)/libdl.so.a: $(LIBDL_SO_OBJS)
579585

586+
$(OBJDIR)/libsetjmp.so.a: $(LIBSETJMP_SO_OBJS)
587+
580588
$(SYSROOT_LIB)/libc.a: $(LIBC_OBJS)
581589

582590
$(SYSROOT_LIB)/libc-printscan-long-double.a: $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS)
@@ -593,6 +601,8 @@ $(SYSROOT_LIB)/libwasi-emulated-signal.a: $(LIBWASI_EMULATED_SIGNAL_OBJS) $(LIBW
593601

594602
$(SYSROOT_LIB)/libdl.a: $(LIBDL_OBJS)
595603

604+
$(SYSROOT_LIB)/libsetjmp.a: $(LIBSETJMP_OBJS)
605+
596606
%.a:
597607
@mkdir -p "$(@D)"
598608
# On Windows, the commandline for the ar invocation got too long, so it needs to be split up.
@@ -622,6 +632,9 @@ $(BULK_MEMORY_OBJS) $(BULK_MEMORY_SO_OBJS): CFLAGS += \
622632
$(BULK_MEMORY_OBJS) $(BULK_MEMORY_SO_OBJS): CFLAGS += \
623633
-DBULK_MEMORY_THRESHOLD=$(BULK_MEMORY_THRESHOLD)
624634

635+
$(LIBSETJMP_OBJS) $(LIBSETJMP_SO_OBJS): CFLAGS += \
636+
-mllvm -wasm-enable-sjlj
637+
625638
$(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS): CFLAGS += \
626639
-D_WASI_EMULATED_SIGNAL
627640

@@ -665,7 +678,7 @@ startup_files $(LIBC_BOTTOM_HALF_ALL_OBJS) $(LIBC_BOTTOM_HALF_ALL_SO_OBJS): CFLA
665678
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/include \
666679
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal
667680

668-
$(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 += \
681+
$(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 += \
669682
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/include \
670683
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal \
671684
-I$(LIBC_TOP_HALF_MUSL_DIR)/arch/wasm32 \
@@ -732,11 +745,15 @@ LIBC_SO = \
732745
$(SYSROOT_LIB)/libwasi-emulated-getpid.so \
733746
$(SYSROOT_LIB)/libwasi-emulated-signal.so \
734747
$(SYSROOT_LIB)/libdl.so
748+
ifeq ($(BUILD_LIBSETJMP),yes)
749+
LIBC_SO += \
750+
$(SYSROOT_LIB)/libsetjmp.so
751+
endif
735752
endif
736753

737754
libc_so: include_dirs $(LIBC_SO)
738755

739-
libc: include_dirs \
756+
STATIC_LIBS = \
740757
$(SYSROOT_LIB)/libc.a \
741758
$(SYSROOT_LIB)/libc-printscan-long-double.a \
742759
$(SYSROOT_LIB)/libc-printscan-no-floating-point.a \
@@ -745,6 +762,12 @@ libc: include_dirs \
745762
$(SYSROOT_LIB)/libwasi-emulated-getpid.a \
746763
$(SYSROOT_LIB)/libwasi-emulated-signal.a \
747764
$(SYSROOT_LIB)/libdl.a
765+
ifeq ($(BUILD_LIBSETJMP),yes)
766+
STATIC_LIBS += \
767+
$(SYSROOT_LIB)/libsetjmp.a
768+
endif
769+
770+
libc: include_dirs $(STATIC_LIBS)
748771

749772
finish: startup_files libc
750773
#
@@ -804,8 +827,10 @@ check-symbols: startup_files libc
804827
#
805828
# Generate a test file that includes all public C header files.
806829
#
830+
# setjmp.h is excluded because it requires a different compiler option
831+
#
807832
cd "$(SYSROOT_INC)" && \
808-
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 \
833+
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 \
809834
echo '#include <'$$header'>' | sed 's/\.\///' ; \
810835
done |LC_ALL=C sort >$(SYSROOT_SHARE)/include-all.c ; \
811836
cd - >/dev/null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typedef unsigned long __jmp_buf[8];

libc-top-half/musl/include/setjmp.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ extern "C" {
77

88
#include <features.h>
99

10-
#ifdef __wasilibc_unmodified_upstream /* WASI has no setjmp */
10+
#ifndef __wasilibc_unmodified_upstream
11+
/* WASI has no setjmp */
12+
#if !defined(__wasm_exception_handling__)
13+
#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.
14+
#endif
15+
#endif
1116
#include <bits/setjmp.h>
1217

1318
typedef struct __jmp_buf_tag {
@@ -40,9 +45,6 @@ int setjmp (jmp_buf) __setjmp_attr;
4045
_Noreturn void longjmp (jmp_buf, int);
4146

4247
#define setjmp setjmp
43-
#else
44-
#warning setjmp is not yet implemented for WASI
45-
#endif
4648

4749
#undef __setjmp_attr
4850

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* a runtime implementation for
3+
* https://github.com/llvm/llvm-project/pull/84137
4+
* https://docs.google.com/document/d/1ZvTPT36K5jjiedF8MCXbEmYjULJjI723aOAks1IdLLg/edit
5+
*/
6+
7+
#include <stddef.h>
8+
#include <stdint.h>
9+
10+
/*
11+
* function prototypes
12+
*/
13+
void __wasm_setjmp(void *env, uint32_t label, void *func_invocation_id);
14+
uint32_t __wasm_setjmp_test(void *env, void *func_invocation_id);
15+
void __wasm_longjmp(void *env, int val);
16+
17+
/*
18+
* jmp_buf should have large enough size and alignment to contain
19+
* this structure.
20+
*/
21+
struct jmp_buf_impl {
22+
void *func_invocation_id;
23+
uint32_t label;
24+
25+
/*
26+
* this is a temorary storage used by the communication between
27+
* __wasm_sjlj_longjmp and WebAssemblyLowerEmscriptenEHSjL-generated
28+
* logic.
29+
* ideally, this can be replaced with multivalue.
30+
*/
31+
struct arg {
32+
void *env;
33+
int val;
34+
} arg;
35+
};
36+
37+
void
38+
__wasm_setjmp(void *env, uint32_t label, void *func_invocation_id)
39+
{
40+
struct jmp_buf_impl *jb = env;
41+
if (label == 0) { /* ABI contract */
42+
__builtin_trap();
43+
}
44+
if (func_invocation_id == NULL) { /* sanity check */
45+
__builtin_trap();
46+
}
47+
jb->func_invocation_id = func_invocation_id;
48+
jb->label = label;
49+
}
50+
51+
uint32_t
52+
__wasm_setjmp_test(void *env, void *func_invocation_id)
53+
{
54+
struct jmp_buf_impl *jb = env;
55+
if (jb->label == 0) { /* ABI contract */
56+
__builtin_trap();
57+
}
58+
if (func_invocation_id == NULL) { /* sanity check */
59+
__builtin_trap();
60+
}
61+
if (jb->func_invocation_id == func_invocation_id) {
62+
return jb->label;
63+
}
64+
return 0;
65+
}
66+
67+
void
68+
__wasm_longjmp(void *env, int val)
69+
{
70+
struct jmp_buf_impl *jb = env;
71+
struct arg *arg = &jb->arg;
72+
/*
73+
* C standard says:
74+
* The longjmp function cannot cause the setjmp macro to return
75+
* the value 0; if val is 0, the setjmp macro returns the value 1.
76+
*/
77+
if (val == 0) {
78+
val = 1;
79+
}
80+
arg->env = env;
81+
arg->val = val;
82+
__builtin_wasm_throw(1, arg); /* 1 == C_LONGJMP */
83+
}

0 commit comments

Comments
 (0)