Skip to content

Commit

Permalink
Add libsetjmp.a/so
Browse files Browse the repository at this point in the history
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:
    WebAssembly#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.
  • Loading branch information
yamt committed Mar 15, 2024
1 parent f493dc2 commit 689b17c
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 7 deletions.
24 changes: 19 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -493,7 +497,6 @@ MUSL_OMIT_HEADERS += \
"netdb.h" \
"resolv.h" \
"pty.h" \
"setjmp.h" \
"ulimit.h" \
"sys/xattr.h" \
"wordexp.h" \
Expand Down Expand Up @@ -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))
Expand All @@ -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) \
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand 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

Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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)
Expand All @@ -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
#
Expand Down
1 change: 1 addition & 0 deletions libc-top-half/musl/arch/wasm32/bits/setjmp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
typedef unsigned long __jmp_buf[8];
8 changes: 6 additions & 2 deletions libc-top-half/musl/include/setjmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ extern "C" {

#include <features.h>

#ifdef __wasilibc_unmodified_upstream /* WASI has no setjmp */
#include <bits/setjmp.h>
/* 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;
Expand Down
83 changes: 83 additions & 0 deletions libc-top-half/musl/src/setjmp/wasm32/rt.c
Original file line number Diff line number Diff line change
@@ -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 <stddef.h>
#include <stdint.h>

/*
* 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 */
}

0 comments on commit 689b17c

Please sign in to comment.