Skip to content

Commit 60b2498

Browse files
authored
wasip2: Improve "command" startup flow (#710)
This commit is an improvement to the WASIp2 target which changes how the interface `wasi:cli/run` is defined and exported. Previously the `wasm32-wasip2` target would define a `_start` wasm export which required the use of the WASIp1-to-WASIp2 adapter to hook it up to `wasi:cli/run`. This meant that despite libc not actually using any WASIp2 APIs the adapter was still used. The purpose of this commit is to change this. The changes made here are: * The linker-level `_start` symbol now has a wasm-level export name of `wasi:cli/run@0.2.0#run`. This means that `_start` isn't a wasm-level export any more, meaning there's nothing for the adapter to import. * The signature of the C-level `_start` function is now different based on WASI version (returns an `int` in WASIp2, nothing on WASIp1) * The `crt1-command.o` object file now contains type information for `wasi:cli/run` (which previously wasn't present anywhere in wasi-libc). This means that when this object is linked in `wit-component` will know what to do after linking. The end result is that binaries are now produced entirely without the WASIp1-to-WASIp2 adapter and linking with `-Wl,--wasi-adapter=none`, for example, produces a working binary. Another minor change made in this commit is that the `crt1-*.c` files are now included in `clang-format` like all other source files. Build-wise this requires the usage of `wasm-tools` at build-time to embed type information in the clang-produced object file for `crt1-command.o`.
1 parent 6036a9f commit 60b2498

7 files changed

Lines changed: 163 additions & 77 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ if (NOT (WASI STREQUAL "p1"))
148148
endif()
149149
include(check-symbols)
150150
include(clang-format)
151+
include(wasm-tools)
151152

152153
# =============================================================================
153154
# Generic top-level build flags/settings

cmake/ba-download.cmake

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ function(ba_download target repo version)
2727

2828
if (target STREQUAL wasmtime)
2929
set(fmt tar.xz)
30-
elseif ((target STREQUAL wasm-component-ld) AND (os STREQUAL windows))
30+
elseif ((os STREQUAL windows) AND
31+
((target STREQUAL wasm-component-ld) OR (target STREQUAL wasm-tools)))
3132
set(fmt zip)
3233
else()
3334
set(fmt tar.gz)
3435
endif()
3536

36-
if (target STREQUAL wit-bindgen)
37+
if (target STREQUAL wit-bindgen OR target STREQUAL wasm-tools)
3738
set(tag v${version})
3839
else()
3940
set(tag ${version})

cmake/wasm-tools.cmake

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Favor `wasm-tools` on the system
2+
find_program(WASM_TOOLS_EXECUTABLE NAMES wasm-tools)
3+
if (NOT WASM_TOOLS_EXECUTABLE)
4+
include(ba-download)
5+
ba_download(
6+
wasm-tools
7+
"https://github.com/bytecodealliance/wasm-tools"
8+
"1.244.0"
9+
)
10+
ExternalProject_Get_Property(wasm-tools SOURCE_DIR)
11+
set(wasm_tools "${SOURCE_DIR}/wasm-tools")
12+
else()
13+
add_custom_target(wasm-tools)
14+
set(wasm_tools ${WASM_TOOLS_EXECUTABLE})
15+
endif()

libc-bottom-half/CMakeLists.txt

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -180,31 +180,94 @@ foreach(obj bottom-half-shared bottom-half-static)
180180
)
181181
endforeach()
182182

183-
add_custom_target(sysroot-startup-objects)
184-
add_dependencies(sysroot sysroot-startup-objects)
185183

186184
# =============================================================================
187185
# startup objects
188186
#
189-
foreach(file crt/crt1.c
190-
crt/crt1-command.c
187+
# This is the logic for building `crt1{,-reactor,-command}.o` objects which
188+
# Clang will link by default based on compiler flags. These are compiled into
189+
# "object libraries" with CMake and then they're copied into the final location
190+
# with adjustments based on WASI versions.
191+
foreach(file crt/crt1-command.c
191192
crt/crt1-reactor.c)
192-
# get the filename without the directory and extension
193-
cmake_path(GET file STEM filename)
193+
cmake_path(GET file STEM stem)
194+
add_library(${stem} OBJECT ${file})
195+
clang_format_target(${stem})
196+
target_link_libraries(${stem} PRIVATE musl-top-half-interface)
197+
set_pic(${stem})
198+
target_compile_options(${stem} PRIVATE -fvisibility=default)
199+
endforeach()
194200

195-
# create a custom target for each file
196-
add_library(${filename}.o OBJECT ${file})
197-
target_link_libraries(${filename}.o PUBLIC musl-top-half-interface)
198-
set_pic(${filename}.o)
199-
target_compile_options(${filename}.o PRIVATE -fvisibility=default)
201+
# crt1-reactor.o is a straight copy of what CMake produces
202+
add_custom_command(
203+
OUTPUT ${SYSROOT_LIB}/crt1-reactor.o
204+
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_OBJECTS:crt1-reactor> ${SYSROOT_LIB}/crt1-reactor.o
205+
DEPENDS crt1-reactor $<TARGET_OBJECTS:crt1-reactor>
206+
)
200207

201-
# add a custom command to compile the file
202-
set(dst ${SYSROOT_LIB}/${filename}.o)
208+
if (WASI STREQUAL "p1")
209+
# wasip1: crt1-command.o is a straight copy of what CMake produces
203210
add_custom_command(
204-
OUTPUT ${dst}
205-
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_OBJECTS:${filename}.o> ${dst}
206-
DEPENDS ${filename}.o
211+
OUTPUT ${SYSROOT_LIB}/crt1-command.o
212+
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_OBJECTS:crt1-command> ${SYSROOT_LIB}/crt1-command.o
213+
DEPENDS crt1-command $<TARGET_OBJECTS:crt1-command>
207214
)
208-
add_custom_target(sysroot-startup-${filename}.o DEPENDS ${dst})
209-
add_dependencies(sysroot-startup-objects sysroot-startup-${filename}.o)
210-
endforeach()
215+
elseif (WASI STREQUAL "p2")
216+
# wasip2: crt1-command.o is modified from what CMake produces to
217+
# additionally have a custom section representing the type information needed
218+
# for its contained export. This is the `wasi:cli/run` interface, for example.
219+
#
220+
# TODO: ideally this wouldn't need `wasm-tools` as that's an extra build tool
221+
# needed here and it's best to slim down dependencies as much as possible.
222+
# This additionally requires downloading/vendoring WITs which isn't great.
223+
# Ideally the type information embedded here would be referenced dierctly in
224+
# `crt1-command.c` itself in the source. That would likely require some
225+
# combination of `__asm__` and `#embed` but I at least couldn't figure out how
226+
# to get that working. I'm also not aware of a way to, in Clang, define a
227+
# global in C that goes into a custom section in the output object. Another
228+
# possible route would be to use the `--relocatable` option of `wasm-ld,` but
229+
# it looks like that loses `__attribute__((export_name("...")))` information
230+
# which this object file relies on. As a workaround `wasm-tools` is used for
231+
# now. If you, dear reader, know of how to do this in the C source itself
232+
# please feel free to open an issue or a PR and maintainers can work with you
233+
# on getting that integrated.
234+
add_custom_command(
235+
OUTPUT ${SYSROOT_LIB}/crt1-command.o
236+
COMMAND
237+
${wasm_tools} component embed
238+
${wasip2_wit_dir}
239+
$<TARGET_OBJECTS:crt1-command>
240+
--world wasi:cli/command@0.2.0
241+
-o ${SYSROOT_LIB}/crt1-command.o
242+
DEPENDS crt1-command wasip2-wits $<TARGET_OBJECTS:crt1-command> wasm-tools
243+
)
244+
elseif (WASI STREQUAL "p3")
245+
add_custom_command(
246+
OUTPUT ${SYSROOT_LIB}/crt1-command.o
247+
COMMAND
248+
${wasm_tools} component embed
249+
${wasip3_wit_dir}
250+
$<TARGET_OBJECTS:crt1-command>
251+
--world wasi:cli/command@0.3.0-rc-2025-09-16
252+
-o ${SYSROOT_LIB}/crt1-command.o
253+
DEPENDS crt1-command wasip3-wits $<TARGET_OBJECTS:crt1-command> wasm-tools
254+
)
255+
else()
256+
message(FATAL_ERROR "Unknown WASI version: ${WASI}")
257+
endif()
258+
259+
# Provide a plain crt1.o for toolchain compatibility, identical to
260+
# `crt1-command.c`
261+
add_custom_command(
262+
OUTPUT ${SYSROOT_LIB}/crt1.o
263+
COMMAND ${CMAKE_COMMAND} -E copy ${SYSROOT_LIB}/crt1-command.o ${SYSROOT_LIB}/crt1.o
264+
DEPENDS ${SYSROOT_LIB}/crt1-command.o
265+
)
266+
267+
add_custom_target(sysroot-startup-objects
268+
DEPENDS
269+
${SYSROOT_LIB}/crt1-reactor.o
270+
${SYSROOT_LIB}/crt1-command.o
271+
${SYSROOT_LIB}/crt1.o
272+
)
273+
add_dependencies(sysroot sysroot-startup-objects)

libc-bottom-half/crt/crt1-command.c

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,54 +9,64 @@ extern void __wasm_call_ctors(void);
99
extern int __main_void(void);
1010
extern void __wasm_call_dtors(void);
1111

12-
__attribute__((export_name("_start")))
13-
void _start(void) {
14-
// Commands should only be called once per instance. This simple check
15-
// ensures that the `_start` function isn't started more than once.
16-
//
17-
// We use `volatile` here to prevent the store to `started` from being
18-
// sunk past any subsequent code, and to prevent any compiler from
19-
// optimizing based on the knowledge that `_start` is the program
20-
// entrypoint.
12+
#if defined(__wasip1__)
13+
__attribute__((export_name("_start"))) void _start(void)
14+
#elif defined(__wasip2__)
15+
// Note that this is manually doing what `wit-bindgen` might otherwise be
16+
// doing. Given the special nature of this symbol this skip the typical
17+
// `wit-bindgen` rigamarole and the signature of this function is simple enough
18+
// that this shouldn't be too problematic (in theory).
19+
__attribute__((export_name("wasi:cli/run@0.2.0#run"))) int _start(void)
20+
#elif defined(__wasip3__)
21+
__attribute__((export_name("wasi:cli/run@0.3.0-rc-2025-09-16#run"))) int
22+
_start(void)
23+
#else
24+
#error "Unsupported WASI version"
25+
#endif
26+
{
27+
// Commands should only be called once per instance. This simple check
28+
// ensures that the `_start` function isn't started more than once.
29+
//
30+
// We use `volatile` here to prevent the store to `started` from being
31+
// sunk past any subsequent code, and to prevent any compiler from
32+
// optimizing based on the knowledge that `_start` is the program
33+
// entrypoint.
2134
#ifdef _REENTRANT
22-
static volatile _Atomic int started = 0;
23-
int expected = 0;
24-
if (!atomic_compare_exchange_strong(&started, &expected, 1)) {
25-
__builtin_trap();
26-
}
35+
static volatile _Atomic int started = 0;
36+
int expected = 0;
37+
if (!atomic_compare_exchange_strong(&started, &expected, 1)) {
38+
__builtin_trap();
39+
}
2740
#else
28-
static volatile int started = 0;
29-
if (started != 0) {
30-
__builtin_trap();
31-
}
32-
started = 1;
41+
static volatile int started = 0;
42+
if (started != 0) {
43+
__builtin_trap();
44+
}
45+
started = 1;
3346
#endif
3447

35-
__wasi_init_tp();
48+
__wasi_init_tp();
3649

37-
// The linker synthesizes this to call constructors.
38-
__wasm_call_ctors();
50+
// The linker synthesizes this to call constructors.
51+
__wasm_call_ctors();
3952

40-
// Call `__main_void` which will either be the application's zero-argument
41-
// `__main_void` function or a libc routine which obtains the command-line
42-
// arguments and calls `__main_argv_argc`.
43-
int r = __main_void();
53+
// Call `__main_void` which will either be the application's zero-argument
54+
// `__main_void` function or a libc routine which obtains the command-line
55+
// arguments and calls `__main_argv_argc`.
56+
int r = __main_void();
4457

45-
// Call atexit functions, destructors, stdio cleanup, etc.
46-
__wasm_call_dtors();
58+
// Call atexit functions, destructors, stdio cleanup, etc.
59+
__wasm_call_dtors();
4760

48-
// If main exited successfully, just return, otherwise call
49-
// `__wasi_proc_exit`.
61+
// If main exited successfully, just return, otherwise call
62+
// `__wasi_proc_exit`.
5063
#if defined(__wasip1__)
51-
if (r != 0) {
52-
__wasi_proc_exit(r);
53-
}
64+
if (r != 0) {
65+
__wasi_proc_exit(r);
66+
}
5467
#elif defined(__wasip2__) || defined(__wasip3__)
55-
if (r != 0) {
56-
exit_result_void_void_t status = { .is_err = true };
57-
exit_exit(&status);
58-
}
68+
return r != 0;
5969
#else
60-
# error "Unsupported WASI version"
70+
#error "Unsupported WASI version"
6171
#endif
6272
}

libc-bottom-half/crt/crt1-reactor.c

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@
44
extern void __wasi_init_tp(void);
55
extern void __wasm_call_ctors(void);
66

7-
__attribute__((export_name("_initialize")))
8-
void _initialize(void) {
7+
__attribute__((export_name("_initialize"))) void _initialize(void) {
98
#if defined(_REENTRANT)
10-
static volatile atomic_int initialized = 0;
11-
int expected = 0;
12-
if (!atomic_compare_exchange_strong(&initialized, &expected, 1)) {
13-
__builtin_trap();
14-
}
9+
static volatile atomic_int initialized = 0;
10+
int expected = 0;
11+
if (!atomic_compare_exchange_strong(&initialized, &expected, 1)) {
12+
__builtin_trap();
13+
}
1514
#else
16-
static volatile int initialized = 0;
17-
if (initialized != 0) {
18-
__builtin_trap();
19-
}
20-
initialized = 1;
15+
static volatile int initialized = 0;
16+
if (initialized != 0) {
17+
__builtin_trap();
18+
}
19+
initialized = 1;
2120
#endif
22-
__wasi_init_tp();
21+
__wasi_init_tp();
2322

24-
// The linker synthesizes this to call constructors.
25-
__wasm_call_ctors();
23+
// The linker synthesizes this to call constructors.
24+
__wasm_call_ctors();
2625
}

libc-bottom-half/crt/crt1.c

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)