diff --git a/.gitignore b/.gitignore index d98e81f0d..2968cf592 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build .vscode dist +test-builds \ No newline at end of file diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 000000000..045155596 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,107 @@ +# WASIX libc tests + +This repo has a small WASIX-focused test suite under `test/wasix`. These tests +compile C/C++ sources to WASI/WASIX wasm and run them with Wasmer. + +The key things that make the suite pass are: + +- **Use a non‑EH sysroot + toolchain** (no wasm exceptions). This mirrors the + working setup in `wasix-tests/wasmer/tests/c-wasi-tests/Makefile`. +- **Do not force `--enable-all`** when running Wasmer. Let Wasmer detect module + features. +- **Pick a Wasmer binary with WASIX support** (the wasix-tests repo build works). + +## Quickstart (known‑good) + +```bash +export PATH="$HOME/.wasixcc/llvm/bin:$PATH" +export CC="$HOME/.wasixcc/llvm/bin/clang" +export CXX="$HOME/.wasixcc/llvm/bin/clang++" +export WASIX_TRIPLE=wasm32-wasi + +# Non‑EH toolchain (avoids legacy-exceptions + libunwind issues) +export TOOLCHAIN="$(pwd)/tools/clang-wasix-pic.cmake_toolchain" + +# Non‑EH sysroot (from wasixcc) +export WASIX_SYSROOT_PIC="$HOME/.wasixcc/sysroot/sysroot" + +# Wasmer runtime + backend +export WASMER_BACKEND_FLAG=--llvm +# Optional: use a specific Wasmer binary (e.g. from wasix-tests) +# export WASMER_BIN="/Users/fessguid/Projects/wasix-tests/wasmer/target/release/wasmer" + +./test/wasix/run_tests.sh +``` + +## Single test + +```bash +./test/wasix/run_tests.sh cpp_executable +``` + +## Why this toolchain/sysroot? + +The tests match the c‑wasi‑tests Makefile in the `wasix-tests` repo: + +- uses `--target=wasm32-wasi` + `-matomics -mbulk-memory -mmutable-globals -pthread` +- **does not enable wasm exceptions** +- runs under Wasmer with `--enable-threads` and standard WASI caps + +Using the non‑EH sysroot (`~/.wasixcc/sysroot/sysroot`) avoids: + +- `legacy_exceptions` validation errors in Wasmer +- missing `libunwind` when using the non‑EH sysroot +- `__c_longjmp` tag import expectations + +## Common problems & fixes + +### 1) `legacy_exceptions feature required for try instruction` + +You are compiling with wasm EH enabled (or using an EH sysroot). Switch to +`tools/clang-wasix-pic.cmake_toolchain` and `~/.wasixcc/sysroot/sysroot`. + +### 2) `unknown import env.__c_longjmp` / `__cpp_exception` + +Your module expects wasm EH tags. Either: + +- use the non‑EH toolchain+sysroot (preferred for these tests), or +- ensure Wasmer supports wasm EH tags and you use an EH sysroot. + +### 3) `wasm-ld: unable to find library -lunwind` + +You are using a toolchain that links `-lunwind` with a sysroot that does not +provide `libunwind` (the non‑EH sysroot). Use the non‑EH toolchain file or an +EH sysroot that includes `libunwind`. + +### 4) CMake warning: `System is unknown to cmake, create: Platform/WASI` + +This is a CMake warning and can be ignored for these tests. + +## Updating libc for local changes + +If you change libc sources in this repo (e.g. `sigsetjmp`), you need those +objects in the sysroot used by the tests. + +One practical path on macOS: + +1) Build libc (C only) in this repo: + - `build32.sh` builds libc into `sysroot/` but may fail later while building + libc++. That is OK if you only need `libc.a`. + - If `sed -i` fails on macOS, use GNU sed (`gsed`) or run the script with a + small sed wrapper. +2) Replace the libc in the wasixcc sysroot: + - copy `sysroot/lib/wasm32-wasi/libc.a` → `$HOME/.wasixcc/sysroot/sysroot/lib/wasm32-wasi/libc.a` +3) Re-run `test/wasix/run_tests.sh` with the same env from Quickstart. + +## Toolchain files + +- `tools/clang-wasix.cmake_toolchain` — enables wasm EH (`-fwasm-exceptions`) and + links `libunwind`. Use with an EH sysroot (e.g. `sysroot-eh`) if needed. +- `tools/clang-wasix-pic.cmake_toolchain` — **non‑EH** toolchain used for tests. + +## Environment variables used by the test runner + +- `TOOLCHAIN` — CMake toolchain file used to build each test. +- `WASIX_SYSROOT_PIC` — sysroot path passed to CMake. +- `WASMER_BACKEND_FLAG` — e.g. `--llvm` (default in test scripts). +- `WASMER_BIN` — optional path to a Wasmer binary (defaults to `wasmer`). diff --git a/WASIX-libc-wasi-usage-categories.md b/WASIX-libc-wasi-usage-categories.md new file mode 100644 index 000000000..58de4f55e --- /dev/null +++ b/WASIX-libc-wasi-usage-categories.md @@ -0,0 +1,416 @@ +# WASI/WASIX-backed libc functions by category + +Total functions: 215 + +## Control Flow/Setjmp (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| longjmp | setjmp.h | wasix-libc/libc-top-half/musl/src/setjmp/setjmplongjmp.c | __wasi_stack_restore | +| setjmp | setjmp.h | wasix-libc/libc-top-half/musl/src/setjmp/setjmplongjmp.c | __wasi_stack_checkpoint | +| siglongjmp | setjmp.h | wasix-libc/libc-top-half/musl/src/signal/siglongjmp.c | __wasi_stack_restore | +| sigsetjmp | setjmp.h | wasix-libc/libc-top-half/musl/src/setjmp/setjmplongjmp.c | __wasi_stack_checkpoint | + +### Progress + +- Ported tests: NetBSD `t_setjmp.c`, `t_threadjmp.c` + bionic `setjmp_test.cpp` (subset) +- New integration test: `wasix-libc/test/wasix/c_setjmp` (covers `setjmp`, `longjmp`, `sigsetjmp`, `siglongjmp`) +- Status (2026-01-30): passing after implementing `sigsetjmp(..., 1)` mask save/restore and tracking masks in `pthread_sigmask` (requires rebuilding libc in the sysroot) +- Notes: `_setjmp`/`_longjmp` not exported in wasm32-wasi symbols, so they were not covered + +## Dynamic Loading (3) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| dlclose | dlfcn.h | wasix-libc/libc-top-half/musl/src/ldso/dlclose.c | __wasi_dl_invalid_handle | +| dlopen | dlfcn.h | wasix-libc/libc-top-half/musl/src/ldso/dlopen.c | __wasi_dlopen | +| dlsym | dlfcn.h | wasix-libc/libc-top-half/musl/src/ldso/dlsym.c | __wasi_dlsym | + +### Progress + +- Ported tests: Wasmer `tests/wasix/dlopen`, `tests/wasix/dl-cache`, `tests/wasix/dl-needed`, Wasmer `tests/c-wasi-tests/dlopen`, FreeBSD `lib/libc/tests/gen/dlopen_empty_test.c`, FreeBSD `libexec/rtld-elf/tests/dlopen_test.c` (dlsym after dlclose), bionic `tests/dlfcn_test.cpp` (RTLD_NOLOAD, RTLD_LOCAL/GLOBAL + RTLD_DEFAULT, empty-symbol dlsym, main-handle global lookup) +- New integration test: `wasix-libc/test/wasix/dynamic_loading` (cases: basic, cache, data-export, needed) +- Status (2026-01-31): passing +- Fixes applied: libc now tracks dlopen handles/flags, enforces `RTLD_NOLOAD` (no implicit load), resolves `RTLD_DEFAULT` via main + RTLD_GLOBAL handles, and rejects `dlsym` on closed/invalid handles. This aligns WASI host calls with POSIX semantics expected by plugin/extension code. + +## Filesystem/Dirent (3) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| fdopendir | dirent.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/dirent/fdopendir.c | __wasi_fd_readdir | +| opendir | dirent.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_readdir, __wasi_getcwd, __wasi_path_open2 | +| readdir | dirent.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c | __wasi_fd_readdir, __wasi_getcwd, __wasi_path_filestat_get | + +### Progress + +- Ported tests: FreeBSD `lib/libc/tests/gen/opendir_test.c` (basic dir listing, ENOENT/ENOTDIR, fdopendir), glibc `opendir-tst1.c` (non-dir check), glibc `tst-fdopendir2.c` (fdopendir on regular file), glibc `bug-readdir1.c` (readdir after closing dirfd), bionic `dirent_test.cpp` (subset: fdopendir invalid/ownership, opendir invalid, readdir end-of-dir errno behavior) +- New integration test: `wasix-libc/test/wasix/c_dirent` +- Status (2026-01-31): passing (with non‑EH sysroot) +- Notes: test focuses on `opendir`, `fdopendir`, `readdir` behavior and error codes using a synthetic directory; no OS-specific `/proc` or FUSE dependencies + +## Filesystem/File I/O (50) + +### Group 1: Process control & snapshot (5) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| __vfork_internal | unistd.h | wasix-libc/libc-top-half/musl/src/process/vfork.c | __wasi_proc_fork_env | +| _fork_internal | unistd.h | wasix-libc/libc-top-half/musl/src/process/fork.c | __wasi_callback_signal, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| fexecve | unistd.h | wasix-libc/libc-top-half/musl/src/process/fexecve.c | __wasi_proc_exec3, __wasi_stack_restore | +| setgroups | grp.h, unistd.h | wasix-libc/libc-top-half/musl/src/linux/setgroups.c | __wasi_callback_signal, __wasi_futex_wait | +| wasix_proc_snapshot | unistd.h | wasix-libc/libc-top-half/musl/src/process/wasix_proc_snapshot.c | __wasi_proc_snapshot | + +### Progress + +- Ported tests: LTP `fork01.c` (basic fork + pid/wait), LTP `fork04.c` (env inheritance), LTP `fork10.c` (shared fd offset), LTP `vfork01.c` (attribute parity), LTP `setgroups01.c` (basic call), glibc `posix/tst-fexecve.c`, NetBSD `tests/lib/libc/c063/t_fexecve.c`, bionic `tests/unistd_test.cpp` (fexecve failure + args/env) +- New integration test: `wasix-libc/test/wasix/c_process_control` (subtests: fork_basic, fork_env_inheritance, fork_fd_offset_shared, fexecve_errors, fexecve_success, wasix_proc_snapshot, vfork_attributes, setgroups_basic) +- Status (2026-01-31): all subtests passing (fork_basic/env/offset, fexecve_errors/success, wasix_proc_snapshot, vfork_attributes, setgroups_basic) +- Notes: getgroups is not exported in wasix-libc, so the setgroups test uses setgroups(0, NULL) as the basic check; vfork_attributes keeps shared state in globals to avoid stack clobber after vfork + +### Group 2: Working directory & access (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| getcwd | unistd.h | wasix-libc/libc-bottom-half/sources/getcwd.c | __wasi_getcwd | +| chdir | unistd.h | wasix-libc/libc-bottom-half/sources/chdir.c | __wasi_chdir, __wasi_getcwd, __wasi_path_filestat_get | +| access | unistd.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_filestat_get | +| faccessat | unistd.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_filestat_get | + +### Group 3: Terminal/TTY (3) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| getpass | unistd.h | wasix-libc/libc-top-half/musl/src/legacy/getpass.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_fd_read, __wasi_getcwd, __wasi_path_open2, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcgetpgrp | unistd.h | wasix-libc/libc-top-half/musl/src/unistd/tcgetpgrp.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcsetpgrp | unistd.h | wasix-libc/libc-top-half/musl/src/unistd/tcsetpgrp.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | + +### Group 4: FD duplication & close (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| close | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/close.c | __wasi_fd_close | +| dup | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/dup.c | __wasi_fd_dup | +| dup2 | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/dup2.c | __wasi_fd_renumber | +| dup3 | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/dup3.c | __wasi_fd_renumber | + +### Group 5: FD control & pipes (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| fcntl | fcntl.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/fcntl/fcntl.c | __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags | +| pipe | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/pipe.c | __wasi_fd_pipe | +| pipe2 | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/pipe2.c | __wasi_fd_pipe | +| __wasilibc_tell | unistd.h, wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_tell.c | __wasi_fd_tell | + +### Group 6: Open & create directories (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| open | fcntl.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_open2 | +| openat | fcntl.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_open2 | +| mkdir | sys/stat.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_create_directory | +| mkdirat | sys/stat.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_create_directory | + +### Group 7: Remove/unlink & readlink (5) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| rmdir | unistd.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_remove_directory | +| unlink | unistd.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_unlink_file | +| unlinkat | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/unlinkat.c | __wasi_getcwd, __wasi_path_remove_directory, __wasi_path_unlink_file | +| readlink | unistd.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_readlink | +| readlinkat | unistd.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_readlink | + +### Group 8: Links & symlinks (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| link | unistd.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_link | +| linkat | unistd.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_link | +| symlink | unistd.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_symlink | +| symlinkat | unistd.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_symlink | + +### Group 9: Stat & metadata (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| stat | sys/stat.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_filestat_get | +| lstat | sys/stat.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_filestat_get | +| fstat | sys/stat.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstat.c | __wasi_fd_filestat_get | +| fstatat | sys/stat.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_filestat_get | + +### Group 10: Timestamps & sync (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| futimens | sys/stat.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/stat/futimens.c | __wasi_fd_filestat_set_times | +| utimensat | sys/stat.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_filestat_set_times | +| fsync | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/fsync.c | __wasi_fd_sync | +| fdatasync | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/fdatasync.c | __wasi_fd_datasync | + +### Group 11: Allocation & size (4) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| ftruncate | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/ftruncate.c | __wasi_fd_filestat_set_size | +| truncate | unistd.h | wasix-libc/libc-bottom-half/sources/truncate.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_filestat_set_size, __wasi_getcwd, __wasi_path_open2 | +| posix_fallocate | fcntl.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fallocate.c | __wasi_fd_allocate | +| posix_fadvise | fcntl.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/fcntl/posix_fadvise.c | __wasi_fd_advise | + +### Group 12: Data I/O (5) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| read | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/read.c | __wasi_fd_read | +| write | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/write.c | __wasi_fd_write | +| pread | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/pread.c | __wasi_fd_fdstat_get, __wasi_fd_pread | +| pwrite | unistd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/pwrite.c | __wasi_fd_fdstat_get, __wasi_fd_pwrite | +| sendfile | sys/sendfile.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/sendfile.c | __wasi_fd_tell, __wasi_sock_send_file | + +## I/O Multiplexing (10) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| epoll_create | sys/epoll.h | wasix-libc/libc-top-half/musl/src/linux/epoll.c | __wasi_epoll_create | +| epoll_create1 | sys/epoll.h | wasix-libc/libc-top-half/musl/src/linux/epoll.c | __wasi_epoll_create | +| epoll_ctl | sys/epoll.h | wasix-libc/libc-top-half/musl/src/linux/epoll.c | __wasi_epoll_ctl | +| epoll_pwait | sys/epoll.h | wasix-libc/libc-top-half/musl/src/linux/epoll.c | __wasi_epoll_wait | +| epoll_wait | sys/epoll.h | wasix-libc/libc-top-half/musl/src/linux/epoll.c | __wasi_epoll_wait | +| eventfd | sys/eventfd.h | wasix-libc/libc-top-half/musl/src/linux/eventfd.c | __wasi_fd_event | +| eventfd_read | sys/eventfd.h | wasix-libc/libc-top-half/musl/src/linux/eventfd.c | __wasi_fd_read | +| eventfd_write | sys/eventfd.h | wasix-libc/libc-top-half/musl/src/linux/eventfd.c | __wasi_fd_write | +| poll | poll.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/poll/poll.c | __wasi_poll_oneoff | +| pselect | sys/select.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c | __wasi_poll_oneoff | + +## Locale/Messages (3) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| catclose | nl_types.h | wasix-libc/libc-top-half/musl/src/locale/catclose.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_pwrite | +| catopen | nl_types.h | wasix-libc/libc-top-half/musl/src/locale/catopen.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_pwrite | +| fmtmsg | fmtmsg.h | wasix-libc/libc-top-half/musl/src/misc/fmtmsg.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_open2 | + +## Misc (13) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| closelog | syslog.h | wasix-libc/libc-top-half/musl/src/misc/syslog.c | __wasi_fd_close | +| mmap | sys/mman.h | wasix-libc/libc-bottom-half/mman/mman.c | __wasi_fd_dup, __wasi_fd_fdstat_get, __wasi_fd_pread | +| msync | sys/mman.h | wasix-libc/libc-bottom-half/mman/mman.c | __wasi_fd_fdstat_get, __wasi_fd_pwrite | +| munmap | sys/mman.h | wasix-libc/libc-bottom-half/mman/mman.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_pwrite | +| openlog | syslog.h | wasix-libc/libc-top-half/musl/src/misc/syslog.c | __wasi_sock_connect, __wasi_sock_open | +| preadv | sys/uio.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/uio/preadv.c | __wasi_fd_pread | +| pwritev | sys/uio.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/uio/pwritev.c | __wasi_fd_pwrite | +| readv | sys/uio.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/uio/readv.c | __wasi_fd_read | +| syslog | syslog.h | wasix-libc/libc-top-half/musl/src/misc/syslog.c | __wasi_clock_time_get, __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_write, __wasi_getcwd, __wasi_path_open2, __wasi_proc_id, __wasi_sock_connect, __wasi_sock_open, __wasi_sock_send | +| thrd_yield | threads.h | wasix-libc/libc-top-half/musl/src/thread/thrd_yield.c | __wasi_sched_yield | +| times | sys/times.h | wasix-libc/libc-top-half/musl/src/time/times.c | __wasi_clock_time_get | +| utime | utime.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_filestat_set_times | +| writev | sys/uio.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/uio/writev.c | __wasi_fd_write | + +## Process/Exit (1) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| abort | stdlib.h | wasix-libc/libc-top-half/musl/src/exit/abort.c | __wasi_callback_signal, __wasi_proc_exit2, __wasi_stack_restore, __wasi_thread_signal | + +## Process/Spawn (19) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| _Exit | stdlib.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/stdlib/_Exit.c | __wasi_proc_exit2, __wasi_stack_restore | +| _Exit | stdlib.h | wasix-libc/libc-top-half/musl/src/exit/_Exit.c | __wasi_proc_exit2, __wasi_stack_restore | +| _Fork | unistd.h | wasix-libc/libc-top-half/musl/src/process/_Fork.c | __wasi_callback_signal, __wasi_proc_exit2, __wasi_proc_fork, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| execv | unistd.h | wasix-libc/libc-top-half/musl/src/process/execv.c | __wasi_proc_exec3, __wasi_stack_restore | +| execve | unistd.h | wasix-libc/libc-top-half/musl/src/process/execve.c | __wasi_proc_exec3, __wasi_stack_restore | +| exit | stdlib.h | wasix-libc/libc-top-half/musl/src/exit/exit.c | __wasi_proc_exit2, __wasi_stack_restore | +| exit | stdlib.h | wasix-libc/libc-top-half/musl/src/exit/exit.c | __wasi_proc_exit2, __wasi_stack_restore | +| fork | unistd.h | wasix-libc/libc-top-half/musl/src/process/fork.c | __wasi_callback_signal, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| getpid | unistd.h | wasix-libc/libc-bottom-half/sources/getpid.c | __wasi_proc_id | +| getppid | unistd.h | wasix-libc/libc-bottom-half/sources/getppid.c | __wasi_proc_id, __wasi_proc_parent | +| posix_spawn | spawn.h | wasix-libc/libc-top-half/musl/src/process/posix_spawn.c | __wasi_callback_signal, __wasi_fd_close, __wasi_fd_pipe, __wasi_fd_read, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_proc_join, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| posix_spawn | spawn.h | wasix-libc/libc-top-half/musl/src/process/posix_spawn.c | __wasi_proc_spawn2 | +| posix_spawnp | spawn.h | wasix-libc/libc-top-half/musl/src/process/posix_spawnp.c | __wasi_callback_signal, __wasi_fd_close, __wasi_fd_pipe, __wasi_fd_read, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_proc_join, __wasi_proc_spawn2, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| system | stdlib.h | wasix-libc/libc-top-half/musl/src/process/system.c | __wasi_callback_signal, __wasi_fd_close, __wasi_fd_pipe, __wasi_fd_read, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_proc_join, __wasi_proc_spawn2, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| vfork | unistd.h | wasix-libc/libc-top-half/musl/src/process/vfork.c | __wasi_callback_signal, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| wait | sys/wait.h | wasix-libc/libc-top-half/musl/src/process/wait.c | __wasi_proc_join | +| wait3 | sys/wait.h | wasix-libc/libc-top-half/musl/src/linux/wait3.c | __wasi_proc_join | +| wait4 | sys/wait.h | wasix-libc/libc-top-half/musl/src/linux/wait4.c | __wasi_proc_join | +| waitpid | sys/wait.h | wasix-libc/libc-top-half/musl/src/process/waitpid.c | __wasi_proc_join | + +## Random (1) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| getrandom | sys/random.h | wasix-libc/libc-bottom-half/sources/getrandom.c | __wasi_random_get | + +## Signals (3) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| kill | signal.h | wasix-libc/libc-top-half/musl/src/signal/kill.c | __wasi_proc_signal | +| raise | signal.h | wasix-libc/libc-top-half/musl/src/signal/raise.c | __wasi_callback_signal, __wasi_thread_signal | +| sigqueue | signal.h | wasix-libc/libc-top-half/musl/src/signal/sigqueue.c | __wasi_callback_signal, __wasi_proc_id | + +## Sockets/Networking (19) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| accept | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/accept.c | __wasi_sock_accept_v2 | +| accept4 | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/accept.c | __wasi_sock_accept_v2 | +| bind | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/bind.c | __wasi_sock_bind | +| connect | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/connect.c | __wasi_sock_connect | +| getnameinfo | netdb.h | wasix-libc/libc-top-half/musl/src/network/getnameinfo.c | __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_getcwd, __wasi_path_open2 | +| getpeername | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/getpeername.c | __wasi_sock_addr_peer | +| getsockname | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/getsockname.c | __wasi_sock_addr_local | +| getsockopt | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/getsockopt.c | __wasi_fd_fdstat_get, __wasi_sock_get_opt_flag, __wasi_sock_get_opt_size, __wasi_sock_get_opt_time | +| listen | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/listen.c | __wasi_sock_listen | +| recv | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/recv.c | __wasi_sock_recv | +| recvfrom | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/recvfrom.c | __wasi_sock_recv_from | +| recvmsg | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/recvmsg.c | __wasi_sock_recv, __wasi_sock_recv_from | +| send | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/send.c | __wasi_sock_send | +| sendmsg | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/sendmsg.c | __wasi_sock_send, __wasi_sock_send_to | +| sendto | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/sendto.c | __wasi_sock_send_to | +| setsockopt | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/setsockopt.c | __wasi_sock_set_opt_flag, __wasi_sock_set_opt_size, __wasi_sock_set_opt_time | +| shutdown | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/shutdown.c | __wasi_sock_shutdown | +| socket | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/socket.c | __wasi_sock_open | +| socketpair | sys/socket.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/socket/socketpair.c | __wasi_sock_pair | + +## StdIO (13) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| fclose | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/fclose.c | __wasi_fd_close | +| fopen | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/fopen.c | __wasi_fd_close, __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_getcwd, __wasi_path_open2 | +| freopen | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/freopen.c | __wasi_fd_close, __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_fd_renumber, __wasi_getcwd, __wasi_path_open2 | +| pclose | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/pclose.c | __wasi_fd_close, __wasi_proc_join | +| popen | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/popen.c | __wasi_callback_signal, __wasi_environ_get, __wasi_environ_sizes_get, __wasi_fd_close, __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_fd_pipe, __wasi_fd_read, __wasi_futex_wait, __wasi_proc_exit2, __wasi_proc_fork, __wasi_proc_join, __wasi_proc_spawn2, __wasi_stack_restore, __wasi_thread_id, __wasi_thread_signal | +| printf | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/printf.c | __wasi_fd_write | +| remove | stdio.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_remove_directory, __wasi_path_unlink_file | +| rename | stdio.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_rename | +| renameat | stdio.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_rename | +| tmpfile | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/tmpfile.c | __wasi_fd_close, __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_getcwd, __wasi_path_open2 | +| vdprintf | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/vdprintf.c | __wasi_fd_write | +| vfprintf | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/vfprintf.c | __wasi_fd_write | +| vsnprintf | stdio.h | wasix-libc/libc-top-half/musl/src/stdio/vsnprintf.c | __wasi_fd_write | + +## System/Config (1) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| sysconf | unistd.h | wasix-libc/libc-top-half/musl/src/conf/sysconf.c | __wasi_thread_parallelism | + +## Terminal/TTY (11) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| ioctl | stropts.h, sys/ioctl.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| isatty | unistd.h | wasix-libc/libc-top-half/musl/src/unistd/isatty.c | __wasi_tty_get | +| tcflow | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcflow.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcflush | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcflush.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcgetattr | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcgetattr.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcgetsid | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcgetsid.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcgetwinsize | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcgetwinsize.c | __wasi_tty_get | +| tcsendbreak | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcsendbreak.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcsetattr | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcsetattr.c | __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_poll_oneoff, __wasi_tty_get, __wasi_tty_set | +| tcsetwinsize | termios.h | wasix-libc/libc-top-half/musl/src/termios/tcsetwinsize.c | __wasi_tty_get, __wasi_tty_set | +| ttyname_r | unistd.h | wasix-libc/libc-top-half/musl/src/unistd/ttyname_r.c | __wasi_fd_filestat_get, __wasi_getcwd, __wasi_path_filestat_get, __wasi_path_readlink, __wasi_tty_get | + +## Threads (9) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| pthread_barrier_destroy | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_barrier_destroy.c | __wasi_futex_wait | +| pthread_barrier_wait | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_barrier_wait.c | __wasi_futex_wait | +| pthread_cancel | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_cancel.c | __wasi_callback_signal, __wasi_thread_signal | +| pthread_getschedparam | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_getschedparam.c | __wasi_callback_signal | +| pthread_kill | signal.h | wasix-libc/libc-top-half/musl/src/thread/pthread_kill.c | __wasi_callback_signal | +| pthread_kill | signal.h | wasix-libc/libc-top-half/musl/src/thread/pthread_kill.c | __wasi_callback_signal, __wasi_thread_signal | +| pthread_setname_np | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_setname_np.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_write, __wasi_getcwd, __wasi_path_open2 | +| pthread_setschedparam | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_setschedparam.c | __wasi_callback_signal | +| pthread_setschedprio | pthread.h | wasix-libc/libc-top-half/musl/src/thread/pthread_setschedprio.c | __wasi_callback_signal | + +## Threads/Scheduling (1) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| sched_yield | sched.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c | __wasi_sched_yield | + +## Time/Clocks (10) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| clock_getres | time.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/time/clock_getres.c | __wasi_clock_res_get | +| clock_nanosleep | time.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/time/clock_nanosleep.c | __wasi_poll_oneoff | +| getdate | time.h | wasix-libc/libc-top-half/musl/src/time/getdate.c | __wasi_fd_close, __wasi_fd_dup2, __wasi_fd_fdflags_get, __wasi_fd_fdflags_set, __wasi_fd_fdstat_get, __wasi_fd_fdstat_set_flags, __wasi_getcwd, __wasi_path_open2 | +| getrusage | sys/resource.h | wasix-libc/libc-top-half/musl/src/misc/getrusage.c | __wasi_clock_time_get | +| gettimeofday | sys/time.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/time/gettimeofday.c | __wasi_clock_time_get | +| setitimer | sys/time.h | wasix-libc/libc-top-half/musl/src/signal/setitimer.c | __wasi_proc_raise_interval | +| setrlimit | sys/resource.h | wasix-libc/libc-top-half/musl/src/misc/setrlimit.c | __wasi_callback_signal, __wasi_futex_wait | +| time | time.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/time/time.c | __wasi_clock_time_get | +| timer_create | time.h | wasix-libc/libc-top-half/musl/src/time/timer_create.c | __wasi_callback_signal, __wasi_futex_wait | +| utimes | sys/time.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_filestat_set_times | + +## WASIX/WASI Internals (41) + +| Function | Header(s) | Source | WASI/WASIX syscalls | +|---|---|---|---| +| __wasilibc_access | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_filestat_get | +| __wasilibc_ensure_environ | wasi/libc-environ.h | wasix-libc/libc-bottom-half/sources/__wasilibc_initialize_environ.c | __wasi_environ_get, __wasi_environ_sizes_get, __wasi_proc_exit2, __wasi_stack_restore | +| __wasilibc_fd_renumber | wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_fd_renumber.c | __wasi_fd_renumber | +| __wasilibc_find_relpath | wasi/libc-find-relpath.h | wasix-libc/libc-bottom-half/sources/preopens.c | __wasi_getcwd | +| __wasilibc_find_relpath_alloc | wasi/libc-find-relpath.h | wasix-libc/libc-bottom-half/sources/chdir.c | __wasi_getcwd | +| __wasilibc_futex_wait_wasix | wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_futex.c | __wasi_futex_wait | +| __wasilibc_futex_wake_wasix | wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_futex.c | __wasi_futex_wake, __wasi_futex_wake_all | +| __wasilibc_get_environ | wasi/libc-environ.h | wasix-libc/libc-bottom-half/sources/__wasilibc_environ.c | __wasi_environ_get, __wasi_environ_sizes_get, __wasi_proc_exit2, __wasi_stack_restore | +| __wasilibc_initialize_environ | wasi/libc-environ.h, wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_initialize_environ.c | __wasi_environ_get, __wasi_environ_sizes_get, __wasi_proc_exit2, __wasi_stack_restore | +| __wasilibc_link | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_link | +| __wasilibc_link_newat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_link | +| __wasilibc_link_oldat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_link | +| __wasilibc_longjmp | wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_stack.c | __wasi_stack_restore | +| __wasilibc_maybe_reinitialize_environ_eagerly | wasi/libc-environ.h | wasix-libc/libc-bottom-half/sources/environ.c | __wasi_environ_get, __wasi_environ_sizes_get, __wasi_proc_exit2, __wasi_stack_restore | +| __wasilibc_nocwd___wasilibc_rmdirat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/sources/__wasilibc_rmdirat.c | __wasi_path_remove_directory | +| __wasilibc_nocwd___wasilibc_unlinkat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/sources/__wasilibc_unlinkat.c | __wasi_path_unlink_file | +| __wasilibc_nocwd_faccessat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/faccessat.c | __wasi_fd_fdstat_get, __wasi_path_filestat_get | +| __wasilibc_nocwd_fstatat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c | __wasi_path_filestat_get | +| __wasilibc_nocwd_linkat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/linkat.c | __wasi_path_link | +| __wasilibc_nocwd_mkdirat_nomode | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/stat/mkdirat.c | __wasi_path_create_directory | +| __wasilibc_nocwd_openat_nomode | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/fcntl/openat.c | __wasi_fd_fdstat_get, __wasi_path_open2 | +| __wasilibc_nocwd_opendirat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/dirent/opendirat.c | __wasi_fd_close, __wasi_fd_fdstat_get, __wasi_fd_readdir, __wasi_path_open2 | +| __wasilibc_nocwd_readlinkat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/readlinkat.c | __wasi_path_readlink | +| __wasilibc_nocwd_renameat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/stdio/renameat.c | __wasi_path_rename | +| __wasilibc_nocwd_symlinkat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/unistd/symlinkat.c | __wasi_path_symlink | +| __wasilibc_nocwd_utimensat | wasi/libc-nocwd.h | wasix-libc/libc-bottom-half/cloudlibc/src/libc/sys/stat/utimensat.c | __wasi_path_filestat_set_times | +| __wasilibc_open_nomode | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_fd_fdstat_get, __wasi_getcwd, __wasi_path_open2 | +| __wasilibc_rename_newat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_rename | +| __wasilibc_rename_oldat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_rename | +| __wasilibc_rmdirat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_remove_directory | +| __wasilibc_setjmp | wasi/libc.h | wasix-libc/libc-bottom-half/sources/__wasilibc_stack.c | __wasi_stack_checkpoint | +| __wasilibc_stat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_filestat_get | +| __wasilibc_unlinkat | wasi/libc.h | wasix-libc/libc-bottom-half/sources/at_fdcwd.c | __wasi_getcwd, __wasi_path_unlink_file | +| __wasilibc_utimens | wasi/libc.h | wasix-libc/libc-bottom-half/sources/posix.c | __wasi_getcwd, __wasi_path_filestat_set_times | +| wasix_call_dynamic | wasix/call_dynamic.h | wasix-libc/libc-top-half/musl/src/wasix/call_dynamic.c | __wasi_call_dynamic | +| wasix_closure_allocate | wasix/closure.h | wasix-libc/libc-top-half/musl/src/wasix/closure_allocate.c | __wasi_closure_allocate | +| wasix_closure_free | wasix/closure.h | wasix-libc/libc-top-half/musl/src/wasix/closure_free.c | __wasi_closure_free | +| wasix_closure_prepare | wasix/closure.h | wasix-libc/libc-top-half/musl/src/wasix/closure_prepare.c | __wasi_closure_prepare | +| wasix_context_destroy | wasix/context.h | wasix-libc/libc-top-half/musl/src/wasix/context.c | __wasi_context_destroy | +| wasix_context_switch | wasix/context.h | wasix-libc/libc-top-half/musl/src/wasix/context.c | __wasi_context_switch | +| wasix_reflect_signature | wasix/reflection.h | wasix-libc/libc-top-half/musl/src/wasix/reflection.c | __wasi_reflect_signature | + +## PROMPT + + + +Great! Now let's find port all the tests for the Control Flow/Setjmp category from /Users/fessguid/Projects/wasix-libc-tests/WASIX-libc-wasi-usage-categories.md document. + + +Reminding again: +- Find all the Functions in that specific category that are listed in a document +- across all the projects (EVERYTHING inside /Users/fessguid/Projects/wasix-libc-tests - all folders, excluding wasix-libc itself) find ALL the tests for those functions and cateogry. +- Then analyse which logic each test tests, remove the duplicates, then port all of those into C integration test similar to what we have already +- Then run all those tests +- Then give me the overview of what you've added and which are working and which are not. + +The task is to verify the quality of our libc implementation - so don't exclude the test if that is not working and not try to bend the test for the WASIX, we are validating it and if we've found an issue in WASIX then it's great. Test IS NOT CONSIDERED A TEST IF IT HAS NO ASSERTS, PORT ALL THE CUSTOM CHECK MACROSES TO assert. + +In the end update the document with covered category with the progress about ported test and short summary diff --git a/libc-top-half/musl/src/ldso/dlclose.c b/libc-top-half/musl/src/ldso/dlclose.c index b0581dd40..96b7f2e26 100644 --- a/libc-top-half/musl/src/ldso/dlclose.c +++ b/libc-top-half/musl/src/ldso/dlclose.c @@ -2,8 +2,23 @@ #include "dynlink.h" #include "wasi/api.h" +extern __wasi_dl_handle_t __wasix_main_dl_handle; +extern int __wasix_dl_release(__wasi_dl_handle_t handle, int *should_close); + int dlclose(void *p) { + if (__wasix_main_dl_handle != 0 && (__wasi_dl_handle_t)p == __wasix_main_dl_handle) + { + return 0; + } + + int should_close = 0; + int known = __wasix_dl_release((__wasi_dl_handle_t)p, &should_close); + if (known && !should_close) + { + return 0; + } + int err = __wasi_dl_invalid_handle((__wasi_dl_handle_t)p); if (err != 0) { diff --git a/libc-top-half/musl/src/ldso/dlopen.c b/libc-top-half/musl/src/ldso/dlopen.c index cb9914a65..c94c505d6 100644 --- a/libc-top-half/musl/src/ldso/dlopen.c +++ b/libc-top-half/musl/src/ldso/dlopen.c @@ -4,6 +4,125 @@ #include "string.h" #include "stdlib.h" +__wasi_dl_handle_t __wasix_main_dl_handle = 0; + +struct __wasix_dl_entry { + char *name; + __wasi_dl_handle_t handle; + int flags; + int refcount; + struct __wasix_dl_entry *next; +}; + +static struct __wasix_dl_entry *__wasix_dl_list = NULL; + +static struct __wasix_dl_entry *__wasix_find_by_name(const char *name) +{ + struct __wasix_dl_entry *cur = __wasix_dl_list; + while (cur) { + if (cur->name && strcmp(cur->name, name) == 0) { + return cur; + } + cur = cur->next; + } + return NULL; +} + +static struct __wasix_dl_entry *__wasix_find_by_handle(__wasi_dl_handle_t handle) +{ + struct __wasix_dl_entry *cur = __wasix_dl_list; + while (cur) { + if (cur->handle == handle) { + return cur; + } + cur = cur->next; + } + return NULL; +} + +int __wasix_dl_handle_alive(__wasi_dl_handle_t handle) +{ + return __wasix_find_by_handle(handle) != NULL; +} + +static void __wasix_add_or_update(const char *name, __wasi_dl_handle_t handle, int mode) +{ + struct __wasix_dl_entry *entry = __wasix_find_by_name(name); + if (entry) { + entry->handle = handle; + entry->flags |= mode; + entry->refcount += 1; + return; + } + + entry = __wasix_find_by_handle(handle); + if (entry) { + entry->flags |= mode; + entry->refcount += 1; + return; + } + + entry = (struct __wasix_dl_entry *)calloc(1, sizeof(*entry)); + if (!entry) { + return; + } + + entry->name = strdup(name); + if (!entry->name) { + free(entry); + return; + } + + entry->handle = handle; + entry->flags = mode; + entry->refcount = 1; + entry->next = __wasix_dl_list; + __wasix_dl_list = entry; +} + +size_t __wasix_dl_copy_global_handles(__wasi_dl_handle_t *out, size_t max) +{ + size_t count = 0; + struct __wasix_dl_entry *cur = __wasix_dl_list; + while (cur && count < max) { + if (cur->flags & RTLD_GLOBAL) { + out[count++] = cur->handle; + } + cur = cur->next; + } + return count; +} + +int __wasix_dl_release(__wasi_dl_handle_t handle, int *should_close) +{ + struct __wasix_dl_entry *cur = __wasix_dl_list; + struct __wasix_dl_entry *prev = NULL; + + while (cur) { + if (cur->handle == handle) { + if (cur->refcount > 1) { + cur->refcount -= 1; + *should_close = 0; + return 1; + } + + if (prev) { + prev->next = cur->next; + } else { + __wasix_dl_list = cur->next; + } + free(cur->name); + free(cur); + *should_close = 1; + return 1; + } + prev = cur; + cur = cur->next; + } + + return 0; +} + void *dlopen(const char *file, int mode) { __wasi_dl_handle_t ret = 0; @@ -12,6 +131,17 @@ void *dlopen(const char *file, int mode) char *ld_library_path = getenv("LD_LIBRARY_PATH"); + if (file && (mode & RTLD_NOLOAD)) { + struct __wasix_dl_entry *entry = __wasix_find_by_name(file); + if (entry) { + entry->flags |= mode; + entry->refcount += 1; + return (void *)entry->handle; + } + __dl_seterr("dlopen failed: library \"%s\" wasn't loaded and RTLD_NOLOAD prevented it", file); + return NULL; + } + int err = __wasi_dlopen(file, mode, (uint8_t *)err_buf, sizeof(err_buf), ld_library_path, &ret); if (err != 0) @@ -27,5 +157,14 @@ void *dlopen(const char *file, int mode) return NULL; } + if (file == NULL && ret != 0) + { + __wasix_main_dl_handle = ret; + } + else if (file != NULL && ret != 0) + { + __wasix_add_or_update(file, ret, mode); + } + return (void *)ret; } diff --git a/libc-top-half/musl/src/ldso/dlsym.c b/libc-top-half/musl/src/ldso/dlsym.c index 976b8eb5d..735f801ef 100644 --- a/libc-top-half/musl/src/ldso/dlsym.c +++ b/libc-top-half/musl/src/ldso/dlsym.c @@ -3,12 +3,71 @@ #include "wasi/api.h" #include "string.h" +extern __wasi_dl_handle_t __wasix_main_dl_handle; +size_t __wasix_dl_copy_global_handles(__wasi_dl_handle_t *out, size_t max); +int __wasix_dl_handle_alive(__wasi_dl_handle_t handle); + +static void *__wasix_dlsym_handle(__wasi_dl_handle_t handle, const char *s, char *err_buf, size_t err_buf_len, int *err_out, __wasi_size_t *ret_out) +{ + err_buf[0] = '\0'; + int err = __wasi_dlsym(handle, s, (uint8_t *)err_buf, err_buf_len, ret_out); + if (err == 0) { + return (void *)*ret_out; + } + *err_out = err; + return NULL; +} + void *dlsym(void *restrict p, const char *restrict s) { __wasi_size_t ret; char err_buf[256]; err_buf[0] = '\0'; + if (p == RTLD_DEFAULT) { + __wasi_dl_handle_t handles[64]; + char last_err_buf[256]; + int last_err = 0; + last_err_buf[0] = '\0'; + + if (__wasix_main_dl_handle) { + void *found = __wasix_dlsym_handle(__wasix_main_dl_handle, s, err_buf, sizeof(err_buf), &last_err, &ret); + if (found) { + return found; + } + if (err_buf[0] != '\0') { + strncpy(last_err_buf, err_buf, sizeof(last_err_buf)); + last_err_buf[sizeof(last_err_buf) - 1] = '\0'; + } + } + + size_t count = __wasix_dl_copy_global_handles(handles, sizeof(handles) / sizeof(handles[0])); + for (size_t i = 0; i < count; i++) { + void *found = __wasix_dlsym_handle(handles[i], s, err_buf, sizeof(err_buf), &last_err, &ret); + if (found) { + return found; + } + if (err_buf[0] != '\0') { + strncpy(last_err_buf, err_buf, sizeof(last_err_buf)); + last_err_buf[sizeof(last_err_buf) - 1] = '\0'; + } + } + + if (last_err_buf[0] != '\0') { + __dl_seterr("%s", last_err_buf); + } else if (last_err != 0) { + __dl_seterr("dlsym failed with error %s", strerror(last_err)); + } else { + __dl_seterr("dlsym failed: symbol \"%s\" not found", s); + } + return NULL; + } + + if (p != NULL && (__wasi_dl_handle_t)p != __wasix_main_dl_handle && !__wasix_dl_handle_alive((__wasi_dl_handle_t)p)) { + __dl_seterr("dlsym failed: invalid handle"); + return NULL; + } + int err = __wasi_dlsym((__wasi_dl_handle_t)p, s, (uint8_t *)err_buf, sizeof(err_buf), &ret); if (err != 0) diff --git a/libc-top-half/musl/src/linux/setgroups.c b/libc-top-half/musl/src/linux/setgroups.c index 525878150..2148b9c2b 100644 --- a/libc-top-half/musl/src/linux/setgroups.c +++ b/libc-top-half/musl/src/linux/setgroups.c @@ -43,6 +43,11 @@ int setgroups(size_t count, const gid_t list[]) __synccall(do_setgroups, &c); return __syscall_ret(c.ret); #else + /* WASIX does not currently support supplementary groups. + * Allow clearing the list as a no-op for compatibility. */ + if (count == 0) { + return 0; + } errno = ENOTSUP; return -1; #endif diff --git a/libc-top-half/musl/src/process/fexecve.c b/libc-top-half/musl/src/process/fexecve.c index 742f0462c..b9979e7f5 100644 --- a/libc-top-half/musl/src/process/fexecve.c +++ b/libc-top-half/musl/src/process/fexecve.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #ifdef __wasilibc_unmodified_upstream #include "syscall.h" #endif @@ -17,7 +19,65 @@ int fexecve(int fd, char *const argv[], char *const envp[]) if (errno == ENOENT) errno = EBADF; return -1; #else - errno = EINVAL; + if (fd < 0) { + errno = EBADF; + return -1; + } + + struct stat st; + if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) { + errno = EACCES; + return -1; + } + + /* Create a temporary copy of the executable, since WASIX exec + * operates by path and does not currently support exec-by-fd. */ + char template_path[] = "./.fexecve-XXXXXX"; + int tmpfd = mkstemp(template_path); + if (tmpfd < 0) { + return -1; + } + (void)fchmod(tmpfd, 0700); + + off_t saved = lseek(fd, 0, SEEK_CUR); + (void)lseek(fd, 0, SEEK_SET); + + char buf[8192]; + for (;;) { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n == 0) break; + if (n < 0) { + int err = errno; + close(tmpfd); + unlink(template_path); + if (saved != (off_t)-1) (void)lseek(fd, saved, SEEK_SET); + errno = err; + return -1; + } + ssize_t off = 0; + while (off < n) { + ssize_t w = write(tmpfd, buf + off, (size_t)(n - off)); + if (w < 0) { + int err = errno; + close(tmpfd); + unlink(template_path); + if (saved != (off_t)-1) (void)lseek(fd, saved, SEEK_SET); + errno = err; + return -1; + } + off += w; + } + } + + if (saved != (off_t)-1) { + (void)lseek(fd, saved, SEEK_SET); + } + close(tmpfd); + + execve(template_path, argv, envp); + int err = errno; + unlink(template_path); + errno = err; return -1; #endif } diff --git a/libc-top-half/musl/src/setjmp/setjmplongjmp.c b/libc-top-half/musl/src/setjmp/setjmplongjmp.c index 05ebc4a7e..a1add5620 100644 --- a/libc-top-half/musl/src/setjmp/setjmplongjmp.c +++ b/libc-top-half/musl/src/setjmp/setjmplongjmp.c @@ -1,5 +1,8 @@ #ifndef __wasilibc_unmodified_upstream #include +#include +#include +#include # ifdef __wasm_exception_handling__ /* @@ -93,9 +96,31 @@ int setjmp (jmp_buf buf) { # endif -// TODO: ignoring signal masking for now -int sigsetjmp(jmp_buf buf, int savesigs) { - return setjmp(buf); +int sigsetjmp(jmp_buf buf, int savesigs) { +#if defined(__wasm_exception_handling__) + (void)savesigs; + return setjmp(buf); +#else + int ret = setjmp(buf); + if (ret == 0) { + __wasi_stack_snapshot_t *snapshot = buf; + if (savesigs) { + sigset_t *mask = malloc(sizeof(sigset_t)); + if (mask && sigprocmask(SIG_SETMASK, NULL, mask) == 0) { + if (snapshot->user != 0) { + free((void *)(uintptr_t)snapshot->user); + } + snapshot->user = (uint64_t)(uintptr_t)mask; + } else if (mask) { + free(mask); + } + } else if (snapshot->user != 0) { + free((void *)(uintptr_t)snapshot->user); + snapshot->user = 0; + } + } + return ret; +#endif } #endif diff --git a/libc-top-half/musl/src/signal/siglongjmp.c b/libc-top-half/musl/src/signal/siglongjmp.c index 577ecb518..e176915a3 100644 --- a/libc-top-half/musl/src/signal/siglongjmp.c +++ b/libc-top-half/musl/src/signal/siglongjmp.c @@ -1,6 +1,6 @@ #include #include -#include +#include #ifdef __wasilibc_unmodified_upstream #include "syscall.h" #endif @@ -8,5 +8,14 @@ _Noreturn void siglongjmp(sigjmp_buf buf, int ret) { +#if defined(__wasilibc_unmodified_upstream) || defined(__wasm_exception_handling__) + longjmp(buf, ret); +#else + __wasi_stack_snapshot_t *snapshot = buf; + if (snapshot->user != 0) { + sigset_t *mask = (sigset_t *)(uintptr_t)snapshot->user; + sigprocmask(SIG_SETMASK, mask, NULL); + } longjmp(buf, ret); -} \ No newline at end of file +#endif +} diff --git a/libc-top-half/musl/src/thread/pthread_sigmask.c b/libc-top-half/musl/src/thread/pthread_sigmask.c index edfc9535e..2d6f5660a 100644 --- a/libc-top-half/musl/src/thread/pthread_sigmask.c +++ b/libc-top-half/musl/src/thread/pthread_sigmask.c @@ -21,6 +21,26 @@ int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict ol #else int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict old) { + static __thread sigset_t sigmask; + + if (set && (unsigned)how - SIG_BLOCK > 2U) return EINVAL; + if (old) *old = sigmask; + + if (set) { + size_t n = sizeof(sigmask.__bits) / sizeof(sigmask.__bits[0]); + switch (how) { + case SIG_BLOCK: + for (size_t i = 0; i < n; i++) sigmask.__bits[i] |= set->__bits[i]; + break; + case SIG_UNBLOCK: + for (size_t i = 0; i < n; i++) sigmask.__bits[i] &= ~set->__bits[i]; + break; + case SIG_SETMASK: + for (size_t i = 0; i < n; i++) sigmask.__bits[i] = set->__bits[i]; + break; + } + } + return 0; } -#endif \ No newline at end of file +#endif diff --git a/test/wasix/c_dirent/CMakeLists.txt b/test/wasix/c_dirent/CMakeLists.txt new file mode 100644 index 000000000..677aff2bf --- /dev/null +++ b/test/wasix/c_dirent/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(CMAKE_C_COMPILER_WORKS TRUE) + +cmake_minimum_required (VERSION 3.5.0) +project (dirent_tests) + +add_executable(main main.c) diff --git a/test/wasix/c_dirent/main.c b/test/wasix/c_dirent/main.c new file mode 100644 index 000000000..f8eecd048 --- /dev/null +++ b/test/wasix/c_dirent/main.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static void unlink_if_exists(const char *path) +{ + if (unlink(path) != 0 && errno != ENOENT) { + perror("unlink"); + assert(0); + } +} + +static void rmdir_if_exists(const char *path) +{ + if (rmdir(path) != 0 && errno != ENOENT) { + perror("rmdir"); + assert(0); + } +} + +static void ensure_clean(void) +{ + unlink_if_exists("dirent_tmp/file1"); + unlink_if_exists("dirent_tmp/file2"); + rmdir_if_exists("dirent_tmp/subdir"); + rmdir_if_exists("dirent_tmp"); +} + +static void write_file(const char *path) +{ + int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644); + assert(fd >= 0); + const char byte = 'x'; + ssize_t written = write(fd, &byte, 1); + assert(written == 1); + assert(close(fd) == 0); +} + +int main(void) +{ + ensure_clean(); + assert(mkdir("dirent_tmp", 0755) == 0); + assert(mkdir("dirent_tmp/subdir", 0755) == 0); + write_file("dirent_tmp/file1"); + + errno = 0; + assert(opendir("dirent_tmp/does-not-exist") == NULL); + assert(errno == ENOENT); + + errno = 0; + assert(opendir("dirent_tmp/file1") == NULL); + assert(errno == ENOTDIR); + + DIR *dirp = opendir("dirent_tmp"); + assert(dirp != NULL); + + int saw_dot = 0; + int saw_dotdot = 0; + int saw_file = 0; + int saw_subdir = 0; + + errno = 0; + struct dirent *ent; + while ((ent = readdir(dirp)) != NULL) { + if (strcmp(ent->d_name, ".") == 0) { + saw_dot = 1; + } else if (strcmp(ent->d_name, "..") == 0) { + saw_dotdot = 1; + } else if (strcmp(ent->d_name, "file1") == 0) { + saw_file = 1; + } else if (strcmp(ent->d_name, "subdir") == 0) { + saw_subdir = 1; + } + } + assert(errno == 0); + assert(saw_dot && saw_dotdot && saw_file && saw_subdir); + assert(closedir(dirp) == 0); + + errno = 0; + assert(fdopendir(-1) == NULL); + assert(errno == EBADF); + + int fd = open("dirent_tmp/file1", O_RDONLY); + assert(fd >= 0); + errno = 0; + assert(fdopendir(fd) == NULL); + assert(errno == ENOTDIR); + assert(close(fd) == 0); + + int dir_fd; +#ifdef O_DIRECTORY + dir_fd = open("dirent_tmp", O_RDONLY | O_DIRECTORY); +#else + dir_fd = open("dirent_tmp", O_RDONLY); +#endif + assert(dir_fd >= 0); + DIR *fd_dir = fdopendir(dir_fd); + assert(fd_dir != NULL); + ent = readdir(fd_dir); + assert(ent != NULL); + assert(closedir(fd_dir) == 0); + + errno = 0; + assert(close(dir_fd) == -1); + assert(errno == EBADF); + + int closed_fd; +#ifdef O_DIRECTORY + closed_fd = open("dirent_tmp", O_RDONLY | O_DIRECTORY); +#else + closed_fd = open("dirent_tmp", O_RDONLY); +#endif + assert(closed_fd >= 0); + assert(close(closed_fd) == 0); + errno = 0; + assert(fdopendir(closed_fd) == NULL); + assert(errno == EBADF); + + DIR *dirp2 = opendir("dirent_tmp"); + assert(dirp2 != NULL); + int dirp2_fd = dirfd(dirp2); + assert(dirp2_fd >= 0); + int close_ok = close(dirp2_fd); + if (close_ok == 0) { + errno = 0; + ent = readdir(dirp2); + assert(ent == NULL); + assert(errno == EBADF); + } + closedir(dirp2); + + ensure_clean(); + return 0; +} diff --git a/test/wasix/c_dirent/test.sh b/test/wasix/c_dirent/test.sh new file mode 100755 index 000000000..37c6eaf2e --- /dev/null +++ b/test/wasix/c_dirent/test.sh @@ -0,0 +1,13 @@ +#!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} + +WASMER_BIN=${WASMER_BIN:-wasmer} + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main +RESULT=$? +if [ "$RESULT" != "0" ]; then + echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr + exit 1 +fi + +echo "c_dirent test passed" diff --git a/test/wasix/c_process_control/CMakeLists.txt b/test/wasix/c_process_control/CMakeLists.txt new file mode 100644 index 000000000..9b0df0b0f --- /dev/null +++ b/test/wasix/c_process_control/CMakeLists.txt @@ -0,0 +1,8 @@ +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(CMAKE_C_COMPILER_WORKS TRUE) + +cmake_minimum_required (VERSION 3.5.0) +project (process_control_tests C) + +add_executable(main main.c) +add_executable(exec_helper exec_helper.c) diff --git a/test/wasix/c_process_control/exec_helper.c b/test/wasix/c_process_control/exec_helper.c new file mode 100644 index 000000000..5dbaa5aa4 --- /dev/null +++ b/test/wasix/c_process_control/exec_helper.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include +#include + +static int write_all(int fd, const char *buf, size_t len) { + while (len > 0) { + ssize_t wrote = write(fd, buf, len); + if (wrote < 0) { + return -1; + } + buf += (size_t)wrote; + len -= (size_t)wrote; + } + return 0; +} + +static int write_file(const char *path, const char *data, size_t len) { + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (fd < 0) { + return -1; + } + int result = write_all(fd, data, len); + int saved = errno; + close(fd); + errno = saved; + return result; +} + +static int do_echo(int argc, char **argv) { + if (argc < 4) { + return 2; + } + const char *out = argv[2]; + size_t total = 0; + for (int i = 3; i < argc; ++i) { + total += strlen(argv[i]); + if (i + 1 < argc) { + total += 1; + } + } + total += 1; + char *buf = (char *)malloc(total + 1); + if (!buf) { + return 3; + } + size_t off = 0; + for (int i = 3; i < argc; ++i) { + size_t len = strlen(argv[i]); + memcpy(buf + off, argv[i], len); + off += len; + if (i + 1 < argc) { + buf[off++] = ' '; + } + } + buf[off++] = '\n'; + buf[off] = '\0'; + int result = write_file(out, buf, off); + int saved = errno; + free(buf); + errno = saved; + return result == 0 ? 0 : 4; +} + +static int do_env(int argc, char **argv) { + if (argc < 4) { + return 2; + } + const char *out = argv[2]; + const char *name = argv[3]; + const char *value = getenv(name); + if (!value) { + value = ""; + } + size_t len = strlen(value); + char *buf = (char *)malloc(len + 2); + if (!buf) { + return 3; + } + memcpy(buf, value, len); + buf[len] = '\n'; + buf[len + 1] = '\0'; + int result = write_file(out, buf, len + 1); + int saved = errno; + free(buf); + errno = saved; + return result == 0 ? 0 : 4; +} + +static int do_touch(int argc, char **argv) { + if (argc < 3) { + return 2; + } + const char *path = argv[2]; + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (fd < 0) { + return 4; + } + close(fd); + return 0; +} + +int main(int argc, char **argv) { + if (argc < 2) { + return 2; + } + if (strcmp(argv[1], "echo") == 0) { + return do_echo(argc, argv); + } + if (strcmp(argv[1], "env") == 0) { + return do_env(argc, argv); + } + if (strcmp(argv[1], "touch") == 0) { + return do_touch(argc, argv); + } + return 2; +} diff --git a/test/wasix/c_process_control/main.c b/test/wasix/c_process_control/main.c new file mode 100644 index 000000000..0a7384f3c --- /dev/null +++ b/test/wasix/c_process_control/main.c @@ -0,0 +1,417 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int write_all(int fd, const char *buf, size_t len) { + while (len > 0) { + ssize_t wrote = write(fd, buf, len); + if (wrote < 0) { + return -1; + } + buf += (size_t)wrote; + len -= (size_t)wrote; + } + return 0; +} + +static void write_file(const char *path, const char *data, size_t len) { + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); + assert(fd >= 0); + assert(write_all(fd, data, len) == 0); + close(fd); +} + +static char *read_file(const char *path) { + int fd = open(path, O_RDONLY); + assert(fd >= 0); + struct stat st; + assert(fstat(fd, &st) == 0); + size_t size = (st.st_size > 0) ? (size_t)st.st_size : 0; + char *buf = (char *)malloc(size + 1); + assert(buf != NULL); + size_t off = 0; + while (off < size) { + ssize_t got = read(fd, buf + off, size - off); + assert(got >= 0); + if (got == 0) { + break; + } + off += (size_t)got; + } + buf[off] = '\0'; + close(fd); + return buf; +} + +static void test_fork_basic(void) { + const char *path = "fork_child_pid.txt"; + unlink(path); + pid_t pid = fork(); + assert(pid >= 0); + if (pid == 0) { + char buf[64]; + int len = snprintf(buf, sizeof(buf), "%d\n", (int)getpid()); + assert(len > 0); + write_file(path, buf, (size_t)len); + _exit(42); + } + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + assert(waited == pid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 42); + char *content = read_file(path); + int child_pid = atoi(content); + free(content); + assert(child_pid == pid); + unlink(path); +} + +static const char *env_names[] = {"TERM", "NoTSetzWq", "TESTPROG"}; +static const char *env_not_set = "getenv() does not find variable set"; + +static char *build_env_dump(void) { + size_t total = 0; + size_t count = sizeof(env_names) / sizeof(env_names[0]); + for (size_t i = 0; i < count; ++i) { + const char *val = getenv(env_names[i]); + if (!val) { + val = env_not_set; + } + total += strlen(env_names[i]) + 1 + strlen(val) + 1; + } + char *buf = (char *)malloc(total + 1); + assert(buf != NULL); + size_t off = 0; + for (size_t i = 0; i < count; ++i) { + const char *val = getenv(env_names[i]); + if (!val) { + val = env_not_set; + } + int written = snprintf(buf + off, total + 1 - off, "%s:%s\n", env_names[i], val); + assert(written > 0); + off += (size_t)written; + } + buf[off] = '\0'; + return buf; +} + +static void test_fork_env_inheritance(void) { + const char *path = "fork_env.out"; + unlink(path); + unsetenv("NoTSetzWq"); + setenv("TESTPROG", "FRKTCS04", 1); + char *expected = build_env_dump(); + pid_t pid = fork(); + assert(pid >= 0); + if (pid == 0) { + char *child_dump = build_env_dump(); + write_file(path, child_dump, strlen(child_dump)); + free(child_dump); + _exit(0); + } + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + assert(waited == pid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 0); + char *actual = read_file(path); + assert(strcmp(actual, expected) == 0); + free(actual); + free(expected); + unlink(path); +} + +static void test_fork_fd_offset_shared(void) { + const char *path = "fork_offset.txt"; + unlink(path); + const char *data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; + write_file(path, data, strlen(data)); + int fd = open(path, O_RDONLY); + assert(fd >= 0); + + pid_t pid = fork(); + assert(pid >= 0); + if (pid == 0) { + off_t off = lseek(fd, 10, SEEK_SET); + assert(off == 10); + _exit(0); + } + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + assert(waited == pid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 0); + + pid = fork(); + assert(pid >= 0); + if (pid == 0) { + char c = '\0'; + ssize_t got = read(fd, &c, 1); + assert(got == 1); + assert(c == 'K'); + _exit(0); + } + waited = waitpid(pid, &status, 0); + assert(waited == pid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 0); + + char c = '\0'; + ssize_t got = read(fd, &c, 1); + assert(got == 1); + assert(c == 'L'); + + close(fd); + unlink(path); +} + +struct attrs { + uid_t ruid; + uid_t euid; + gid_t rgid; + gid_t egid; + mode_t mask; + char cwd[PATH_MAX]; + dev_t root_dev; + ino_t root_ino; + dev_t cwd_dev; + ino_t cwd_ino; +}; + +static volatile int vfork_child_error_step = 0; +static volatile int vfork_child_error_errno = 0; +static volatile int vfork_child_reached = 0; +static struct attrs vfork_parent_attrs; +static struct attrs vfork_child_attrs; +static pid_t vfork_pid; +static pid_t vfork_waited; +static int vfork_status; +static int vfork_exit_status; + +static void fill_attrs(struct attrs *out) { + out->ruid = getuid(); + out->euid = geteuid(); + out->rgid = getgid(); + out->egid = getegid(); + mode_t mask = umask(0); + umask(mask); + out->mask = mask; + assert(getcwd(out->cwd, sizeof(out->cwd)) != NULL); + struct stat st; + assert(stat("/", &st) == 0); + out->root_dev = st.st_dev; + out->root_ino = st.st_ino; + assert(stat(out->cwd, &st) == 0); + out->cwd_dev = st.st_dev; + out->cwd_ino = st.st_ino; +} + +static void test_vfork_attributes(void) { + vfork_child_error_step = 0; + vfork_child_error_errno = 0; + vfork_child_reached = 0; + fill_attrs(&vfork_parent_attrs); + vfork_pid = vfork(); + assert(vfork_pid >= 0); + if (vfork_pid == 0) { + vfork_child_reached = 1; + vfork_child_attrs.ruid = getuid(); + vfork_child_attrs.euid = geteuid(); + vfork_child_attrs.rgid = getgid(); + vfork_child_attrs.egid = getegid(); + mode_t mask = umask(0); + umask(mask); + vfork_child_attrs.mask = mask; + if (getcwd(vfork_child_attrs.cwd, sizeof(vfork_child_attrs.cwd)) == NULL) { + vfork_child_error_step = 1; + vfork_child_error_errno = errno; + _exit(1); + } + struct stat st; + if (stat("/", &st) != 0) { + vfork_child_error_step = 2; + vfork_child_error_errno = errno; + _exit(1); + } + vfork_child_attrs.root_dev = st.st_dev; + vfork_child_attrs.root_ino = st.st_ino; + if (stat(vfork_child_attrs.cwd, &st) != 0) { + vfork_child_error_step = 3; + vfork_child_error_errno = errno; + _exit(1); + } + vfork_child_attrs.cwd_dev = st.st_dev; + vfork_child_attrs.cwd_ino = st.st_ino; + _exit(0); + } + vfork_status = 0; + vfork_waited = waitpid(vfork_pid, &vfork_status, 0); + assert(vfork_waited == vfork_pid); + assert(WIFEXITED(vfork_status)); + vfork_exit_status = WEXITSTATUS(vfork_status); + if (vfork_exit_status != 0) { + fprintf(stderr, "vfork child failed (reached=%d) step %d errno %d status=0x%x exit=%d\n", + vfork_child_reached, vfork_child_error_step, + vfork_child_error_errno, vfork_status, vfork_exit_status); + assert(vfork_exit_status == 0); + } + + assert(vfork_parent_attrs.ruid == vfork_child_attrs.ruid); + assert(vfork_parent_attrs.euid == vfork_child_attrs.euid); + assert(vfork_parent_attrs.rgid == vfork_child_attrs.rgid); + assert(vfork_parent_attrs.egid == vfork_child_attrs.egid); + assert(vfork_parent_attrs.mask == vfork_child_attrs.mask); + assert(strcmp(vfork_parent_attrs.cwd, vfork_child_attrs.cwd) == 0); + assert(vfork_parent_attrs.root_dev == vfork_child_attrs.root_dev); + assert(vfork_parent_attrs.root_ino == vfork_child_attrs.root_ino); + assert(vfork_parent_attrs.cwd_dev == vfork_child_attrs.cwd_dev); + assert(vfork_parent_attrs.cwd_ino == vfork_child_attrs.cwd_ino); +} + +static void test_setgroups_basic(void) { + assert(geteuid() == 0); + int ret = setgroups(0, NULL); + if (ret != 0) { + fprintf(stderr, "setgroups(0, NULL) failed errno %d\n", errno); + } + assert(ret == 0); +} + +static void run_fexecve_child(char *const argv[], char *const envp[]) { + int fd = open("./exec_helper", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "open exec_helper failed errno %d\n", errno); + assert(fd >= 0); + } + pid_t pid = fork(); + assert(pid >= 0); + if (pid == 0) { + fexecve(fd, argv, envp); + _exit(errno); + } + close(fd); + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + assert(waited == pid); + assert(WIFEXITED(status)); + if (WEXITSTATUS(status) == ENOSYS) { + assert(!"fexecve is unimplemented"); + } + assert(WEXITSTATUS(status) == 0); +} + +static void test_fexecve_errors(void) { + char *argv[] = {(char *)"exec_helper", (char *)"echo", (char *)"out", (char *)"x", NULL}; + char *envp[] = {NULL}; + errno = 0; + assert(fexecve(-1, argv, envp) == -1); + if (errno != EBADF) { + fprintf(stderr, "fexecve(-1) errno %d (expected EBADF)\n", errno); + } + assert(errno == EBADF); + + int fd = open("/", O_RDONLY); + assert(fd >= 0); + errno = 0; + assert(fexecve(fd, argv, envp) == -1); + if (errno != EACCES) { + fprintf(stderr, "fexecve(dirfd) errno %d (expected EACCES)\n", errno); + } + assert(errno == EACCES); + close(fd); +} + +static void test_fexecve_success(void) { + char *envp[] = {(char *)"A=B", NULL}; + + const char *echo_out = "exec_echo.out"; + unlink(echo_out); + char *argv_echo[] = {(char *)"exec_helper", (char *)"echo", (char *)echo_out, + (char *)"hello", (char *)"world", NULL}; + run_fexecve_child(argv_echo, envp); + char *echo_content = read_file(echo_out); + assert(strcmp(echo_content, "hello world\n") == 0); + free(echo_content); + unlink(echo_out); + + const char *env_out = "exec_env.out"; + unlink(env_out); + char *argv_env[] = {(char *)"exec_helper", (char *)"env", (char *)env_out, + (char *)"A", NULL}; + run_fexecve_child(argv_env, envp); + char *env_content = read_file(env_out); + assert(strcmp(env_content, "B\n") == 0); + free(env_content); + unlink(env_out); + + const char *touch_path = "exec_touch.txt"; + unlink(touch_path); + char *argv_touch[] = {(char *)"exec_helper", (char *)"touch", (char *)touch_path, NULL}; + run_fexecve_child(argv_touch, envp); + assert(access(touch_path, F_OK) == 0); + unlink(touch_path); +} + +static void test_wasix_proc_snapshot(void) { + int ret = wasix_proc_snapshot(); + assert(ret == 0); +} + +int main(void) { + struct { + const char *name; + void (*fn)(void); + } cases[] = { + {"fork_basic", test_fork_basic}, + {"fork_env_inheritance", test_fork_env_inheritance}, + {"fork_fd_offset_shared", test_fork_fd_offset_shared}, + {"fexecve_errors", test_fexecve_errors}, + {"fexecve_success", test_fexecve_success}, + {"wasix_proc_snapshot", test_wasix_proc_snapshot}, + {"vfork_attributes", test_vfork_attributes}, + {"setgroups_basic", test_setgroups_basic}, + }; + + int failures = 0; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + pid_t pid = fork(); + assert(pid >= 0); + if (pid == 0) { + cases[i].fn(); + _exit(0); + } + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + assert(waited == pid); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + printf("%s ok\n", cases[i].name); + } else if (WIFSIGNALED(status)) { + printf("%s failed (signal %d)\n", cases[i].name, WTERMSIG(status)); + failures++; + } else { + printf("%s failed (exit %d)\n", cases[i].name, WEXITSTATUS(status)); + failures++; + } + fflush(stdout); + } + + if (failures == 0) { + printf("c_process_control test passed\n"); + return 0; + } + + printf("c_process_control test failed (%d failure%s)\n", failures, + failures == 1 ? "" : "s"); + return 1; +} diff --git a/test/wasix/c_process_control/test.sh b/test/wasix/c_process_control/test.sh new file mode 100644 index 000000000..5cb9467c2 --- /dev/null +++ b/test/wasix/c_process_control/test.sh @@ -0,0 +1,22 @@ +#!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} + +WASMER_BIN=${WASMER_BIN:-wasmer} +WASM_OPT_BIN=${WASM_OPT_BIN:-$HOME/.wasixcc/binaryen/bin/wasm-opt} + +if [ ! -x "$WASM_OPT_BIN" ]; then + echo "Test failed: wasm-opt not found at $WASM_OPT_BIN (set WASM_OPT_BIN)" > /dev/stderr + exit 1 +fi + +"$WASM_OPT_BIN" --asyncify ./main -o ./main +"$WASM_OPT_BIN" --asyncify ./exec_helper -o ./exec_helper + +$WASMER_BIN run --verbose $BACKEND_FLAG --dir . ./main +RESULT=$? +if [ "$RESULT" != "0" ]; then + echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr + exit 1 +fi + +echo "c_process_control test passed" diff --git a/test/wasix/c_pthreads/test.sh b/test/wasix/c_pthreads/test.sh index caf5767a1..9820dc5a5 100644 --- a/test/wasix/c_pthreads/test.sh +++ b/test/wasix/c_pthreads/test.sh @@ -1,11 +1,14 @@ #!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} tmpfile=$(mktemp) -wasmer run --verbose --enable-all ./main > $tmpfile +WASMER_BIN=${WASMER_BIN:-wasmer} + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main > $tmpfile RESULT=$? if [ "$RESULT" != "0" ]; then echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr exit 1 fi -echo "c_pthreads test passed" \ No newline at end of file +echo "c_pthreads test passed" diff --git a/test/wasix/c_setjmp/CMakeLists.txt b/test/wasix/c_setjmp/CMakeLists.txt new file mode 100644 index 000000000..3ec40060e --- /dev/null +++ b/test/wasix/c_setjmp/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required (VERSION 3.5.0) +project (setjmp_tests) + +add_executable(main main.c) diff --git a/test/wasix/c_setjmp/main.c b/test/wasix/c_setjmp/main.c new file mode 100644 index 000000000..cbdfd0038 --- /dev/null +++ b/test/wasix/c_setjmp/main.c @@ -0,0 +1,133 @@ +// Ported from NetBSD libc setjmp tests and Android bionic setjmp smoke tests. +// Sources: +// - freebsd-src/contrib/netbsd-tests/lib/libc/setjmp/t_setjmp.c +// - freebsd-src/contrib/netbsd-tests/lib/libc/setjmp/t_threadjmp.c +// - bionic/tests/setjmp_test.cpp (subset) + +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(__wasilibc_unmodified_upstream) && !defined(__wasm_exception_handling__) +extern int sigsetjmp(sigjmp_buf, int); +extern void siglongjmp(sigjmp_buf, int); +#endif + +static void set_sigabrt_blocked(int blocked) { + sigset_t ss; + assert(sigemptyset(&ss) == 0); + if (blocked) { + assert(sigaddset(&ss, SIGABRT) == 0); + } + assert(sigprocmask(SIG_SETMASK, &ss, NULL) == 0); +} + +static int is_sigabrt_blocked(void) { + sigset_t ss; + assert(sigprocmask(SIG_SETMASK, NULL, &ss) == 0); + return sigismember(&ss, SIGABRT) == 1; +} + +static void test_setjmp_longjmp_value(void) { + jmp_buf jb; + int value = setjmp(jb); + if (value == 0) { + longjmp(jb, 123); + assert(0 && "longjmp should not return"); + } + assert(value == 123); +} + +static void test_longjmp_zero_returns_one(void) { + jmp_buf jb; + int value = setjmp(jb); + if (value == 0) { + longjmp(jb, 0); + assert(0 && "longjmp should not return"); + } + assert(value == 1); +} + +static void test_sigsetjmp_siglongjmp_value(void) { + sigjmp_buf sjb; + int value = sigsetjmp(sjb, 0); + if (value == 0) { + siglongjmp(sjb, 789); + assert(0 && "siglongjmp should not return"); + } + assert(value == 789); + + value = sigsetjmp(sjb, 1); + if (value == 0) { + siglongjmp(sjb, 0xabc); + assert(0 && "siglongjmp should not return"); + } + assert(value == 0xabc); +} + +static void test_signal_mask_behavior(void) { + jmp_buf jb; + sigjmp_buf sjb; + + // sigsetjmp(0)/siglongjmp: no mask save/restore. + set_sigabrt_blocked(1); + if (sigsetjmp(sjb, 0) == 0) { + set_sigabrt_blocked(0); + siglongjmp(sjb, 1); + assert(0 && "siglongjmp should not return"); + } + assert(is_sigabrt_blocked() == 0); + set_sigabrt_blocked(0); + + // sigsetjmp(1)/siglongjmp: mask save/restore. + set_sigabrt_blocked(1); + if (sigsetjmp(sjb, 1) == 0) { + set_sigabrt_blocked(0); + siglongjmp(sjb, 1); + assert(0 && "siglongjmp should not return"); + } + assert(is_sigabrt_blocked() == 1); + set_sigabrt_blocked(0); +} + +static void test_thread_self_consistency(void) { + pthread_t self = pthread_self(); + jmp_buf jb; + sigjmp_buf sjb; + + if (setjmp(jb) == 0) { + longjmp(jb, 1); + assert(0 && "longjmp should not return"); + } + assert(pthread_equal(self, pthread_self())); + + if (sigsetjmp(sjb, 0) == 0) { + siglongjmp(sjb, 1); + assert(0 && "siglongjmp should not return"); + } + assert(pthread_equal(self, pthread_self())); + + if (sigsetjmp(sjb, 1) == 0) { + siglongjmp(sjb, 1); + assert(0 && "siglongjmp should not return"); + } + assert(pthread_equal(self, pthread_self())); +} + +int main(void) { + test_setjmp_longjmp_value(); + test_longjmp_zero_returns_one(); + test_sigsetjmp_siglongjmp_value(); + test_signal_mask_behavior(); + test_thread_self_consistency(); + return 0; +} diff --git a/test/wasix/c_setjmp/test.sh b/test/wasix/c_setjmp/test.sh new file mode 100755 index 000000000..07cb4c34d --- /dev/null +++ b/test/wasix/c_setjmp/test.sh @@ -0,0 +1,21 @@ +#!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} + +WASMER_BIN=${WASMER_BIN:-wasmer} +WASM_OPT_BIN=${WASM_OPT_BIN:-$HOME/.wasixcc/binaryen/bin/wasm-opt} + +if [ ! -x "$WASM_OPT_BIN" ]; then + echo "Test failed: wasm-opt not found at $WASM_OPT_BIN (set WASM_OPT_BIN)" > /dev/stderr + exit 1 +fi + +"$WASM_OPT_BIN" --asyncify ./main -o ./main + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main +RESULT=$? +if [ "$RESULT" != "0" ]; then + echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr + exit 1 +fi + +echo "c_setjmp test passed" diff --git a/test/wasix/cpp_atomic/test.sh b/test/wasix/cpp_atomic/test.sh index f14bd188b..a407e84b7 100644 --- a/test/wasix/cpp_atomic/test.sh +++ b/test/wasix/cpp_atomic/test.sh @@ -1,9 +1,12 @@ #!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} -wasmer run --verbose --enable-all ./main +WASMER_BIN=${WASMER_BIN:-wasmer} + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main RESULT=$? if [ "$RESULT" != "0" ]; then echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr exit 1 fi -echo "cpp_atomic test passed" \ No newline at end of file +echo "cpp_atomic test passed" diff --git a/test/wasix/cpp_executable/test.sh b/test/wasix/cpp_executable/test.sh index e67e4c62d..ed9300710 100644 --- a/test/wasix/cpp_executable/test.sh +++ b/test/wasix/cpp_executable/test.sh @@ -1,9 +1,12 @@ #!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} -wasmer run --verbose --enable-all ./main +WASMER_BIN=${WASMER_BIN:-wasmer} + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main RESULT=$? if [ "$RESULT" != "123" ]; then echo "Test failed: different exit code ($RESULT vs. 123)" > /dev/stderr exit 1 fi -echo "cpp_executable test passed" \ No newline at end of file +echo "cpp_executable test passed" diff --git a/test/wasix/cpp_iostream/test.sh b/test/wasix/cpp_iostream/test.sh index 0fcb58b6f..3c95435bd 100644 --- a/test/wasix/cpp_iostream/test.sh +++ b/test/wasix/cpp_iostream/test.sh @@ -1,9 +1,12 @@ #!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} -wasmer run --verbose --enable-all ./main +WASMER_BIN=${WASMER_BIN:-wasmer} + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main RESULT=$? if [ "$RESULT" != "0" ]; then echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr exit 1 fi -echo "cpp_iostream test passed" \ No newline at end of file +echo "cpp_iostream test passed" diff --git a/test/wasix/cpp_threads/test.sh b/test/wasix/cpp_threads/test.sh index a5bfdea3b..40589962a 100644 --- a/test/wasix/cpp_threads/test.sh +++ b/test/wasix/cpp_threads/test.sh @@ -1,11 +1,14 @@ #!/bin/bash +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} tmpfile=$(mktemp) -wasmer run --verbose --enable-all ./main > $tmpfile +WASMER_BIN=${WASMER_BIN:-wasmer} + +$WASMER_BIN run --verbose $BACKEND_FLAG ./main > $tmpfile RESULT=$? if [ "$RESULT" != "0" ]; then echo "Test failed: different exit code ($RESULT vs. 0)" > /dev/stderr exit 1 fi -echo "cpp_threads test passed" \ No newline at end of file +echo "cpp_threads test passed" diff --git a/test/wasix/dynamic_loading/CMakeLists.txt b/test/wasix/dynamic_loading/CMakeLists.txt new file mode 100644 index 000000000..eddf2645e --- /dev/null +++ b/test/wasix/dynamic_loading/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.5.0) +project(dynamic_loading C) + +add_custom_target(main ALL + COMMAND ${CMAKE_COMMAND} -E echo "dynamic_loading: build happens in test.sh" +) diff --git a/test/wasix/dynamic_loading/basic_main.c b/test/wasix/dynamic_loading/basic_main.c new file mode 100644 index 000000000..b0f031756 --- /dev/null +++ b/test/wasix/dynamic_loading/basic_main.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include + +int main_test_function(int x) +{ + return x + 5; +} + +int main(void) +{ + dlerror(); + void *self = dlopen(NULL, RTLD_NOW); + if (!self) { + fprintf(stderr, "dlopen(NULL) failed: %s\n", dlerror()); + } + assert(self != NULL); + + dlerror(); + int (*self_fn)(int) = (int (*)(int))dlsym(self, "main_test_function"); + char *err = dlerror(); + if (err) { + fprintf(stderr, "dlsym(main_test_function) failed: %s\n", err); + } + assert(err == NULL); + assert(self_fn != NULL); + assert(self_fn(2) == 7); + + assert(dlclose(self) == 0); + + dlerror(); + void *noload = dlopen("./libbasic.so", RTLD_NOW | RTLD_NOLOAD); + err = dlerror(); + assert(noload == NULL); + assert(err && *err); + + dlerror(); + void *missing_file = dlopen("./missing.so", RTLD_NOW); + err = dlerror(); + assert(missing_file == NULL); + assert(err && *err); + + void *handle_local = dlopen("./libbasic.so", RTLD_NOW | RTLD_LOCAL); + if (!handle_local) { + fprintf(stderr, "dlopen(RTLD_LOCAL) failed: %s\n", dlerror()); + } + assert(handle_local != NULL); + + dlerror(); + void *local_default = dlsym(RTLD_DEFAULT, "side_func"); + err = dlerror(); + assert(local_default == NULL); + assert(err && *err); + + dlerror(); + int (*side_func_local)(int) = (int (*)(int))dlsym(handle_local, "side_func"); + err = dlerror(); + if (err) { + fprintf(stderr, "dlsym(side_func) failed: %s\n", err); + } + assert(err == NULL); + assert(side_func_local != NULL); + assert(side_func_local(42) == 84); + + assert(dlclose(handle_local) == 0); + + void *handle = dlopen("./libbasic.so", RTLD_NOW | RTLD_GLOBAL); + if (!handle) { + fprintf(stderr, "dlopen(RTLD_GLOBAL) failed: %s\n", dlerror()); + } + assert(handle != NULL); + + dlerror(); + void *global_default = dlsym(RTLD_DEFAULT, "side_func"); + err = dlerror(); + assert(err == NULL); + assert(global_default != NULL); + + dlerror(); + void *noload_after = dlopen("./libbasic.so", RTLD_NOW | RTLD_NOLOAD); + err = dlerror(); + assert(err == NULL); + assert(noload_after != NULL); + assert(noload_after == handle); + + dlerror(); + int (*side_func)(int) = (int (*)(int))dlsym(handle, "side_func"); + err = dlerror(); + if (err) { + fprintf(stderr, "dlsym(side_func) failed: %s\n", err); + } + assert(err == NULL); + assert(side_func != NULL); + assert(side_func(42) == 84); + + dlerror(); + void *empty_sym = dlsym(handle, ""); + err = dlerror(); + assert(empty_sym == NULL); + assert(err && *err); + + dlerror(); + void *missing = dlsym(handle, "missing_symbol"); + err = dlerror(); + assert(missing == NULL); + assert(err && *err); + + dlerror(); + void *main_handle = dlopen(NULL, RTLD_NOW); + if (!main_handle) { + fprintf(stderr, "dlopen(NULL) failed: %s\n", dlerror()); + } + assert(main_handle != NULL); + dlerror(); + int (*main_handle_fn)(int) = (int (*)(int))dlsym(main_handle, "main_test_function"); + err = dlerror(); + assert(err == NULL); + assert(main_handle_fn != NULL); + assert(main_handle_fn(3) == 8); + assert(dlclose(main_handle) == 0); + + assert(dlclose(noload_after) == 0); + assert(dlclose(handle) == 0); + + dlerror(); + void *closed_sym = dlsym(handle, "side_func"); + err = dlerror(); + assert(closed_sym == NULL); + assert(err && *err); + + int bad = dlclose((void *)0xffffff); + assert(bad != 0); + err = dlerror(); + assert(err && *err); + + int fd = open("empty.so", O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + fprintf(stderr, "open(empty.so) failed: %s\n", strerror(errno)); + } + assert(fd >= 0); + close(fd); + + dlerror(); + void *empty = dlopen("./empty.so", RTLD_NOW); + err = dlerror(); + assert(empty == NULL); + assert(err && *err); + unlink("empty.so"); + + return 0; +} diff --git a/test/wasix/dynamic_loading/basic_side.c b/test/wasix/dynamic_loading/basic_side.c new file mode 100644 index 000000000..523d301a7 --- /dev/null +++ b/test/wasix/dynamic_loading/basic_side.c @@ -0,0 +1,4 @@ +int side_func(int x) +{ + return x * 2; +} diff --git a/test/wasix/dynamic_loading/cache_main.c b/test/wasix/dynamic_loading/cache_main.c new file mode 100644 index 000000000..50c46abac --- /dev/null +++ b/test/wasix/dynamic_loading/cache_main.c @@ -0,0 +1,43 @@ +#include +#include +#include + +int main(void) +{ + void *handle1 = dlopen("./libcache1.so", RTLD_NOW | RTLD_GLOBAL); + if (!handle1) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + } + assert(handle1 != NULL); + + int (*side_func1)(int) = (int (*)(int))dlsym(handle1, "side_func"); + if (!side_func1) { + fprintf(stderr, "dlsym failed: %s\n", dlerror()); + } + assert(side_func1 != NULL); + + int res = side_func1(2); + assert(res == 44); + + void *handle2 = dlopen("./libcache2.so", RTLD_NOW | RTLD_GLOBAL); + if (!handle2) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + } + assert(handle2 != NULL); + + int (*side_func2)(int) = (int (*)(int))dlsym(handle2, "side_func"); + if (!side_func2) { + fprintf(stderr, "dlsym failed: %s\n", dlerror()); + } + assert(side_func2 != NULL); + + assert(side_func1 != side_func2); + + res = side_func2(2); + assert(res == 4); + + assert(dlclose(handle1) == 0); + assert(dlclose(handle2) == 0); + + return 0; +} diff --git a/test/wasix/dynamic_loading/cache_side1.c b/test/wasix/dynamic_loading/cache_side1.c new file mode 100644 index 000000000..2e84a642d --- /dev/null +++ b/test/wasix/dynamic_loading/cache_side1.c @@ -0,0 +1,4 @@ +int side_func(int x) +{ + return x + 42; +} diff --git a/test/wasix/dynamic_loading/cache_side2.c b/test/wasix/dynamic_loading/cache_side2.c new file mode 100644 index 000000000..523d301a7 --- /dev/null +++ b/test/wasix/dynamic_loading/cache_side2.c @@ -0,0 +1,4 @@ +int side_func(int x) +{ + return x * 2; +} diff --git a/test/wasix/dynamic_loading/data_main.c b/test/wasix/dynamic_loading/data_main.c new file mode 100644 index 000000000..33a504959 --- /dev/null +++ b/test/wasix/dynamic_loading/data_main.c @@ -0,0 +1,42 @@ +#include +#include +#include + +int main(void) +{ + void *handle = dlopen("./libdata1.so", RTLD_NOW | RTLD_GLOBAL); + if (!handle) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + } + assert(handle != NULL); + + dlerror(); + int *data_export = (int *)dlsym(handle, "data_export"); + char *err = dlerror(); + if (err) { + fprintf(stderr, "dlsym(data_export) failed: %s\n", err); + } + assert(err == NULL); + assert(data_export != NULL); + assert(*data_export == 42); + + dlerror(); + int (*func_export)(void) = (int (*)(void))dlsym(handle, "func_export"); + err = dlerror(); + if (err) { + fprintf(stderr, "dlsym(func_export) failed: %s\n", err); + } + assert(err == NULL); + assert(func_export != NULL); + assert(func_export() == 234); + + dlerror(); + void *local_sym = dlsym(handle, "local_function"); + err = dlerror(); + assert(local_sym == NULL); + assert(err && *err); + + assert(dlclose(handle) == 0); + + return 0; +} diff --git a/test/wasix/dynamic_loading/data_side1.c b/test/wasix/dynamic_loading/data_side1.c new file mode 100644 index 000000000..7b1419d55 --- /dev/null +++ b/test/wasix/dynamic_loading/data_side1.c @@ -0,0 +1,21 @@ +int data_export = 42; + +extern int data_export2; +extern int func_export2(void); + +static void local_function(int *i) +{ + *i += 1; +} + +int func_export(void) +{ + int x = 123; + local_function(&x); + + int res = func_export2(); + res += data_export2; + (void)res; + + return 234; +} diff --git a/test/wasix/dynamic_loading/data_side2.c b/test/wasix/dynamic_loading/data_side2.c new file mode 100644 index 000000000..239a16a39 --- /dev/null +++ b/test/wasix/dynamic_loading/data_side2.c @@ -0,0 +1,6 @@ +int data_export2 = 10101; + +int func_export2(void) +{ + return 987; +} diff --git a/test/wasix/dynamic_loading/needed_main.c b/test/wasix/dynamic_loading/needed_main.c new file mode 100644 index 000000000..4bd36d1d7 --- /dev/null +++ b/test/wasix/dynamic_loading/needed_main.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include + +extern int main_needed_func(int); + +int main_exported(void) +{ + return 85; +} + +static void *thread_func(void *arg) +{ + (void)arg; + for (;;) { + sleep(1); + } + return NULL; +} + +int main(void) +{ + pthread_t thread; + int rc = pthread_create(&thread, NULL, thread_func, NULL); + assert(rc == 0); + + assert(main_needed_func(42) == 43); + + void *handle = dlopen("./libside.so", RTLD_NOW | RTLD_GLOBAL); + if (!handle) { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + } + assert(handle != NULL); + + int (*side_func)(int) = (int (*)(int))dlsym(handle, "side_func"); + if (!side_func) { + fprintf(stderr, "dlsym failed: %s\n", dlerror()); + } + assert(side_func != NULL); + + int res = side_func(42); + assert(res == 92); + + assert(dlclose(handle) == 0); + + pthread_kill(thread, SIGTERM); + + return 0; +} diff --git a/test/wasix/dynamic_loading/needed_main_needed.c b/test/wasix/dynamic_loading/needed_main_needed.c new file mode 100644 index 000000000..6caee534d --- /dev/null +++ b/test/wasix/dynamic_loading/needed_main_needed.c @@ -0,0 +1,4 @@ +int main_needed_func(int x) +{ + return x + 1; +} diff --git a/test/wasix/dynamic_loading/needed_side.c b/test/wasix/dynamic_loading/needed_side.c new file mode 100644 index 000000000..5befc3cb4 --- /dev/null +++ b/test/wasix/dynamic_loading/needed_side.c @@ -0,0 +1,23 @@ +#include + +extern int side_needed_func(int); +extern int main_exported(void); +int (*main_exported_ptr)(void) = main_exported; + +int side_func(int x) +{ + int (*side_func_ptr)(int) = side_func; + if (side_func_ptr != side_func) + { + printf("side_needed_func pointer mismatch\n"); + return -1; + } + + if (main_exported_ptr() != 85) + { + printf("main_exported returned unexpected value\n"); + return -1; + } + + return side_needed_func(x) * 2; +} diff --git a/test/wasix/dynamic_loading/needed_side_needed.c b/test/wasix/dynamic_loading/needed_side_needed.c new file mode 100644 index 000000000..4ffb5e29c --- /dev/null +++ b/test/wasix/dynamic_loading/needed_side_needed.c @@ -0,0 +1,22 @@ +#include + +extern int main_exported(void); +int (*main_exported_ptr)(void) = main_exported; + +int side_needed_func(int x) +{ + int (*side_needed_func_ptr)(int) = side_needed_func; + if (side_needed_func_ptr != side_needed_func) + { + printf("side_needed_func pointer mismatch\n"); + return -1; + } + + if (main_exported_ptr() != 85) + { + printf("main_exported returned unexpected value\n"); + return -1; + } + + return x + 4; +} diff --git a/test/wasix/dynamic_loading/test.sh b/test/wasix/dynamic_loading/test.sh new file mode 100755 index 000000000..47da8889c --- /dev/null +++ b/test/wasix/dynamic_loading/test.sh @@ -0,0 +1,71 @@ +#!/bin/bash +set -euo pipefail + +BACKEND_FLAG=${WASMER_BACKEND_FLAG:---llvm} +WASMER_BIN=${WASMER_BIN:-wasmer} +WASIXCC_BIN=${WASIXCC_BIN:-wasixcc} +SYSROOT=${WASIX_SYSROOT_PIC:-${WASIX_SYSROOT:-$HOME/.wasixcc/sysroot/sysroot}} + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +export WASIXCC_WASM_EXCEPTIONS=${WASIXCC_WASM_EXCEPTIONS:-yes} +export WASIXCC_PIC=${WASIXCC_PIC:-yes} + +run_case() { + local name="$1" + if ! (cd "$name" && "$WASMER_BIN" run --verbose $BACKEND_FLAG ./main.wasm --volume .); then + echo "dynamic_loading case failed: $name" >&2 + return 1 + fi + return 0 +} + +build_basic() { + local out="basic" + mkdir -p "$out" + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/basic_main.c" -o "$out/main.wasm" -Wl,-pie + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/basic_side.c" -o "$out/libbasic.so" -Wl,-shared +} + +build_cache() { + local out="cache" + mkdir -p "$out" + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/cache_main.c" -o "$out/main.wasm" -Wl,-pie + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/cache_side1.c" -o "$out/libcache1.so" -Wl,-shared + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/cache_side2.c" -o "$out/libcache2.so" -Wl,-shared +} + +build_data() { + local out="data" + mkdir -p "$out" + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/data_side2.c" -o "$out/libdata2.so" -Wl,-shared + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/data_side1.c" "$out/libdata2.so" -o "$out/libdata1.so" -Wl,-shared -Wl,-rpath,\$ORIGIN + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/data_main.c" -o "$out/main.wasm" -Wl,-pie +} + +build_needed() { + local out="needed" + mkdir -p "$out" + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/needed_main_needed.c" -o "$out/libmain-needed.so" -Wl,-shared + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/needed_main.c" "$out/libmain-needed.so" -o "$out/main.wasm" -Wl,-pie -Wl,-rpath,\$ORIGIN + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/needed_side_needed.c" -o "$out/libside-needed.so" -Wl,-shared + "$WASIXCC_BIN" --sysroot "$SYSROOT" "$SCRIPT_DIR/needed_side.c" "$out/libside-needed.so" -o "$out/libside.so" -Wl,-shared -Wl,-rpath,\$ORIGIN +} + +build_basic +build_cache +build_data +build_needed + +FAIL=0 +run_case basic || FAIL=1 +run_case cache || FAIL=1 +run_case data || FAIL=1 +run_case needed || FAIL=1 + +if [ "$FAIL" -ne 0 ]; then + echo "dynamic_loading test failed" >&2 + exit 1 +fi + +echo "dynamic_loading test passed" diff --git a/test/wasix/run_tests.sh b/test/wasix/run_tests.sh index 3337a95c7..2c14eea9c 100755 --- a/test/wasix/run_tests.sh +++ b/test/wasix/run_tests.sh @@ -5,6 +5,7 @@ TESTS= STATUS=0 FAILED_TESTS="" PASSED_TESTS="" +SYSROOT=${WASIX_SYSROOT_PIC:-${WASIX_SYSROOT:-/opt/wasix-sysroot}} if [ ! -z "${@:1}" ]; then TESTS=${@:1} @@ -15,7 +16,7 @@ fi for test in $TESTS; do mkdir -p ${BUILD_DIR}/${test} cd ${BUILD_DIR}/${test} - cmake --fresh -DCMAKE_MAKE_PROGRAM=make -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN -DCMAKE_SYSROOT=/opt/wasix-sysroot $SCRIPT_DIR/${test} + cmake --fresh -DCMAKE_MAKE_PROGRAM=make -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN -DCMAKE_SYSROOT=$SYSROOT $SCRIPT_DIR/${test} cmake --build . --target all bash $SCRIPT_DIR/${test}/test.sh RESULT=$? diff --git a/tools/clang-wasix-pic.cmake_toolchain b/tools/clang-wasix-pic.cmake_toolchain new file mode 100644 index 000000000..8d9ffa573 --- /dev/null +++ b/tools/clang-wasix-pic.cmake_toolchain @@ -0,0 +1,43 @@ +# Cmake toolchain description file for WASI (PIC, no wasm EH) +cmake_minimum_required(VERSION 3.5.0) + +set(COMPILE_FLAGS "-O2 -matomics -mbulk-memory -mmutable-globals -pthread -mthread-model posix -ftls-model=local-exec -fno-trapping-math -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_PROCESS_CLOCKS -fno-exceptions") +set(LINK_FLAGS "-lwasi-emulated-mman -lwasi-emulated-process-clocks -lwasi-emulated-getpid -fno-exceptions -Wl,--shared-memory -Wl,--max-memory=4294967296 -Wl,--import-memory -Wl,--export-dynamic -Wl,--export=__heap_base -Wl,--export=__stack_pointer -Wl,--export=__data_end -Wl,--export=__wasm_init_tls -Wl,--export=__wasm_signal -Wl,--export=__tls_size -Wl,--export=__tls_align -Wl,--export=__tls_base") + +set(CMAKE_SYSTEM_NAME WASI) # Generic for now, to not trigger a Warning +set(CMAKE_SYSTEM_VERSION 1) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(CMAKE_C_COMPILER_ID Clang) +if(DEFINED ENV{WASIX_TRIPLE}) + set(triple $ENV{WASIX_TRIPLE}) +else() + set(triple wasm32-wasmer-wasi) +endif() +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILE_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILE_FLAGS}") +set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} ${LINK_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINK_FLAGS}") +set(CMAKE_${lang}_COMPILE_OPTIONS_SYSROOT "--sysroot=") + +if(DEFINED ENV{CC}) + set(CMAKE_C_COMPILER $ENV{CC}) +else() + set(CMAKE_C_COMPILER clang) +endif() +if(DEFINED ENV{CXX}) + set(CMAKE_CXX_COMPILER $ENV{CXX}) +else() + set(CMAKE_CXX_COMPILER clang++) +endif() +set(CMAKE_LINKER wasm-ld) +set(CMAKE_AR llvm-ar) + +# Don't look in the sysroot for executables to run during the build +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Only look in the sysroot (not in the host paths) for the rest +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)