Skip to content

Commit d4dae89

Browse files
authored
add shared library support (#429)
* add shared library support This adds support for building WASI shared libraries per https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md. For the time being, the goal is to allow "pseudo-dynamic" linking using the Component Model per https://github.com/WebAssembly/component-model/blob/main/design/mvp/examples/SharedEverythingDynamicLinking.md. This requires all libraries to be available when the component is created, but still allows runtime symbol resolution via `dlopen`/`dlsym` backed by a static lookup table. This is sufficient to support Python native extensions, for example. A complete demo using `wit-component` is available at https://github.com/dicej/component-linking-demo. This commit adds support for building `libc.so`, `libc++.so`, and `libc++abi.so` alongside their static counterparts. Notes: - I had to refactor `errno` support a bit to avoid a spurious `_ZTH5errno` (AKA "thread-local initialization routine for errno") import in `libc++.so`. - Long double print and scan are included by default in `libc.so` rather than in a separate library. - `__main_argc_argv` is now a weak symbol since it's not relevant for reactors. - `dlopen`/`dlsym` rely on a lookup table provided by the "dynamic" linker via `__wasm_set_libraries`. Not all flags are supported yet, and unrecognized flags will result in an error. - This requires https://reviews.llvm.org/D153293, which we will need to backport to LLVM 16 until 17 is released. I'll open a `wasi-sdk` PR with that change and various Makefile tweaks to support shared libraries. - `libc.so` is temporarily disabled for the `wasi-threads` build until someone can make `wasi_thread_start.s` position-independent. Signed-off-by: Joel Dice <joel.dice@fermyon.com> build `-fPIC` .o files separately from non-`-fPIC` ones This allows us to build both libc.so and libc.a without incurring indirection penalties in the latter. Signed-off-by: Joel Dice <joel.dice@fermyon.com> only build libc.so when explicitly requested Shared library support in LLVM for non-Emscripten Wasm targets will be added in version 17, which has not yet been released, so we should not attempt to build libc.so by default (at least not yet). Signed-off-by: Joel Dice <joel.dice@fermyon.com> remove dl.c I'll open a separate PR for this later. Signed-off-by: Joel Dice <joel.dice@fermyon.com> update `check-symbols` files Signed-off-by: Joel Dice <joel.dice@fermyon.com> * generate separate .so files for emulated features Signed-off-by: Joel Dice <joel.dice@fermyon.com> * revert errno changes in favor of a smaller change @yamt pointed out there's an easier way to address the `_ZTH5errno` issue I described in an earlier commit: use `_Thread_local` for both C and C++. This gives us a simpler ABI and avoids needing to import a thread-local initializer for `errno` in libc++.so. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * remove redundant `$(OBJDIR)/%.long-double.pic.o` rule in Makefile Signed-off-by: Joel Dice <joel.dice@fermyon.com> * consolidate libwasi-emulated-*.so into a single library Signed-off-by: Joel Dice <joel.dice@fermyon.com> * add comment explaining use of `--whole-archive` Signed-off-by: Joel Dice <joel.dice@fermyon.com> * Revert "remove redundant `$(OBJDIR)/%.long-double.pic.o` rule in Makefile" This reverts commit dbe2cb1. * move `__main_void` from __main_void.c to crt1-command.c This and `__main_argc_argv` are only relevant for commands (not reactors), so it makes sense to scope them accordingly. In addition, the latter was being imported from libc.so, forcing applications to provide it even if it wasn't relevant. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * Revert "consolidate libwasi-emulated-*.so into a single library" This reverts commit c651822. * build crt1-*.o with `-fPIC` This ensures they can be used in a PIE or PIC context. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * ignore `__memory_base` when checking undefined symbols Whether this symbol appears varies between LLVM versions. Signed-off-by: Joel Dice <joel.dice@fermyon.com> * Revert "move `__main_void` from __main_void.c to crt1-command.c" This reverts commit f303835. * add explanatory comments to __main_void.c Signed-off-by: Joel Dice <joel.dice@fermyon.com> * add `__wasilibc_unmodified_upstream` and comment to `__lctrans_cur` Signed-off-by: Joel Dice <joel.dice@fermyon.com> --------- Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 7b4705f commit d4dae89

6 files changed

Lines changed: 98 additions & 16 deletions

File tree

Makefile

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,52 @@ endif
472472

473473
default: finish
474474

475+
LIBC_SO_OBJS = $(patsubst %.o,%.pic.o,$(filter-out $(MUSL_PRINTSCAN_OBJS),$(LIBC_OBJS)))
476+
MUSL_PRINTSCAN_LONG_DOUBLE_SO_OBJS = $(patsubst %.o,%.pic.o,$(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS))
477+
LIBWASI_EMULATED_MMAN_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_MMAN_OBJS))
478+
LIBWASI_EMULATED_PROCESS_CLOCKS_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_PROCESS_CLOCKS_OBJS))
479+
LIBWASI_EMULATED_GETPID_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_GETPID_OBJS))
480+
LIBWASI_EMULATED_SIGNAL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_SIGNAL_OBJS))
481+
LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS))
482+
BULK_MEMORY_SO_OBJS = $(patsubst %.o,%.pic.o,$(BULK_MEMORY_OBJS))
483+
DLMALLOC_SO_OBJS = $(patsubst %.o,%.pic.o,$(DLMALLOC_OBJS))
484+
LIBC_BOTTOM_HALF_ALL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBC_BOTTOM_HALF_ALL_OBJS))
485+
LIBC_TOP_HALF_ALL_SO_OBJS = $(patsubst %.o,%.pic.o,$(LIBC_TOP_HALF_ALL_OBJS))
486+
487+
PIC_OBJS = \
488+
$(LIBC_SO_OBJS) \
489+
$(MUSL_PRINTSCAN_LONG_DOUBLE_SO_OBJS) \
490+
$(LIBWASI_EMULATED_MMAN_SO_OBJS) \
491+
$(LIBWASI_EMULATED_PROCESS_CLOCKS_SO_OBJS) \
492+
$(LIBWASI_EMULATED_GETPID_SO_OBJS) \
493+
$(LIBWASI_EMULATED_SIGNAL_SO_OBJS) \
494+
$(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS) \
495+
$(BULK_MEMORY_SO_OBJS) \
496+
$(DLMALLOC_SO_OBJS) \
497+
$(LIBC_BOTTOM_HALF_ALL_SO_OBJS) \
498+
$(LIBC_TOP_HALF_ALL_SO_OBJS) \
499+
$(LIBC_BOTTOM_HALF_CRT_OBJS)
500+
501+
# TODO: Specify SDK version, e.g. libc.so.wasi-sdk-21, as SO_NAME once `wasm-ld`
502+
# supports it.
503+
#
504+
# Note that we collect the object files for each shared library into a .a and
505+
# link that using `--whole-archive` rather than pass the object files directly
506+
# to CC. This is a workaround for a Windows command line size limitation. See
507+
# the `%.a` rule below for details.
508+
$(SYSROOT_LIB)/%.so: $(OBJDIR)/%.so.a $(BUILTINS_LIB)
509+
$(CC) -nostdlib -shared -o $@ -Wl,--whole-archive $< -Wl,--no-whole-archive $(BUILTINS_LIB)
510+
511+
$(OBJDIR)/libc.so.a: $(LIBC_SO_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_SO_OBJS)
512+
513+
$(OBJDIR)/libwasi-emulated-mman.so.a: $(LIBWASI_EMULATED_MMAN_SO_OBJS)
514+
515+
$(OBJDIR)/libwasi-emulated-process-clocks.so.a: $(LIBWASI_EMULATED_PROCESS_CLOCKS_SO_OBJS)
516+
517+
$(OBJDIR)/libwasi-emulated-getpid.so.a: $(LIBWASI_EMULATED_GETPID_SO_OBJS)
518+
519+
$(OBJDIR)/libwasi-emulated-signal.so.a: $(LIBWASI_EMULATED_SIGNAL_SO_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS)
520+
475521
$(SYSROOT_LIB)/libc.a: $(LIBC_OBJS)
476522

477523
$(SYSROOT_LIB)/libc-printscan-long-double.a: $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS)
@@ -497,6 +543,8 @@ $(SYSROOT_LIB)/libwasi-emulated-signal.a: $(LIBWASI_EMULATED_SIGNAL_OBJS) $(LIBW
497543
# silently dropping the tail.
498544
$(AR) crs $@ $(wordlist 800, 100000, $(sort $^))
499545

546+
$(PIC_OBJS): CFLAGS += -fPIC -fvisibility=default
547+
500548
$(MUSL_PRINTSCAN_OBJS): CFLAGS += \
501549
-D__wasilibc_printscan_no_long_double \
502550
-D__wasilibc_printscan_full_support_option="\"add -lc-printscan-long-double to the link command\""
@@ -507,15 +555,23 @@ $(MUSL_PRINTSCAN_NO_FLOATING_POINT_OBJS): CFLAGS += \
507555

508556
# TODO: apply -mbulk-memory globally, once
509557
# https://github.com/llvm/llvm-project/issues/52618 is resolved
510-
$(BULK_MEMORY_OBJS): CFLAGS += \
558+
$(BULK_MEMORY_OBJS) $(BULK_MEMORY_SO_OBJS): CFLAGS += \
511559
-mbulk-memory
512560

513-
$(BULK_MEMORY_OBJS): CFLAGS += \
561+
$(BULK_MEMORY_OBJS) $(BULK_MEMORY_SO_OBJS): CFLAGS += \
514562
-DBULK_MEMORY_THRESHOLD=$(BULK_MEMORY_THRESHOLD)
515563

516-
$(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS): CFLAGS += \
564+
$(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_SO_OBJS): CFLAGS += \
517565
-D_WASI_EMULATED_SIGNAL
518566

567+
$(OBJDIR)/%.long-double.pic.o: %.c include_dirs
568+
@mkdir -p "$(@D)"
569+
$(CC) $(CFLAGS) -MD -MP -o $@ -c $<
570+
571+
$(OBJDIR)/%.pic.o: %.c include_dirs
572+
@mkdir -p "$(@D)"
573+
$(CC) $(CFLAGS) -MD -MP -o $@ -c $<
574+
519575
$(OBJDIR)/%.long-double.o: %.c include_dirs
520576
@mkdir -p "$(@D)"
521577
$(CC) $(CFLAGS) -MD -MP -o $@ -c $<
@@ -534,17 +590,17 @@ $(OBJDIR)/%.o: %.s include_dirs
534590

535591
-include $(shell find $(OBJDIR) -name \*.d)
536592

537-
$(DLMALLOC_OBJS): CFLAGS += \
593+
$(DLMALLOC_OBJS) $(DLMALLOC_SO_OBJS): CFLAGS += \
538594
-I$(DLMALLOC_INC)
539595

540-
startup_files $(LIBC_BOTTOM_HALF_ALL_OBJS): CFLAGS += \
596+
startup_files $(LIBC_BOTTOM_HALF_ALL_OBJS) $(LIBC_BOTTOM_HALF_ALL_SO_OBJS): CFLAGS += \
541597
-I$(LIBC_BOTTOM_HALF_HEADERS_PRIVATE) \
542598
-I$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC_INC) \
543599
-I$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC) \
544600
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/include \
545601
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal
546602

547-
$(LIBC_TOP_HALF_ALL_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS) $(MUSL_PRINTSCAN_NO_FLOATING_POINT_OBJS) $(LIBWASI_EMULATED_SIGNAL_MUSL_OBJS): CFLAGS += \
603+
$(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): CFLAGS += \
548604
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/include \
549605
-I$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal \
550606
-I$(LIBC_TOP_HALF_MUSL_DIR)/arch/wasm32 \
@@ -558,7 +614,7 @@ $(LIBC_TOP_HALF_ALL_OBJS) $(MUSL_PRINTSCAN_LONG_DOUBLE_OBJS) $(MUSL_PRINTSCAN_NO
558614
-Wno-dangling-else \
559615
-Wno-unknown-pragmas
560616

561-
$(LIBWASI_EMULATED_PROCESS_CLOCKS_OBJS): CFLAGS += \
617+
$(LIBWASI_EMULATED_PROCESS_CLOCKS_OBJS) $(LIBWASI_EMULATED_PROCESS_CLOCKS_SO_OBJS): CFLAGS += \
562618
-I$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)
563619

564620
# emmalloc uses a lot of pointer type-punning, which is UB under strict aliasing,
@@ -596,6 +652,20 @@ startup_files: include_dirs $(LIBC_BOTTOM_HALF_CRT_OBJS)
596652
mkdir -p "$(SYSROOT_LIB)" && \
597653
cp $(LIBC_BOTTOM_HALF_CRT_OBJS) "$(SYSROOT_LIB)"
598654

655+
# TODO: As of this writing, wasi_thread_start.s uses non-position-independent
656+
# code, and I'm not sure how to make it position-independent. Once we've done
657+
# that, we can enable libc.so for the wasi-threads build.
658+
ifneq ($(THREAD_MODEL), posix)
659+
LIBC_SO = \
660+
$(SYSROOT_LIB)/libc.so \
661+
$(SYSROOT_LIB)/libwasi-emulated-mman.so \
662+
$(SYSROOT_LIB)/libwasi-emulated-process-clocks.so \
663+
$(SYSROOT_LIB)/libwasi-emulated-getpid.so \
664+
$(SYSROOT_LIB)/libwasi-emulated-signal.so
665+
endif
666+
667+
libc_so: include_dirs $(LIBC_SO)
668+
599669
libc: include_dirs \
600670
$(SYSROOT_LIB)/libc.a \
601671
$(SYSROOT_LIB)/libc-printscan-long-double.a \
@@ -645,7 +715,7 @@ check-symbols: startup_files libc
645715
for undef_sym in $$("$(NM)" --undefined-only "$(SYSROOT_LIB)"/libc.a "$(SYSROOT_LIB)"/libc-*.a "$(SYSROOT_LIB)"/*.o \
646716
|grep ' U ' |sed 's/.* U //' |LC_ALL=C sort |uniq); do \
647717
grep -q '\<'$$undef_sym'\>' "$(DEFINED_SYMBOLS)" || echo $$undef_sym; \
648-
done | grep -v "^__mul" > "$(UNDEFINED_SYMBOLS)"
718+
done | grep -E -v "^__mul|__memory_base" > "$(UNDEFINED_SYMBOLS)"
649719
grep '^_*imported_wasi_' "$(UNDEFINED_SYMBOLS)" \
650720
> "$(SYSROOT_LIB)/libc.imports"
651721

@@ -728,4 +798,4 @@ clean:
728798
$(RM) -r "$(OBJDIR)"
729799
$(RM) -r "$(SYSROOT)"
730800

731-
.PHONY: default startup_files libc finish install include_dirs clean
801+
.PHONY: default startup_files libc libc_so finish install include_dirs clean check-symbols

expected/wasm32-wasi-threads/undefined-symbols.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ __imported_wasi_snapshot_preview1_sock_shutdown
6262
__imported_wasi_thread_spawn
6363
__letf2
6464
__lttf2
65-
__main_argc_argv
6665
__netf2
6766
__stack_pointer
6867
__subtf3

expected/wasm32-wasi/undefined-symbols.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ __imported_wasi_snapshot_preview1_sock_send
5959
__imported_wasi_snapshot_preview1_sock_shutdown
6060
__letf2
6161
__lttf2
62-
__main_argc_argv
6362
__netf2
6463
__stack_pointer
6564
__subtf3

libc-bottom-half/headers/public/__errno.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,11 @@
55
extern "C" {
66
#endif
77

8-
#ifdef __cplusplus
9-
extern thread_local int errno;
10-
#else
118
extern _Thread_local int errno;
12-
#endif
139

1410
#define errno errno
1511

1612
#ifdef __cplusplus
1713
}
1814
#endif
19-
2015
#endif

libc-bottom-half/sources/__main_void.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,22 @@
33
#include <sysexits.h>
44

55
// The user's `main` function, expecting arguments.
6+
//
7+
// Note that we make this a weak symbol so that it will have a
8+
// `WASM_SYM_BINDING_WEAK` flag in libc.so, which tells the dynamic linker that
9+
// it need not be defined (e.g. in reactor-style apps with no main function).
10+
// See also the TODO comment on `__main_void` below.
11+
__attribute__((__weak__))
612
int __main_argc_argv(int argc, char *argv[]);
713

814
// If the user's `main` function expects arguments, the compiler will rename
915
// it to `__main_argc_argv`, and this version will get linked in, which
1016
// initializes the argument data and calls `__main_argc_argv`.
17+
//
18+
// TODO: Ideally this function would be defined in a crt*.o file and linked in
19+
// as necessary by the Clang driver. However, moving it to crt1-command.c
20+
// breaks `--no-gc-sections`, so we'll probably need to create a new file
21+
// (e.g. crt0.o or crtend.o) and teach Clang to use it when needed.
1122
__attribute__((__weak__, nodebug))
1223
int __main_void(void) {
1324
__wasi_errno_t err;

libc-top-half/musl/src/internal/locale_impl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ extern hidden const struct __locale_struct __c_dot_utf8_locale;
2828
hidden const struct __locale_map *__get_locale(int, const char *);
2929
hidden const char *__mo_lookup(const void *, size_t, const char *);
3030
hidden const char *__lctrans(const char *, const struct __locale_map *);
31+
#ifdef __wasilibc_unmodified_upstream
3132
hidden const char *__lctrans_cur(const char *);
33+
#else
34+
// We make this visible in the wasi-libc build because
35+
// libwasi-emulated-signal.so needs to import it from libc.so. If we ever
36+
// decide to merge libwasi-emulated-signal.so into libc.so, this will no longer
37+
// be necessary.
38+
const char *__lctrans_cur(const char *);
39+
#endif
3240
hidden const char *__lctrans_impl(const char *, const struct __locale_map *);
3341
hidden int __loc_is_allocated(locale_t);
3442
hidden char *__gettextdomain(void);

0 commit comments

Comments
 (0)