From 0e098a9a25ba25de17683c8c84517f814c60b802 Mon Sep 17 00:00:00 2001 From: SheatNoisette <45624871+SheatNoisette@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:03:45 +0100 Subject: [PATCH] wasm: support WASI program arguments and IO functions (#26294) --- vlib/builtin/wasm/string.v | 20 +++ vlib/builtin/wasm/wasi/wasi_notd_no_imports.v | 18 ++- vlib/v/gen/wasm/mem.v | 6 + vlib/v/gen/wasm/tests/wasi_api.vv | 126 ++++++++++++++++++ vlib/v/gen/wasm/tests/wasi_api.vv.out | 15 +++ 5 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 vlib/v/gen/wasm/tests/wasi_api.vv create mode 100644 vlib/v/gen/wasm/tests/wasi_api.vv.out diff --git a/vlib/builtin/wasm/string.v b/vlib/builtin/wasm/string.v index a38022e98..4b93d3440 100644 --- a/vlib/builtin/wasm/string.v +++ b/vlib/builtin/wasm/string.v @@ -75,3 +75,23 @@ pub fn (s string) at(idx int) u8 { } return unsafe { s.str[idx] } } + +// Convert C string to V string +pub fn cstring_to_vstring(ptr &u8) string { + if ptr == 0 { + return '' + } + + mut len := 0 + unsafe { + for ptr[len] != 0 { + len++ + } + } + unsafe { + return string{ + str: ptr + len: len + } + } +} diff --git a/vlib/builtin/wasm/wasi/wasi_notd_no_imports.v b/vlib/builtin/wasm/wasi/wasi_notd_no_imports.v index 8db3eb4e6..d930a4373 100644 --- a/vlib/builtin/wasm/wasi/wasi_notd_no_imports.v +++ b/vlib/builtin/wasm/wasi/wasi_notd_no_imports.v @@ -1,6 +1,7 @@ module builtin -struct CIOVec { +pub struct CIOVec { +pub: buf &u8 len usize } @@ -8,9 +9,24 @@ struct CIOVec { type Errno = u16 type FileDesc = int +@[wasm_import_namespace: 'wasi_snapshot_preview1'] +fn WASM.args_sizes_get(argc &u32, argc_buf_size &u32) Errno + +@[wasm_import_namespace: 'wasi_snapshot_preview1'] +fn WASM.args_get(argv &&u8, argv_buf &u8) Errno + @[wasm_import_namespace: 'wasi_snapshot_preview1'] fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno +@[wasm_import_namespace: 'wasi_snapshot_preview1'] +fn WASM.fd_read(fd FileDesc, iovs &CIOVec, iovs_len usize, nread &usize) Errno + +@[wasm_import_namespace: 'wasi_snapshot_preview1'] +fn WASM.fd_sync(fd FileDesc) Errno + +@[wasm_import_namespace: 'wasi_snapshot_preview1'] +fn WASM.random_get(buf &u8, buf_len usize) Errno + @[wasm_import_namespace: 'wasi_snapshot_preview1'] @[noreturn] fn WASM.proc_exit(rval int) diff --git a/vlib/v/gen/wasm/mem.v b/vlib/v/gen/wasm/mem.v index 5fe912250..282d46573 100644 --- a/vlib/v/gen/wasm/mem.v +++ b/vlib/v/gen/wasm/mem.v @@ -72,6 +72,12 @@ pub fn (mut g Gen) get_var_from_expr(node ast.Expr) ?Var { ast.ParExpr { return g.get_var_from_expr(node.expr) } + ast.CastExpr { + // For cast expressions like &u32(), we need to look through + // the cast to get the underlying variable because WASM don't have + // really pointers like in C + return g.get_var_from_expr(node.expr) + } ast.SelectorExpr { mut addr := g.get_var_from_expr(node.expr) or { // if place { diff --git a/vlib/v/gen/wasm/tests/wasi_api.vv b/vlib/v/gen/wasm/tests/wasi_api.vv new file mode 100644 index 000000000..ce4744e9c --- /dev/null +++ b/vlib/v/gen/wasm/tests/wasi_api.vv @@ -0,0 +1,126 @@ +struct Args { + argc u32 + argc_buf_size u32 +} + +fn wasi_args_sizes_get() { + args := Args{} + if WASM.args_sizes_get(&args.argc, &args.argc_buf_size) == 0 { + println('args_sizes_get: ok') + } + if args.argc >= 1 { + println('args_sizes_get: argc >= 1') + } + if args.argc_buf_size > 0 { + println('args_sizes_get: argc_buf_size > 0') + } +} + +fn wasi_args_get() { + unsafe { + args := Args{} + WASM.args_sizes_get(&args.argc, &args.argc_buf_size) + + mut argv_buf := malloc(args.argc_buf_size + 1) + mut argv_ptrs := malloc(args.argc) + + if WASM.args_get(&argv_ptrs[0], &argv_buf[0]) == 0 { + println('args_get: ok') + } + + // @TODO: Check if the end ends with .wasm with a proper builtin + if cstring_to_vstring(argv_buf).len == (args.argc_buf_size) - 1 { + println('wasi_args_get: ok') + } + } +} + +fn wasi_random_get() { + mut random_table := [128]u8{} + + errno := WASM.random_get(&random_table[0], 128) + if errno == 0 { + println('random_get: ok') + } + + mut min := u8(255) + mut max := u8(0) + mut all_same := true + first_val := random_table[0] + + for i in 0 .. 128 { + val := random_table[i] + if val < min { + min = val + } + if val > max { + max = val + } + if val != first_val { + all_same = false + } + } + + println('random_get: has low values = ${min < 200}') + println('random_get: has high values = ${max > 55}') + println('random_get: values vary = ${!all_same}') +} + +fn wasi_fd_write() { + message := 'fd_write: Hello from WASI WOORLD!\n' + message_struct := CIOVec{ + buf: message.str + len: usize(message.len) + } + + if WASM.fd_write(1, &message_struct, 1, 0) == 0 { + println('fd_write: ok') + } +} + +fn wasi_fd_write_multiple_iovecs() { + msg1 := 'Multiple ' + msg2 := 'IO ' + msg3 := 'vectors yay!\n' + + iovs := [ + CIOVec{ + buf: msg1.str + len: usize(msg1.len) + }, + CIOVec{ + buf: msg2.str + len: usize(msg2.len) + }, + CIOVec{ + buf: msg3.str + len: usize(msg3.len) + }, + ]! + + if WASM.fd_write(1, &iovs[0], 3, 0) == 0 { + println('fd_write_multi: ok') + } +} + +fn wasi_fd_sync() { + // Flush stdout (fd 1) + if WASM.fd_sync(1) == 0 { + println('fd_sync: ok on stdout') + } + + // Flush stderr (fd 2) + if WASM.fd_sync(2) == 0 { + println('fd_sync: ok on stderr') + } +} + +fn main() { + wasi_args_sizes_get() + wasi_args_get() + wasi_random_get() + wasi_fd_write() + wasi_fd_write_multiple_iovecs() + wasi_fd_sync() + // @TODO: Find a way to test fd_read ? +} diff --git a/vlib/v/gen/wasm/tests/wasi_api.vv.out b/vlib/v/gen/wasm/tests/wasi_api.vv.out new file mode 100644 index 000000000..ca0e98b2d --- /dev/null +++ b/vlib/v/gen/wasm/tests/wasi_api.vv.out @@ -0,0 +1,15 @@ +args_sizes_get: ok +args_sizes_get: argc >= 1 +args_sizes_get: argc_buf_size > 0 +args_get: ok +wasi_args_get: ok +random_get: ok +random_get: has low values = true +random_get: has high values = true +random_get: values vary = true +fd_write: Hello from WASI WOORLD! +fd_write: ok +Multiple IO vectors yay! +fd_write_multi: ok +fd_sync: ok on stdout +fd_sync: ok on stderr \ No newline at end of file -- 2.39.5