diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c0550c26..3da53accd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,7 @@ if (NOT (WASI STREQUAL "p1")) endif() include(check-symbols) include(clang-format) +include(wasm-tools) # ============================================================================= # Generic top-level build flags/settings diff --git a/cmake/ba-download.cmake b/cmake/ba-download.cmake index c89a8449c..c3e2dc46f 100644 --- a/cmake/ba-download.cmake +++ b/cmake/ba-download.cmake @@ -27,13 +27,14 @@ function(ba_download target repo version) if (target STREQUAL wasmtime) set(fmt tar.xz) - elseif ((target STREQUAL wasm-component-ld) AND (os STREQUAL windows)) + elseif ((os STREQUAL windows) AND + ((target STREQUAL wasm-component-ld) OR (target STREQUAL wasm-tools))) set(fmt zip) else() set(fmt tar.gz) endif() - if (target STREQUAL wit-bindgen) + if (target STREQUAL wit-bindgen OR target STREQUAL wasm-tools) set(tag v${version}) else() set(tag ${version}) diff --git a/cmake/wasm-tools.cmake b/cmake/wasm-tools.cmake new file mode 100644 index 000000000..6018a9918 --- /dev/null +++ b/cmake/wasm-tools.cmake @@ -0,0 +1,15 @@ +# Favor `wasm-tools` on the system +find_program(WASM_TOOLS_EXECUTABLE NAMES wasm-tools) +if (NOT WASM_TOOLS_EXECUTABLE) + include(ba-download) + ba_download( + wasm-tools + "https://github.com/bytecodealliance/wasm-tools" + "1.244.0" + ) + ExternalProject_Get_Property(wasm-tools SOURCE_DIR) + set(wasm_tools "${SOURCE_DIR}/wasm-tools") +else() + add_custom_target(wasm-tools) + set(wasm_tools ${WASM_TOOLS_EXECUTABLE}) +endif() diff --git a/libc-bottom-half/CMakeLists.txt b/libc-bottom-half/CMakeLists.txt index 1561266ed..b6445f924 100644 --- a/libc-bottom-half/CMakeLists.txt +++ b/libc-bottom-half/CMakeLists.txt @@ -180,31 +180,94 @@ foreach(obj bottom-half-shared bottom-half-static) ) endforeach() -add_custom_target(sysroot-startup-objects) -add_dependencies(sysroot sysroot-startup-objects) # ============================================================================= # startup objects # -foreach(file crt/crt1.c - crt/crt1-command.c +# This is the logic for building `crt1{,-reactor,-command}.o` objects which +# Clang will link by default based on compiler flags. These are compiled into +# "object libraries" with CMake and then they're copied into the final location +# with adjustments based on WASI versions. +foreach(file crt/crt1-command.c crt/crt1-reactor.c) - # get the filename without the directory and extension - cmake_path(GET file STEM filename) + cmake_path(GET file STEM stem) + add_library(${stem} OBJECT ${file}) + clang_format_target(${stem}) + target_link_libraries(${stem} PRIVATE musl-top-half-interface) + set_pic(${stem}) + target_compile_options(${stem} PRIVATE -fvisibility=default) +endforeach() - # create a custom target for each file - add_library(${filename}.o OBJECT ${file}) - target_link_libraries(${filename}.o PUBLIC musl-top-half-interface) - set_pic(${filename}.o) - target_compile_options(${filename}.o PRIVATE -fvisibility=default) +# crt1-reactor.o is a straight copy of what CMake produces +add_custom_command( + OUTPUT ${SYSROOT_LIB}/crt1-reactor.o + COMMAND ${CMAKE_COMMAND} -E copy $ ${SYSROOT_LIB}/crt1-reactor.o + DEPENDS crt1-reactor $ +) - # add a custom command to compile the file - set(dst ${SYSROOT_LIB}/${filename}.o) +if (WASI STREQUAL "p1") + # wasip1: crt1-command.o is a straight copy of what CMake produces add_custom_command( - OUTPUT ${dst} - COMMAND ${CMAKE_COMMAND} -E copy $ ${dst} - DEPENDS ${filename}.o + OUTPUT ${SYSROOT_LIB}/crt1-command.o + COMMAND ${CMAKE_COMMAND} -E copy $ ${SYSROOT_LIB}/crt1-command.o + DEPENDS crt1-command $ ) - add_custom_target(sysroot-startup-${filename}.o DEPENDS ${dst}) - add_dependencies(sysroot-startup-objects sysroot-startup-${filename}.o) -endforeach() +elseif (WASI STREQUAL "p2") + # wasip2: crt1-command.o is modified from what CMake produces to + # additionally have a custom section representing the type information needed + # for its contained export. This is the `wasi:cli/run` interface, for example. + # + # TODO: ideally this wouldn't need `wasm-tools` as that's an extra build tool + # needed here and it's best to slim down dependencies as much as possible. + # This additionally requires downloading/vendoring WITs which isn't great. + # Ideally the type information embedded here would be referenced dierctly in + # `crt1-command.c` itself in the source. That would likely require some + # combination of `__asm__` and `#embed` but I at least couldn't figure out how + # to get that working. I'm also not aware of a way to, in Clang, define a + # global in C that goes into a custom section in the output object. Another + # possible route would be to use the `--relocatable` option of `wasm-ld,` but + # it looks like that loses `__attribute__((export_name("...")))` information + # which this object file relies on. As a workaround `wasm-tools` is used for + # now. If you, dear reader, know of how to do this in the C source itself + # please feel free to open an issue or a PR and maintainers can work with you + # on getting that integrated. + add_custom_command( + OUTPUT ${SYSROOT_LIB}/crt1-command.o + COMMAND + ${wasm_tools} component embed + ${wasip2_wit_dir} + $ + --world wasi:cli/command@0.2.0 + -o ${SYSROOT_LIB}/crt1-command.o + DEPENDS crt1-command wasip2-wits $ wasm-tools + ) +elseif (WASI STREQUAL "p3") + add_custom_command( + OUTPUT ${SYSROOT_LIB}/crt1-command.o + COMMAND + ${wasm_tools} component embed + ${wasip3_wit_dir} + $ + --world wasi:cli/command@0.3.0-rc-2025-09-16 + -o ${SYSROOT_LIB}/crt1-command.o + DEPENDS crt1-command wasip3-wits $ wasm-tools + ) +else() + message(FATAL_ERROR "Unknown WASI version: ${WASI}") +endif() + +# Provide a plain crt1.o for toolchain compatibility, identical to +# `crt1-command.c` +add_custom_command( + OUTPUT ${SYSROOT_LIB}/crt1.o + COMMAND ${CMAKE_COMMAND} -E copy ${SYSROOT_LIB}/crt1-command.o ${SYSROOT_LIB}/crt1.o + DEPENDS ${SYSROOT_LIB}/crt1-command.o +) + +add_custom_target(sysroot-startup-objects + DEPENDS + ${SYSROOT_LIB}/crt1-reactor.o + ${SYSROOT_LIB}/crt1-command.o + ${SYSROOT_LIB}/crt1.o +) +add_dependencies(sysroot sysroot-startup-objects) diff --git a/libc-bottom-half/crt/crt1-command.c b/libc-bottom-half/crt/crt1-command.c index 69f340f4b..63e556779 100644 --- a/libc-bottom-half/crt/crt1-command.c +++ b/libc-bottom-half/crt/crt1-command.c @@ -9,54 +9,64 @@ extern void __wasm_call_ctors(void); extern int __main_void(void); extern void __wasm_call_dtors(void); -__attribute__((export_name("_start"))) -void _start(void) { - // Commands should only be called once per instance. This simple check - // ensures that the `_start` function isn't started more than once. - // - // We use `volatile` here to prevent the store to `started` from being - // sunk past any subsequent code, and to prevent any compiler from - // optimizing based on the knowledge that `_start` is the program - // entrypoint. +#if defined(__wasip1__) +__attribute__((export_name("_start"))) void _start(void) +#elif defined(__wasip2__) +// Note that this is manually doing what `wit-bindgen` might otherwise be +// doing. Given the special nature of this symbol this skip the typical +// `wit-bindgen` rigamarole and the signature of this function is simple enough +// that this shouldn't be too problematic (in theory). +__attribute__((export_name("wasi:cli/run@0.2.0#run"))) int _start(void) +#elif defined(__wasip3__) +__attribute__((export_name("wasi:cli/run@0.3.0-rc-2025-09-16#run"))) int +_start(void) +#else +#error "Unsupported WASI version" +#endif +{ + // Commands should only be called once per instance. This simple check + // ensures that the `_start` function isn't started more than once. + // + // We use `volatile` here to prevent the store to `started` from being + // sunk past any subsequent code, and to prevent any compiler from + // optimizing based on the knowledge that `_start` is the program + // entrypoint. #ifdef _REENTRANT - static volatile _Atomic int started = 0; - int expected = 0; - if (!atomic_compare_exchange_strong(&started, &expected, 1)) { - __builtin_trap(); - } + static volatile _Atomic int started = 0; + int expected = 0; + if (!atomic_compare_exchange_strong(&started, &expected, 1)) { + __builtin_trap(); + } #else - static volatile int started = 0; - if (started != 0) { - __builtin_trap(); - } - started = 1; + static volatile int started = 0; + if (started != 0) { + __builtin_trap(); + } + started = 1; #endif - __wasi_init_tp(); + __wasi_init_tp(); - // The linker synthesizes this to call constructors. - __wasm_call_ctors(); + // The linker synthesizes this to call constructors. + __wasm_call_ctors(); - // Call `__main_void` which will either be the application's zero-argument - // `__main_void` function or a libc routine which obtains the command-line - // arguments and calls `__main_argv_argc`. - int r = __main_void(); + // Call `__main_void` which will either be the application's zero-argument + // `__main_void` function or a libc routine which obtains the command-line + // arguments and calls `__main_argv_argc`. + int r = __main_void(); - // Call atexit functions, destructors, stdio cleanup, etc. - __wasm_call_dtors(); + // Call atexit functions, destructors, stdio cleanup, etc. + __wasm_call_dtors(); - // If main exited successfully, just return, otherwise call - // `__wasi_proc_exit`. + // If main exited successfully, just return, otherwise call + // `__wasi_proc_exit`. #if defined(__wasip1__) - if (r != 0) { - __wasi_proc_exit(r); - } + if (r != 0) { + __wasi_proc_exit(r); + } #elif defined(__wasip2__) || defined(__wasip3__) - if (r != 0) { - exit_result_void_void_t status = { .is_err = true }; - exit_exit(&status); - } + return r != 0; #else -# error "Unsupported WASI version" +#error "Unsupported WASI version" #endif } diff --git a/libc-bottom-half/crt/crt1-reactor.c b/libc-bottom-half/crt/crt1-reactor.c index 3e9a27b98..bd693245e 100644 --- a/libc-bottom-half/crt/crt1-reactor.c +++ b/libc-bottom-half/crt/crt1-reactor.c @@ -4,23 +4,22 @@ extern void __wasi_init_tp(void); extern void __wasm_call_ctors(void); -__attribute__((export_name("_initialize"))) -void _initialize(void) { +__attribute__((export_name("_initialize"))) void _initialize(void) { #if defined(_REENTRANT) - static volatile atomic_int initialized = 0; - int expected = 0; - if (!atomic_compare_exchange_strong(&initialized, &expected, 1)) { - __builtin_trap(); - } + static volatile atomic_int initialized = 0; + int expected = 0; + if (!atomic_compare_exchange_strong(&initialized, &expected, 1)) { + __builtin_trap(); + } #else - static volatile int initialized = 0; - if (initialized != 0) { - __builtin_trap(); - } - initialized = 1; + static volatile int initialized = 0; + if (initialized != 0) { + __builtin_trap(); + } + initialized = 1; #endif - __wasi_init_tp(); + __wasi_init_tp(); - // The linker synthesizes this to call constructors. - __wasm_call_ctors(); + // The linker synthesizes this to call constructors. + __wasm_call_ctors(); } diff --git a/libc-bottom-half/crt/crt1.c b/libc-bottom-half/crt/crt1.c deleted file mode 100644 index cbbe07379..000000000 --- a/libc-bottom-half/crt/crt1.c +++ /dev/null @@ -1,3 +0,0 @@ -// We compile a plain crt1.o for toolchain compatibility, but it's -// identical to crt1-command.o. -#include "crt1-command.c"