From bb3f6f05db42f79415c3d1eb522f4d60d82e10d6 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Thu, 15 Jan 2026 00:07:15 +0100 Subject: [PATCH] working stdout --- expected/wasm32-wasip3/defined-symbols.txt | 2 + libc-bottom-half/CMakeLists.txt | 1 + .../cloudlibc/src/libc/unistd/read.c | 4 +- .../cloudlibc/src/libc/unistd/write.c | 4 +- .../headers/private/wasi/descriptor_table.h | 11 ++ .../headers/private/wasi/file_utils.h | 7 + libc-bottom-half/sources/wasip3_file_utils.c | 86 ++++++++++ libc-bottom-half/sources/wasip3_stdio.c | 151 +++++++++++++++++- test/CMakeLists.txt | 4 +- 9 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 libc-bottom-half/sources/wasip3_file_utils.c diff --git a/expected/wasm32-wasip3/defined-symbols.txt b/expected/wasm32-wasip3/defined-symbols.txt index 523c3b58d..8b1f5bc0f 100644 --- a/expected/wasm32-wasip3/defined-symbols.txt +++ b/expected/wasm32-wasip3/defined-symbols.txt @@ -336,6 +336,7 @@ __wasilibc_open_nomode __wasilibc_populate_preopens __wasilibc_pthread_self __wasilibc_random +__wasilibc_read3 __wasilibc_rename_newat __wasilibc_rename_oldat __wasilibc_reset_preopens @@ -344,6 +345,7 @@ __wasilibc_stat __wasilibc_tell __wasilibc_unlinkat __wasilibc_utimens +__wasilibc_write3 __wasm_call_dtors __wcscoll_l __wcsftime_l diff --git a/libc-bottom-half/CMakeLists.txt b/libc-bottom-half/CMakeLists.txt index d0aca4732..294b64bf0 100644 --- a/libc-bottom-half/CMakeLists.txt +++ b/libc-bottom-half/CMakeLists.txt @@ -164,6 +164,7 @@ if (WASI STREQUAL "p3") list(APPEND bottom_half_sources sources/wasip3.c sources/wasip3_file.c + sources/wasip3_file_utils.c sources/wasip3_stdio.c sources/wasip3_subtask.c sources/wasip3_tcp.c diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/read.c b/libc-bottom-half/cloudlibc/src/libc/unistd/read.c index eb842976f..ec1f18f35 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/read.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/read.c @@ -53,9 +53,7 @@ ssize_t read(int fildes, void *buf, size_t nbyte) { *off += contents.len; return contents.len; #elif defined(__wasip3__) - // TODO(wasip3) - errno = ENOTSUP; - return -1; + return __wasilibc_read3(fildes, buf, nbyte); #else # error "Unsupported WASI version" #endif diff --git a/libc-bottom-half/cloudlibc/src/libc/unistd/write.c b/libc-bottom-half/cloudlibc/src/libc/unistd/write.c index 0f2f9275f..d7287fd3f 100644 --- a/libc-bottom-half/cloudlibc/src/libc/unistd/write.c +++ b/libc-bottom-half/cloudlibc/src/libc/unistd/write.c @@ -72,9 +72,7 @@ ssize_t write(int fildes, const void *buf, size_t nbyte) { *off += contents.len; return contents.len; #elif defined(__wasip3__) - // TODO(wasip3) - errno = ENOTSUP; - return -1; + return __wasilibc_write3(fildes, buf, nbyte); #else # error "Unknown WASI version" #endif diff --git a/libc-bottom-half/headers/private/wasi/descriptor_table.h b/libc-bottom-half/headers/private/wasi/descriptor_table.h index 217728215..99e57fb39 100644 --- a/libc-bottom-half/headers/private/wasi/descriptor_table.h +++ b/libc-bottom-half/headers/private/wasi/descriptor_table.h @@ -8,6 +8,11 @@ #include #include +#ifdef __wasip3__ +// create an alias to distinguish the handle type in the API +typedef uint32_t waitable_t; +#endif + /** * Operations that are required of all descriptors registered as file * descriptors. @@ -37,6 +42,12 @@ typedef struct descriptor_vtable_t { /// Same as `get_read_stream`, but for output streams. int (*get_write_stream)(void*, streams_borrow_output_stream_t*, off_t**, poll_own_pollable_t**); #endif +#ifdef __wasip3__ + /// Start an asynchronous read or write, returns zero on success. + /// Stores the waitable, status and offset location. + int (*read3)(void*, void *buf, size_t nbyte, waitable_t *waitable, wasip3_waitable_status_t *out, off_t**); + int (*write3)(void*, void const *buf, size_t nbyte, waitable_t *waitable, wasip3_waitable_status_t *out, off_t**); +#endif /// Sets the nonblocking flag for this object to the specified value. int (*set_blocking)(void*, bool); diff --git a/libc-bottom-half/headers/private/wasi/file_utils.h b/libc-bottom-half/headers/private/wasi/file_utils.h index 5e04d5394..781e29891 100644 --- a/libc-bottom-half/headers/private/wasi/file_utils.h +++ b/libc-bottom-half/headers/private/wasi/file_utils.h @@ -96,4 +96,11 @@ static unsigned dir_entry_type_to_d_type(filesystem_descriptor_type_t ty) { #endif +#ifdef __wasip3__ +#include + +ssize_t __wasilibc_write3(int fildes, void const *buf, size_t nbyte); +ssize_t __wasilibc_read3(int fildes, void *buf, size_t nbyte); +#endif + #endif diff --git a/libc-bottom-half/sources/wasip3_file_utils.c b/libc-bottom-half/sources/wasip3_file_utils.c new file mode 100644 index 000000000..7b8d31d5a --- /dev/null +++ b/libc-bottom-half/sources/wasip3_file_utils.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +#ifdef __wasip3__ +ssize_t __wasilibc_write3(int fildes, void const *buf, size_t nbyte) { + descriptor_table_entry_t *entry = descriptor_table_get_ref(fildes); + if (!entry) + return -1; + if (!entry->vtable->write3) { + errno = EOPNOTSUPP; + return -1; + } + off_t *off; + waitable_t output_stream; + wasip3_waitable_status_t status; + if ((*entry->vtable->write3)(entry->data, buf, nbyte, &output_stream, &status, + &off) < 0) + return -1; + if (status == WASIP3_WAITABLE_STATUS_BLOCKED) { + wasip3_waitable_set_t set = wasip3_waitable_set_new(); + wasip3_waitable_join(output_stream, set); + wasip3_event_t event; + wasip3_waitable_set_wait(set, &event); + assert(event.event == WASIP3_EVENT_STREAM_WRITE); + assert(event.waitable == output_stream); + // remove from set + wasip3_waitable_join(output_stream, 0); + wasip3_waitable_set_drop(set); + ssize_t bytes_written = event.code; + if (off) + *off += bytes_written; + return bytes_written; + } else if (WASIP3_WAITABLE_STATE(status) == WASIP3_WAITABLE_COMPLETED) { + ssize_t bytes_written = WASIP3_WAITABLE_COUNT(status); + if (off) + *off += bytes_written; + return bytes_written; + } else { + abort(); + } +} + +ssize_t __wasilibc_read3(int fildes, void *buf, size_t nbyte) { + descriptor_table_entry_t *entry = descriptor_table_get_ref(fildes); + if (!entry) + return -1; + if (!entry->vtable->read3) { + errno = EOPNOTSUPP; + return -1; + } + off_t *off; + waitable_t waitable; + wasip3_waitable_status_t status; + if ((*entry->vtable->read3)(entry->data, buf, nbyte, &waitable, &status, + &off) < 0) + return -1; + if (status == WASIP3_WAITABLE_STATUS_BLOCKED) { + wasip3_waitable_set_t set = wasip3_waitable_set_new(); + wasip3_waitable_join(waitable, set); + wasip3_event_t event; + wasip3_waitable_set_wait(set, &event); + assert(event.event == WASIP3_EVENT_STREAM_READ); + assert(event.waitable == waitable); + // remove from set + wasip3_waitable_join(waitable, 0); + wasip3_waitable_set_drop(set); + ssize_t bytes_read = event.code; + if (off) + *off += bytes_read; + return bytes_read; + } else if (WASIP3_WAITABLE_STATE(status) == WASIP3_WAITABLE_COMPLETED) { + ssize_t bytes_read = WASIP3_WAITABLE_COUNT(status); + if (off) + *off += bytes_read; + return bytes_read; + } else if (WASIP3_WAITABLE_STATE(status) == WASIP3_WAITABLE_DROPPED) { + return 0; + } else { + abort(); + } +} +#endif // __wasip3__ diff --git a/libc-bottom-half/sources/wasip3_stdio.c b/libc-bottom-half/sources/wasip3_stdio.c index 5de4aa9f6..51b60babc 100644 --- a/libc-bottom-half/sources/wasip3_stdio.c +++ b/libc-bottom-half/sources/wasip3_stdio.c @@ -2,10 +2,155 @@ #include #ifdef __wasip3__ +#include +#include +#include +#include +#include + +typedef struct { + stdin_stream_u8_t input; + stdin_future_result_void_error_code_t future; + terminal_input_own_terminal_input_t terminal_in; + + stdin_stream_u8_writer_t stdout; + // contents will be filled by host (once stdout has an error) + stdout_result_void_error_code_t stdout_result; + wasip3_subtask_t stdout_task; + terminal_output_own_terminal_output_t terminal_out; +} stdio3_t; + +static void stdio3_free(void *data) { + stdio3_t *stdio = (stdio3_t *)data; + if (stdio->terminal_in.__handle) + terminal_input_terminal_input_drop_own(stdio->terminal_in); + if (stdio->future) + stdin_future_result_void_error_code_drop_readable(stdio->future); + if (stdio->input) + stdin_stream_u8_drop_readable(stdio->input); + + if (stdio->terminal_out.__handle) + terminal_output_terminal_output_drop_own(stdio->terminal_out); + if (stdio->stdout_task) + wasip3_subtask_cancel(stdio->stdout_task); + if (stdio->stdout) + stdin_stream_u8_drop_writable(stdio->stdout); + free(stdio); +} + +static int stdio3_write(void *data, void const *buf, size_t nbyte, + waitable_t *waitable, wasip3_waitable_status_t *out, + off_t **offs) { + stdio3_t *stdio = (stdio3_t *)data; + if (!stdio->stdout) { + errno = EBADF; + return -1; + } + *waitable = stdio->stdout; + *out = stdin_stream_u8_write(stdio->stdout, buf, nbyte); + *offs = NULL; + return 0; +} + +static int stdio3_read(void *data, void *buf, size_t nbyte, + waitable_t *waitable, wasip3_waitable_status_t *out, + off_t **offs) { + stdio3_t *stdio = (stdio3_t *)data; + if (!stdio->input) { + errno = EBADF; + return -1; + } + *waitable = stdio->input; + *out = stdin_stream_u8_read(stdio->input, buf, nbyte); + *offs = NULL; + return 0; +} + +static int stdio3_fstat(void *data, struct stat *buf) { + memset(buf, 0, sizeof(*buf)); + return 0; +} + +static int stdio3_fcntl_getfl(void *data) { + stdio3_t *stdio = (stdio3_t *)data; + if (stdio->stdout == 0) { + return O_RDONLY; + } else { + return O_WRONLY; + } +} + +static int stdio3_isatty(void *data) { + stdio3_t *stdio = (stdio3_t *)data; + return stdio->terminal_in.__handle != 0 || stdio->terminal_out.__handle != 0; +} + +static descriptor_vtable_t stdio3_vtable = { + .free = stdio3_free, + .read3 = stdio3_read, + .write3 = stdio3_write, + .fstat = stdio3_fstat, + .fcntl_getfl = stdio3_fcntl_getfl, + .isatty = stdio3_isatty, +}; + +static int stdio_add_input() { + stdio3_t *stdio = calloc(1, sizeof(stdio3_t)); + if (!stdio) { + errno = ENOMEM; + return -1; + } + stdin_tuple2_stream_u8_future_result_void_error_code_t stdin; + stdin_read_via_stream(&stdin); + + if (!terminal_stdin_get_terminal_stdin(&stdio->terminal_in)) + stdio->terminal_in.__handle = 0; + + stdio->input = stdin.f0; + stdio->future = stdin.f1; + + descriptor_table_entry_t entry; + entry.vtable = &stdio3_vtable; + entry.data = stdio; + return descriptor_table_insert(entry); +} + +static int stdio3_add_output( + // int fd, + wasip3_subtask_status_t (*func)(stdin_stream_u8_t data, + stdout_result_void_error_code_t *result), + bool (*terminal)(terminal_stdout_own_terminal_output_t *ret)) { + stdio3_t *stdio = calloc(1, sizeof(stdio3_t)); + if (!stdio) { + errno = ENOMEM; + return -1; + } + stdin_stream_u8_t read_side = stdin_stream_u8_new(&stdio->stdout); + wasip3_subtask_status_t res = (*func)(read_side, &stdio->stdout_result); + stdio->stdout_task = WASIP3_SUBTASK_HANDLE(res); + + if (!(*terminal)(&stdio->terminal_out)) + stdio->terminal_out.__handle = 0; + + descriptor_table_entry_t entry; + entry.vtable = &stdio3_vtable; + entry.data = stdio; + return descriptor_table_insert(entry); +} int __wasilibc_init_stdio() { - // TODO(wasip3) - errno = EOPNOTSUPP; - return -1; + if (stdio_add_input() < 0) + return -1; + if (stdio3_add_output(stdout_write_via_stream, + terminal_stdout_get_terminal_stdout) < 0) + return -1; + // assuming that stdout and stderr functions are compatible + if (stdio3_add_output( + (wasip3_subtask_status_t (*)( + stdin_stream_u8_t, + stdout_result_void_error_code_t *))stderr_write_via_stream, + terminal_stderr_get_terminal_stderr) < 0) + return -1; + return 0; } #endif // __wasip3__ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 38c599658..9e33f2a82 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -259,7 +259,7 @@ add_wasilibc_test(ftruncate.c FS FAILP3) add_wasilibc_test(fts.c FS FAILP3) add_wasilibc_test(fwscanf.c FS FAILP3) add_wasilibc_test(getentropy.c) -add_wasilibc_test(hello.c PASS_REGULAR_EXPRESSION "Hello, World!" FAILP3) +add_wasilibc_test(hello.c PASS_REGULAR_EXPRESSION "Hello, World!") add_wasilibc_test(ioctl.c FS FAILP3) add_wasilibc_test(isatty.c FS FAILP3) add_wasilibc_test(link.c FS FAILP3) @@ -277,7 +277,7 @@ add_wasilibc_test(rename.c FS FAILP3) add_wasilibc_test(rmdir.c FS FAILP3) add_wasilibc_test(scandir.c FS FAILP3) add_wasilibc_test(stat.c FS FAILP3) -add_wasilibc_test(stdio.c FS FAILP3) +add_wasilibc_test(stdio.c FS) add_wasilibc_test(strchrnul.c LDFLAGS -Wl,--stack-first -Wl,--initial-memory=327680) add_wasilibc_test(strlen.c LDFLAGS -Wl,--stack-first -Wl,--initial-memory=327680) add_wasilibc_test(strptime.c)