Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
build
.vscode
dist
test-builds
107 changes: 107 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -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`).
416 changes: 416 additions & 0 deletions WASIX-libc-wasi-usage-categories.md

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions libc-top-half/musl/src/ldso/dlclose.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
139 changes: 139 additions & 0 deletions libc-top-half/musl/src/ldso/dlopen.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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;
}
59 changes: 59 additions & 0 deletions libc-top-half/musl/src/ldso/dlsym.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions libc-top-half/musl/src/linux/setgroups.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading