From c3c806d1426982ba0570b7e72efcfa8be4be3e43 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Oct 2022 13:34:43 -0700 Subject: [PATCH 001/153] An initial preview1 polyfill stub. --- src/lib.rs | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000000..6abd502cc58e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,460 @@ +#![allow(unused_variables)] + +wit_bindgen_guest_rust::import!("wit/wasi-clocks.wit.md"); +wit_bindgen_guest_rust::import!("wit/wasi-default-clocks.wit.md"); +wit_bindgen_guest_rust::import!("wit/wasi-filesystem.wit.md"); +wit_bindgen_guest_rust::import!("wit/wasi-logging.wit.md"); +wit_bindgen_guest_rust::import!("wit/wasi-poll.wit.md"); +wit_bindgen_guest_rust::import!("wit/wasi-random.wit.md"); + +use wasi::*; + +/// Read command-line argument data. +/// The size of the array should match that returned by `args_sizes_get` +#[no_mangle] +pub extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { + todo!() +} + +/// Return command-line argument data sizes. +#[no_mangle] +pub extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { + todo!() +} + +/// Read environment variable data. +/// The sizes of the buffers should match that returned by `environ_sizes_get`. +#[no_mangle] +pub extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { + todo!() +} + +/// Return environment variable data sizes. +#[no_mangle] +pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut Size) -> Errno { + todo!() +} + +/// Return the resolution of a clock. +/// Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, +/// return `errno::inval`. +/// Note: This is similar to `clock_getres` in POSIX. +#[no_mangle] +pub extern "C" fn clock_res_get(id: Clockid, resolution: *mut Timestamp) -> Errno { + todo!() +} + +/// Return the time value of a clock. +/// Note: This is similar to `clock_gettime` in POSIX. +#[no_mangle] +pub extern "C" fn clock_time_get(id: Clockid, precision: Timestamp, time: *mut Timestamp) -> Errno { + todo!() +} + +/// Provide file advisory information on a file descriptor. +/// Note: This is similar to `posix_fadvise` in POSIX. +#[no_mangle] +pub extern "C" fn fd_advise(fd: Fd, offset: Filesize, len: Filesize, advice: Advice) -> Errno { + todo!() +} + +/// Force the allocation of space in a file. +/// Note: This is similar to `posix_fallocate` in POSIX. +#[no_mangle] +pub extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { + todo!() +} + +/// Close a file descriptor. +/// Note: This is similar to `close` in POSIX. +#[no_mangle] +pub extern "C" fn fd_close(fd: Fd) -> Errno { + todo!() +} + +/// Synchronize the data of a file to disk. +/// Note: This is similar to `fdatasync` in POSIX. +#[no_mangle] +pub extern "C" fn fd_datasync(fd: Fd) -> Errno { + todo!() +} + +/// Get the attributes of a file descriptor. +/// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. +#[no_mangle] +pub extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { + todo!() +} + +/// Adjust the flags associated with a file descriptor. +/// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. +#[no_mangle] +pub extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { + todo!() +} + +/// Adjust the rights associated with a file descriptor. +/// This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights +#[no_mangle] +pub extern "C" fn fd_fdstat_set_rights( + fd: Fd, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, +) -> Errno { + todo!() +} + +/// Return the attributes of an open file. +#[no_mangle] +pub extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { + todo!() +} + +/// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. +/// Note: This is similar to `ftruncate` in POSIX. +#[no_mangle] +pub extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { + todo!() +} + +/// Adjust the timestamps of an open file or directory. +/// Note: This is similar to `futimens` in POSIX. +#[no_mangle] +pub extern "C" fn fd_filestat_set_times( + fd: Fd, + atim: Timestamp, + mtim: Timestamp, + fst_flags: Fstflags, +) -> Errno { + todo!() +} + +/// Read from a file descriptor, without using and updating the file descriptor's offset. +/// Note: This is similar to `preadv` in POSIX. +#[no_mangle] +pub extern "C" fn fd_pread( + fd: Fd, + iovs_ptr: *const Iovec, + iovs_len: usize, + offset: Filesize, + nread: *mut Size, +) -> Errno { + todo!() +} + +/// Return a description of the given preopened file descriptor. +#[no_mangle] +pub extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { + todo!() +} + +/// Return a description of the given preopened file descriptor. +#[no_mangle] +pub extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { + todo!() +} + +/// Write to a file descriptor, without using and updating the file descriptor's offset. +/// Note: This is similar to `pwritev` in POSIX. +#[no_mangle] +pub extern "C" fn fd_pwrite( + fd: Fd, + iovs_ptr: *const Ciovec, + iovs_len: usize, + offset: Filesize, + nwritten: *mut Size, +) -> Errno { + todo!() +} + +/// Read from a file descriptor. +/// Note: This is similar to `readv` in POSIX. +#[no_mangle] +pub extern "C" fn fd_read( + fd: Fd, + iovs_ptr: *const Iovec, + iovs_len: usize, + nread: *mut Size, +) -> Errno { + todo!() +} + +/// Read directory entries from a directory. +/// When successful, the contents of the output buffer consist of a sequence of +/// directory entries. Each directory entry consists of a `dirent` object, +/// followed by `dirent::d_namlen` bytes holding the name of the directory +/// entry. +/// This function fills the output buffer as much as possible, potentially +/// truncating the last directory entry. This allows the caller to grow its +/// read buffer size in case it's too small to fit a single large directory +/// entry, or skip the oversized directory entry. +#[no_mangle] +pub extern "C" fn fd_readdir( + fd: Fd, + buf: *mut u8, + buf_len: Size, + cookie: Dircookie, + bufused: *mut Size, +) -> Errno { + todo!() +} + +/// Atomically replace a file descriptor by renumbering another file descriptor. +/// Due to the strong focus on thread safety, this environment does not provide +/// a mechanism to duplicate or renumber a file descriptor to an arbitrary +/// number, like `dup2()`. This would be prone to race conditions, as an actual +/// file descriptor with the same number could be allocated by a different +/// thread at the same time. +/// This function provides a way to atomically renumber file descriptors, which +/// would disappear if `dup2()` were to be removed entirely. +#[no_mangle] +pub extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { + todo!() +} + +/// Move the offset of a file descriptor. +/// Note: This is similar to `lseek` in POSIX. +#[no_mangle] +pub extern "C" fn fd_seek( + fd: Fd, + offset: Filedelta, + whence: Whence, + newoffset: *mut Filesize, +) -> Errno { + todo!() +} + +/// Synchronize the data and metadata of a file to disk. +/// Note: This is similar to `fsync` in POSIX. +#[no_mangle] +pub extern "C" fn fd_sync(fd: Fd) -> Errno { + todo!() +} + +/// Return the current offset of a file descriptor. +/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. +#[no_mangle] +pub extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { + todo!() +} + +/// Write to a file descriptor. +/// Note: This is similar to `writev` in POSIX. +#[no_mangle] +pub extern "C" fn fd_write( + fd: Fd, + iovs_ptr: *const Ciovec, + iovs_len: usize, + nwritten: *mut Size, +) -> Errno { + todo!() +} + +/// Create a directory. +/// Note: This is similar to `mkdirat` in POSIX. +#[no_mangle] +pub extern "C" fn path_create_directory(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { + todo!() +} + +/// Return the attributes of a file or directory. +/// Note: This is similar to `stat` in POSIX. +#[no_mangle] +pub extern "C" fn path_filestat_get( + fd: Fd, + flags: Lookupflags, + path_ptr: *const u8, + path_len: usize, + buf: *mut Filestat, +) -> Errno { + todo!() +} + +/// Adjust the timestamps of a file or directory. +/// Note: This is similar to `utimensat` in POSIX. +#[no_mangle] +pub extern "C" fn path_filestat_set_times( + fd: Fd, + flags: Lookupflags, + path_ptr: *const u8, + path_len: usize, + atim: Timestamp, + mtim: Timestamp, + fst_flags: Fstflags, +) -> Errno { + todo!() +} + +/// Create a hard link. +/// Note: This is similar to `linkat` in POSIX. +#[no_mangle] +pub extern "C" fn path_link( + old_fd: Fd, + old_flags: Lookupflags, + old_path_ptr: *const u8, + old_path_len: usize, + new_fd: Fd, + new_path_ptr: *const u8, + new_path_len: usize, +) -> Errno { + todo!() +} + +/// Open a file or directory. +/// The returned file descriptor is not guaranteed to be the lowest-numbered +/// file descriptor not currently open; it is randomized to prevent +/// applications from depending on making assumptions about indexes, since this +/// is error-prone in multi-threaded contexts. The returned file descriptor is +/// guaranteed to be less than 2**31. +/// Note: This is similar to `openat` in POSIX. +#[no_mangle] +pub extern "C" fn path_open( + fd: Fd, + dirflags: Lookupflags, + path_ptr: *const u8, + path_len: usize, + oflags: Oflags, + fs_rights_base: Rights, + fs_rights_inheriting: Rights, + fdflags: Fdflags, + opened_fd: *mut Fd, +) -> Errno { + todo!() +} + +/// Read the contents of a symbolic link. +/// Note: This is similar to `readlinkat` in POSIX. +#[no_mangle] +pub extern "C" fn path_readlink( + fd: Fd, + path_ptr: *const u8, + path_len: usize, + buf: *mut u8, + buf_len: Size, + bufused: *mut Size, +) -> Errno { + todo!() +} + +/// Remove a directory. +/// Return `errno::notempty` if the directory is not empty. +/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. +#[no_mangle] +pub extern "C" fn path_remove_directory(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { + todo!() +} + +/// Rename a file or directory. +/// Note: This is similar to `renameat` in POSIX. +#[no_mangle] +pub extern "C" fn path_rename( + fd: Fd, + old_path_ptr: *const u8, + old_path_len: usize, + new_fd: Fd, + new_path_ptr: *const u8, + new_path_len: usize, +) -> Errno { + todo!() +} + +/// Create a symbolic link. +/// Note: This is similar to `symlinkat` in POSIX. +#[no_mangle] +pub extern "C" fn path_symlink( + old_path_ptr: *const u8, + old_path_len: usize, + fd: Fd, + new_path_ptr: *const u8, + new_path_len: usize, +) -> Errno { + todo!() +} + +/// Unlink a file. +/// Return `errno::isdir` if the path refers to a directory. +/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. +#[no_mangle] +pub extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { + todo!() +} + +/// Concurrently poll for the occurrence of a set of events. +#[no_mangle] +pub extern "C" fn poll_oneoff( + r#in: *const Subscription, + out: *mut Event, + nsubscriptions: Size, + nevents: *mut Size, +) -> Errno { + todo!() +} + +/// Terminate the process normally. An exit code of 0 indicates successful +/// termination of the program. The meanings of other values is dependent on +/// the environment. +#[no_mangle] +pub extern "C" fn proc_exit(rval: Exitcode) -> ! { + todo!() +} + +/// Send a signal to the process of the calling thread. +/// Note: This is similar to `raise` in POSIX. +#[no_mangle] +pub extern "C" fn proc_raise(sig: Signal) -> Errno { + todo!() +} + +/// Temporarily yield execution of the calling thread. +/// Note: This is similar to `sched_yield` in POSIX. +#[no_mangle] +pub extern "C" fn sched_yield() -> Errno { + todo!() +} + +/// Write high-quality random data into a buffer. +/// This function blocks when the implementation is unable to immediately +/// provide sufficient high-quality random data. +/// This function may execute slowly, so when large mounts of random data are +/// required, it's advisable to use this function to seed a pseudo-random +/// number generator, rather than to provide the random data directly. +#[no_mangle] +pub extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { + todo!() +} + +/// Receive a message from a socket. +/// Note: This is similar to `recv` in POSIX, though it also supports reading +/// the data into multiple buffers in the manner of `readv`. +#[no_mangle] +pub extern "C" fn sock_recv( + fd: Fd, + ri_data_ptr: *const Iovec, + ri_data_len: usize, + ri_flags: Riflags, + ro_datalen: *mut Size, + ro_flags: *mut Roflags, +) -> Errno { + todo!() +} + +/// Send a message on a socket. +/// Note: This is similar to `send` in POSIX, though it also supports writing +/// the data from multiple buffers in the manner of `writev`. +#[no_mangle] +pub extern "C" fn sock_send( + fd: Fd, + si_data_ptr: *const Ciovec, + si_data_len: usize, + si_flags: Siflags, + so_datalen: *mut Size, +) -> Errno { + todo!() +} + +/// Shut down socket send and receive channels. +/// Note: This is similar to `shutdown` in POSIX. +#[no_mangle] +pub extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { + todo!() +} From 9f317d0d431acf872ada52f9fdb07f6d93f70f7a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Oct 2022 07:54:30 -0500 Subject: [PATCH 002/153] Add test on CI and infrastructure for wasm globals (#1) * Updates * Add a verification test to CI * Produce a global-referencing object on stable * Restrict CI again --- build.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 112 +++++++++++++++---------- 2 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 build.rs diff --git a/build.rs b/build.rs new file mode 100644 index 000000000000..0c12e7f94d30 --- /dev/null +++ b/build.rs @@ -0,0 +1,235 @@ +use std::env; +use std::path::PathBuf; + +const SYMBOL: &str = "replace_realloc_global"; + +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let wasm = build_raw_intrinsics(); + let archive = build_archive(&wasm); + + std::fs::write(out_dir.join("libwasm-raw-intrinsics.a"), &archive).unwrap(); + println!("cargo:rustc-link-lib=static=wasm-raw-intrinsics"); + println!( + "cargo:rustc-link-search=native={}", + out_dir.to_str().unwrap() + ); +} + +/// This function will produce a wasm module which is itself an object file +/// that is the basic equivalent of: +/// +/// ```rust +/// std::arch::global_asm!( +/// " +/// .globaltype internal_realloc_global, i32 +/// internal_realloc_global: +/// " +/// ); +/// +/// #[no_mangle] +/// fn replace_realloc_global(val: usize) -> usize { +/// unsafe { +/// let ret: usize; +/// std::arch::asm!( +/// " +/// global.get internal_realloc_global +/// local.set {} +/// local.get {} +/// global.set internal_realloc_global +/// ", +/// out(local) ret, +/// in(local) val, +/// options(nostack) +/// ); +/// ret +/// } +/// } +/// ``` +/// +/// The main trickiness here is getting the `reloc.CODE` and `linking` sections +/// right. +fn build_raw_intrinsics() -> Vec { + use wasm_encoder::Instruction::*; + use wasm_encoder::*; + + let mut module = Module::new(); + + // One function type, i32 -> i32 + let mut types = TypeSection::new(); + types.function([ValType::I32], [ValType::I32]); + module.section(&types); + + // One function, using the type we just added + let mut funcs = FunctionSection::new(); + funcs.function(0); + module.section(&funcs); + + // This is the `internal_realloc_global` definition + let mut globals = GlobalSection::new(); + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + }, + &ConstExpr::i32_const(0), + ); + module.section(&globals); + + // Here the `code` section is defined. This is tricky because an offset is + // needed within the code section itself for the `reloc.CODE` section + // later. At this time `wasm-encoder` doesn't give enough functionality to + // use the high-level APIs. so everything is done manually here. + // + // First the function body is created and then it's appended into a code + // section. + let mut body = Vec::new(); + 0u32.encode(&mut body); // no locals + let global_offset1 = body.len() + 1; + // global.get 0 ;; but with maximal encoding of the 0 + body.extend_from_slice(&[0x23, 0x80, 0x80, 0x80, 0x80, 0x00]); + LocalGet(0).encode(&mut body); + let global_offset2 = body.len() + 1; + // global.set 0 ;; but with maximal encoding of the 0 + body.extend_from_slice(&[0x24, 0x81, 0x80, 0x80, 0x80, 0x00]); + End.encode(&mut body); + + let mut code = Vec::new(); + 1u32.encode(&mut code); // one function + body.len().encode(&mut code); // length of the function + let body_offset = code.len(); + code.extend_from_slice(&body); // the function itself + module.section(&RawSection { + id: SectionId::Code as u8, + data: &code, + }); + + // Calculate the relocation offsets within the `code` section itself by + // adding the start of where the body was placed to the offset within the + // body. + let global_offset1 = global_offset1 + body_offset; + let global_offset2 = global_offset2 + body_offset; + + // Here the linking section is constructed. There are two symbols described + // here, one for the function that we injected and one for the global + // that was injected. The injected global here is referenced in the + // relocations below. + // + // More information about this format is at + // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md + { + let mut linking = Vec::new(); + linking.push(0x02); // version + + linking.push(0x08); // `WASM_SYMBOL_TABLE` + let mut subsection = Vec::new(); + 2u32.encode(&mut subsection); // 2 symbols + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 0x00u32.encode(&mut subsection); // function index + SYMBOL.encode(&mut subsection); // symbol name + + subsection.push(0x02); // SYMTAB_GLOBAL + 0x02.encode(&mut subsection); // flags + 0x00u32.encode(&mut subsection); // global index + "internal_realloc_global".encode(&mut subsection); // symbol name + + subsection.encode(&mut linking); + module.section(&CustomSection { + name: "linking", + data: &linking, + }); + } + + // A `reloc.CODE` section is appended here with two relocations for the + // two `global`-referencing instructions that were added. + { + let mut reloc = Vec::new(); + 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) + 2u32.encode(&mut reloc); // 2 relocations + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offset1.encode(&mut reloc); // offset + 0x01u32.encode(&mut reloc); // symbol index + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offset2.encode(&mut reloc); // offset + 0x01u32.encode(&mut reloc); // symbol index + + module.section(&CustomSection { + name: "reloc.CODE", + data: &reloc, + }); + } + + module.finish() +} + +/// This function produces the output of `llvm-ar crus libfoo.a foo.o` given +/// the object file above as input. The archive is what's eventually fed to +/// LLD. +/// +/// Like above this is still tricky, mainly around the production of the symbol +/// table. +fn build_archive(wasm: &[u8]) -> Vec { + use object::{bytes_of, endian::BigEndian, U32Bytes}; + + let mut archive = Vec::new(); + archive.extend_from_slice(&object::archive::MAGIC); + + // The symbol table is in the "GNU" format which means it has a structure + // that looks like: + // + // * a big-endian 32-bit integer for the number of symbols + // * N big-endian 32-bit integers for the offset to the object file, within + // the entire archive, for which object has the symbol + // * N nul-delimited strings for each symbol + // + // Here we're building an archive with just one symbol so it's a bit + // easier. Note though we don't know the offset of our `intrinsics.o` up + // front so it's left as 0 for now and filled in later. + let mut symbol_table = Vec::new(); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 1))); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + symbol_table.extend_from_slice(SYMBOL.as_bytes()); + symbol_table.push(0x00); + + archive.extend_from_slice(bytes_of(&object::archive::Header { + name: *b"/ ", + date: *b"0 ", + uid: *b"0 ", + gid: *b"0 ", + mode: *b"0 ", + size: format!("{:<10}", symbol_table.len()) + .as_bytes() + .try_into() + .unwrap(), + terminator: object::archive::TERMINATOR, + })); + let symtab_offset = archive.len(); + archive.extend_from_slice(&symbol_table); + + // All archive memberes must start on even offsets + if archive.len() & 1 == 1 { + archive.push(0x00); + } + + // Now that we have the starting offset of the `intrinsics.o` file go back + // and fill in the offset within the symbol table generated earlier. + let member_offset = archive.len(); + archive[symtab_offset + 4..][..4].copy_from_slice(bytes_of(&U32Bytes::new( + BigEndian, + member_offset.try_into().unwrap(), + ))); + + archive.extend_from_slice(object::bytes_of(&object::archive::Header { + name: *b"intrinsics.o ", + date: *b"0 ", + uid: *b"0 ", + gid: *b"0 ", + mode: *b"644 ", + size: format!("{:<10}", wasm.len()).as_bytes().try_into().unwrap(), + terminator: object::archive::TERMINATOR, + })); + archive.extend_from_slice(&wasm); + archive +} diff --git a/src/lib.rs b/src/lib.rs index 6abd502cc58e..4771fa09e8eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,32 +7,54 @@ wit_bindgen_guest_rust::import!("wit/wasi-logging.wit.md"); wit_bindgen_guest_rust::import!("wit/wasi-poll.wit.md"); wit_bindgen_guest_rust::import!("wit/wasi-random.wit.md"); +use std::arch::wasm32::unreachable; use wasi::*; +extern "C" { + fn replace_realloc_global(val: usize) -> usize; +} + +#[no_mangle] +pub unsafe extern "C" fn cabi_realloc( + old_ptr: *mut u8, + old_size: usize, + _align: usize, + _new_size: usize, +) -> *mut u8 { + if !old_ptr.is_null() || old_size != 0 { + unreachable(); + } + let base = replace_realloc_global(0); + if base == 0 { + unreachable(); + } + base as *mut u8 +} + /// Read command-line argument data. /// The size of the array should match that returned by `args_sizes_get` #[no_mangle] pub extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { - todo!() + unreachable() } /// Return command-line argument data sizes. #[no_mangle] pub extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { - todo!() + unreachable() } /// Read environment variable data. /// The sizes of the buffers should match that returned by `environ_sizes_get`. #[no_mangle] pub extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { - todo!() + unreachable() } /// Return environment variable data sizes. #[no_mangle] pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut Size) -> Errno { - todo!() + unreachable() } /// Return the resolution of a clock. @@ -41,56 +63,56 @@ pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut /// Note: This is similar to `clock_getres` in POSIX. #[no_mangle] pub extern "C" fn clock_res_get(id: Clockid, resolution: *mut Timestamp) -> Errno { - todo!() + unreachable() } /// Return the time value of a clock. /// Note: This is similar to `clock_gettime` in POSIX. #[no_mangle] pub extern "C" fn clock_time_get(id: Clockid, precision: Timestamp, time: *mut Timestamp) -> Errno { - todo!() + unreachable() } /// Provide file advisory information on a file descriptor. /// Note: This is similar to `posix_fadvise` in POSIX. #[no_mangle] pub extern "C" fn fd_advise(fd: Fd, offset: Filesize, len: Filesize, advice: Advice) -> Errno { - todo!() + unreachable() } /// Force the allocation of space in a file. /// Note: This is similar to `posix_fallocate` in POSIX. #[no_mangle] pub extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { - todo!() + unreachable() } /// Close a file descriptor. /// Note: This is similar to `close` in POSIX. #[no_mangle] pub extern "C" fn fd_close(fd: Fd) -> Errno { - todo!() + unreachable() } /// Synchronize the data of a file to disk. /// Note: This is similar to `fdatasync` in POSIX. #[no_mangle] pub extern "C" fn fd_datasync(fd: Fd) -> Errno { - todo!() + unreachable() } /// Get the attributes of a file descriptor. /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] pub extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { - todo!() + unreachable() } /// Adjust the flags associated with a file descriptor. /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[no_mangle] pub extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { - todo!() + unreachable() } /// Adjust the rights associated with a file descriptor. @@ -101,20 +123,20 @@ pub extern "C" fn fd_fdstat_set_rights( fs_rights_base: Rights, fs_rights_inheriting: Rights, ) -> Errno { - todo!() + unreachable() } /// Return the attributes of an open file. #[no_mangle] pub extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { - todo!() + unreachable() } /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. /// Note: This is similar to `ftruncate` in POSIX. #[no_mangle] pub extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { - todo!() + unreachable() } /// Adjust the timestamps of an open file or directory. @@ -126,7 +148,7 @@ pub extern "C" fn fd_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - todo!() + unreachable() } /// Read from a file descriptor, without using and updating the file descriptor's offset. @@ -139,19 +161,19 @@ pub extern "C" fn fd_pread( offset: Filesize, nread: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Return a description of the given preopened file descriptor. #[no_mangle] pub extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { - todo!() + unreachable() } /// Return a description of the given preopened file descriptor. #[no_mangle] pub extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { - todo!() + unreachable() } /// Write to a file descriptor, without using and updating the file descriptor's offset. @@ -164,7 +186,7 @@ pub extern "C" fn fd_pwrite( offset: Filesize, nwritten: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Read from a file descriptor. @@ -176,7 +198,7 @@ pub extern "C" fn fd_read( iovs_len: usize, nread: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Read directory entries from a directory. @@ -196,7 +218,7 @@ pub extern "C" fn fd_readdir( cookie: Dircookie, bufused: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Atomically replace a file descriptor by renumbering another file descriptor. @@ -209,7 +231,7 @@ pub extern "C" fn fd_readdir( /// would disappear if `dup2()` were to be removed entirely. #[no_mangle] pub extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { - todo!() + unreachable() } /// Move the offset of a file descriptor. @@ -221,21 +243,21 @@ pub extern "C" fn fd_seek( whence: Whence, newoffset: *mut Filesize, ) -> Errno { - todo!() + unreachable() } /// Synchronize the data and metadata of a file to disk. /// Note: This is similar to `fsync` in POSIX. #[no_mangle] pub extern "C" fn fd_sync(fd: Fd) -> Errno { - todo!() + unreachable() } /// Return the current offset of a file descriptor. /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. #[no_mangle] pub extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { - todo!() + unreachable() } /// Write to a file descriptor. @@ -247,14 +269,14 @@ pub extern "C" fn fd_write( iovs_len: usize, nwritten: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Create a directory. /// Note: This is similar to `mkdirat` in POSIX. #[no_mangle] pub extern "C" fn path_create_directory(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { - todo!() + unreachable() } /// Return the attributes of a file or directory. @@ -267,7 +289,7 @@ pub extern "C" fn path_filestat_get( path_len: usize, buf: *mut Filestat, ) -> Errno { - todo!() + unreachable() } /// Adjust the timestamps of a file or directory. @@ -282,7 +304,7 @@ pub extern "C" fn path_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - todo!() + unreachable() } /// Create a hard link. @@ -297,7 +319,7 @@ pub extern "C" fn path_link( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - todo!() + unreachable() } /// Open a file or directory. @@ -319,7 +341,7 @@ pub extern "C" fn path_open( fdflags: Fdflags, opened_fd: *mut Fd, ) -> Errno { - todo!() + unreachable() } /// Read the contents of a symbolic link. @@ -333,7 +355,7 @@ pub extern "C" fn path_readlink( buf_len: Size, bufused: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Remove a directory. @@ -341,7 +363,7 @@ pub extern "C" fn path_readlink( /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. #[no_mangle] pub extern "C" fn path_remove_directory(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { - todo!() + unreachable() } /// Rename a file or directory. @@ -355,7 +377,7 @@ pub extern "C" fn path_rename( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - todo!() + unreachable() } /// Create a symbolic link. @@ -368,7 +390,7 @@ pub extern "C" fn path_symlink( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - todo!() + unreachable() } /// Unlink a file. @@ -376,7 +398,7 @@ pub extern "C" fn path_symlink( /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. #[no_mangle] pub extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { - todo!() + unreachable() } /// Concurrently poll for the occurrence of a set of events. @@ -387,7 +409,7 @@ pub extern "C" fn poll_oneoff( nsubscriptions: Size, nevents: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Terminate the process normally. An exit code of 0 indicates successful @@ -395,21 +417,21 @@ pub extern "C" fn poll_oneoff( /// the environment. #[no_mangle] pub extern "C" fn proc_exit(rval: Exitcode) -> ! { - todo!() + unreachable() } /// Send a signal to the process of the calling thread. /// Note: This is similar to `raise` in POSIX. #[no_mangle] pub extern "C" fn proc_raise(sig: Signal) -> Errno { - todo!() + unreachable() } /// Temporarily yield execution of the calling thread. /// Note: This is similar to `sched_yield` in POSIX. #[no_mangle] pub extern "C" fn sched_yield() -> Errno { - todo!() + unreachable() } /// Write high-quality random data into a buffer. @@ -420,7 +442,7 @@ pub extern "C" fn sched_yield() -> Errno { /// number generator, rather than to provide the random data directly. #[no_mangle] pub extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { - todo!() + unreachable() } /// Receive a message from a socket. @@ -435,7 +457,7 @@ pub extern "C" fn sock_recv( ro_datalen: *mut Size, ro_flags: *mut Roflags, ) -> Errno { - todo!() + unreachable() } /// Send a message on a socket. @@ -449,12 +471,12 @@ pub extern "C" fn sock_send( si_flags: Siflags, so_datalen: *mut Size, ) -> Errno { - todo!() + unreachable() } /// Shut down socket send and receive channels. /// Note: This is similar to `shutdown` in POSIX. #[no_mangle] pub extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { - todo!() + unreachable() } From 77cfc4d6ec39f00cbbf44c5aa2b6f68578b0efe2 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 07:19:24 -0700 Subject: [PATCH 003/153] Add a comment about a disabled lint. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4771fa09e8eb..770dfa1cbb11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(unused_variables)] +#![allow(unused_variables)] // TODO: remove this when more things are implemented wit_bindgen_guest_rust::import!("wit/wasi-clocks.wit.md"); wit_bindgen_guest_rust::import!("wit/wasi-default-clocks.wit.md"); From 3c9e371737933876d3fa19a9f6e852ce1ea4bce1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 07:19:29 -0700 Subject: [PATCH 004/153] Add an `extern "C"` to the Rust code comment. --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 0c12e7f94d30..6fcea1bbe5f3 100644 --- a/build.rs +++ b/build.rs @@ -28,7 +28,7 @@ fn main() { /// ); /// /// #[no_mangle] -/// fn replace_realloc_global(val: usize) -> usize { +/// extern "C" fn replace_realloc_global(val: usize) -> usize { /// unsafe { /// let ret: usize; /// std::arch::asm!( From 7ad3b586b3e52d81a09e6e2fe50cf0e38bb32333 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 07:19:31 -0700 Subject: [PATCH 005/153] Use `*mut u8` instead of `usize` to hold pointers. This will reduce casting, and better preserve pointer provenance. --- build.rs | 4 ++-- src/lib.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.rs b/build.rs index 6fcea1bbe5f3..4368e1fa846e 100644 --- a/build.rs +++ b/build.rs @@ -28,9 +28,9 @@ fn main() { /// ); /// /// #[no_mangle] -/// extern "C" fn replace_realloc_global(val: usize) -> usize { +/// extern "C" fn replace_realloc_global(val: *mut u8) -> *mut u8 { /// unsafe { -/// let ret: usize; +/// let ret: *mut u8; /// std::arch::asm!( /// " /// global.get internal_realloc_global diff --git a/src/lib.rs b/src/lib.rs index 770dfa1cbb11..3d745b4e51b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,11 @@ wit_bindgen_guest_rust::import!("wit/wasi-poll.wit.md"); wit_bindgen_guest_rust::import!("wit/wasi-random.wit.md"); use std::arch::wasm32::unreachable; +use std::ptr::null_mut; use wasi::*; extern "C" { - fn replace_realloc_global(val: usize) -> usize; + fn replace_realloc_global(val: *mut u8) -> *mut u8; } #[no_mangle] @@ -24,8 +25,8 @@ pub unsafe extern "C" fn cabi_realloc( if !old_ptr.is_null() || old_size != 0 { unreachable(); } - let base = replace_realloc_global(0); - if base == 0 { + let base = replace_realloc_global(null_mut()); + if base.is_null() { unreachable(); } base as *mut u8 From 1b3377011e3085be05323b0756614de790050b89 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 06:51:15 -0700 Subject: [PATCH 006/153] Mark the WASI functions `unsafe`. --- src/lib.rs | 112 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3d745b4e51b3..84c658f74cc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,26 +35,29 @@ pub unsafe extern "C" fn cabi_realloc( /// Read command-line argument data. /// The size of the array should match that returned by `args_sizes_get` #[no_mangle] -pub extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { +pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { unreachable() } /// Return command-line argument data sizes. #[no_mangle] -pub extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { +pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { unreachable() } /// Read environment variable data. /// The sizes of the buffers should match that returned by `environ_sizes_get`. #[no_mangle] -pub extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { +pub unsafe extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { unreachable() } /// Return environment variable data sizes. #[no_mangle] -pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut Size) -> Errno { +pub unsafe extern "C" fn environ_sizes_get( + environc: *mut Size, + environ_buf_size: *mut Size, +) -> Errno { unreachable() } @@ -63,63 +66,72 @@ pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut /// return `errno::inval`. /// Note: This is similar to `clock_getres` in POSIX. #[no_mangle] -pub extern "C" fn clock_res_get(id: Clockid, resolution: *mut Timestamp) -> Errno { +pub unsafe extern "C" fn clock_res_get(id: Clockid, resolution: *mut Timestamp) -> Errno { unreachable() } /// Return the time value of a clock. /// Note: This is similar to `clock_gettime` in POSIX. #[no_mangle] -pub extern "C" fn clock_time_get(id: Clockid, precision: Timestamp, time: *mut Timestamp) -> Errno { +pub unsafe extern "C" fn clock_time_get( + id: Clockid, + precision: Timestamp, + time: *mut Timestamp, +) -> Errno { unreachable() } /// Provide file advisory information on a file descriptor. /// Note: This is similar to `posix_fadvise` in POSIX. #[no_mangle] -pub extern "C" fn fd_advise(fd: Fd, offset: Filesize, len: Filesize, advice: Advice) -> Errno { +pub unsafe extern "C" fn fd_advise( + fd: Fd, + offset: Filesize, + len: Filesize, + advice: Advice, +) -> Errno { unreachable() } /// Force the allocation of space in a file. /// Note: This is similar to `posix_fallocate` in POSIX. #[no_mangle] -pub extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { +pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { unreachable() } /// Close a file descriptor. /// Note: This is similar to `close` in POSIX. #[no_mangle] -pub extern "C" fn fd_close(fd: Fd) -> Errno { +pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { unreachable() } /// Synchronize the data of a file to disk. /// Note: This is similar to `fdatasync` in POSIX. #[no_mangle] -pub extern "C" fn fd_datasync(fd: Fd) -> Errno { +pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { unreachable() } /// Get the attributes of a file descriptor. /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] -pub extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { +pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { unreachable() } /// Adjust the flags associated with a file descriptor. /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[no_mangle] -pub extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { +pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { unreachable() } /// Adjust the rights associated with a file descriptor. /// This can only be used to remove rights, and returns `errno::notcapable` if called in a way that would attempt to add rights #[no_mangle] -pub extern "C" fn fd_fdstat_set_rights( +pub unsafe extern "C" fn fd_fdstat_set_rights( fd: Fd, fs_rights_base: Rights, fs_rights_inheriting: Rights, @@ -129,21 +141,21 @@ pub extern "C" fn fd_fdstat_set_rights( /// Return the attributes of an open file. #[no_mangle] -pub extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { +pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { unreachable() } /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. /// Note: This is similar to `ftruncate` in POSIX. #[no_mangle] -pub extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { +pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { unreachable() } /// Adjust the timestamps of an open file or directory. /// Note: This is similar to `futimens` in POSIX. #[no_mangle] -pub extern "C" fn fd_filestat_set_times( +pub unsafe extern "C" fn fd_filestat_set_times( fd: Fd, atim: Timestamp, mtim: Timestamp, @@ -155,7 +167,7 @@ pub extern "C" fn fd_filestat_set_times( /// Read from a file descriptor, without using and updating the file descriptor's offset. /// Note: This is similar to `preadv` in POSIX. #[no_mangle] -pub extern "C" fn fd_pread( +pub unsafe extern "C" fn fd_pread( fd: Fd, iovs_ptr: *const Iovec, iovs_len: usize, @@ -167,20 +179,20 @@ pub extern "C" fn fd_pread( /// Return a description of the given preopened file descriptor. #[no_mangle] -pub extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { +pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { unreachable() } /// Return a description of the given preopened file descriptor. #[no_mangle] -pub extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { +pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { unreachable() } /// Write to a file descriptor, without using and updating the file descriptor's offset. /// Note: This is similar to `pwritev` in POSIX. #[no_mangle] -pub extern "C" fn fd_pwrite( +pub unsafe extern "C" fn fd_pwrite( fd: Fd, iovs_ptr: *const Ciovec, iovs_len: usize, @@ -193,7 +205,7 @@ pub extern "C" fn fd_pwrite( /// Read from a file descriptor. /// Note: This is similar to `readv` in POSIX. #[no_mangle] -pub extern "C" fn fd_read( +pub unsafe extern "C" fn fd_read( fd: Fd, iovs_ptr: *const Iovec, iovs_len: usize, @@ -212,7 +224,7 @@ pub extern "C" fn fd_read( /// read buffer size in case it's too small to fit a single large directory /// entry, or skip the oversized directory entry. #[no_mangle] -pub extern "C" fn fd_readdir( +pub unsafe extern "C" fn fd_readdir( fd: Fd, buf: *mut u8, buf_len: Size, @@ -231,14 +243,14 @@ pub extern "C" fn fd_readdir( /// This function provides a way to atomically renumber file descriptors, which /// would disappear if `dup2()` were to be removed entirely. #[no_mangle] -pub extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { +pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { unreachable() } /// Move the offset of a file descriptor. /// Note: This is similar to `lseek` in POSIX. #[no_mangle] -pub extern "C" fn fd_seek( +pub unsafe extern "C" fn fd_seek( fd: Fd, offset: Filedelta, whence: Whence, @@ -250,21 +262,21 @@ pub extern "C" fn fd_seek( /// Synchronize the data and metadata of a file to disk. /// Note: This is similar to `fsync` in POSIX. #[no_mangle] -pub extern "C" fn fd_sync(fd: Fd) -> Errno { +pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { unreachable() } /// Return the current offset of a file descriptor. /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. #[no_mangle] -pub extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { +pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { unreachable() } /// Write to a file descriptor. /// Note: This is similar to `writev` in POSIX. #[no_mangle] -pub extern "C" fn fd_write( +pub unsafe extern "C" fn fd_write( fd: Fd, iovs_ptr: *const Ciovec, iovs_len: usize, @@ -276,14 +288,18 @@ pub extern "C" fn fd_write( /// Create a directory. /// Note: This is similar to `mkdirat` in POSIX. #[no_mangle] -pub extern "C" fn path_create_directory(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { +pub unsafe extern "C" fn path_create_directory( + fd: Fd, + path_ptr: *const u8, + path_len: usize, +) -> Errno { unreachable() } /// Return the attributes of a file or directory. /// Note: This is similar to `stat` in POSIX. #[no_mangle] -pub extern "C" fn path_filestat_get( +pub unsafe extern "C" fn path_filestat_get( fd: Fd, flags: Lookupflags, path_ptr: *const u8, @@ -296,7 +312,7 @@ pub extern "C" fn path_filestat_get( /// Adjust the timestamps of a file or directory. /// Note: This is similar to `utimensat` in POSIX. #[no_mangle] -pub extern "C" fn path_filestat_set_times( +pub unsafe extern "C" fn path_filestat_set_times( fd: Fd, flags: Lookupflags, path_ptr: *const u8, @@ -311,7 +327,7 @@ pub extern "C" fn path_filestat_set_times( /// Create a hard link. /// Note: This is similar to `linkat` in POSIX. #[no_mangle] -pub extern "C" fn path_link( +pub unsafe extern "C" fn path_link( old_fd: Fd, old_flags: Lookupflags, old_path_ptr: *const u8, @@ -331,7 +347,7 @@ pub extern "C" fn path_link( /// guaranteed to be less than 2**31. /// Note: This is similar to `openat` in POSIX. #[no_mangle] -pub extern "C" fn path_open( +pub unsafe extern "C" fn path_open( fd: Fd, dirflags: Lookupflags, path_ptr: *const u8, @@ -348,12 +364,12 @@ pub extern "C" fn path_open( /// Read the contents of a symbolic link. /// Note: This is similar to `readlinkat` in POSIX. #[no_mangle] -pub extern "C" fn path_readlink( +pub unsafe extern "C" fn path_readlink( fd: Fd, path_ptr: *const u8, path_len: usize, buf: *mut u8, - buf_len: Size, + _buf_len: Size, bufused: *mut Size, ) -> Errno { unreachable() @@ -363,14 +379,18 @@ pub extern "C" fn path_readlink( /// Return `errno::notempty` if the directory is not empty. /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. #[no_mangle] -pub extern "C" fn path_remove_directory(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { +pub unsafe extern "C" fn path_remove_directory( + fd: Fd, + path_ptr: *const u8, + path_len: usize, +) -> Errno { unreachable() } /// Rename a file or directory. /// Note: This is similar to `renameat` in POSIX. #[no_mangle] -pub extern "C" fn path_rename( +pub unsafe extern "C" fn path_rename( fd: Fd, old_path_ptr: *const u8, old_path_len: usize, @@ -384,7 +404,7 @@ pub extern "C" fn path_rename( /// Create a symbolic link. /// Note: This is similar to `symlinkat` in POSIX. #[no_mangle] -pub extern "C" fn path_symlink( +pub unsafe extern "C" fn path_symlink( old_path_ptr: *const u8, old_path_len: usize, fd: Fd, @@ -398,13 +418,13 @@ pub extern "C" fn path_symlink( /// Return `errno::isdir` if the path refers to a directory. /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. #[no_mangle] -pub extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { +pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { unreachable() } /// Concurrently poll for the occurrence of a set of events. #[no_mangle] -pub extern "C" fn poll_oneoff( +pub unsafe extern "C" fn poll_oneoff( r#in: *const Subscription, out: *mut Event, nsubscriptions: Size, @@ -417,21 +437,21 @@ pub extern "C" fn poll_oneoff( /// termination of the program. The meanings of other values is dependent on /// the environment. #[no_mangle] -pub extern "C" fn proc_exit(rval: Exitcode) -> ! { +pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! { unreachable() } /// Send a signal to the process of the calling thread. /// Note: This is similar to `raise` in POSIX. #[no_mangle] -pub extern "C" fn proc_raise(sig: Signal) -> Errno { +pub unsafe extern "C" fn proc_raise(sig: Signal) -> Errno { unreachable() } /// Temporarily yield execution of the calling thread. /// Note: This is similar to `sched_yield` in POSIX. #[no_mangle] -pub extern "C" fn sched_yield() -> Errno { +pub unsafe extern "C" fn sched_yield() -> Errno { unreachable() } @@ -442,7 +462,7 @@ pub extern "C" fn sched_yield() -> Errno { /// required, it's advisable to use this function to seed a pseudo-random /// number generator, rather than to provide the random data directly. #[no_mangle] -pub extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { +pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { unreachable() } @@ -450,7 +470,7 @@ pub extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { /// Note: This is similar to `recv` in POSIX, though it also supports reading /// the data into multiple buffers in the manner of `readv`. #[no_mangle] -pub extern "C" fn sock_recv( +pub unsafe extern "C" fn sock_recv( fd: Fd, ri_data_ptr: *const Iovec, ri_data_len: usize, @@ -465,7 +485,7 @@ pub extern "C" fn sock_recv( /// Note: This is similar to `send` in POSIX, though it also supports writing /// the data from multiple buffers in the manner of `writev`. #[no_mangle] -pub extern "C" fn sock_send( +pub unsafe extern "C" fn sock_send( fd: Fd, si_data_ptr: *const Ciovec, si_data_len: usize, @@ -478,6 +498,6 @@ pub extern "C" fn sock_send( /// Shut down socket send and receive channels. /// Note: This is similar to `shutdown` in POSIX. #[no_mangle] -pub extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { +pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable() } From 3a9d47613819687b0df03eae998d152fc5f84935 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 08:08:37 -0700 Subject: [PATCH 007/153] Fix the inline asm comment to match the code. --- build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/build.rs b/build.rs index 4368e1fa846e..1fc8cabe467d 100644 --- a/build.rs +++ b/build.rs @@ -34,7 +34,6 @@ fn main() { /// std::arch::asm!( /// " /// global.get internal_realloc_global -/// local.set {} /// local.get {} /// global.set internal_realloc_global /// ", From 2b14421dc7c197ec30c45d7b48e74c6a5d737d12 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 09:06:53 -0700 Subject: [PATCH 008/153] Implement a polyfill for `path_readlink`. Implement a polyfill for `path_readlink` in terms of the preview2 `readlink_at` function. This involves adding a second wasm global, to hold the buffer size. --- build.rs | 104 ++++++++++++++++++++++++++++++---------- src/lib.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 207 insertions(+), 34 deletions(-) diff --git a/build.rs b/build.rs index 1fc8cabe467d..4cfa12c2083e 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,8 @@ use std::env; use std::path::PathBuf; -const SYMBOL: &str = "replace_realloc_global"; +const REPLACE_REALLOC_GLOBAL_PTR: &str = "replace_realloc_global_ptr"; +const REPLACE_REALLOC_GLOBAL_LEN: &str = "replace_realloc_global_len"; fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); @@ -22,24 +23,44 @@ fn main() { /// ```rust /// std::arch::global_asm!( /// " -/// .globaltype internal_realloc_global, i32 -/// internal_realloc_global: +/// .globaltype internal_realloc_global_ptr, i32 +/// internal_realloc_global_ptr: +/// .globaltype internal_realloc_global_len, i32 +/// internal_realloc_global_len: /// " /// ); /// /// #[no_mangle] -/// extern "C" fn replace_realloc_global(val: *mut u8) -> *mut u8 { +/// extern "C" fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8 { /// unsafe { /// let ret: *mut u8; /// std::arch::asm!( /// " -/// global.get internal_realloc_global +/// global.get internal_realloc_global_ptr /// local.get {} -/// global.set internal_realloc_global +/// global.set internal_realloc_global_ptr /// ", /// out(local) ret, /// in(local) val, -/// options(nostack) +/// options(nostack, readonly) +/// ); +/// ret +/// } +/// } +/// +/// #[no_mangle] +/// extern "C" fn replace_realloc_global_len(val: usize) -> usize { +/// unsafe { +/// let ret: usize; +/// std::arch::asm!( +/// " +/// global.get internal_realloc_global_len +/// local.get {} +/// global.set internal_realloc_global_len +/// ", +/// out(local) ret, +/// in(local) val, +/// options(nostack, readonly) /// ); /// ret /// } @@ -59,13 +80,22 @@ fn build_raw_intrinsics() -> Vec { types.function([ValType::I32], [ValType::I32]); module.section(&types); - // One function, using the type we just added + // Two functions, using the type we just added let mut funcs = FunctionSection::new(); funcs.function(0); + funcs.function(0); module.section(&funcs); - // This is the `internal_realloc_global` definition let mut globals = GlobalSection::new(); + // This is the `internal_realloc_global_ptr` definition + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + }, + &ConstExpr::i32_const(0), + ); + // This is the `internal_realloc_global_len` definition globals.global( GlobalType { val_type: ValType::I32, @@ -84,19 +114,22 @@ fn build_raw_intrinsics() -> Vec { // section. let mut body = Vec::new(); 0u32.encode(&mut body); // no locals - let global_offset1 = body.len() + 1; + let global_offset0 = body.len() + 1; // global.get 0 ;; but with maximal encoding of the 0 body.extend_from_slice(&[0x23, 0x80, 0x80, 0x80, 0x80, 0x00]); LocalGet(0).encode(&mut body); - let global_offset2 = body.len() + 1; + let global_offset1 = body.len() + 1; // global.set 0 ;; but with maximal encoding of the 0 body.extend_from_slice(&[0x24, 0x81, 0x80, 0x80, 0x80, 0x00]); End.encode(&mut body); let mut code = Vec::new(); - 1u32.encode(&mut code); // one function + 2u32.encode(&mut code); // two functions body.len().encode(&mut code); // length of the function - let body_offset = code.len(); + let body_offset0 = code.len(); + code.extend_from_slice(&body); // the function itself + body.len().encode(&mut code); // length of the function + let body_offset1 = code.len(); code.extend_from_slice(&body); // the function itself module.section(&RawSection { id: SectionId::Code as u8, @@ -106,8 +139,10 @@ fn build_raw_intrinsics() -> Vec { // Calculate the relocation offsets within the `code` section itself by // adding the start of where the body was placed to the offset within the // body. - let global_offset1 = global_offset1 + body_offset; - let global_offset2 = global_offset2 + body_offset; + let global_offset0_0 = body_offset0 + global_offset0; + let global_offset0_1 = body_offset0 + global_offset1; + let global_offset1_0 = body_offset1 + global_offset0; + let global_offset1_1 = body_offset1 + global_offset1; // Here the linking section is constructed. There are two symbols described // here, one for the function that we injected and one for the global @@ -122,17 +157,27 @@ fn build_raw_intrinsics() -> Vec { linking.push(0x08); // `WASM_SYMBOL_TABLE` let mut subsection = Vec::new(); - 2u32.encode(&mut subsection); // 2 symbols + 4u32.encode(&mut subsection); // 4 symbols subsection.push(0x00); // SYMTAB_FUNCTION 0x00.encode(&mut subsection); // flags 0x00u32.encode(&mut subsection); // function index - SYMBOL.encode(&mut subsection); // symbol name + REPLACE_REALLOC_GLOBAL_PTR.encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 0x01u32.encode(&mut subsection); // function index + REPLACE_REALLOC_GLOBAL_LEN.encode(&mut subsection); // symbol name + + subsection.push(0x02); // SYMTAB_GLOBAL + 0x02.encode(&mut subsection); // flags + 0x00u32.encode(&mut subsection); // global index + "internal_realloc_global_ptr".encode(&mut subsection); // symbol name subsection.push(0x02); // SYMTAB_GLOBAL 0x02.encode(&mut subsection); // flags 0x00u32.encode(&mut subsection); // global index - "internal_realloc_global".encode(&mut subsection); // symbol name + "internal_realloc_global_len".encode(&mut subsection); // symbol name subsection.encode(&mut linking); module.section(&CustomSection { @@ -146,13 +191,19 @@ fn build_raw_intrinsics() -> Vec { { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 2u32.encode(&mut reloc); // 2 relocations + 4u32.encode(&mut reloc); // 4 relocations reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset1.encode(&mut reloc); // offset - 0x01u32.encode(&mut reloc); // symbol index + global_offset0_0.encode(&mut reloc); // offset + 0x02u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset2.encode(&mut reloc); // offset - 0x01u32.encode(&mut reloc); // symbol index + global_offset0_1.encode(&mut reloc); // offset + 0x02u32.encode(&mut reloc); // symbol index + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offset1_0.encode(&mut reloc); // offset + 0x03u32.encode(&mut reloc); // symbol index + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offset1_1.encode(&mut reloc); // offset + 0x03u32.encode(&mut reloc); // symbol index module.section(&CustomSection { name: "reloc.CODE", @@ -187,9 +238,12 @@ fn build_archive(wasm: &[u8]) -> Vec { // easier. Note though we don't know the offset of our `intrinsics.o` up // front so it's left as 0 for now and filled in later. let mut symbol_table = Vec::new(); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 1))); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 2))); symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); - symbol_table.extend_from_slice(SYMBOL.as_bytes()); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + symbol_table.extend_from_slice(REPLACE_REALLOC_GLOBAL_PTR.as_bytes()); + symbol_table.push(0x00); + symbol_table.extend_from_slice(REPLACE_REALLOC_GLOBAL_LEN.as_bytes()); symbol_table.push(0x00); archive.extend_from_slice(bytes_of(&object::archive::Header { diff --git a/src/lib.rs b/src/lib.rs index 84c658f74cc4..bda7bfa26034 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,32 @@ wit_bindgen_guest_rust::import!("wit/wasi-logging.wit.md"); wit_bindgen_guest_rust::import!("wit/wasi-poll.wit.md"); wit_bindgen_guest_rust::import!("wit/wasi-random.wit.md"); -use std::arch::wasm32::unreachable; -use std::ptr::null_mut; +use core::arch::wasm32::unreachable; +use core::mem::forget; +use core::ptr::null_mut; +use core::{slice, str}; use wasi::*; extern "C" { - fn replace_realloc_global(val: *mut u8) -> *mut u8; + fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8; + fn replace_realloc_global_len(val: usize) -> usize; +} + +/// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy the +/// next request. +unsafe fn register_buffer(buf: *mut u8, buf_len: usize) { + let old_ptr = replace_realloc_global_ptr(buf); + assert!(old_ptr.is_null()); + let old_len = replace_realloc_global_len(buf_len); + assert_eq!(old_len, 0); +} + +/// Unregister `buf` and `buf_len`, which should have been used exactly once. +unsafe fn unregister_buffer(buf: *mut u8, buf_len: usize) { + let old_ptr = replace_realloc_global_ptr(null_mut()); + assert_eq!(old_ptr, buf); + let old_len = replace_realloc_global_len(0); + assert_eq!(old_len, buf_len); } #[no_mangle] @@ -20,16 +40,20 @@ pub unsafe extern "C" fn cabi_realloc( old_ptr: *mut u8, old_size: usize, _align: usize, - _new_size: usize, + new_size: usize, ) -> *mut u8 { if !old_ptr.is_null() || old_size != 0 { unreachable(); } - let base = replace_realloc_global(null_mut()); - if base.is_null() { + let ptr = replace_realloc_global_ptr(null_mut()); + if ptr.is_null() { unreachable(); } - base as *mut u8 + let len = replace_realloc_global_len(0); + if len < new_size { + unreachable(); + } + ptr } /// Read command-line argument data. @@ -369,10 +393,32 @@ pub unsafe extern "C" fn path_readlink( path_ptr: *const u8, path_len: usize, buf: *mut u8, - _buf_len: Size, + buf_len: Size, bufused: *mut Size, ) -> Errno { - unreachable() + let fd = wasi_filesystem::Descriptor::from_raw(fd as _); + + let path = match str::from_utf8(slice::from_raw_parts(path_ptr, path_len)) { + Ok(path) => path, + Err(_utf8_error) => return ERRNO_ILSEQ, + }; + + register_buffer(buf, buf_len); + + let result = fd.readlink_at(path); + + unregister_buffer(buf, buf_len); + + match result { + Ok(ref path) => { + assert_eq!(path.as_ptr(), buf); + assert!(path.len() <= buf_len); + *bufused = path.len(); + forget(path); + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } } /// Remove a directory. @@ -501,3 +547,76 @@ pub unsafe extern "C" fn sock_send( pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable() } + +fn errno_from_wasi_filesystem(err: wasi_filesystem::Errno) -> Errno { + match err { + wasi_filesystem::Errno::Toobig => ERRNO_2BIG, + wasi_filesystem::Errno::Access => ERRNO_ACCES, + wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, + wasi_filesystem::Errno::Addrnotavail => ERRNO_ADDRNOTAVAIL, + wasi_filesystem::Errno::Afnosupport => ERRNO_AFNOSUPPORT, + wasi_filesystem::Errno::Again => ERRNO_AGAIN, + wasi_filesystem::Errno::Already => ERRNO_ALREADY, + wasi_filesystem::Errno::Badmsg => ERRNO_BADMSG, + wasi_filesystem::Errno::Busy => ERRNO_BUSY, + wasi_filesystem::Errno::Canceled => ERRNO_CANCELED, + wasi_filesystem::Errno::Child => ERRNO_CHILD, + wasi_filesystem::Errno::Connaborted => ERRNO_CONNABORTED, + wasi_filesystem::Errno::Connrefused => ERRNO_CONNREFUSED, + wasi_filesystem::Errno::Connreset => ERRNO_CONNRESET, + wasi_filesystem::Errno::Deadlk => ERRNO_DEADLK, + wasi_filesystem::Errno::Destaddrreq => ERRNO_DESTADDRREQ, + wasi_filesystem::Errno::Dquot => ERRNO_DQUOT, + wasi_filesystem::Errno::Exist => ERRNO_EXIST, + wasi_filesystem::Errno::Fault => ERRNO_FAULT, + wasi_filesystem::Errno::Fbig => ERRNO_FBIG, + wasi_filesystem::Errno::Hostunreach => ERRNO_HOSTUNREACH, + wasi_filesystem::Errno::Idrm => ERRNO_IDRM, + wasi_filesystem::Errno::Ilseq => ERRNO_ILSEQ, + wasi_filesystem::Errno::Inprogress => ERRNO_INPROGRESS, + wasi_filesystem::Errno::Intr => ERRNO_INTR, + wasi_filesystem::Errno::Inval => ERRNO_INVAL, + wasi_filesystem::Errno::Io => ERRNO_IO, + wasi_filesystem::Errno::Isconn => ERRNO_ISCONN, + wasi_filesystem::Errno::Isdir => ERRNO_ISDIR, + wasi_filesystem::Errno::Loop => ERRNO_LOOP, + wasi_filesystem::Errno::Mfile => ERRNO_MFILE, + wasi_filesystem::Errno::Mlink => ERRNO_MLINK, + wasi_filesystem::Errno::Msgsize => ERRNO_MSGSIZE, + wasi_filesystem::Errno::Multihop => ERRNO_MULTIHOP, + wasi_filesystem::Errno::Nametoolong => ERRNO_NAMETOOLONG, + wasi_filesystem::Errno::Netdown => ERRNO_NETDOWN, + wasi_filesystem::Errno::Netreset => ERRNO_NETRESET, + wasi_filesystem::Errno::Netunreach => ERRNO_NETUNREACH, + wasi_filesystem::Errno::Nfile => ERRNO_NFILE, + wasi_filesystem::Errno::Nobufs => ERRNO_NOBUFS, + wasi_filesystem::Errno::Nodev => ERRNO_NODEV, + wasi_filesystem::Errno::Noent => ERRNO_NOENT, + wasi_filesystem::Errno::Noexec => ERRNO_NOEXEC, + wasi_filesystem::Errno::Nolck => ERRNO_NOLCK, + wasi_filesystem::Errno::Nolink => ERRNO_NOLINK, + wasi_filesystem::Errno::Nomem => ERRNO_NOMEM, + wasi_filesystem::Errno::Nomsg => ERRNO_NOMSG, + wasi_filesystem::Errno::Noprotoopt => ERRNO_NOPROTOOPT, + wasi_filesystem::Errno::Nospc => ERRNO_NOSPC, + wasi_filesystem::Errno::Nosys => ERRNO_NOSYS, + wasi_filesystem::Errno::Notdir => ERRNO_NOTDIR, + wasi_filesystem::Errno::Notempty => ERRNO_NOTEMPTY, + wasi_filesystem::Errno::Notrecoverable => ERRNO_NOTRECOVERABLE, + wasi_filesystem::Errno::Notsup => ERRNO_NOTSUP, + wasi_filesystem::Errno::Notty => ERRNO_NOTTY, + wasi_filesystem::Errno::Nxio => ERRNO_NXIO, + wasi_filesystem::Errno::Overflow => ERRNO_OVERFLOW, + wasi_filesystem::Errno::Ownerdead => ERRNO_OWNERDEAD, + wasi_filesystem::Errno::Perm => ERRNO_PERM, + wasi_filesystem::Errno::Pipe => ERRNO_PIPE, + wasi_filesystem::Errno::Range => ERRNO_RANGE, + wasi_filesystem::Errno::Rofs => ERRNO_ROFS, + wasi_filesystem::Errno::Spipe => ERRNO_SPIPE, + wasi_filesystem::Errno::Srch => ERRNO_SRCH, + wasi_filesystem::Errno::Stale => ERRNO_STALE, + wasi_filesystem::Errno::Timedout => ERRNO_TIMEDOUT, + wasi_filesystem::Errno::Txtbsy => ERRNO_TXTBSY, + wasi_filesystem::Errno::Xdev => ERRNO_XDEV, + } +} From 541eba9b3b829d8311ba93cb160b21e33a729a46 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 4 Oct 2022 17:49:41 -0700 Subject: [PATCH 009/153] Use raw_strings, disable integer overflow checks, and suppress jump tables. This also updates the verifier to accept the new wasi imports. --- src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bda7bfa26034..c5fcb1fff95b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,55 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented -wit_bindgen_guest_rust::import!("wit/wasi-clocks.wit.md"); -wit_bindgen_guest_rust::import!("wit/wasi-default-clocks.wit.md"); -wit_bindgen_guest_rust::import!("wit/wasi-filesystem.wit.md"); -wit_bindgen_guest_rust::import!("wit/wasi-logging.wit.md"); -wit_bindgen_guest_rust::import!("wit/wasi-poll.wit.md"); -wit_bindgen_guest_rust::import!("wit/wasi-random.wit.md"); +wit_bindgen_guest_rust::import!({ + paths: [ + "wit/wasi-clocks.wit.md", + "wit/wasi-default-clocks.wit.md", + "wit/wasi-filesystem.wit.md", + "wit/wasi-logging.wit.md", + "wit/wasi-poll.wit.md", + "wit/wasi-random.wit.md" + ], + raw_strings, + unchecked +}); use core::arch::wasm32::unreachable; use core::mem::forget; use core::ptr::null_mut; -use core::{slice, str}; +use core::slice; use wasi::*; +extern crate alloc; + +// We're avoiding static initializers, so replace the standard assert macros +// with simpler implementation. +macro_rules! assert { + ($cond:expr $(,)?) => { + if !$cond { + unreachable() + } + }; +} +macro_rules! assert_eq { + ($left:expr, $right:expr $(,)?) => { + assert!($left == $right); + }; +} + +// We're avoiding static initializers, so don't link in the default allocator. +struct Alloc {} +unsafe impl alloc::alloc::GlobalAlloc for Alloc { + unsafe fn alloc(&self, _: alloc::alloc::Layout) -> *mut u8 { + unreachable() + } + unsafe fn dealloc(&self, _: *mut u8, _: alloc::alloc::Layout) { + unreachable() + } +} +#[global_allocator] +static ALLOC: Alloc = Alloc {}; + +// These functions are defined by the object that the build.rs script produces. extern "C" { fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8; fn replace_realloc_global_len(val: usize) -> usize; @@ -398,10 +435,7 @@ pub unsafe extern "C" fn path_readlink( ) -> Errno { let fd = wasi_filesystem::Descriptor::from_raw(fd as _); - let path = match str::from_utf8(slice::from_raw_parts(path_ptr, path_len)) { - Ok(path) => path, - Err(_utf8_error) => return ERRNO_ILSEQ, - }; + let path = slice::from_raw_parts(path_ptr, path_len); register_buffer(buf, buf_len); @@ -548,9 +582,10 @@ pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable() } +#[inline(never)] // Disable inlining as this is bulky and relatively cold. fn errno_from_wasi_filesystem(err: wasi_filesystem::Errno) -> Errno { match err { - wasi_filesystem::Errno::Toobig => ERRNO_2BIG, + wasi_filesystem::Errno::Toobig => black_box(ERRNO_2BIG), wasi_filesystem::Errno::Access => ERRNO_ACCES, wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, wasi_filesystem::Errno::Addrnotavail => ERRNO_ADDRNOTAVAIL, @@ -620,3 +655,10 @@ fn errno_from_wasi_filesystem(err: wasi_filesystem::Errno) -> Errno { wasi_filesystem::Errno::Xdev => ERRNO_XDEV, } } + +// A black box to prevent the optimizer from generating a lookup table +// from the match above, which would require a static initializer. +#[inline(never)] +fn black_box(x: Errno) -> Errno { + unsafe { core::ptr::read_volatile(&x) } +} From 394847a9dda9622932c053884b520d998041644c Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 13:18:22 -0700 Subject: [PATCH 010/153] Use a compiler_fence. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c5fcb1fff95b..bd7f44a1ab4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -658,7 +658,7 @@ fn errno_from_wasi_filesystem(err: wasi_filesystem::Errno) -> Errno { // A black box to prevent the optimizer from generating a lookup table // from the match above, which would require a static initializer. -#[inline(never)] fn black_box(x: Errno) -> Errno { - unsafe { core::ptr::read_volatile(&x) } + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + x } From 7823878a41381b33d7e5fbd77e79e94fded1ab3a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 13:40:17 -0700 Subject: [PATCH 011/153] Add a slow-path to handle smaller buffers passed to `path_readlink`. --- src/lib.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bd7f44a1ab4b..9e3b9fa92651 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,11 @@ use core::ptr::null_mut; use core::slice; use wasi::*; +/// The maximum path length. WASI doesn't explicitly guarantee this, but all +/// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this +/// polyfill. +const PATH_MAX: usize = 4096; + extern crate alloc; // We're avoiding static initializers, so replace the standard assert macros @@ -437,6 +442,13 @@ pub unsafe extern "C" fn path_readlink( let path = slice::from_raw_parts(path_ptr, path_len); + // If the user gave us a buffer shorter than `PATH_MAX`, it may not be + // long enough to accept the actual path. `cabi_realloc` can't fail, + // so instead we handle this case specially. + if buf_len < PATH_MAX { + return path_readlink_slow(fd, path, buf, buf_len, bufused); + } + register_buffer(buf, buf_len); let result = fd.readlink_at(path); @@ -447,6 +459,7 @@ pub unsafe extern "C" fn path_readlink( Ok(ref path) => { assert_eq!(path.as_ptr(), buf); assert!(path.len() <= buf_len); + *bufused = path.len(); forget(path); ERRNO_SUCCESS @@ -455,6 +468,40 @@ pub unsafe extern "C" fn path_readlink( } } +// Slow-path for `path_readlink` that allocates a buffer on the stack to +// ensure that it has a big enough buffer. +unsafe fn path_readlink_slow( + fd: wasi_filesystem::Descriptor, + path: &[u8], + buf: *mut u8, + buf_len: Size, + bufused: *mut Size, +) -> Errno { + let mut buffer = core::mem::MaybeUninit::<[u8; PATH_MAX]>::uninit(); + + register_buffer(buffer.as_mut_ptr().cast(), PATH_MAX); + + let result = fd.readlink_at(path); + + unregister_buffer(buffer.as_mut_ptr().cast(), PATH_MAX); + + match result { + Ok(ref path) => { + assert_eq!(path.as_ptr(), buffer.as_ptr().cast()); + assert!(path.len() <= PATH_MAX); + + // Preview1 follows POSIX in truncating the returned path if + // it doesn't fit. + let len = core::cmp::min(path.len(), buf_len); + core::ptr::copy_nonoverlapping(buffer.as_ptr().cast(), buf, len); + *bufused = len; + forget(path); + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } +} + /// Remove a directory. /// Return `errno::notempty` if the directory is not empty. /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. From 105914e24588e4b01a580dbc913302589e4c0fd1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 13:52:24 -0700 Subject: [PATCH 012/153] These `forget`s aren't needed because they just have references. --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9e3b9fa92651..a70981655513 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ wit_bindgen_guest_rust::import!({ }); use core::arch::wasm32::unreachable; -use core::mem::forget; use core::ptr::null_mut; use core::slice; use wasi::*; @@ -461,7 +460,6 @@ pub unsafe extern "C" fn path_readlink( assert!(path.len() <= buf_len); *bufused = path.len(); - forget(path); ERRNO_SUCCESS } Err(err) => errno_from_wasi_filesystem(err), @@ -495,7 +493,6 @@ unsafe fn path_readlink_slow( let len = core::cmp::min(path.len(), buf_len); core::ptr::copy_nonoverlapping(buffer.as_ptr().cast(), buf, len); *bufused = len; - forget(path); ERRNO_SUCCESS } Err(err) => errno_from_wasi_filesystem(err), From b02371bc1083358b87eec4a5333e9c315cd73ec7 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 13:56:56 -0700 Subject: [PATCH 013/153] Properly forget returned buffers. --- src/lib.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a70981655513..b861643f4d7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ wit_bindgen_guest_rust::import!({ }); use core::arch::wasm32::unreachable; +use core::mem::forget; use core::ptr::null_mut; use core::slice; use wasi::*; @@ -454,16 +455,22 @@ pub unsafe extern "C" fn path_readlink( unregister_buffer(buf, buf_len); - match result { - Ok(ref path) => { + let return_value = match &result { + Ok(path) => { assert_eq!(path.as_ptr(), buf); assert!(path.len() <= buf_len); *bufused = path.len(); ERRNO_SUCCESS } - Err(err) => errno_from_wasi_filesystem(err), - } + Err(err) => errno_from_wasi_filesystem(*err), + }; + + // The returned string's memory was allocated in `buf`, so don't separately + // free it. + forget(result); + + return_value } // Slow-path for `path_readlink` that allocates a buffer on the stack to @@ -483,8 +490,8 @@ unsafe fn path_readlink_slow( unregister_buffer(buffer.as_mut_ptr().cast(), PATH_MAX); - match result { - Ok(ref path) => { + let return_value = match &result { + Ok(path) => { assert_eq!(path.as_ptr(), buffer.as_ptr().cast()); assert!(path.len() <= PATH_MAX); @@ -495,8 +502,14 @@ unsafe fn path_readlink_slow( *bufused = len; ERRNO_SUCCESS } - Err(err) => errno_from_wasi_filesystem(err), - } + Err(err) => errno_from_wasi_filesystem(*err), + }; + + // The returned string's memory was allocated in `buf`, so don't separately + // free it. + forget(result); + + return_value } /// Remove a directory. From f27cb005474c00e4c9182a53f3ef72c9961bf35a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 13:57:12 -0700 Subject: [PATCH 014/153] Remove the code that disabled the global allocator. --- src/lib.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b861643f4d7a..f7265033531e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,19 +41,6 @@ macro_rules! assert_eq { }; } -// We're avoiding static initializers, so don't link in the default allocator. -struct Alloc {} -unsafe impl alloc::alloc::GlobalAlloc for Alloc { - unsafe fn alloc(&self, _: alloc::alloc::Layout) -> *mut u8 { - unreachable() - } - unsafe fn dealloc(&self, _: *mut u8, _: alloc::alloc::Layout) { - unreachable() - } -} -#[global_allocator] -static ALLOC: Alloc = Alloc {}; - // These functions are defined by the object that the build.rs script produces. extern "C" { fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8; From 533bd312910c089a2e5683800eb551a327f2b830 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 14:08:45 -0700 Subject: [PATCH 015/153] Mark `path_readlink_slow` as inline(never). --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f7265033531e..62ab73be793e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -460,8 +460,9 @@ pub unsafe extern "C" fn path_readlink( return_value } -// Slow-path for `path_readlink` that allocates a buffer on the stack to -// ensure that it has a big enough buffer. +/// Slow-path for `path_readlink` that allocates a buffer on the stack to +/// ensure that it has a big enough buffer. +#[inline(never)] // Disable inlining as this has a large stack buffer. unsafe fn path_readlink_slow( fd: wasi_filesystem::Descriptor, path: &[u8], From 22def1791a3ee25f97ccc97f4782b04ed97b69ad Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Oct 2022 16:11:49 -0700 Subject: [PATCH 016/153] Minor cleanups. --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 62ab73be793e..972a861875d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,10 +24,8 @@ use wasi::*; /// polyfill. const PATH_MAX: usize = 4096; -extern crate alloc; - // We're avoiding static initializers, so replace the standard assert macros -// with simpler implementation. +// with simpler implementations. macro_rules! assert { ($cond:expr $(,)?) => { if !$cond { From bec95fa51a187c3827290d4f5d4403638b8d145a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 6 Oct 2022 14:38:15 -0700 Subject: [PATCH 017/153] Remove `unregister_buffer`. When `cabi_realloc` uses the buffer pointer, it sets the stored buffer pointer to null, so we don't need a separate `unregister_buffer` call. --- src/lib.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 972a861875d5..8c4da415cea4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,14 +54,6 @@ unsafe fn register_buffer(buf: *mut u8, buf_len: usize) { assert_eq!(old_len, 0); } -/// Unregister `buf` and `buf_len`, which should have been used exactly once. -unsafe fn unregister_buffer(buf: *mut u8, buf_len: usize) { - let old_ptr = replace_realloc_global_ptr(null_mut()); - assert_eq!(old_ptr, buf); - let old_len = replace_realloc_global_len(0); - assert_eq!(old_len, buf_len); -} - #[no_mangle] pub unsafe extern "C" fn cabi_realloc( old_ptr: *mut u8, @@ -438,8 +430,6 @@ pub unsafe extern "C" fn path_readlink( let result = fd.readlink_at(path); - unregister_buffer(buf, buf_len); - let return_value = match &result { Ok(path) => { assert_eq!(path.as_ptr(), buf); @@ -474,8 +464,6 @@ unsafe fn path_readlink_slow( let result = fd.readlink_at(path); - unregister_buffer(buffer.as_mut_ptr().cast(), PATH_MAX); - let return_value = match &result { Ok(path) => { assert_eq!(path.as_ptr(), buffer.as_ptr().cast()); From cd71cd1cd5d72f55ae3a5df3e3395a430058fcd1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 7 Oct 2022 14:57:25 -0700 Subject: [PATCH 018/153] Implement file descriptors. --- build.rs | 158 +++++++++++++++++++++++++++-------------------------- src/lib.rs | 111 +++++++++++++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 83 deletions(-) diff --git a/build.rs b/build.rs index 4cfa12c2083e..bc39d5c2d566 100644 --- a/build.rs +++ b/build.rs @@ -1,13 +1,21 @@ use std::env; use std::path::PathBuf; -const REPLACE_REALLOC_GLOBAL_PTR: &str = "replace_realloc_global_ptr"; -const REPLACE_REALLOC_GLOBAL_LEN: &str = "replace_realloc_global_len"; - fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let wasm = build_raw_intrinsics(); - let archive = build_archive(&wasm); + + let mut function_names = Vec::new(); + function_names.push("replace_realloc_global_ptr".to_owned()); + function_names.push("replace_realloc_global_len".to_owned()); + function_names.push("replace_fds".to_owned()); + + let mut global_names = Vec::new(); + global_names.push("internal_realloc_global_ptr".to_owned()); + global_names.push("internal_realloc_global_len".to_owned()); + global_names.push("internal_fds".to_owned()); + + let wasm = build_raw_intrinsics(&function_names, &global_names); + let archive = build_archive(&wasm, &function_names); std::fs::write(out_dir.join("libwasm-raw-intrinsics.a"), &archive).unwrap(); println!("cargo:rustc-link-lib=static=wasm-raw-intrinsics"); @@ -27,6 +35,8 @@ fn main() { /// internal_realloc_global_ptr: /// .globaltype internal_realloc_global_len, i32 /// internal_realloc_global_len: +/// .globaltype internal_fds, i32 +/// internal_fds: /// " /// ); /// @@ -69,40 +79,35 @@ fn main() { /// /// The main trickiness here is getting the `reloc.CODE` and `linking` sections /// right. -fn build_raw_intrinsics() -> Vec { +fn build_raw_intrinsics(function_names: &[String], global_names: &[String]) -> Vec { use wasm_encoder::Instruction::*; use wasm_encoder::*; let mut module = Module::new(); - // One function type, i32 -> i32 + // All our functions have the same type, i32 -> i32 let mut types = TypeSection::new(); types.function([ValType::I32], [ValType::I32]); module.section(&types); - // Two functions, using the type we just added + // Declare the functions, using the type we just added. let mut funcs = FunctionSection::new(); - funcs.function(0); - funcs.function(0); + for _ in function_names { + funcs.function(0); + } module.section(&funcs); + // Declare the globals. let mut globals = GlobalSection::new(); - // This is the `internal_realloc_global_ptr` definition - globals.global( - GlobalType { - val_type: ValType::I32, - mutable: true, - }, - &ConstExpr::i32_const(0), - ); - // This is the `internal_realloc_global_len` definition - globals.global( - GlobalType { - val_type: ValType::I32, - mutable: true, - }, - &ConstExpr::i32_const(0), - ); + for _ in global_names { + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + }, + &ConstExpr::i32_const(0), + ); + } module.section(&globals); // Here the `code` section is defined. This is tricky because an offset is @@ -123,14 +128,15 @@ fn build_raw_intrinsics() -> Vec { body.extend_from_slice(&[0x24, 0x81, 0x80, 0x80, 0x80, 0x00]); End.encode(&mut body); + let mut body_offsets = Vec::new(); + let mut code = Vec::new(); - 2u32.encode(&mut code); // two functions - body.len().encode(&mut code); // length of the function - let body_offset0 = code.len(); - code.extend_from_slice(&body); // the function itself - body.len().encode(&mut code); // length of the function - let body_offset1 = code.len(); - code.extend_from_slice(&body); // the function itself + function_names.len().encode(&mut code); + for _ in function_names { + body.len().encode(&mut code); // length of the function + body_offsets.push(code.len()); + code.extend_from_slice(&body); // the function itself + } module.section(&RawSection { id: SectionId::Code as u8, data: &code, @@ -139,10 +145,14 @@ fn build_raw_intrinsics() -> Vec { // Calculate the relocation offsets within the `code` section itself by // adding the start of where the body was placed to the offset within the // body. - let global_offset0_0 = body_offset0 + global_offset0; - let global_offset0_1 = body_offset0 + global_offset1; - let global_offset1_0 = body_offset1 + global_offset0; - let global_offset1_1 = body_offset1 + global_offset1; + let global_offsets0 = body_offsets + .iter() + .map(|x| x + global_offset0) + .collect::>(); + let global_offsets1 = body_offsets + .iter() + .map(|x| x + global_offset1) + .collect::>(); // Here the linking section is constructed. There are two symbols described // here, one for the function that we injected and one for the global @@ -157,27 +167,21 @@ fn build_raw_intrinsics() -> Vec { linking.push(0x08); // `WASM_SYMBOL_TABLE` let mut subsection = Vec::new(); - 4u32.encode(&mut subsection); // 4 symbols - - subsection.push(0x00); // SYMTAB_FUNCTION - 0x00.encode(&mut subsection); // flags - 0x00u32.encode(&mut subsection); // function index - REPLACE_REALLOC_GLOBAL_PTR.encode(&mut subsection); // symbol name - - subsection.push(0x00); // SYMTAB_FUNCTION - 0x00.encode(&mut subsection); // flags - 0x01u32.encode(&mut subsection); // function index - REPLACE_REALLOC_GLOBAL_LEN.encode(&mut subsection); // symbol name + 6u32.encode(&mut subsection); // 6 symbols (3 functions + 3 globals) - subsection.push(0x02); // SYMTAB_GLOBAL - 0x02.encode(&mut subsection); // flags - 0x00u32.encode(&mut subsection); // global index - "internal_realloc_global_ptr".encode(&mut subsection); // symbol name + for (index, name) in function_names.iter().enumerate() { + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + index.encode(&mut subsection); // function index + name.encode(&mut subsection); // symbol name + } - subsection.push(0x02); // SYMTAB_GLOBAL - 0x02.encode(&mut subsection); // flags - 0x00u32.encode(&mut subsection); // global index - "internal_realloc_global_len".encode(&mut subsection); // symbol name + for (index, name) in global_names.iter().enumerate() { + subsection.push(0x02); // SYMTAB_GLOBAL + 0x02.encode(&mut subsection); // flags + index.encode(&mut subsection); // global index + name.encode(&mut subsection); // symbol name + } subsection.encode(&mut linking); module.section(&CustomSection { @@ -191,19 +195,15 @@ fn build_raw_intrinsics() -> Vec { { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 4u32.encode(&mut reloc); // 4 relocations - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset0_0.encode(&mut reloc); // offset - 0x02u32.encode(&mut reloc); // symbol index - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset0_1.encode(&mut reloc); // offset - 0x02u32.encode(&mut reloc); // symbol index - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset1_0.encode(&mut reloc); // offset - 0x03u32.encode(&mut reloc); // symbol index - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset1_1.encode(&mut reloc); // offset - 0x03u32.encode(&mut reloc); // symbol index + 6u32.encode(&mut reloc); // 6 relocations + for index in 0..global_names.len() { + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offsets0[index as usize].encode(&mut reloc); // offset + (function_names.len() + index).encode(&mut reloc); // symbol index + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offsets1[index as usize].encode(&mut reloc); // offset + (function_names.len() + index).encode(&mut reloc); // symbol index + } module.section(&CustomSection { name: "reloc.CODE", @@ -220,7 +220,7 @@ fn build_raw_intrinsics() -> Vec { /// /// Like above this is still tricky, mainly around the production of the symbol /// table. -fn build_archive(wasm: &[u8]) -> Vec { +fn build_archive(wasm: &[u8], function_names: &[String]) -> Vec { use object::{bytes_of, endian::BigEndian, U32Bytes}; let mut archive = Vec::new(); @@ -238,13 +238,17 @@ fn build_archive(wasm: &[u8]) -> Vec { // easier. Note though we don't know the offset of our `intrinsics.o` up // front so it's left as 0 for now and filled in later. let mut symbol_table = Vec::new(); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 2))); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); - symbol_table.extend_from_slice(REPLACE_REALLOC_GLOBAL_PTR.as_bytes()); - symbol_table.push(0x00); - symbol_table.extend_from_slice(REPLACE_REALLOC_GLOBAL_LEN.as_bytes()); - symbol_table.push(0x00); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new( + BigEndian, + function_names.len().try_into().unwrap(), + ))); + for _ in function_names { + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + } + for name in function_names { + symbol_table.extend_from_slice(name.as_bytes()); + symbol_table.push(0x00); + } archive.extend_from_slice(bytes_of(&object::archive::Header { name: *b"/ ", diff --git a/src/lib.rs b/src/lib.rs index 8c4da415cea4..117dc273dcbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ macro_rules! assert_eq { extern "C" { fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8; fn replace_realloc_global_len(val: usize) -> usize; + fn replace_fds(val: *mut u8) -> *mut u8; } /// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy the @@ -250,11 +251,44 @@ pub unsafe extern "C" fn fd_pwrite( #[no_mangle] pub unsafe extern "C" fn fd_read( fd: Fd, - iovs_ptr: *const Iovec, - iovs_len: usize, + mut iovs_ptr: *const Iovec, + mut iovs_len: usize, nread: *mut Size, ) -> Errno { - unreachable() + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nread = 0; + return ERRNO_SUCCESS; + } + + match Descriptor::get(fd) { + Descriptor::File(file) => { + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + + register_buffer(ptr, len); + + let result = file + .fd + .pread(len.try_into().unwrap_or(u32::MAX), file.position); + + match result { + Ok(data) => { + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + *nread = data.len(); + file.position += data.len() as u64; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } + } + Descriptor::Closed | Descriptor::Log => ERRNO_BADF, + } } /// Read directory entries from a directory. @@ -321,11 +355,48 @@ pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_write( fd: Fd, - iovs_ptr: *const Ciovec, - iovs_len: usize, + mut iovs_ptr: *const Ciovec, + mut iovs_len: usize, nwritten: *mut Size, ) -> Errno { - unreachable() + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nwritten = 0; + return ERRNO_SUCCESS; + } + + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + + match Descriptor::get(fd) { + Descriptor::File(file) => { + register_buffer(ptr as *mut _, len); + + let result = file + .fd + .pwrite(slice::from_raw_parts(ptr, len), file.position); + + match result { + Ok(bytes) => { + *nwritten = bytes as usize; + file.position += bytes as u64; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } + } + Descriptor::Log => { + let bytes = slice::from_raw_parts(ptr, len); + wasi_logging::log(wasi_logging::Level::Info, "I/O".as_bytes(), bytes); + *nwritten = len; + ERRNO_SUCCESS + } + Descriptor::Closed => ERRNO_BADF, + } } /// Create a directory. @@ -693,3 +764,31 @@ fn black_box(x: Errno) -> Errno { core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); x } + +#[repr(C)] +pub enum Descriptor { + Closed, + File(File), + Log, +} + +#[repr(C)] +pub struct File { + fd: wasi_filesystem::Descriptor, + position: u64, +} + +impl Descriptor { + fn get(fd: Fd) -> &'static mut Descriptor { + unsafe { + let mut fds = replace_fds(null_mut()); + if fds.is_null() { + fds = core::arch::wasm32::memory_grow(0, 1) as *mut u8; + } + let result = &mut *fds.cast::().add(fd as usize); + let fds = replace_fds(fds); + assert!(fds.is_null()); + result + } + } +} From c0551ad504c6c0d275991dd991899f8d5b6130e2 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 7 Oct 2022 14:57:40 -0700 Subject: [PATCH 019/153] Implement more stuff. --- src/lib.rs | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 117dc273dcbf..19d8a7ca26f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ wit_bindgen_guest_rust::import!({ use core::arch::wasm32::unreachable; use core::mem::forget; -use core::ptr::null_mut; +use core::ptr::{copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; @@ -80,20 +80,30 @@ pub unsafe extern "C" fn cabi_realloc( /// The size of the array should match that returned by `args_sizes_get` #[no_mangle] pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { - unreachable() + // TODO: Use real arguments. + copy_nonoverlapping("wasm\0".as_ptr(), argv_buf, 5); + argv.add(0).write(argv_buf); + argv.add(1).write(null_mut()); + ERRNO_SUCCESS } /// Return command-line argument data sizes. #[no_mangle] pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { - unreachable() + // TODO: Use real arguments. + *argc = 1; + *argv_buf_size = 5; + ERRNO_SUCCESS } /// Read environment variable data. /// The sizes of the buffers should match that returned by `environ_sizes_get`. #[no_mangle] pub unsafe extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { - unreachable() + // TODO: Use real env vars. + *environ = null_mut(); + let _ = environ_buf; + ERRNO_SUCCESS } /// Return environment variable data sizes. @@ -102,7 +112,10 @@ pub unsafe extern "C" fn environ_sizes_get( environc: *mut Size, environ_buf_size: *mut Size, ) -> Errno { - unreachable() + // TODO: Use real env vars. + *environc = 0; + *environ_buf_size = 0; + ERRNO_SUCCESS } /// Return the resolution of a clock. @@ -634,7 +647,9 @@ pub unsafe extern "C" fn proc_raise(sig: Signal) -> Errno { /// Note: This is similar to `sched_yield` in POSIX. #[no_mangle] pub unsafe extern "C" fn sched_yield() -> Errno { - unreachable() + // TODO: This is not yet covered in Preview2. + + ERRNO_SUCCESS } /// Write high-quality random data into a buffer. @@ -645,7 +660,17 @@ pub unsafe extern "C" fn sched_yield() -> Errno { /// number generator, rather than to provide the random data directly. #[no_mangle] pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { - unreachable() + register_buffer(buf, buf_len); + + assert_eq!(buf_len as u32 as Size, buf_len); + let result = wasi_random::getrandom(buf_len as u32); + assert_eq!(result.as_ptr(), buf); + + // The returned buffer's memory was allocated in `buf`, so don't separately + // free it. + forget(result); + + ERRNO_SUCCESS } /// Receive a message from a socket. From ab9a1ac6bdd2cc528096be9e7c35d9de6d8d4d9a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 7 Oct 2022 15:45:15 -0700 Subject: [PATCH 020/153] Implement more functions. --- src/lib.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 19d8a7ca26f7..d4cde129654e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,14 +168,86 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { /// Note: This is similar to `fdatasync` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => match file.fd.datasync() { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_INVAL, + Descriptor::Closed => ERRNO_BADF, + } } /// Get the attributes of a file descriptor. /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => { + let info = match file.fd.info() { + Ok(info) => info, + Err(err) => return errno_from_wasi_filesystem(err), + }; + + let fs_filetype = match info.type_ { + wasi_filesystem::Type::RegularFile => FILETYPE_REGULAR_FILE, + wasi_filesystem::Type::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::Type::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::Type::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + wasi_filesystem::Type::Fifo => FILETYPE_UNKNOWN, + wasi_filesystem::Type::Socket => FILETYPE_SOCKET_STREAM, + wasi_filesystem::Type::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::Type::Unknown => FILETYPE_UNKNOWN, + }; + + let mut fs_flags = 0; + let mut fs_rights_base = !0; + if !info.flags.contains(wasi_filesystem::Flags::READ) { + fs_rights_base &= !RIGHTS_FD_READ; + } + if !info.flags.contains(wasi_filesystem::Flags::WRITE) { + fs_rights_base &= !RIGHTS_FD_WRITE; + } + if info.flags.contains(wasi_filesystem::Flags::APPEND) { + fs_flags |= FDFLAGS_APPEND; + } + if info.flags.contains(wasi_filesystem::Flags::DSYNC) { + fs_flags |= FDFLAGS_DSYNC; + } + if info.flags.contains(wasi_filesystem::Flags::NONBLOCK) { + fs_flags |= FDFLAGS_NONBLOCK; + } + if info.flags.contains(wasi_filesystem::Flags::RSYNC) { + fs_flags |= FDFLAGS_RSYNC; + } + if info.flags.contains(wasi_filesystem::Flags::SYNC) { + fs_flags |= FDFLAGS_SYNC; + } + let fs_rights_inheriting = fs_rights_base; + + stat.write(Fdstat { + fs_filetype, + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + ERRNO_SUCCESS + } + Descriptor::Log => { + let fs_filetype = FILETYPE_UNKNOWN; + let fs_flags = 0; + let fs_rights_base = !RIGHTS_FD_READ; + let fs_rights_inheriting = fs_rights_base; + stat.write(Fdstat { + fs_filetype, + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + ERRNO_SUCCESS + } + Descriptor::Closed => ERRNO_BADF, + } } /// Adjust the flags associated with a file descriptor. @@ -346,21 +418,68 @@ pub unsafe extern "C" fn fd_seek( whence: Whence, newoffset: *mut Filesize, ) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => { + let from = match whence { + WHENCE_SET => { + let offset = match offset.try_into() { + Ok(offset) => offset, + Err(_) => return ERRNO_INVAL, + }; + wasi_filesystem::SeekFrom::Set(offset) + } + WHENCE_CUR => wasi_filesystem::SeekFrom::Cur(offset), + WHENCE_END => { + let offset = match offset.try_into() { + Ok(offset) => offset, + Err(_) => return ERRNO_INVAL, + }; + wasi_filesystem::SeekFrom::End(offset) + } + _ => return ERRNO_INVAL, + }; + match file.fd.seek(from) { + Ok(result) => { + *newoffset = result; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } + } + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Synchronize the data and metadata of a file to disk. /// Note: This is similar to `fsync` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => match file.fd.sync() { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_INVAL, + Descriptor::Closed => ERRNO_BADF, + } } /// Return the current offset of a file descriptor. /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => match file.fd.tell() { + Ok(result) => { + *offset = result; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Write to a file descriptor. From 3cc7342a5fc34806e768bfc13656d6b1cef72d9d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 7 Oct 2022 16:13:53 -0700 Subject: [PATCH 021/153] Fixes to avoid static inits. --- src/lib.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d4cde129654e..3ee156a0816b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,12 @@ pub unsafe extern "C" fn cabi_realloc( #[no_mangle] pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { // TODO: Use real arguments. - copy_nonoverlapping("wasm\0".as_ptr(), argv_buf, 5); + // Store bytes one at a time to avoid needing a static init. + argv_buf.add(0).write(b'w'); + argv_buf.add(1).write(b'a'); + argv_buf.add(2).write(b's'); + argv_buf.add(3).write(b'm'); + argv_buf.add(4).write(b'\0'); argv.add(0).write(argv_buf); argv.add(1).write(null_mut()); ERRNO_SUCCESS @@ -357,16 +362,18 @@ pub unsafe extern "C" fn fd_read( register_buffer(ptr, len); - let result = file - .fd - .pread(len.try_into().unwrap_or(u32::MAX), file.position); - + let read_len: u32 = match len.try_into() { + Ok(len) => len, + Err(_) => u32::MAX, + }; + let result = file.fd.pread(read_len, file.position); match result { Ok(data) => { assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); *nread = data.len(); file.position += data.len() as u64; + forget(data); ERRNO_SUCCESS } Err(err) => errno_from_wasi_filesystem(err), @@ -523,7 +530,8 @@ pub unsafe extern "C" fn fd_write( } Descriptor::Log => { let bytes = slice::from_raw_parts(ptr, len); - wasi_logging::log(wasi_logging::Level::Info, "I/O".as_bytes(), bytes); + let context: [u8; 3] = [b'I', b'/', b'O']; + wasi_logging::log(wasi_logging::Level::Info, &context, bytes); *nwritten = len; ERRNO_SUCCESS } @@ -675,7 +683,7 @@ unsafe fn path_readlink_slow( // Preview1 follows POSIX in truncating the returned path if // it doesn't fit. let len = core::cmp::min(path.len(), buf_len); - core::ptr::copy_nonoverlapping(buffer.as_ptr().cast(), buf, len); + copy_nonoverlapping(buffer.as_ptr().cast(), buf, len); *bufused = len; ERRNO_SUCCESS } From a30558aa3e4a9c6be27874d861df992c4c8923d7 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 7 Oct 2022 16:28:18 -0700 Subject: [PATCH 022/153] Add a file descriptor bounds check. --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3ee156a0816b..90ef42fb8e6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ wit_bindgen_guest_rust::import!({ }); use core::arch::wasm32::unreachable; -use core::mem::forget; +use core::mem::{forget, size_of}; use core::ptr::{copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; @@ -937,6 +937,10 @@ impl Descriptor { if fds.is_null() { fds = core::arch::wasm32::memory_grow(0, 1) as *mut u8; } + // We allocated a page; abort if that's not enough. + if fd as usize * size_of::() >= 65536 { + unreachable() + } let result = &mut *fds.cast::().add(fd as usize); let fds = replace_fds(fds); assert!(fds.is_null()); From ac64e345b47f0309b91811b4263ce4370cef2dfa Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 7 Oct 2022 16:30:54 -0700 Subject: [PATCH 023/153] Simplify a few casts. --- src/lib.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 90ef42fb8e6c..62da938f7dee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -362,10 +362,8 @@ pub unsafe extern "C" fn fd_read( register_buffer(ptr, len); - let read_len: u32 = match len.try_into() { - Ok(len) => len, - Err(_) => u32::MAX, - }; + // We can cast between a `usize`-sized value and `usize`. + let read_len = len as _; let result = file.fd.pread(read_len, file.position); match result { Ok(data) => { @@ -427,22 +425,12 @@ pub unsafe extern "C" fn fd_seek( ) -> Errno { match Descriptor::get(fd) { Descriptor::File(file) => { + // It's ok to cast these indices; the WASI API will fail if + // the resulting values are out of range. let from = match whence { - WHENCE_SET => { - let offset = match offset.try_into() { - Ok(offset) => offset, - Err(_) => return ERRNO_INVAL, - }; - wasi_filesystem::SeekFrom::Set(offset) - } + WHENCE_SET => wasi_filesystem::SeekFrom::Set(offset as _), WHENCE_CUR => wasi_filesystem::SeekFrom::Cur(offset), - WHENCE_END => { - let offset = match offset.try_into() { - Ok(offset) => offset, - Err(_) => return ERRNO_INVAL, - }; - wasi_filesystem::SeekFrom::End(offset) - } + WHENCE_END => wasi_filesystem::SeekFrom::End(offset as _), _ => return ERRNO_INVAL, }; match file.fd.seek(from) { From 0e29983b115147fac8e5e1424d9a682fd0a87d1f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 10 Oct 2022 12:21:46 -0700 Subject: [PATCH 024/153] Multiply memory.grow's result by the page size, and handle failure. --- src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 62da938f7dee..8c8117016790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -918,15 +918,21 @@ pub struct File { position: u64, } +const PAGE_SIZE: usize = 65536; + impl Descriptor { fn get(fd: Fd) -> &'static mut Descriptor { unsafe { let mut fds = replace_fds(null_mut()); if fds.is_null() { - fds = core::arch::wasm32::memory_grow(0, 1) as *mut u8; + let grew = core::arch::wasm32::memory_grow(0, 1); + if grew == usize::MAX { + unreachable(); + } + fds = (grew * PAGE_SIZE) as *mut u8; } // We allocated a page; abort if that's not enough. - if fd as usize * size_of::() >= 65536 { + if fd as usize * size_of::() >= PAGE_SIZE { unreachable() } let result = &mut *fds.cast::().add(fd as usize); From 629231e93932cf4ed8de45cd076969936d4be5e8 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 10 Oct 2022 12:21:50 -0700 Subject: [PATCH 025/153] Remove an unneeded `register_buffer`. --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8c8117016790..261df14277b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,8 +501,6 @@ pub unsafe extern "C" fn fd_write( match Descriptor::get(fd) { Descriptor::File(file) => { - register_buffer(ptr as *mut _, len); - let result = file .fd .pwrite(slice::from_raw_parts(ptr, len), file.position); From b5175d35a5f969fa2f16fd5d7ba3dbb41b49ec3f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 18 Oct 2022 09:07:05 -0700 Subject: [PATCH 026/153] Convert to use `u32` indices instead of handles. This is a temporary experiment. --- src/lib.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 261df14277b2..0e367754b769 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,7 +174,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { match Descriptor::get(fd) { - Descriptor::File(file) => match file.fd.datasync() { + Descriptor::File(file) => match wasi_filesystem::datasync(file.fd) { Ok(()) => ERRNO_SUCCESS, Err(err) => errno_from_wasi_filesystem(err), }, @@ -189,7 +189,7 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { match Descriptor::get(fd) { Descriptor::File(file) => { - let info = match file.fd.info() { + let info = match wasi_filesystem::info(file.fd) { Ok(info) => info, Err(err) => return errno_from_wasi_filesystem(err), }; @@ -364,7 +364,7 @@ pub unsafe extern "C" fn fd_read( // We can cast between a `usize`-sized value and `usize`. let read_len = len as _; - let result = file.fd.pread(read_len, file.position); + let result = wasi_filesystem::pread(file.fd, read_len, file.position); match result { Ok(data) => { assert_eq!(data.as_ptr(), ptr); @@ -433,7 +433,7 @@ pub unsafe extern "C" fn fd_seek( WHENCE_END => wasi_filesystem::SeekFrom::End(offset as _), _ => return ERRNO_INVAL, }; - match file.fd.seek(from) { + match wasi_filesystem::seek(file.fd, from) { Ok(result) => { *newoffset = result; ERRNO_SUCCESS @@ -451,7 +451,7 @@ pub unsafe extern "C" fn fd_seek( #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { match Descriptor::get(fd) { - Descriptor::File(file) => match file.fd.sync() { + Descriptor::File(file) => match wasi_filesystem::sync(file.fd) { Ok(()) => ERRNO_SUCCESS, Err(err) => errno_from_wasi_filesystem(err), }, @@ -465,7 +465,7 @@ pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { match Descriptor::get(fd) { - Descriptor::File(file) => match file.fd.tell() { + Descriptor::File(file) => match wasi_filesystem::tell(file.fd) { Ok(result) => { *offset = result; ERRNO_SUCCESS @@ -501,9 +501,7 @@ pub unsafe extern "C" fn fd_write( match Descriptor::get(fd) { Descriptor::File(file) => { - let result = file - .fd - .pwrite(slice::from_raw_parts(ptr, len), file.position); + let result = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), file.position); match result { Ok(bytes) => { @@ -612,8 +610,6 @@ pub unsafe extern "C" fn path_readlink( buf_len: Size, bufused: *mut Size, ) -> Errno { - let fd = wasi_filesystem::Descriptor::from_raw(fd as _); - let path = slice::from_raw_parts(path_ptr, path_len); // If the user gave us a buffer shorter than `PATH_MAX`, it may not be @@ -625,7 +621,7 @@ pub unsafe extern "C" fn path_readlink( register_buffer(buf, buf_len); - let result = fd.readlink_at(path); + let result = wasi_filesystem::readlink_at(fd, path); let return_value = match &result { Ok(path) => { @@ -659,7 +655,7 @@ unsafe fn path_readlink_slow( register_buffer(buffer.as_mut_ptr().cast(), PATH_MAX); - let result = fd.readlink_at(path); + let result = wasi_filesystem::readlink_at(fd, path); let return_value = match &result { Ok(path) => { From 66f6c66431d8a47b3df2284e783d78a13750f11b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 24 Oct 2022 14:49:30 -0700 Subject: [PATCH 027/153] fd_info fixup --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e367754b769..49f199efc033 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,7 +189,7 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { match Descriptor::get(fd) { Descriptor::File(file) => { - let info = match wasi_filesystem::info(file.fd) { + let info = match wasi_filesystem::fd_info(file.fd) { Ok(info) => info, Err(err) => return errno_from_wasi_filesystem(err), }; @@ -501,7 +501,8 @@ pub unsafe extern "C" fn fd_write( match Descriptor::get(fd) { Descriptor::File(file) => { - let result = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), file.position); + let result = + wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), file.position); match result { Ok(bytes) => { From 5db1242d99005c14617d62a740527dd7f6527bc6 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 24 Oct 2022 15:05:14 -0700 Subject: [PATCH 028/153] switch to latest wit_bindgen_guest_rust macro --- src/lib.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 49f199efc033..acc8fbff0d03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,13 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented -wit_bindgen_guest_rust::import!({ - paths: [ - "wit/wasi-clocks.wit.md", - "wit/wasi-default-clocks.wit.md", - "wit/wasi-filesystem.wit.md", - "wit/wasi-logging.wit.md", - "wit/wasi-poll.wit.md", - "wit/wasi-random.wit.md" - ], +wit_bindgen_guest_rust::generate!({ + import: "wit/wasi-clocks.wit.md", + import: "wit/wasi-default-clocks.wit.md", + import: "wit/wasi-filesystem.wit.md", + import: "wit/wasi-logging.wit.md", + import: "wit/wasi-poll.wit.md", + import: "wit/wasi-random.wit.md", + name: "wasi", raw_strings, unchecked }); From 3b637e228aa521562ae55911eb8dc18b1ab86c9d Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 28 Oct 2022 14:14:14 -0700 Subject: [PATCH 029/153] notes for what to do next --- src/lib.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index acc8fbff0d03..a63b60613c0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ wit_bindgen_guest_rust::generate!({ import: "wit/wasi-logging.wit.md", import: "wit/wasi-poll.wit.md", import: "wit/wasi-random.wit.md", - name: "wasi", + default: "wit/command.wit.md", + name: "wasi_command", raw_strings, unchecked }); @@ -18,6 +19,32 @@ use core::ptr::{copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; +struct Export; +impl wasi_command::WasiCommand for Export { + fn command( + stdin: wasi_filesystem::Descriptor, + stdout: wasi_filesystem::Descriptor, /* TODO add the environment Vec<(String, String)> */ + /* TODO logging shouldnt be as ambient? */ + ) { + *Descriptor::get(0) = Descriptor::File(File { + fd: stdin, + position: 0, + }); + *Descriptor::get(1) = Descriptor::File(File { + fd: stdout, + position: 0, + }); + *Descriptor::get(2) = Descriptor::Log; + /* TODO we need to be able to import the adaptee module's _start func somehow: + * this is probably a special case in wit-component + #[no_mangle] + extern "C" fn _start(); + start() + */ + } +} +export_wasi_command!(Export); + /// The maximum path length. WASI doesn't explicitly guarantee this, but all /// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this /// polyfill. From 5abb9d380d22b6bc9ad744cb0609e8f660f53eda Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 7 Nov 2022 16:26:02 -0800 Subject: [PATCH 030/153] adapter: take args, have two cabi_{import,export}_realloc funcs, and call adaptee start --- src/lib.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a63b60613c0e..8693554f7828 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,8 @@ impl wasi_command::WasiCommand for Export { fn command( stdin: wasi_filesystem::Descriptor, stdout: wasi_filesystem::Descriptor, /* TODO add the environment Vec<(String, String)> */ - /* TODO logging shouldnt be as ambient? */ + /* TODO logging shouldnt be as ambient? */ + args: Vec>, ) { *Descriptor::get(0) = Descriptor::File(File { fd: stdin, @@ -35,12 +36,16 @@ impl wasi_command::WasiCommand for Export { position: 0, }); *Descriptor::get(2) = Descriptor::Log; - /* TODO we need to be able to import the adaptee module's _start func somehow: - * this is probably a special case in wit-component - #[no_mangle] - extern "C" fn _start(); - start() - */ + + // FIXME do something with the args. but definitely mem::forget them, + // because we dont want to Drop them - they arent allocated by the std allocator, + // they are allocated by the export_realloc + std::mem::forget(args); + #[link(wasm_import_module = "__main_module__")] + extern "C" { + fn _start(); + } + unsafe { _start() } } } export_wasi_command!(Export); @@ -82,7 +87,7 @@ unsafe fn register_buffer(buf: *mut u8, buf_len: usize) { } #[no_mangle] -pub unsafe extern "C" fn cabi_realloc( +pub unsafe extern "C" fn cabi_import_realloc( old_ptr: *mut u8, old_size: usize, _align: usize, @@ -102,6 +107,17 @@ pub unsafe extern "C" fn cabi_realloc( ptr } +#[no_mangle] +pub unsafe extern "C" fn cabi_export_realloc( + old_ptr: *mut u8, + old_size: usize, + _align: usize, + new_size: usize, +) -> *mut u8 { + // TODO implement a bump allocator in terms of memory.grow here + unreachable() +} + /// Read command-line argument data. /// The size of the array should match that returned by `args_sizes_get` #[no_mangle] From 2ab5b8cd38c8a6201790741b0daeeb973fe0171a Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 9 Nov 2022 15:17:46 -0800 Subject: [PATCH 031/153] skip codegen for command, and fill in the entrypoint manually --- src/lib.rs | 53 ++++++++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8693554f7828..3fb3eadecd1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,12 @@ wit_bindgen_guest_rust::generate!({ import: "wit/wasi-random.wit.md", default: "wit/command.wit.md", name: "wasi_command", + no_std, raw_strings, - unchecked + unchecked, + // The generated definition of command will pull in std, so we are defining it + // manually below instead + skip: ["command"], }); use core::arch::wasm32::unreachable; @@ -19,36 +23,24 @@ use core::ptr::{copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; -struct Export; -impl wasi_command::WasiCommand for Export { - fn command( - stdin: wasi_filesystem::Descriptor, - stdout: wasi_filesystem::Descriptor, /* TODO add the environment Vec<(String, String)> */ - /* TODO logging shouldnt be as ambient? */ - args: Vec>, - ) { - *Descriptor::get(0) = Descriptor::File(File { - fd: stdin, - position: 0, - }); - *Descriptor::get(1) = Descriptor::File(File { - fd: stdout, - position: 0, - }); - *Descriptor::get(2) = Descriptor::Log; - - // FIXME do something with the args. but definitely mem::forget them, - // because we dont want to Drop them - they arent allocated by the std allocator, - // they are allocated by the export_realloc - std::mem::forget(args); - #[link(wasm_import_module = "__main_module__")] - extern "C" { - fn _start(); - } - unsafe { _start() } +#[export_name = "command"] +unsafe extern "C" fn command_entrypoint(stdin: i32, stdout: i32, _args_ptr: i32, _args_len: i32) { + *Descriptor::get(0) = Descriptor::File(File { + fd: stdin as u32, + position: 0, + }); + *Descriptor::get(1) = Descriptor::File(File { + fd: stdout as u32, + position: 0, + }); + *Descriptor::get(2) = Descriptor::Log; + + #[link(wasm_import_module = "__main_module__")] + extern "C" { + fn _start(); } + _start() } -export_wasi_command!(Export); /// The maximum path length. WASI doesn't explicitly guarantee this, but all /// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this @@ -111,10 +103,9 @@ pub unsafe extern "C" fn cabi_import_realloc( pub unsafe extern "C" fn cabi_export_realloc( old_ptr: *mut u8, old_size: usize, - _align: usize, + align: usize, new_size: usize, ) -> *mut u8 { - // TODO implement a bump allocator in terms of memory.grow here unreachable() } From 58497fdafe7884e9b599ebc8c8dedc1297ca8b10 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 9 Nov 2022 16:37:26 -0800 Subject: [PATCH 032/153] cabi_export_realloc is another bad allocator for our bad allocator museum --- src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3fb3eadecd1a..80a0ed152e01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,17 @@ pub unsafe extern "C" fn cabi_export_realloc( align: usize, new_size: usize, ) -> *mut u8 { - unreachable() + if !old_ptr.is_null() { + unreachable(); + } + if new_size > PAGE_SIZE { + unreachable(); + } + let grew = core::arch::wasm32::memory_grow(0, 1); + if grew == usize::MAX { + unreachable(); + } + (grew * PAGE_SIZE) as *mut u8 } /// Read command-line argument data. From 4974e79fd74070f4db376650fb1ef92a1d1d2697 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 22 Nov 2022 15:20:09 -0800 Subject: [PATCH 033/153] Implement polyfills for several wasi-filesystem functions Implement fd_advise, fd_filestat_set_times, path_filestat_set_times, and path_remove_directory. --- src/lib.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 80a0ed152e01..a2466bf0b535 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,7 +195,23 @@ pub unsafe extern "C" fn fd_advise( len: Filesize, advice: Advice, ) -> Errno { - unreachable() + let advice = match advice { + ADVICE_NORMAL => wasi_filesystem::Advice::Normal, + ADVICE_SEQUENTIAL => wasi_filesystem::Advice::Sequential, + ADVICE_RANDOM => wasi_filesystem::Advice::Random, + ADVICE_WILLNEED => wasi_filesystem::Advice::WillNeed, + ADVICE_DONTNEED => wasi_filesystem::Advice::DontNeed, + ADVICE_NOREUSE => wasi_filesystem::Advice::NoReuse, + _ => return ERRNO_INVAL, + }; + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::fadvise(file.fd, offset, len, advice) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Force the allocation of space in a file. @@ -338,7 +354,30 @@ pub unsafe extern "C" fn fd_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - unreachable() + let atim = + if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { + wasi_filesystem::NewTimestamp::Now + } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { + wasi_filesystem::NewTimestamp::Timestamp(atim) + } else { + wasi_filesystem::NewTimestamp::NoChange + }; + let mtim = + if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { + wasi_filesystem::NewTimestamp::Now + } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { + wasi_filesystem::NewTimestamp::Timestamp(mtim) + } else { + wasi_filesystem::NewTimestamp::NoChange + }; + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::set_times(file.fd, atim, mtim) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Read from a file descriptor, without using and updating the file descriptor's offset. @@ -603,7 +642,36 @@ pub unsafe extern "C" fn path_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - unreachable() + let atim = + if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { + wasi_filesystem::NewTimestamp::Now + } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { + wasi_filesystem::NewTimestamp::Timestamp(atim) + } else { + wasi_filesystem::NewTimestamp::NoChange + }; + let mtim = + if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { + wasi_filesystem::NewTimestamp::Now + } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { + wasi_filesystem::NewTimestamp::Timestamp(mtim) + } else { + wasi_filesystem::NewTimestamp::NoChange + }; + + let path = slice::from_raw_parts(path_ptr, path_len); + let at_flags = at_flags_from_lookupflags(flags); + + match Descriptor::get(fd) { + Descriptor::File(file) => { + match wasi_filesystem::set_times_at(file.fd, at_flags, path, atim, mtim) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + } + } + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Create a hard link. @@ -732,7 +800,16 @@ pub unsafe extern "C" fn path_remove_directory( path_ptr: *const u8, path_len: usize, ) -> Errno { - unreachable() + let path = slice::from_raw_parts(path_ptr, path_len); + + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::remove_directory_at(file.fd, path) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Rename a file or directory. @@ -862,6 +939,14 @@ pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable() } +fn at_flags_from_lookupflags(flags: Lookupflags) -> wasi_filesystem::AtFlags { + if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW { + wasi_filesystem::AtFlags::SYMLINK_FOLLOW + } else { + wasi_filesystem::AtFlags::empty() + } +} + #[inline(never)] // Disable inlining as this is bulky and relatively cold. fn errno_from_wasi_filesystem(err: wasi_filesystem::Errno) -> Errno { match err { From 41b94d53694af2f4a8e16d594ecb23ccf200bdc4 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 22 Nov 2022 15:41:49 -0800 Subject: [PATCH 034/153] Implement fd_filestat_set_size, fd_pread, and fd_pwrite. --- src/lib.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a2466bf0b535..d9e7cadb3921 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -342,7 +342,14 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { /// Note: This is similar to `ftruncate` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::set_size(file.fd, size) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_SPIPE, + Descriptor::Closed => ERRNO_BADF, + } } /// Adjust the timestamps of an open file or directory. @@ -385,12 +392,44 @@ pub unsafe extern "C" fn fd_filestat_set_times( #[no_mangle] pub unsafe extern "C" fn fd_pread( fd: Fd, - iovs_ptr: *const Iovec, - iovs_len: usize, + mut iovs_ptr: *const Iovec, + mut iovs_len: usize, offset: Filesize, nread: *mut Size, ) -> Errno { - unreachable() + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nread = 0; + return ERRNO_SUCCESS; + } + + match Descriptor::get(fd) { + Descriptor::File(file) => { + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + + register_buffer(ptr, len); + + // We can cast between a `usize`-sized value and `usize`. + let read_len = len as _; + let result = wasi_filesystem::pread(file.fd, read_len, offset); + match result { + Ok(data) => { + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + *nread = data.len(); + forget(data); + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } + } + Descriptor::Closed | Descriptor::Log => ERRNO_BADF, + } } /// Return a description of the given preopened file descriptor. @@ -410,12 +449,45 @@ pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Si #[no_mangle] pub unsafe extern "C" fn fd_pwrite( fd: Fd, - iovs_ptr: *const Ciovec, - iovs_len: usize, + mut iovs_ptr: *const Ciovec, + mut iovs_len: usize, offset: Filesize, nwritten: *mut Size, ) -> Errno { - unreachable() + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nwritten = 0; + return ERRNO_SUCCESS; + } + + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + + match Descriptor::get(fd) { + Descriptor::File(file) => { + let result = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), offset); + + match result { + Ok(bytes) => { + *nwritten = bytes as usize; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + } + } + Descriptor::Log => { + let bytes = slice::from_raw_parts(ptr, len); + let context: [u8; 3] = [b'I', b'/', b'O']; + wasi_logging::log(wasi_logging::Level::Info, &context, bytes); + *nwritten = len; + ERRNO_SUCCESS + } + Descriptor::Closed => ERRNO_BADF, + } } /// Read from a file descriptor. From 4a102a2bf78b879b32dea65dbb9a5cfe2504bc79 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 23 Nov 2022 09:43:40 -0800 Subject: [PATCH 035/153] Implement path_create_directory, path_filestat_get, path_link. --- src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9e7cadb3921..efefd98fc660 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -686,7 +686,16 @@ pub unsafe extern "C" fn path_create_directory( path_ptr: *const u8, path_len: usize, ) -> Errno { - unreachable() + let path = slice::from_raw_parts(path_ptr, path_len); + + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::create_directory_at(file.fd, path) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Closed => ERRNO_BADF, + Descriptor::Log => ERRNO_NOTDIR, + } } /// Return the attributes of a file or directory. @@ -699,7 +708,42 @@ pub unsafe extern "C" fn path_filestat_get( path_len: usize, buf: *mut Filestat, ) -> Errno { - unreachable() + let path = slice::from_raw_parts(path_ptr, path_len); + let at_flags = at_flags_from_lookupflags(flags); + + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::stat_at(file.fd, at_flags, path) { + Ok(stat) => { + let filetype = match stat.type_ { + wasi_filesystem::Type::Unknown => FILETYPE_UNKNOWN, + wasi_filesystem::Type::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::Type::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::Type::RegularFile => FILETYPE_REGULAR_FILE, + // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and + // FILETYPE_SOCKET_DGRAM. + wasi_filesystem::Type::Socket => unreachable(), + wasi_filesystem::Type::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::Type::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + // preview1 never had a FIFO code. + wasi_filesystem::Type::Fifo => FILETYPE_UNKNOWN, + }; + *buf = Filestat { + dev: stat.dev, + ino: stat.ino, + filetype, + nlink: stat.nlink, + size: stat.size, + atim: stat.atim, + mtim: stat.mtim, + ctim: stat.ctim, + }; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Closed => ERRNO_BADF, + Descriptor::Log => ERRNO_NOTDIR, + } } /// Adjust the timestamps of a file or directory. @@ -741,8 +785,8 @@ pub unsafe extern "C" fn path_filestat_set_times( Err(err) => errno_from_wasi_filesystem(err), } } - Descriptor::Log => ERRNO_SPIPE, Descriptor::Closed => ERRNO_BADF, + Descriptor::Log => ERRNO_NOTDIR, } } @@ -758,7 +802,20 @@ pub unsafe extern "C" fn path_link( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - unreachable() + let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); + let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); + let at_flags = at_flags_from_lookupflags(old_flags); + + match (Descriptor::get(old_fd), Descriptor::get(new_fd)) { + (Descriptor::File(old_file), Descriptor::File(new_file)) => { + match wasi_filesystem::link_at(old_file.fd, at_flags, old_path, new_file.fd, new_path) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + } + } + (_, Descriptor::Closed) | (Descriptor::Closed, _) => ERRNO_BADF, + _ => ERRNO_NOTDIR, + } } /// Open a file or directory. @@ -879,8 +936,8 @@ pub unsafe extern "C" fn path_remove_directory( Ok(()) => ERRNO_SUCCESS, Err(err) => errno_from_wasi_filesystem(err), }, - Descriptor::Log => ERRNO_SPIPE, Descriptor::Closed => ERRNO_BADF, + Descriptor::Log => ERRNO_NOTDIR, } } From be17956d8bdd8b4ef5de5548cf91b49de9e80374 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 23 Nov 2022 10:00:54 -0800 Subject: [PATCH 036/153] Implement path_rename, path_symlink, and path_unlink_file. --- src/lib.rs | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index efefd98fc660..0b698df53a24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -945,14 +945,26 @@ pub unsafe extern "C" fn path_remove_directory( /// Note: This is similar to `renameat` in POSIX. #[no_mangle] pub unsafe extern "C" fn path_rename( - fd: Fd, + old_fd: Fd, old_path_ptr: *const u8, old_path_len: usize, new_fd: Fd, new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - unreachable() + let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); + let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); + + match (Descriptor::get(old_fd), Descriptor::get(new_fd)) { + (Descriptor::File(old_file), Descriptor::File(new_file)) => { + match wasi_filesystem::rename_at(old_file.fd, old_path, new_file.fd, new_path) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + } + } + (_, Descriptor::Closed) | (Descriptor::Closed, _) => ERRNO_BADF, + _ => ERRNO_NOTDIR, + } } /// Create a symbolic link. @@ -965,7 +977,17 @@ pub unsafe extern "C" fn path_symlink( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - unreachable() + let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); + let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); + + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::symlink_at(file.fd, old_path, new_path) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Closed => ERRNO_BADF, + _ => ERRNO_NOTDIR, + } } /// Unlink a file. @@ -973,7 +995,16 @@ pub unsafe extern "C" fn path_symlink( /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. #[no_mangle] pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { - unreachable() + let path = slice::from_raw_parts(path_ptr, path_len); + + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::unlink_file_at(file.fd, path) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Closed => ERRNO_BADF, + _ => ERRNO_NOTDIR, + } } /// Concurrently poll for the occurrence of a set of events. From 28cb8bd9325811f07dd992bb5de70f3e430fbd22 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 23 Nov 2022 11:05:49 -0800 Subject: [PATCH 037/153] Swap `info` and `fd-info` See the last commit in https://github.com/WebAssembly/wasi-filesystem/pull/66 for details. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0b698df53a24..0adf33a789de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,7 +248,7 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { match Descriptor::get(fd) { Descriptor::File(file) => { - let info = match wasi_filesystem::fd_info(file.fd) { + let info = match wasi_filesystem::info(file.fd) { Ok(info) => info, Err(err) => return errno_from_wasi_filesystem(err), }; From 87854bbb43a43891c0e4a8903f3174a9ca3cc813 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 23 Nov 2022 15:37:27 -0800 Subject: [PATCH 038/153] Implement `fd_fdstat_get` and `fd_fdstat_set_flags`. This incoporates several wasi-filesystem repo changes too. --- src/lib.rs | 114 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0adf33a789de..795ef89e17b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,43 +248,47 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { match Descriptor::get(fd) { Descriptor::File(file) => { - let info = match wasi_filesystem::info(file.fd) { + let flags = match wasi_filesystem::flags(file.fd) { + Ok(info) => info, + Err(err) => return errno_from_wasi_filesystem(err), + }; + let type_ = match wasi_filesystem::todo_type(file.fd) { Ok(info) => info, Err(err) => return errno_from_wasi_filesystem(err), }; - let fs_filetype = match info.type_ { - wasi_filesystem::Type::RegularFile => FILETYPE_REGULAR_FILE, - wasi_filesystem::Type::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::Type::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::Type::CharacterDevice => FILETYPE_CHARACTER_DEVICE, - wasi_filesystem::Type::Fifo => FILETYPE_UNKNOWN, - wasi_filesystem::Type::Socket => FILETYPE_SOCKET_STREAM, - wasi_filesystem::Type::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::Type::Unknown => FILETYPE_UNKNOWN, + let fs_filetype = match type_ { + wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, + wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, + wasi_filesystem::DescriptorType::Socket => FILETYPE_SOCKET_STREAM, + wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, }; let mut fs_flags = 0; let mut fs_rights_base = !0; - if !info.flags.contains(wasi_filesystem::Flags::READ) { + if !flags.contains(wasi_filesystem::DescriptorFlags::READ) { fs_rights_base &= !RIGHTS_FD_READ; } - if !info.flags.contains(wasi_filesystem::Flags::WRITE) { + if !flags.contains(wasi_filesystem::DescriptorFlags::WRITE) { fs_rights_base &= !RIGHTS_FD_WRITE; } - if info.flags.contains(wasi_filesystem::Flags::APPEND) { + if flags.contains(wasi_filesystem::DescriptorFlags::APPEND) { fs_flags |= FDFLAGS_APPEND; } - if info.flags.contains(wasi_filesystem::Flags::DSYNC) { + if flags.contains(wasi_filesystem::DescriptorFlags::DSYNC) { fs_flags |= FDFLAGS_DSYNC; } - if info.flags.contains(wasi_filesystem::Flags::NONBLOCK) { + if flags.contains(wasi_filesystem::DescriptorFlags::NONBLOCK) { fs_flags |= FDFLAGS_NONBLOCK; } - if info.flags.contains(wasi_filesystem::Flags::RSYNC) { + if flags.contains(wasi_filesystem::DescriptorFlags::RSYNC) { fs_flags |= FDFLAGS_RSYNC; } - if info.flags.contains(wasi_filesystem::Flags::SYNC) { + if flags.contains(wasi_filesystem::DescriptorFlags::SYNC) { fs_flags |= FDFLAGS_SYNC; } let fs_rights_inheriting = fs_rights_base; @@ -318,7 +322,31 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { - unreachable() + let mut new_flags = wasi_filesystem::DescriptorFlags::empty(); + if flags & FDFLAGS_APPEND == FDFLAGS_APPEND { + new_flags |= wasi_filesystem::DescriptorFlags::APPEND; + } + if flags & FDFLAGS_DSYNC == FDFLAGS_DSYNC { + new_flags |= wasi_filesystem::DescriptorFlags::DSYNC; + } + if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { + new_flags |= wasi_filesystem::DescriptorFlags::NONBLOCK; + } + if flags & FDFLAGS_RSYNC == FDFLAGS_RSYNC { + new_flags |= wasi_filesystem::DescriptorFlags::RSYNC; + } + if flags & FDFLAGS_SYNC == FDFLAGS_SYNC { + new_flags |= wasi_filesystem::DescriptorFlags::SYNC; + } + + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::set_flags(file.fd, new_flags) { + Ok(()) => ERRNO_SUCCESS, + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Log => ERRNO_INVAL, + Descriptor::Closed => ERRNO_BADF, + } } /// Adjust the rights associated with a file descriptor. @@ -335,7 +363,39 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( /// Return the attributes of an open file. #[no_mangle] pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { - unreachable() + match Descriptor::get(fd) { + Descriptor::File(file) => match wasi_filesystem::stat(file.fd) { + Ok(stat) => { + let filetype = match stat.type_ { + wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, + wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, + // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and + // FILETYPE_SOCKET_DGRAM. + wasi_filesystem::DescriptorType::Socket => unreachable(), + wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + // preview1 never had a FIFO code. + wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, + }; + *buf = Filestat { + dev: stat.dev, + ino: stat.ino, + filetype, + nlink: stat.nlink, + size: stat.size, + atim: stat.atim, + mtim: stat.mtim, + ctim: stat.ctim, + }; + ERRNO_SUCCESS + } + Err(err) => errno_from_wasi_filesystem(err), + }, + Descriptor::Closed => ERRNO_BADF, + Descriptor::Log => ERRNO_NOTDIR, + } } /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. @@ -715,17 +775,17 @@ pub unsafe extern "C" fn path_filestat_get( Descriptor::File(file) => match wasi_filesystem::stat_at(file.fd, at_flags, path) { Ok(stat) => { let filetype = match stat.type_ { - wasi_filesystem::Type::Unknown => FILETYPE_UNKNOWN, - wasi_filesystem::Type::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::Type::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::Type::RegularFile => FILETYPE_REGULAR_FILE, + wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, + wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::Type::Socket => unreachable(), - wasi_filesystem::Type::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::Type::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + wasi_filesystem::DescriptorType::Socket => unreachable(), + wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, // preview1 never had a FIFO code. - wasi_filesystem::Type::Fifo => FILETYPE_UNKNOWN, + wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, }; *buf = Filestat { dev: stat.dev, From acf9dc2ee99abff22739de778ce1ab04ac3454d5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 30 Nov 2022 13:44:22 -0600 Subject: [PATCH 039/153] Implement `clock_{time,res}_get` (#12) Currently traps for the `*_CPUTIME_*` clocks and doesn't check for overflow on nanoseconds. --- src/lib.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 795ef89e17b4..001c24526de2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,8 +171,21 @@ pub unsafe extern "C" fn environ_sizes_get( /// return `errno::inval`. /// Note: This is similar to `clock_getres` in POSIX. #[no_mangle] -pub unsafe extern "C" fn clock_res_get(id: Clockid, resolution: *mut Timestamp) -> Errno { - unreachable() +pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno { + match id { + CLOCKID_MONOTONIC => { + let res = wasi_clocks::monotonic_clock_resolution( + wasi_default_clocks::default_monotonic_clock(), + ); + *resolution = res; + } + CLOCKID_REALTIME => { + let res = wasi_clocks::wall_clock_resolution(wasi_default_clocks::default_wall_clock()); + *resolution = u64::from(res.nanoseconds) + res.seconds * 1_000_000_000; + } + _ => unreachable(), + } + ERRNO_SUCCESS } /// Return the time value of a clock. @@ -180,10 +193,21 @@ pub unsafe extern "C" fn clock_res_get(id: Clockid, resolution: *mut Timestamp) #[no_mangle] pub unsafe extern "C" fn clock_time_get( id: Clockid, - precision: Timestamp, - time: *mut Timestamp, + _precision: Timestamp, + time: &mut Timestamp, ) -> Errno { - unreachable() + match id { + CLOCKID_MONOTONIC => { + *time = + wasi_clocks::monotonic_clock_now(wasi_default_clocks::default_monotonic_clock()); + } + CLOCKID_REALTIME => { + let res = wasi_clocks::wall_clock_now(wasi_default_clocks::default_wall_clock()); + *time = u64::from(res.nanoseconds) + res.seconds * 1_000_000_000; + } + _ => unreachable(), + } + ERRNO_SUCCESS } /// Provide file advisory information on a file descriptor. From 567d941facf6a0f3afb0375b891b3b16ce0dc404 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 30 Nov 2022 18:03:09 -0600 Subject: [PATCH 040/153] Update Wasmtime/tooling dependencies (#14) This forces all the `*.wit.md` file to go into one large `*.wit` file for now which will get split up later once `use` is re-introduced. --- src/lib.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 001c24526de2..0818c544253b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,26 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented -wit_bindgen_guest_rust::generate!({ - import: "wit/wasi-clocks.wit.md", - import: "wit/wasi-default-clocks.wit.md", - import: "wit/wasi-filesystem.wit.md", - import: "wit/wasi-logging.wit.md", - import: "wit/wasi-poll.wit.md", - import: "wit/wasi-random.wit.md", - default: "wit/command.wit.md", - name: "wasi_command", - no_std, - raw_strings, - unchecked, - // The generated definition of command will pull in std, so we are defining it - // manually below instead - skip: ["command"], -}); - +use crate::bindings::{ + wasi_clocks, wasi_default_clocks, wasi_filesystem, wasi_logging, wasi_random, +}; use core::arch::wasm32::unreachable; use core::mem::{forget, size_of}; use core::ptr::{copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; +mod bindings { + wit_bindgen_guest_rust::generate!({ + path: "wit/wasi.wit", + no_std, + raw_strings, + unchecked, + // The generated definition of command will pull in std, so we are defining it + // manually below instead + skip: ["command"], + }); +} + #[export_name = "command"] unsafe extern "C" fn command_entrypoint(stdin: i32, stdout: i32, _args_ptr: i32, _args_len: i32) { *Descriptor::get(0) = Descriptor::File(File { From 2503102e91b2617a4d4402c2266afe8dfa208ba1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Dec 2022 14:19:52 -0600 Subject: [PATCH 041/153] Simplify global state management in adapter (#13) * Simplify global state management in adapter I was looking recently to implement args-related syscalls but that would require yet-more globals and yet-more state to be managed. Instead of adding a slew of new globals for this which are all manually kept in sync I opted to instead redesign how global state is managed in the adapter. The previous multiple `global`s are all removed in favor of just one, as sort of a "tls slot" of sorts. This one remaining slot points to a one-time-allocated `State` object which internally stores information like buffer metadata, fd information, etc. Along the way I've also simplified syscalls with new methods and `?`-using closures. * Turn off incremental for dev builds Helps with CGU splitting and ensuring that appropriate code is produced even without `--release`. * Review comments * Add accessors with specific errors * Update handling of `*_global_ptr` * Update internal mutability around path buffer Use an `UnsafeCell` layering to indicate that mutation may happen through `&T`. --- build.rs | 188 +++++------ src/lib.rs | 936 +++++++++++++++++++++++++---------------------------- 2 files changed, 530 insertions(+), 594 deletions(-) diff --git a/build.rs b/build.rs index bc39d5c2d566..0f3884b4bfe8 100644 --- a/build.rs +++ b/build.rs @@ -4,18 +4,8 @@ use std::path::PathBuf; fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let mut function_names = Vec::new(); - function_names.push("replace_realloc_global_ptr".to_owned()); - function_names.push("replace_realloc_global_len".to_owned()); - function_names.push("replace_fds".to_owned()); - - let mut global_names = Vec::new(); - global_names.push("internal_realloc_global_ptr".to_owned()); - global_names.push("internal_realloc_global_len".to_owned()); - global_names.push("internal_fds".to_owned()); - - let wasm = build_raw_intrinsics(&function_names, &global_names); - let archive = build_archive(&wasm, &function_names); + let wasm = build_raw_intrinsics(); + let archive = build_archive(&wasm); std::fs::write(out_dir.join("libwasm-raw-intrinsics.a"), &archive).unwrap(); println!("cargo:rustc-link-lib=static=wasm-raw-intrinsics"); @@ -31,27 +21,20 @@ fn main() { /// ```rust /// std::arch::global_asm!( /// " -/// .globaltype internal_realloc_global_ptr, i32 -/// internal_realloc_global_ptr: -/// .globaltype internal_realloc_global_len, i32 -/// internal_realloc_global_len: -/// .globaltype internal_fds, i32 -/// internal_fds: +/// .globaltype internal_global_ptr, i32 +/// internal_global_ptr: /// " /// ); /// /// #[no_mangle] -/// extern "C" fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8 { +/// extern "C" fn get_global_ptr() -> *mut u8 { /// unsafe { /// let ret: *mut u8; /// std::arch::asm!( /// " -/// global.get internal_realloc_global_ptr -/// local.get {} -/// global.set internal_realloc_global_ptr +/// global.get internal_global_ptr /// ", /// out(local) ret, -/// in(local) val, /// options(nostack, readonly) /// ); /// ret @@ -59,55 +42,48 @@ fn main() { /// } /// /// #[no_mangle] -/// extern "C" fn replace_realloc_global_len(val: usize) -> usize { +/// extern "C" fn set_global_ptr(val: *mut u8) { /// unsafe { -/// let ret: usize; /// std::arch::asm!( /// " -/// global.get internal_realloc_global_len /// local.get {} -/// global.set internal_realloc_global_len +/// global.set internal_global_ptr /// ", -/// out(local) ret, /// in(local) val, /// options(nostack, readonly) /// ); -/// ret /// } /// } /// ``` /// /// The main trickiness here is getting the `reloc.CODE` and `linking` sections /// right. -fn build_raw_intrinsics(function_names: &[String], global_names: &[String]) -> Vec { +fn build_raw_intrinsics() -> Vec { use wasm_encoder::Instruction::*; use wasm_encoder::*; let mut module = Module::new(); - // All our functions have the same type, i32 -> i32 let mut types = TypeSection::new(); - types.function([ValType::I32], [ValType::I32]); + types.function([], [ValType::I32]); + types.function([ValType::I32], []); module.section(&types); // Declare the functions, using the type we just added. let mut funcs = FunctionSection::new(); - for _ in function_names { - funcs.function(0); - } + funcs.function(0); + funcs.function(1); module.section(&funcs); // Declare the globals. let mut globals = GlobalSection::new(); - for _ in global_names { - globals.global( - GlobalType { - val_type: ValType::I32, - mutable: true, - }, - &ConstExpr::i32_const(0), - ); - } + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + }, + &ConstExpr::i32_const(0), + ); module.section(&globals); // Here the `code` section is defined. This is tricky because an offset is @@ -117,43 +93,44 @@ fn build_raw_intrinsics(function_names: &[String], global_names: &[String]) -> V // // First the function body is created and then it's appended into a code // section. - let mut body = Vec::new(); - 0u32.encode(&mut body); // no locals - let global_offset0 = body.len() + 1; - // global.get 0 ;; but with maximal encoding of the 0 - body.extend_from_slice(&[0x23, 0x80, 0x80, 0x80, 0x80, 0x00]); - LocalGet(0).encode(&mut body); - let global_offset1 = body.len() + 1; - // global.set 0 ;; but with maximal encoding of the 0 - body.extend_from_slice(&[0x24, 0x81, 0x80, 0x80, 0x80, 0x00]); - End.encode(&mut body); - - let mut body_offsets = Vec::new(); let mut code = Vec::new(); - function_names.len().encode(&mut code); - for _ in function_names { + 2u32.encode(&mut code); + + // get_global_ptr + let global_offset0 = { + let mut body = Vec::new(); + 0u32.encode(&mut body); // no locals + let global_offset = body.len() + 1; + // global.get 0 ;; but with maximal encoding of the 0 + body.extend_from_slice(&[0x23, 0x80, 0x80, 0x80, 0x80, 0x00]); + End.encode(&mut body); body.len().encode(&mut code); // length of the function - body_offsets.push(code.len()); + let offset = code.len() + global_offset; code.extend_from_slice(&body); // the function itself - } + offset + }; + + // set_global_ptr + let global_offset1 = { + let mut body = Vec::new(); + 0u32.encode(&mut body); // no locals + LocalGet(0).encode(&mut body); + let global_offset = body.len() + 1; + // global.set 0 ;; but with maximal encoding of the 0 + body.extend_from_slice(&[0x24, 0x80, 0x80, 0x80, 0x80, 0x00]); + End.encode(&mut body); + body.len().encode(&mut code); // length of the function + let offset = code.len() + global_offset; + code.extend_from_slice(&body); // the function itself + offset + }; + module.section(&RawSection { id: SectionId::Code as u8, data: &code, }); - // Calculate the relocation offsets within the `code` section itself by - // adding the start of where the body was placed to the offset within the - // body. - let global_offsets0 = body_offsets - .iter() - .map(|x| x + global_offset0) - .collect::>(); - let global_offsets1 = body_offsets - .iter() - .map(|x| x + global_offset1) - .collect::>(); - // Here the linking section is constructed. There are two symbols described // here, one for the function that we injected and one for the global // that was injected. The injected global here is referenced in the @@ -167,21 +144,22 @@ fn build_raw_intrinsics(function_names: &[String], global_names: &[String]) -> V linking.push(0x08); // `WASM_SYMBOL_TABLE` let mut subsection = Vec::new(); - 6u32.encode(&mut subsection); // 6 symbols (3 functions + 3 globals) + 3u32.encode(&mut subsection); // 3 symbols (2 functions + 1 global) + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 0u32.encode(&mut subsection); // function index + "get_global_ptr".encode(&mut subsection); // symbol name - for (index, name) in function_names.iter().enumerate() { - subsection.push(0x00); // SYMTAB_FUNCTION - 0x00.encode(&mut subsection); // flags - index.encode(&mut subsection); // function index - name.encode(&mut subsection); // symbol name - } + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 1u32.encode(&mut subsection); // function index + "set_global_ptr".encode(&mut subsection); // symbol name - for (index, name) in global_names.iter().enumerate() { - subsection.push(0x02); // SYMTAB_GLOBAL - 0x02.encode(&mut subsection); // flags - index.encode(&mut subsection); // global index - name.encode(&mut subsection); // symbol name - } + subsection.push(0x02); // SYMTAB_GLOBAL + 0x02.encode(&mut subsection); // flags + 0u32.encode(&mut subsection); // global index + "internal_global_ptr".encode(&mut subsection); // symbol name subsection.encode(&mut linking); module.section(&CustomSection { @@ -195,15 +173,15 @@ fn build_raw_intrinsics(function_names: &[String], global_names: &[String]) -> V { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 6u32.encode(&mut reloc); // 6 relocations - for index in 0..global_names.len() { - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offsets0[index as usize].encode(&mut reloc); // offset - (function_names.len() + index).encode(&mut reloc); // symbol index - reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offsets1[index as usize].encode(&mut reloc); // offset - (function_names.len() + index).encode(&mut reloc); // symbol index - } + 2u32.encode(&mut reloc); // 2 relocations + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offset0.encode(&mut reloc); // offset + 2u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + global_offset1.encode(&mut reloc); // offset + 2u32.encode(&mut reloc); // symbol index module.section(&CustomSection { name: "reloc.CODE", @@ -220,7 +198,7 @@ fn build_raw_intrinsics(function_names: &[String], global_names: &[String]) -> V /// /// Like above this is still tricky, mainly around the production of the symbol /// table. -fn build_archive(wasm: &[u8], function_names: &[String]) -> Vec { +fn build_archive(wasm: &[u8]) -> Vec { use object::{bytes_of, endian::BigEndian, U32Bytes}; let mut archive = Vec::new(); @@ -238,17 +216,11 @@ fn build_archive(wasm: &[u8], function_names: &[String]) -> Vec { // easier. Note though we don't know the offset of our `intrinsics.o` up // front so it's left as 0 for now and filled in later. let mut symbol_table = Vec::new(); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new( - BigEndian, - function_names.len().try_into().unwrap(), - ))); - for _ in function_names { - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); - } - for name in function_names { - symbol_table.extend_from_slice(name.as_bytes()); - symbol_table.push(0x00); - } + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 2))); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + symbol_table.extend_from_slice(b"get_global_ptr\0"); + symbol_table.extend_from_slice(b"set_global_ptr\0"); archive.extend_from_slice(bytes_of(&object::archive::Header { name: *b"/ ", @@ -277,6 +249,10 @@ fn build_archive(wasm: &[u8], function_names: &[String]) -> Vec { BigEndian, member_offset.try_into().unwrap(), ))); + archive[symtab_offset + 8..][..4].copy_from_slice(bytes_of(&U32Bytes::new( + BigEndian, + member_offset.try_into().unwrap(), + ))); archive.extend_from_slice(object::bytes_of(&object::archive::Header { name: *b"intrinsics.o ", diff --git a/src/lib.rs b/src/lib.rs index 0818c544253b..746b7450d03c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,9 @@ use crate::bindings::{ wasi_clocks, wasi_default_clocks, wasi_filesystem, wasi_logging, wasi_random, }; use core::arch::wasm32::unreachable; -use core::mem::{forget, size_of}; -use core::ptr::{copy_nonoverlapping, null_mut}; +use core::cell::{Cell, RefCell, UnsafeCell}; +use core::mem::{forget, size_of, MaybeUninit}; +use core::ptr::{self, copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; @@ -23,28 +24,26 @@ mod bindings { #[export_name = "command"] unsafe extern "C" fn command_entrypoint(stdin: i32, stdout: i32, _args_ptr: i32, _args_len: i32) { - *Descriptor::get(0) = Descriptor::File(File { - fd: stdin as u32, - position: 0, + State::with_mut(|state| { + state.push_desc(Descriptor::File(File { + fd: stdin as u32, + position: Cell::new(0), + }))?; + state.push_desc(Descriptor::File(File { + fd: stdout as u32, + position: Cell::new(0), + }))?; + state.push_desc(Descriptor::Log)?; + Ok(()) }); - *Descriptor::get(1) = Descriptor::File(File { - fd: stdout as u32, - position: 0, - }); - *Descriptor::get(2) = Descriptor::Log; #[link(wasm_import_module = "__main_module__")] extern "C" { fn _start(); } - _start() + _start(); } -/// The maximum path length. WASI doesn't explicitly guarantee this, but all -/// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this -/// polyfill. -const PATH_MAX: usize = 4096; - // We're avoiding static initializers, so replace the standard assert macros // with simpler implementations. macro_rules! assert { @@ -60,22 +59,6 @@ macro_rules! assert_eq { }; } -// These functions are defined by the object that the build.rs script produces. -extern "C" { - fn replace_realloc_global_ptr(val: *mut u8) -> *mut u8; - fn replace_realloc_global_len(val: usize) -> usize; - fn replace_fds(val: *mut u8) -> *mut u8; -} - -/// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy the -/// next request. -unsafe fn register_buffer(buf: *mut u8, buf_len: usize) { - let old_ptr = replace_realloc_global_ptr(buf); - assert!(old_ptr.is_null()); - let old_len = replace_realloc_global_len(buf_len); - assert_eq!(old_len, 0); -} - #[no_mangle] pub unsafe extern "C" fn cabi_import_realloc( old_ptr: *mut u8, @@ -86,14 +69,18 @@ pub unsafe extern "C" fn cabi_import_realloc( if !old_ptr.is_null() || old_size != 0 { unreachable(); } - let ptr = replace_realloc_global_ptr(null_mut()); - if ptr.is_null() { - unreachable(); - } - let len = replace_realloc_global_len(0); - if len < new_size { - unreachable(); - } + let mut ptr = null_mut::(); + State::with(|state| { + ptr = state.buffer_ptr.replace(null_mut()); + if ptr.is_null() { + unreachable(); + } + let len = state.buffer_len.replace(0); + if len < new_size { + unreachable(); + } + Ok(()) + }); ptr } @@ -226,14 +213,11 @@ pub unsafe extern "C" fn fd_advise( ADVICE_NOREUSE => wasi_filesystem::Advice::NoReuse, _ => return ERRNO_INVAL, }; - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::fadvise(file.fd, offset, len, advice) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_SPIPE, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_seekable_file(fd)?; + wasi_filesystem::fadvise(file.fd, offset, len, advice)?; + Ok(()) + }) } /// Force the allocation of space in a file. @@ -254,30 +238,21 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { /// Note: This is similar to `fdatasync` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::datasync(file.fd) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_INVAL, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_file(fd)?; + wasi_filesystem::datasync(file.fd)?; + Ok(()) + }) } /// Get the attributes of a file descriptor. /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { - match Descriptor::get(fd) { + State::with(|state| match state.get(fd)? { Descriptor::File(file) => { - let flags = match wasi_filesystem::flags(file.fd) { - Ok(info) => info, - Err(err) => return errno_from_wasi_filesystem(err), - }; - let type_ = match wasi_filesystem::todo_type(file.fd) { - Ok(info) => info, - Err(err) => return errno_from_wasi_filesystem(err), - }; + let flags = wasi_filesystem::flags(file.fd)?; + let type_ = wasi_filesystem::todo_type(file.fd)?; let fs_filetype = match type_ { wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, @@ -321,7 +296,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { fs_rights_base, fs_rights_inheriting, }); - ERRNO_SUCCESS + Ok(()) } Descriptor::Log => { let fs_filetype = FILETYPE_UNKNOWN; @@ -334,10 +309,10 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { fs_rights_base, fs_rights_inheriting, }); - ERRNO_SUCCESS + Ok(()) } - Descriptor::Closed => ERRNO_BADF, - } + Descriptor::Closed => Err(ERRNO_BADF), + }) } /// Adjust the flags associated with a file descriptor. @@ -361,14 +336,11 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { new_flags |= wasi_filesystem::DescriptorFlags::SYNC; } - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::set_flags(file.fd, new_flags) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_INVAL, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_file(fd)?; + wasi_filesystem::set_flags(file.fd, new_flags)?; + Ok(()) + }) } /// Adjust the rights associated with a file descriptor. @@ -385,53 +357,45 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( /// Return the attributes of an open file. #[no_mangle] pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::stat(file.fd) { - Ok(stat) => { - let filetype = match stat.type_ { - wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, - wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, - // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and - // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::DescriptorType::Socket => unreachable(), - wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, - // preview1 never had a FIFO code. - wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, - }; - *buf = Filestat { - dev: stat.dev, - ino: stat.ino, - filetype, - nlink: stat.nlink, - size: stat.size, - atim: stat.atim, - mtim: stat.mtim, - ctim: stat.ctim, - }; - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Closed => ERRNO_BADF, - Descriptor::Log => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_file(fd)?; + let stat = wasi_filesystem::stat(file.fd)?; + let filetype = match stat.type_ { + wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, + wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, + // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and + // FILETYPE_SOCKET_DGRAM. + wasi_filesystem::DescriptorType::Socket => unreachable(), + wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + // preview1 never had a FIFO code. + wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, + }; + *buf = Filestat { + dev: stat.dev, + ino: stat.ino, + filetype, + nlink: stat.nlink, + size: stat.size, + atim: stat.atim, + mtim: stat.mtim, + ctim: stat.ctim, + }; + Ok(()) + }) } /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. /// Note: This is similar to `ftruncate` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::set_size(file.fd, size) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_SPIPE, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_file(fd)?; + wasi_filesystem::set_size(file.fd, size)?; + Ok(()) + }) } /// Adjust the timestamps of an open file or directory. @@ -459,14 +423,12 @@ pub unsafe extern "C" fn fd_filestat_set_times( } else { wasi_filesystem::NewTimestamp::NoChange }; - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::set_times(file.fd, atim, mtim) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_SPIPE, - Descriptor::Closed => ERRNO_BADF, - } + + State::with(|state| { + let file = state.get_file(fd)?; + wasi_filesystem::set_times(file.fd, atim, mtim)?; + Ok(()) + }) } /// Read from a file descriptor, without using and updating the file descriptor's offset. @@ -489,29 +451,20 @@ pub unsafe extern "C" fn fd_pread( return ERRNO_SUCCESS; } - match Descriptor::get(fd) { - Descriptor::File(file) => { - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; - - register_buffer(ptr, len); - - // We can cast between a `usize`-sized value and `usize`. - let read_len = len as _; - let result = wasi_filesystem::pread(file.fd, read_len, offset); - match result { - Ok(data) => { - assert_eq!(data.as_ptr(), ptr); - assert!(data.len() <= len); - *nread = data.len(); - forget(data); - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - } - } - Descriptor::Closed | Descriptor::Log => ERRNO_BADF, - } + State::with(|state| { + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + state.register_buffer(ptr, len); + + let read_len = u32::try_from(len).unwrap(); + let file = state.get_file(fd)?; + let data = wasi_filesystem::pread(file.fd, read_len, offset)?; + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + *nread = data.len(); + forget(data); + Ok(()) + }) } /// Return a description of the given preopened file descriptor. @@ -549,27 +502,22 @@ pub unsafe extern "C" fn fd_pwrite( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - match Descriptor::get(fd) { + State::with(|state| match state.get(fd)? { Descriptor::File(file) => { - let result = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), offset); - - match result { - Ok(bytes) => { - *nwritten = bytes as usize; - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - } + let bytes = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), offset)?; + + *nwritten = bytes as usize; + Ok(()) } Descriptor::Log => { let bytes = slice::from_raw_parts(ptr, len); let context: [u8; 3] = [b'I', b'/', b'O']; wasi_logging::log(wasi_logging::Level::Info, &context, bytes); *nwritten = len; - ERRNO_SUCCESS + Ok(()) } - Descriptor::Closed => ERRNO_BADF, - } + Descriptor::Closed => Err(ERRNO_BADF), + }) } /// Read from a file descriptor. @@ -591,30 +539,22 @@ pub unsafe extern "C" fn fd_read( return ERRNO_SUCCESS; } - match Descriptor::get(fd) { - Descriptor::File(file) => { - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; - - register_buffer(ptr, len); - - // We can cast between a `usize`-sized value and `usize`. - let read_len = len as _; - let result = wasi_filesystem::pread(file.fd, read_len, file.position); - match result { - Ok(data) => { - assert_eq!(data.as_ptr(), ptr); - assert!(data.len() <= len); - *nread = data.len(); - file.position += data.len() as u64; - forget(data); - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - } - } - Descriptor::Closed | Descriptor::Log => ERRNO_BADF, - } + State::with(|state| { + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + + state.register_buffer(ptr, len); + + let read_len = u32::try_from(len).unwrap(); + let file = state.get_file(fd)?; + let data = wasi_filesystem::pread(file.fd, read_len, file.position.get())?; + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + *nread = data.len(); + file.position.set(file.position.get() + data.len() as u64); + forget(data); + Ok(()) + }) } /// Read directory entries from a directory. @@ -659,58 +599,42 @@ pub unsafe extern "C" fn fd_seek( whence: Whence, newoffset: *mut Filesize, ) -> Errno { - match Descriptor::get(fd) { - Descriptor::File(file) => { - // It's ok to cast these indices; the WASI API will fail if - // the resulting values are out of range. - let from = match whence { - WHENCE_SET => wasi_filesystem::SeekFrom::Set(offset as _), - WHENCE_CUR => wasi_filesystem::SeekFrom::Cur(offset), - WHENCE_END => wasi_filesystem::SeekFrom::End(offset as _), - _ => return ERRNO_INVAL, - }; - match wasi_filesystem::seek(file.fd, from) { - Ok(result) => { - *newoffset = result; - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - } - } - Descriptor::Log => ERRNO_SPIPE, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_seekable_file(fd)?; + // It's ok to cast these indices; the WASI API will fail if + // the resulting values are out of range. + let from = match whence { + WHENCE_SET => wasi_filesystem::SeekFrom::Set(offset as _), + WHENCE_CUR => wasi_filesystem::SeekFrom::Cur(offset), + WHENCE_END => wasi_filesystem::SeekFrom::End(offset as _), + _ => return Err(ERRNO_INVAL), + }; + let result = wasi_filesystem::seek(file.fd, from)?; + *newoffset = result; + Ok(()) + }) } /// Synchronize the data and metadata of a file to disk. /// Note: This is similar to `fsync` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::sync(file.fd) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_INVAL, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_file(fd)?; + wasi_filesystem::sync(file.fd)?; + Ok(()) + }) } /// Return the current offset of a file descriptor. /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::tell(file.fd) { - Ok(result) => { - *offset = result; - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Log => ERRNO_SPIPE, - Descriptor::Closed => ERRNO_BADF, - } + State::with(|state| { + let file = state.get_seekable_file(fd)?; + *offset = wasi_filesystem::tell(file.fd)?; + Ok(()) + }) } /// Write to a file descriptor. @@ -735,29 +659,27 @@ pub unsafe extern "C" fn fd_write( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - match Descriptor::get(fd) { + State::with(|state| match state.get(fd)? { Descriptor::File(file) => { - let result = - wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), file.position); - - match result { - Ok(bytes) => { - *nwritten = bytes as usize; - file.position += bytes as u64; - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - } + let bytes = wasi_filesystem::pwrite( + file.fd, + slice::from_raw_parts(ptr, len), + file.position.get(), + )?; + + *nwritten = bytes as usize; + file.position.set(file.position.get() + u64::from(bytes)); + Ok(()) } Descriptor::Log => { let bytes = slice::from_raw_parts(ptr, len); let context: [u8; 3] = [b'I', b'/', b'O']; wasi_logging::log(wasi_logging::Level::Info, &context, bytes); *nwritten = len; - ERRNO_SUCCESS + Ok(()) } - Descriptor::Closed => ERRNO_BADF, - } + Descriptor::Closed => Err(ERRNO_BADF), + }) } /// Create a directory. @@ -770,14 +692,11 @@ pub unsafe extern "C" fn path_create_directory( ) -> Errno { let path = slice::from_raw_parts(path_ptr, path_len); - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::create_directory_at(file.fd, path) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Closed => ERRNO_BADF, - Descriptor::Log => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_dir(fd)?; + wasi_filesystem::create_directory_at(file.fd, path)?; + Ok(()) + }) } /// Return the attributes of a file or directory. @@ -793,39 +712,34 @@ pub unsafe extern "C" fn path_filestat_get( let path = slice::from_raw_parts(path_ptr, path_len); let at_flags = at_flags_from_lookupflags(flags); - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::stat_at(file.fd, at_flags, path) { - Ok(stat) => { - let filetype = match stat.type_ { - wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, - wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, - // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and - // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::DescriptorType::Socket => unreachable(), - wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, - // preview1 never had a FIFO code. - wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, - }; - *buf = Filestat { - dev: stat.dev, - ino: stat.ino, - filetype, - nlink: stat.nlink, - size: stat.size, - atim: stat.atim, - mtim: stat.mtim, - ctim: stat.ctim, - }; - ERRNO_SUCCESS - } - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Closed => ERRNO_BADF, - Descriptor::Log => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_dir(fd)?; + let stat = wasi_filesystem::stat_at(file.fd, at_flags, path)?; + let filetype = match stat.type_ { + wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, + wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, + // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and + // FILETYPE_SOCKET_DGRAM. + wasi_filesystem::DescriptorType::Socket => unreachable(), + wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + // preview1 never had a FIFO code. + wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, + }; + *buf = Filestat { + dev: stat.dev, + ino: stat.ino, + filetype, + nlink: stat.nlink, + size: stat.size, + atim: stat.atim, + mtim: stat.mtim, + ctim: stat.ctim, + }; + Ok(()) + }) } /// Adjust the timestamps of a file or directory. @@ -860,16 +774,11 @@ pub unsafe extern "C" fn path_filestat_set_times( let path = slice::from_raw_parts(path_ptr, path_len); let at_flags = at_flags_from_lookupflags(flags); - match Descriptor::get(fd) { - Descriptor::File(file) => { - match wasi_filesystem::set_times_at(file.fd, at_flags, path, atim, mtim) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - } - } - Descriptor::Closed => ERRNO_BADF, - Descriptor::Log => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_dir(fd)?; + wasi_filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; + Ok(()) + }) } /// Create a hard link. @@ -888,16 +797,12 @@ pub unsafe extern "C" fn path_link( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); let at_flags = at_flags_from_lookupflags(old_flags); - match (Descriptor::get(old_fd), Descriptor::get(new_fd)) { - (Descriptor::File(old_file), Descriptor::File(new_file)) => { - match wasi_filesystem::link_at(old_file.fd, at_flags, old_path, new_file.fd, new_path) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - } - } - (_, Descriptor::Closed) | (Descriptor::Closed, _) => ERRNO_BADF, - _ => ERRNO_NOTDIR, - } + State::with(|state| { + let old = state.get_dir(old_fd)?.fd; + let new = state.get_dir(new_fd)?.fd; + wasi_filesystem::link_at(old, at_flags, old_path, new, new_path)?; + Ok(()) + }) } /// Open a file or directory. @@ -935,71 +840,38 @@ pub unsafe extern "C" fn path_readlink( ) -> Errno { let path = slice::from_raw_parts(path_ptr, path_len); - // If the user gave us a buffer shorter than `PATH_MAX`, it may not be - // long enough to accept the actual path. `cabi_realloc` can't fail, - // so instead we handle this case specially. - if buf_len < PATH_MAX { - return path_readlink_slow(fd, path, buf, buf_len, bufused); - } - - register_buffer(buf, buf_len); - - let result = wasi_filesystem::readlink_at(fd, path); - - let return_value = match &result { - Ok(path) => { - assert_eq!(path.as_ptr(), buf); - assert!(path.len() <= buf_len); + State::with(|state| { + // If the user gave us a buffer shorter than `PATH_MAX`, it may not be + // long enough to accept the actual path. `cabi_realloc` can't fail, + // so instead we handle this case specially. + let use_state_buf = buf_len < PATH_MAX; - *bufused = path.len(); - ERRNO_SUCCESS + if use_state_buf { + state.register_buffer(state.path_buf.get().cast(), PATH_MAX); + } else { + state.register_buffer(buf, buf_len); } - Err(err) => errno_from_wasi_filesystem(*err), - }; - - // The returned string's memory was allocated in `buf`, so don't separately - // free it. - forget(result); - - return_value -} -/// Slow-path for `path_readlink` that allocates a buffer on the stack to -/// ensure that it has a big enough buffer. -#[inline(never)] // Disable inlining as this has a large stack buffer. -unsafe fn path_readlink_slow( - fd: wasi_filesystem::Descriptor, - path: &[u8], - buf: *mut u8, - buf_len: Size, - bufused: *mut Size, -) -> Errno { - let mut buffer = core::mem::MaybeUninit::<[u8; PATH_MAX]>::uninit(); - - register_buffer(buffer.as_mut_ptr().cast(), PATH_MAX); + let file = state.get_dir(fd)?; + let path = wasi_filesystem::readlink_at(file.fd, path)?; - let result = wasi_filesystem::readlink_at(fd, path); + assert_eq!(path.as_ptr(), buf); + assert!(path.len() <= buf_len); - let return_value = match &result { - Ok(path) => { - assert_eq!(path.as_ptr(), buffer.as_ptr().cast()); - assert!(path.len() <= PATH_MAX); - - // Preview1 follows POSIX in truncating the returned path if - // it doesn't fit. + *bufused = path.len(); + if use_state_buf { + // Preview1 follows POSIX in truncating the returned path if it + // doesn't fit. let len = core::cmp::min(path.len(), buf_len); - copy_nonoverlapping(buffer.as_ptr().cast(), buf, len); - *bufused = len; - ERRNO_SUCCESS + copy_nonoverlapping(path.as_ptr().cast(), buf, len); } - Err(err) => errno_from_wasi_filesystem(*err), - }; - // The returned string's memory was allocated in `buf`, so don't separately - // free it. - forget(result); + // The returned string's memory was allocated in `buf`, so don't separately + // free it. + forget(path); - return_value + Ok(()) + }) } /// Remove a directory. @@ -1013,14 +885,11 @@ pub unsafe extern "C" fn path_remove_directory( ) -> Errno { let path = slice::from_raw_parts(path_ptr, path_len); - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::remove_directory_at(file.fd, path) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Closed => ERRNO_BADF, - Descriptor::Log => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_dir(fd)?; + wasi_filesystem::remove_directory_at(file.fd, path)?; + Ok(()) + }) } /// Rename a file or directory. @@ -1037,16 +906,12 @@ pub unsafe extern "C" fn path_rename( let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); - match (Descriptor::get(old_fd), Descriptor::get(new_fd)) { - (Descriptor::File(old_file), Descriptor::File(new_file)) => { - match wasi_filesystem::rename_at(old_file.fd, old_path, new_file.fd, new_path) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - } - } - (_, Descriptor::Closed) | (Descriptor::Closed, _) => ERRNO_BADF, - _ => ERRNO_NOTDIR, - } + State::with(|state| { + let old = state.get_dir(old_fd)?.fd; + let new = state.get_dir(new_fd)?.fd; + wasi_filesystem::rename_at(old, old_path, new, new_path)?; + Ok(()) + }) } /// Create a symbolic link. @@ -1062,14 +927,11 @@ pub unsafe extern "C" fn path_symlink( let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::symlink_at(file.fd, old_path, new_path) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Closed => ERRNO_BADF, - _ => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_dir(fd)?; + wasi_filesystem::symlink_at(file.fd, old_path, new_path)?; + Ok(()) + }) } /// Unlink a file. @@ -1079,14 +941,11 @@ pub unsafe extern "C" fn path_symlink( pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { let path = slice::from_raw_parts(path_ptr, path_len); - match Descriptor::get(fd) { - Descriptor::File(file) => match wasi_filesystem::unlink_file_at(file.fd, path) { - Ok(()) => ERRNO_SUCCESS, - Err(err) => errno_from_wasi_filesystem(err), - }, - Descriptor::Closed => ERRNO_BADF, - _ => ERRNO_NOTDIR, - } + State::with(|state| { + let file = state.get_dir(fd)?; + wasi_filesystem::unlink_file_at(file.fd, path)?; + Ok(()) + }) } /// Concurrently poll for the occurrence of a set of events. @@ -1132,17 +991,19 @@ pub unsafe extern "C" fn sched_yield() -> Errno { /// number generator, rather than to provide the random data directly. #[no_mangle] pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { - register_buffer(buf, buf_len); + State::with(|state| { + state.register_buffer(buf, buf_len); - assert_eq!(buf_len as u32 as Size, buf_len); - let result = wasi_random::getrandom(buf_len as u32); - assert_eq!(result.as_ptr(), buf); + assert_eq!(buf_len as u32 as Size, buf_len); + let result = wasi_random::getrandom(buf_len as u32); + assert_eq!(result.as_ptr(), buf); - // The returned buffer's memory was allocated in `buf`, so don't separately - // free it. - forget(result); + // The returned buffer's memory was allocated in `buf`, so don't separately + // free it. + forget(result); - ERRNO_SUCCESS + Ok(()) + }) } /// Receive a message from a socket. @@ -1189,77 +1050,79 @@ fn at_flags_from_lookupflags(flags: Lookupflags) -> wasi_filesystem::AtFlags { } } -#[inline(never)] // Disable inlining as this is bulky and relatively cold. -fn errno_from_wasi_filesystem(err: wasi_filesystem::Errno) -> Errno { - match err { - wasi_filesystem::Errno::Toobig => black_box(ERRNO_2BIG), - wasi_filesystem::Errno::Access => ERRNO_ACCES, - wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, - wasi_filesystem::Errno::Addrnotavail => ERRNO_ADDRNOTAVAIL, - wasi_filesystem::Errno::Afnosupport => ERRNO_AFNOSUPPORT, - wasi_filesystem::Errno::Again => ERRNO_AGAIN, - wasi_filesystem::Errno::Already => ERRNO_ALREADY, - wasi_filesystem::Errno::Badmsg => ERRNO_BADMSG, - wasi_filesystem::Errno::Busy => ERRNO_BUSY, - wasi_filesystem::Errno::Canceled => ERRNO_CANCELED, - wasi_filesystem::Errno::Child => ERRNO_CHILD, - wasi_filesystem::Errno::Connaborted => ERRNO_CONNABORTED, - wasi_filesystem::Errno::Connrefused => ERRNO_CONNREFUSED, - wasi_filesystem::Errno::Connreset => ERRNO_CONNRESET, - wasi_filesystem::Errno::Deadlk => ERRNO_DEADLK, - wasi_filesystem::Errno::Destaddrreq => ERRNO_DESTADDRREQ, - wasi_filesystem::Errno::Dquot => ERRNO_DQUOT, - wasi_filesystem::Errno::Exist => ERRNO_EXIST, - wasi_filesystem::Errno::Fault => ERRNO_FAULT, - wasi_filesystem::Errno::Fbig => ERRNO_FBIG, - wasi_filesystem::Errno::Hostunreach => ERRNO_HOSTUNREACH, - wasi_filesystem::Errno::Idrm => ERRNO_IDRM, - wasi_filesystem::Errno::Ilseq => ERRNO_ILSEQ, - wasi_filesystem::Errno::Inprogress => ERRNO_INPROGRESS, - wasi_filesystem::Errno::Intr => ERRNO_INTR, - wasi_filesystem::Errno::Inval => ERRNO_INVAL, - wasi_filesystem::Errno::Io => ERRNO_IO, - wasi_filesystem::Errno::Isconn => ERRNO_ISCONN, - wasi_filesystem::Errno::Isdir => ERRNO_ISDIR, - wasi_filesystem::Errno::Loop => ERRNO_LOOP, - wasi_filesystem::Errno::Mfile => ERRNO_MFILE, - wasi_filesystem::Errno::Mlink => ERRNO_MLINK, - wasi_filesystem::Errno::Msgsize => ERRNO_MSGSIZE, - wasi_filesystem::Errno::Multihop => ERRNO_MULTIHOP, - wasi_filesystem::Errno::Nametoolong => ERRNO_NAMETOOLONG, - wasi_filesystem::Errno::Netdown => ERRNO_NETDOWN, - wasi_filesystem::Errno::Netreset => ERRNO_NETRESET, - wasi_filesystem::Errno::Netunreach => ERRNO_NETUNREACH, - wasi_filesystem::Errno::Nfile => ERRNO_NFILE, - wasi_filesystem::Errno::Nobufs => ERRNO_NOBUFS, - wasi_filesystem::Errno::Nodev => ERRNO_NODEV, - wasi_filesystem::Errno::Noent => ERRNO_NOENT, - wasi_filesystem::Errno::Noexec => ERRNO_NOEXEC, - wasi_filesystem::Errno::Nolck => ERRNO_NOLCK, - wasi_filesystem::Errno::Nolink => ERRNO_NOLINK, - wasi_filesystem::Errno::Nomem => ERRNO_NOMEM, - wasi_filesystem::Errno::Nomsg => ERRNO_NOMSG, - wasi_filesystem::Errno::Noprotoopt => ERRNO_NOPROTOOPT, - wasi_filesystem::Errno::Nospc => ERRNO_NOSPC, - wasi_filesystem::Errno::Nosys => ERRNO_NOSYS, - wasi_filesystem::Errno::Notdir => ERRNO_NOTDIR, - wasi_filesystem::Errno::Notempty => ERRNO_NOTEMPTY, - wasi_filesystem::Errno::Notrecoverable => ERRNO_NOTRECOVERABLE, - wasi_filesystem::Errno::Notsup => ERRNO_NOTSUP, - wasi_filesystem::Errno::Notty => ERRNO_NOTTY, - wasi_filesystem::Errno::Nxio => ERRNO_NXIO, - wasi_filesystem::Errno::Overflow => ERRNO_OVERFLOW, - wasi_filesystem::Errno::Ownerdead => ERRNO_OWNERDEAD, - wasi_filesystem::Errno::Perm => ERRNO_PERM, - wasi_filesystem::Errno::Pipe => ERRNO_PIPE, - wasi_filesystem::Errno::Range => ERRNO_RANGE, - wasi_filesystem::Errno::Rofs => ERRNO_ROFS, - wasi_filesystem::Errno::Spipe => ERRNO_SPIPE, - wasi_filesystem::Errno::Srch => ERRNO_SRCH, - wasi_filesystem::Errno::Stale => ERRNO_STALE, - wasi_filesystem::Errno::Timedout => ERRNO_TIMEDOUT, - wasi_filesystem::Errno::Txtbsy => ERRNO_TXTBSY, - wasi_filesystem::Errno::Xdev => ERRNO_XDEV, +impl From for Errno { + #[inline(never)] // Disable inlining as this is bulky and relatively cold. + fn from(err: wasi_filesystem::Errno) -> Errno { + match err { + wasi_filesystem::Errno::Toobig => black_box(ERRNO_2BIG), + wasi_filesystem::Errno::Access => ERRNO_ACCES, + wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, + wasi_filesystem::Errno::Addrnotavail => ERRNO_ADDRNOTAVAIL, + wasi_filesystem::Errno::Afnosupport => ERRNO_AFNOSUPPORT, + wasi_filesystem::Errno::Again => ERRNO_AGAIN, + wasi_filesystem::Errno::Already => ERRNO_ALREADY, + wasi_filesystem::Errno::Badmsg => ERRNO_BADMSG, + wasi_filesystem::Errno::Busy => ERRNO_BUSY, + wasi_filesystem::Errno::Canceled => ERRNO_CANCELED, + wasi_filesystem::Errno::Child => ERRNO_CHILD, + wasi_filesystem::Errno::Connaborted => ERRNO_CONNABORTED, + wasi_filesystem::Errno::Connrefused => ERRNO_CONNREFUSED, + wasi_filesystem::Errno::Connreset => ERRNO_CONNRESET, + wasi_filesystem::Errno::Deadlk => ERRNO_DEADLK, + wasi_filesystem::Errno::Destaddrreq => ERRNO_DESTADDRREQ, + wasi_filesystem::Errno::Dquot => ERRNO_DQUOT, + wasi_filesystem::Errno::Exist => ERRNO_EXIST, + wasi_filesystem::Errno::Fault => ERRNO_FAULT, + wasi_filesystem::Errno::Fbig => ERRNO_FBIG, + wasi_filesystem::Errno::Hostunreach => ERRNO_HOSTUNREACH, + wasi_filesystem::Errno::Idrm => ERRNO_IDRM, + wasi_filesystem::Errno::Ilseq => ERRNO_ILSEQ, + wasi_filesystem::Errno::Inprogress => ERRNO_INPROGRESS, + wasi_filesystem::Errno::Intr => ERRNO_INTR, + wasi_filesystem::Errno::Inval => ERRNO_INVAL, + wasi_filesystem::Errno::Io => ERRNO_IO, + wasi_filesystem::Errno::Isconn => ERRNO_ISCONN, + wasi_filesystem::Errno::Isdir => ERRNO_ISDIR, + wasi_filesystem::Errno::Loop => ERRNO_LOOP, + wasi_filesystem::Errno::Mfile => ERRNO_MFILE, + wasi_filesystem::Errno::Mlink => ERRNO_MLINK, + wasi_filesystem::Errno::Msgsize => ERRNO_MSGSIZE, + wasi_filesystem::Errno::Multihop => ERRNO_MULTIHOP, + wasi_filesystem::Errno::Nametoolong => ERRNO_NAMETOOLONG, + wasi_filesystem::Errno::Netdown => ERRNO_NETDOWN, + wasi_filesystem::Errno::Netreset => ERRNO_NETRESET, + wasi_filesystem::Errno::Netunreach => ERRNO_NETUNREACH, + wasi_filesystem::Errno::Nfile => ERRNO_NFILE, + wasi_filesystem::Errno::Nobufs => ERRNO_NOBUFS, + wasi_filesystem::Errno::Nodev => ERRNO_NODEV, + wasi_filesystem::Errno::Noent => ERRNO_NOENT, + wasi_filesystem::Errno::Noexec => ERRNO_NOEXEC, + wasi_filesystem::Errno::Nolck => ERRNO_NOLCK, + wasi_filesystem::Errno::Nolink => ERRNO_NOLINK, + wasi_filesystem::Errno::Nomem => ERRNO_NOMEM, + wasi_filesystem::Errno::Nomsg => ERRNO_NOMSG, + wasi_filesystem::Errno::Noprotoopt => ERRNO_NOPROTOOPT, + wasi_filesystem::Errno::Nospc => ERRNO_NOSPC, + wasi_filesystem::Errno::Nosys => ERRNO_NOSYS, + wasi_filesystem::Errno::Notdir => ERRNO_NOTDIR, + wasi_filesystem::Errno::Notempty => ERRNO_NOTEMPTY, + wasi_filesystem::Errno::Notrecoverable => ERRNO_NOTRECOVERABLE, + wasi_filesystem::Errno::Notsup => ERRNO_NOTSUP, + wasi_filesystem::Errno::Notty => ERRNO_NOTTY, + wasi_filesystem::Errno::Nxio => ERRNO_NXIO, + wasi_filesystem::Errno::Overflow => ERRNO_OVERFLOW, + wasi_filesystem::Errno::Ownerdead => ERRNO_OWNERDEAD, + wasi_filesystem::Errno::Perm => ERRNO_PERM, + wasi_filesystem::Errno::Pipe => ERRNO_PIPE, + wasi_filesystem::Errno::Range => ERRNO_RANGE, + wasi_filesystem::Errno::Rofs => ERRNO_ROFS, + wasi_filesystem::Errno::Spipe => ERRNO_SPIPE, + wasi_filesystem::Errno::Srch => ERRNO_SRCH, + wasi_filesystem::Errno::Stale => ERRNO_STALE, + wasi_filesystem::Errno::Timedout => ERRNO_TIMEDOUT, + wasi_filesystem::Errno::Txtbsy => ERRNO_TXTBSY, + wasi_filesystem::Errno::Xdev => ERRNO_XDEV, + } } } @@ -1280,30 +1143,127 @@ pub enum Descriptor { #[repr(C)] pub struct File { fd: wasi_filesystem::Descriptor, - position: u64, + position: Cell, } const PAGE_SIZE: usize = 65536; -impl Descriptor { - fn get(fd: Fd) -> &'static mut Descriptor { +/// The maximum path length. WASI doesn't explicitly guarantee this, but all +/// popular OS's have a `PATH_MAX` of at most 4096, so that's enough for this +/// polyfill. +const PATH_MAX: usize = 4096; + +struct State { + buffer_ptr: Cell<*mut u8>, + buffer_len: Cell, + ndescriptors: usize, + descriptors: MaybeUninit<[Descriptor; 128]>, + path_buf: UnsafeCell>, +} + +#[allow(improper_ctypes)] +extern "C" { + fn get_global_ptr() -> *const RefCell; + fn set_global_ptr(a: *const RefCell); +} + +impl State { + fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno { + let ptr = State::ptr(); + let ptr = ptr.try_borrow().unwrap_or_else(|_| unreachable()); + let ret = f(&*ptr); + match ret { + Ok(()) => ERRNO_SUCCESS, + Err(err) => err, + } + } + + fn with_mut(f: impl FnOnce(&mut State) -> Result<(), Errno>) -> Errno { + let ptr = State::ptr(); + let mut ptr = ptr.try_borrow_mut().unwrap_or_else(|_| unreachable()); + let ret = f(&mut *ptr); + match ret { + Ok(()) => ERRNO_SUCCESS, + Err(err) => err, + } + } + + fn ptr() -> &'static RefCell { + assert!(size_of::() <= PAGE_SIZE); unsafe { - let mut fds = replace_fds(null_mut()); - if fds.is_null() { - let grew = core::arch::wasm32::memory_grow(0, 1); - if grew == usize::MAX { - unreachable(); - } - fds = (grew * PAGE_SIZE) as *mut u8; + let mut ptr = get_global_ptr(); + if ptr.is_null() { + ptr = State::new(); + set_global_ptr(ptr); } - // We allocated a page; abort if that's not enough. - if fd as usize * size_of::() >= PAGE_SIZE { - unreachable() + &*ptr + } + } + + #[cold] + fn new() -> &'static RefCell { + let grew = core::arch::wasm32::memory_grow(0, 1); + if grew == usize::MAX { + unreachable(); + } + let ret = (grew * PAGE_SIZE) as *mut RefCell; + unsafe { + ret.write(RefCell::new(State { + buffer_ptr: Cell::new(null_mut()), + buffer_len: Cell::new(0), + ndescriptors: 0, + descriptors: MaybeUninit::uninit(), + path_buf: UnsafeCell::new(MaybeUninit::uninit()), + })); + &*ret + } + } + + fn push_desc(&mut self, desc: Descriptor) -> Result { + unsafe { + let descriptors = self.descriptors.as_mut_ptr(); + if self.ndescriptors >= (*descriptors).len() { + return Err(ERRNO_INVAL); } - let result = &mut *fds.cast::().add(fd as usize); - let fds = replace_fds(fds); - assert!(fds.is_null()); - result + ptr::addr_of_mut!((*descriptors)[self.ndescriptors]).write(desc); + self.ndescriptors += 1; + Ok(Fd::try_from(self.ndescriptors - 1).unwrap()) + } + } + + fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { + let index = usize::try_from(fd).unwrap(); + if index < self.ndescriptors { + unsafe { (*self.descriptors.as_ptr()).get(index).ok_or(ERRNO_BADF) } + } else { + Err(ERRNO_BADF) + } + } + + fn get_file_with_log_error(&self, fd: Fd, log_error: Errno) -> Result<&File, Errno> { + match self.get(fd)? { + Descriptor::File(file) => Ok(file), + Descriptor::Log => Err(log_error), + Descriptor::Closed => Err(ERRNO_BADF), } } + + fn get_file(&self, fd: Fd) -> Result<&File, Errno> { + self.get_file_with_log_error(fd, ERRNO_INVAL) + } + + fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { + self.get_file_with_log_error(fd, ERRNO_NOTDIR) + } + + fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { + self.get_file_with_log_error(fd, ERRNO_SPIPE) + } + + /// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy + /// the next request. + fn register_buffer(&self, buf: *mut u8, buf_len: usize) { + self.buffer_ptr.set(buf); + self.buffer_len.set(buf_len); + } } From b5411878b7d14e8c8a9b270276b5afeae68d2c38 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 1 Dec 2022 15:03:56 -0600 Subject: [PATCH 042/153] Implement `args_{get,sizes_get}` functions (#16) This commit updates the implementation of `cabi_export_realloc` to allocate from a bump-allocated-region in `State` rather than allocating a separate page for each argument as previously done. Additionally the argument data is now stored within `State` as well enabling a full implementation of the `args_get` and `args_sizes_get` syscalls. --- src/lib.rs | 158 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 124 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 746b7450d03c..98f299372455 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,17 +23,23 @@ mod bindings { } #[export_name = "command"] -unsafe extern "C" fn command_entrypoint(stdin: i32, stdout: i32, _args_ptr: i32, _args_len: i32) { +unsafe extern "C" fn command_entrypoint( + stdin: u32, + stdout: u32, + args_ptr: *const WasmStr, + args_len: usize, +) { State::with_mut(|state| { state.push_desc(Descriptor::File(File { - fd: stdin as u32, + fd: stdin, position: Cell::new(0), }))?; state.push_desc(Descriptor::File(File { - fd: stdout as u32, + fd: stdout, position: Cell::new(0), }))?; state.push_desc(Descriptor::Log)?; + state.args = Some(slice::from_raw_parts(args_ptr, args_len)); Ok(()) }); @@ -84,6 +90,13 @@ pub unsafe extern "C" fn cabi_import_realloc( ptr } +/// This allocator is only used for the `command` entrypoint. +/// +/// The implementation here is a bump allocator into `State::command_data` which +/// traps when it runs out of data. This means that the total size of +/// arguments/env/etc coming into a component is bounded by the current 64k +/// (ish) limit. That's just an implementation limit though which can be lifted +/// by dynamically calling `memory.grow` as necessary for more data. #[no_mangle] pub unsafe extern "C" fn cabi_export_realloc( old_ptr: *mut u8, @@ -91,42 +104,69 @@ pub unsafe extern "C" fn cabi_export_realloc( align: usize, new_size: usize, ) -> *mut u8 { - if !old_ptr.is_null() { - unreachable(); - } - if new_size > PAGE_SIZE { - unreachable(); - } - let grew = core::arch::wasm32::memory_grow(0, 1); - if grew == usize::MAX { + if !old_ptr.is_null() || old_size != 0 { unreachable(); } - (grew * PAGE_SIZE) as *mut u8 + let mut ret = null_mut::(); + State::with_mut(|state| { + let data = state.command_data.as_mut_ptr(); + let ptr = usize::try_from(state.command_data_next).unwrap(); + + // "oom" as too much argument data tried to flow into the component. + // Ideally this would have a better error message? + if ptr + new_size > (*data).len() { + unreachable(); + } + state.command_data_next += new_size as u16; + ret = (*data).as_mut_ptr().add(ptr); + Ok(()) + }); + ret } /// Read command-line argument data. /// The size of the array should match that returned by `args_sizes_get` #[no_mangle] -pub unsafe extern "C" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno { - // TODO: Use real arguments. - // Store bytes one at a time to avoid needing a static init. - argv_buf.add(0).write(b'w'); - argv_buf.add(1).write(b'a'); - argv_buf.add(2).write(b's'); - argv_buf.add(3).write(b'm'); - argv_buf.add(4).write(b'\0'); - argv.add(0).write(argv_buf); - argv.add(1).write(null_mut()); - ERRNO_SUCCESS +pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) -> Errno { + State::with(|state| { + if let Some(args) = state.args { + for arg in args { + // Copy the argument into `argv_buf` which must be sized + // appropriately by the caller. + ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len); + *argv_buf.add(arg.len) = 0; + + // Copy the argument pointer into the `argv` buf + *argv = argv_buf; + + // Update our pointers past what's written to prepare for the + // next argument. + argv = argv.add(1); + argv_buf = argv_buf.add(arg.len + 1); + } + } + Ok(()) + }) } /// Return command-line argument data sizes. #[no_mangle] pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { - // TODO: Use real arguments. - *argc = 1; - *argv_buf_size = 5; - ERRNO_SUCCESS + State::with(|state| { + match state.args { + Some(args) => { + *argc = args.len(); + // Add one to each length for the terminating nul byte added by + // the `args_get` function. + *argv_buf_size = args.iter().map(|s| s.len + 1).sum(); + } + None => { + *argc = 0; + *argv_buf_size = 0; + } + } + Ok(()) + }) } /// Read environment variable data. @@ -1153,14 +1193,61 @@ const PAGE_SIZE: usize = 65536; /// polyfill. const PATH_MAX: usize = 4096; +const MAX_DESCRIPTORS: usize = 128; + struct State { + /// Used by `register_buffer` to coordinate allocations with + /// `cabi_import_realloc`. buffer_ptr: Cell<*mut u8>, buffer_len: Cell, - ndescriptors: usize, - descriptors: MaybeUninit<[Descriptor; 128]>, + + /// Storage of mapping from preview1 file descriptors to preview2 file + /// descriptors. + ndescriptors: u16, + descriptors: MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>, + + /// Auxiliary storage to handle the `path_readlink` function. path_buf: UnsafeCell>, + + /// Storage area for data passed to the `command` entrypoint. The + /// `command_data` is a block of memory which is dynamically allocated from + /// in `cabi_export_realloc`. The `command_data_next` is the + /// bump-allocated-pointer of where to allocate from next. + command_data: MaybeUninit<[u8; command_data_size()]>, + command_data_next: u16, + + /// Arguments passed to the `command` entrypoint + args: Option<&'static [WasmStr]>, } +struct WasmStr { + ptr: *const u8, + len: usize, +} + +const fn command_data_size() -> usize { + // The total size of the struct should be a page, so start there + let mut start = PAGE_SIZE; + + // Remove the big chunks of the struct, the `path_buf` and `descriptors` + // fields. + start -= PATH_MAX; + start -= size_of::() * MAX_DESCRIPTORS; + + // Remove miscellaneous metadata also stored in state. + start -= 5 * size_of::(); + + // Everything else is the `command_data` allocation. + start +} + +// Statically assert that the `State` structure is the size of a wasm page. This +// mostly guarantees that it's not larger than one page which is relied upon +// below. +const _: () = { + let _size_assert: [(); PAGE_SIZE] = [(); size_of::()]; +}; + #[allow(improper_ctypes)] extern "C" { fn get_global_ptr() -> *const RefCell; @@ -1189,7 +1276,6 @@ impl State { } fn ptr() -> &'static RefCell { - assert!(size_of::() <= PAGE_SIZE); unsafe { let mut ptr = get_global_ptr(); if ptr.is_null() { @@ -1214,6 +1300,9 @@ impl State { ndescriptors: 0, descriptors: MaybeUninit::uninit(), path_buf: UnsafeCell::new(MaybeUninit::uninit()), + command_data: MaybeUninit::uninit(), + command_data_next: 0, + args: None, })); &*ret } @@ -1222,18 +1311,19 @@ impl State { fn push_desc(&mut self, desc: Descriptor) -> Result { unsafe { let descriptors = self.descriptors.as_mut_ptr(); - if self.ndescriptors >= (*descriptors).len() { + let ndescriptors = usize::try_from(self.ndescriptors).unwrap(); + if ndescriptors >= (*descriptors).len() { return Err(ERRNO_INVAL); } - ptr::addr_of_mut!((*descriptors)[self.ndescriptors]).write(desc); + ptr::addr_of_mut!((*descriptors)[ndescriptors]).write(desc); self.ndescriptors += 1; - Ok(Fd::try_from(self.ndescriptors - 1).unwrap()) + Ok(Fd::from(self.ndescriptors - 1)) } } fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { let index = usize::try_from(fd).unwrap(); - if index < self.ndescriptors { + if index < usize::try_from(self.ndescriptors).unwrap() { unsafe { (*self.descriptors.as_ptr()).get(index).ok_or(ERRNO_BADF) } } else { Err(ERRNO_BADF) From 20bccd27cbce8227c771991e91baad586abb479b Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 6 Dec 2022 11:08:54 -0700 Subject: [PATCH 043/153] sketch of `poll_oneoff` polyfill (#11) This adds a `poll_oneoff` implementation to src/lib.rs. It's completely untested. I was planning on adding a host implementation and using that to test end-to-end, but that raised some tough questions about how much of the existing `wasi-common` scheduler(s) should be reused. I've decided to focus on other, more widely-used parts of WASI first, but I wanted to share the work I've already done here. Note that I've moved the clock- and socket-specific functions out of `wasi-poll` and into `wasi-clocks` and `wasi-tcp`, respectively. The latter is a new interface which will eventually contain functions and types resembling @npmccallum's https://github.com/npmccallum/wasi-snapshot-preview2#wasi-tcp proposal. Per discussion with @sunfishcode: - `wasi-tcp` includes an `error` enum type, intended to represent only socket-related errors. - It also includes a `socket` pseudo-handle type, distinct from `wasi-filesystem`'s `descriptor` type. These fine-grained types help move us away from the "everything is a file descriptor" and "all errors are errnos" approaches of Preview 1. If we decide `poll-oneoff` should be usable with files as well as sockets, we can add `subscribe-read` and `subscribe-write` functions to `wasi-filesystem` which accept file `descriptor`s. Likewise for any other pseudo-handle type from which we'd like to create futures. Signed-off-by: Joel Dice Signed-off-by: Joel Dice --- src/lib.rs | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 226 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 98f299372455..549bc40e0f45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,17 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_clocks, wasi_default_clocks, wasi_filesystem, wasi_logging, wasi_random, + wasi_clocks, wasi_default_clocks, wasi_filesystem, wasi_logging, wasi_poll, wasi_random, + wasi_tcp, }; use core::arch::wasm32::unreachable; use core::cell::{Cell, RefCell, UnsafeCell}; -use core::mem::{forget, size_of, MaybeUninit}; +use core::ffi::c_void; +use core::mem::{self, forget, size_of, MaybeUninit}; use core::ptr::{self, copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; +use wasi_poll::WasiFuture; mod bindings { wit_bindgen_guest_rust::generate!({ @@ -65,6 +68,14 @@ macro_rules! assert_eq { }; } +fn unwrap(maybe: Option) -> T { + if let Some(value) = maybe { + value + } else { + unreachable() + } +} + #[no_mangle] pub unsafe extern "C" fn cabi_import_realloc( old_ptr: *mut u8, @@ -351,6 +362,8 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } + // TODO: Handle socket case here once `wasi-tcp` has been fleshed out + Descriptor::Socket(_) => unreachable(), Descriptor::Closed => Err(ERRNO_BADF), }) } @@ -556,6 +569,8 @@ pub unsafe extern "C" fn fd_pwrite( *nwritten = len; Ok(()) } + // TODO: Handle socket case here once `wasi-tcp` has been fleshed out + Descriptor::Socket(_) => unreachable(), Descriptor::Closed => Err(ERRNO_BADF), }) } @@ -718,6 +733,8 @@ pub unsafe extern "C" fn fd_write( *nwritten = len; Ok(()) } + // TODO: Handle socket case here once `wasi-tcp` has been fleshed out + Descriptor::Socket(_) => unreachable(), Descriptor::Closed => Err(ERRNO_BADF), }) } @@ -988,6 +1005,44 @@ pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: }) } +struct Futures { + pointer: *mut WasiFuture, + index: usize, + length: usize, +} + +impl Futures { + unsafe fn push(&mut self, future: WasiFuture) { + assert!(self.index < self.length); + *self.pointer.add(self.index) = future; + self.index += 1; + } +} + +impl Drop for Futures { + fn drop(&mut self) { + for i in 0..self.index { + wasi_poll::drop_future(unsafe { *self.pointer.add(i) }) + } + } +} + +impl From for Errno { + fn from(error: wasi_tcp::Error) -> Errno { + use wasi_tcp::Error::*; + + match error { + ConnectionAborted => black_box(ERRNO_CONNABORTED), + ConnectionRefused => ERRNO_CONNREFUSED, + ConnectionReset => ERRNO_CONNRESET, + HostUnreachable => ERRNO_HOSTUNREACH, + NetworkDown => ERRNO_NETDOWN, + NetworkUnreachable => ERRNO_NETUNREACH, + Timeout => ERRNO_TIMEDOUT, + } + } +} + /// Concurrently poll for the occurrence of a set of events. #[no_mangle] pub unsafe extern "C" fn poll_oneoff( @@ -996,7 +1051,161 @@ pub unsafe extern "C" fn poll_oneoff( nsubscriptions: Size, nevents: *mut Size, ) -> Errno { - unreachable() + *nevents = 0; + + let subscriptions = slice::from_raw_parts(r#in, nsubscriptions); + + // We're going to split the `nevents` buffer into two non-overlapping buffers: one to store the future handles, + // and the other to store the bool results. + // + // First, we assert that this is possible: + assert!(mem::align_of::() >= mem::align_of::()); + assert!(mem::align_of::() >= mem::align_of::()); + assert!( + unwrap(nsubscriptions.checked_mul(mem::size_of::())) + > unwrap( + unwrap(nsubscriptions.checked_mul(mem::size_of::())) + .checked_add(unwrap(nsubscriptions.checked_mul(mem::size_of::()))) + ) + ); + + let futures = out as *mut c_void as *mut WasiFuture; + let results = futures.add(nsubscriptions) as *mut c_void as *mut u8; + + State::with(|state| { + state.register_buffer( + results, + unwrap(nsubscriptions.checked_mul(mem::size_of::())), + ); + + let mut futures = Futures { + pointer: futures, + index: 0, + length: nsubscriptions, + }; + + for subscription in subscriptions { + futures.push(match subscription.u.tag { + 0 => { + let clock = &subscription.u.u.clock; + let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) == 0; + match clock.id { + CLOCKID_REALTIME => wasi_clocks::subscribe_wall_clock( + wasi_clocks::Datetime { + seconds: unwrap((clock.timeout / 1_000_000_000).try_into().ok()), + nanoseconds: unwrap( + (clock.timeout % 1_000_000_000).try_into().ok(), + ), + }, + absolute, + ), + + CLOCKID_MONOTONIC => { + wasi_clocks::subscribe_monotonic_clock(clock.timeout, absolute) + } + + _ => return Err(ERRNO_INVAL), + } + } + + 1 => wasi_tcp::subscribe_read( + state.get_socket(subscription.u.u.fd_read.file_descriptor)?, + ), + + 2 => wasi_tcp::subscribe_write( + state.get_socket(subscription.u.u.fd_write.file_descriptor)?, + ), + + _ => return Err(ERRNO_INVAL), + }); + } + + let vec = wasi_poll::poll_oneoff(slice::from_raw_parts(futures.pointer, futures.length)); + + assert!(vec.len() == nsubscriptions); + assert_eq!(vec.as_ptr(), results); + mem::forget(vec); + + drop(futures); + + let ready = subscriptions + .iter() + .enumerate() + .filter_map(|(i, s)| (*results.add(i) != 0).then_some(s)); + + let mut count = 0; + + for subscription in ready { + let error; + let type_; + let nbytes; + let flags; + + match subscription.u.tag { + 0 => { + error = ERRNO_SUCCESS; + type_ = EVENTTYPE_CLOCK; + nbytes = 0; + flags = 0; + } + + 1 => { + type_ = EVENTTYPE_FD_READ; + match wasi_tcp::bytes_readable(subscription.u.u.fd_read.file_descriptor) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.nbytes; + flags = if result.is_closed { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } + } + + 2 => { + type_ = EVENTTYPE_FD_WRITE; + match wasi_tcp::bytes_writable(subscription.u.u.fd_write.file_descriptor) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.nbytes; + flags = if result.is_closed { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } + } + + _ => unreachable(), + } + + *out.add(count) = Event { + userdata: subscription.userdata, + error, + type_, + fd_readwrite: EventFdReadwrite { nbytes, flags }, + }; + + count += 1; + } + + *nevents = count; + + Ok(()) + }) } /// Terminate the process normally. An exit code of 0 indicates successful @@ -1177,6 +1386,7 @@ fn black_box(x: Errno) -> Errno { pub enum Descriptor { Closed, File(File), + Socket(wasi_tcp::Socket), Log, } @@ -1330,24 +1540,32 @@ impl State { } } - fn get_file_with_log_error(&self, fd: Fd, log_error: Errno) -> Result<&File, Errno> { + fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { Descriptor::File(file) => Ok(file), - Descriptor::Log => Err(log_error), + Descriptor::Log | Descriptor::Socket(_) => Err(error), + Descriptor::Closed => Err(ERRNO_BADF), + } + } + + fn get_socket(&self, fd: Fd) -> Result { + match self.get(fd)? { + Descriptor::Socket(socket) => Ok(*socket), + Descriptor::Log | Descriptor::File(_) => Err(ERRNO_INVAL), Descriptor::Closed => Err(ERRNO_BADF), } } fn get_file(&self, fd: Fd) -> Result<&File, Errno> { - self.get_file_with_log_error(fd, ERRNO_INVAL) + self.get_file_with_error(fd, ERRNO_INVAL) } fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { - self.get_file_with_log_error(fd, ERRNO_NOTDIR) + self.get_file_with_error(fd, ERRNO_NOTDIR) } fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { - self.get_file_with_log_error(fd, ERRNO_SPIPE) + self.get_file_with_error(fd, ERRNO_SPIPE) } /// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy From 3477cd02856ae3720202efccf93cb2dc75634ab9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 6 Dec 2022 20:20:25 -0600 Subject: [PATCH 044/153] Update a few aspects of the adapter build (#17) * Update a few aspects of the adapter build * Use the `wasm32-unknown-unknown` target for the adapter and specify flags in `.cargo/config.toml` to avoid having to pass the same flags everywhere. This allows using `wasm32-wasi` for tests to ensure the flags only apply to the adapter. * Use `opt-level=s` since speed is not of the utmost concern for this wasm but since it's likely to be included in many places size is likely more important. * Use `strip = 'debuginfo'` for the release build to remove the standard library's debugging information which isn't necessary. * Remove `debug = 0` from the `dev` profile to have debugging information for development. * Add a small `README.md` describing what's here for now. * Move `command` support behind a `command` feature This commit adds a `command` feature to main crate to avoid importing the `_start` function when the `command` feature is disabled, making this adapter useful for non-command WASI programs as well. For now this still emits the `command` export in the final component but with `use` in `*.wit` files it should be easier to avoid that export. --- src/lib.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 549bc40e0f45..571b627aaf02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,13 +25,19 @@ mod bindings { }); } -#[export_name = "command"] -unsafe extern "C" fn command_entrypoint( +#[no_mangle] +pub unsafe extern "C" fn command( stdin: u32, stdout: u32, args_ptr: *const WasmStr, args_len: usize, ) { + // TODO: ideally turning off `command` would remove this import and the + // `*.wit` metadata entirely but doing that ergonomically will likely + // require some form of `use` to avoid duplicating lots of `*.wit` bits. + if !cfg!(feature = "command") { + unreachable(); + } State::with_mut(|state| { state.push_desc(Descriptor::File(File { fd: stdin, @@ -1430,7 +1436,7 @@ struct State { args: Option<&'static [WasmStr]>, } -struct WasmStr { +pub struct WasmStr { ptr: *const u8, len: usize, } From 91fff49f0f706c4d8b38d5b647fe265e9e2901c8 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 7 Dec 2022 15:45:11 -0800 Subject: [PATCH 045/153] Implement `path_open`. (#18) * Implement `path_open`. * Implement `fd_close`, and finish `path_open`. * Stub out `close` in the host impl. --- src/lib.rs | 147 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 571b627aaf02..24c1d372949d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,14 @@ fn unwrap(maybe: Option) -> T { } } +fn unwrap_result(result: Result) -> T { + if let Ok(value) = result { + value + } else { + unreachable() + } +} + #[no_mangle] pub unsafe extern "C" fn cabi_import_realloc( old_ptr: *mut u8, @@ -288,7 +296,23 @@ pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> /// Note: This is similar to `close` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { - unreachable() + State::with_mut(|state| { + let closed = state.closed; + let desc = state.get_mut(fd)?; + + match desc { + Descriptor::File(file) => { + wasi_filesystem::close(file.fd); + } + Descriptor::Log => {} + Descriptor::Socket(_) => unreachable(), + Descriptor::Closed(_) => return Err(ERRNO_BADF), + } + + *desc = Descriptor::Closed(closed); + state.closed = Some(fd); + Ok(()) + }) } /// Synchronize the data of a file to disk. @@ -370,7 +394,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { } // TODO: Handle socket case here once `wasi-tcp` has been fleshed out Descriptor::Socket(_) => unreachable(), - Descriptor::Closed => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -577,7 +601,7 @@ pub unsafe extern "C" fn fd_pwrite( } // TODO: Handle socket case here once `wasi-tcp` has been fleshed out Descriptor::Socket(_) => unreachable(), - Descriptor::Closed => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -741,7 +765,7 @@ pub unsafe extern "C" fn fd_write( } // TODO: Handle socket case here once `wasi-tcp` has been fleshed out Descriptor::Socket(_) => unreachable(), - Descriptor::Closed => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -887,7 +911,49 @@ pub unsafe extern "C" fn path_open( fdflags: Fdflags, opened_fd: *mut Fd, ) -> Errno { - unreachable() + drop(fs_rights_inheriting); + + let path = slice::from_raw_parts(path_ptr, path_len); + let at_flags = at_flags_from_lookupflags(dirflags); + let o_flags = o_flags_from_oflags(oflags); + let flags = flags_from_descriptor_flags(fs_rights_base, fdflags); + let mode = wasi_filesystem::Mode::READABLE | wasi_filesystem::Mode::WRITEABLE; + + State::with_mut(|state| { + let desc = state.get_mut(fd)?; + + let file = state.get_dir(fd)?; + let result = wasi_filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; + let desc = Descriptor::File(File { + fd: result, + position: Cell::new(0), + }); + + let fd = match state.closed { + // No free fds; create a new one. + None => match state.push_desc(desc) { + Ok(new) => new, + Err(err) => { + wasi_filesystem::close(result); + return Err(err); + } + }, + // `recycle_fd` is a free fd. + Some(recycle_fd) => { + let recycle_desc = unwrap_result(state.get_mut(recycle_fd)); + let next_closed = match recycle_desc { + Descriptor::Closed(next) => *next, + _ => unreachable(), + }; + *recycle_desc = desc; + state.closed = next_closed; + recycle_fd + } + }; + + *opened_fd = fd; + Ok(()) + }) } /// Read the contents of a symbolic link. @@ -1305,6 +1371,52 @@ fn at_flags_from_lookupflags(flags: Lookupflags) -> wasi_filesystem::AtFlags { } } +fn o_flags_from_oflags(flags: Oflags) -> wasi_filesystem::OFlags { + let mut o_flags = wasi_filesystem::OFlags::empty(); + if flags & OFLAGS_CREAT == OFLAGS_CREAT { + o_flags |= wasi_filesystem::OFlags::CREATE; + } + if flags & OFLAGS_DIRECTORY == OFLAGS_DIRECTORY { + o_flags |= wasi_filesystem::OFlags::DIRECTORY; + } + if flags & OFLAGS_EXCL == OFLAGS_EXCL { + o_flags |= wasi_filesystem::OFlags::EXCL; + } + if flags & OFLAGS_TRUNC == OFLAGS_TRUNC { + o_flags |= wasi_filesystem::OFlags::TRUNC; + } + o_flags +} + +fn flags_from_descriptor_flags( + rights: Rights, + fdflags: Fdflags, +) -> wasi_filesystem::DescriptorFlags { + let mut flags = wasi_filesystem::DescriptorFlags::empty(); + if rights & wasi::RIGHTS_FD_READ == wasi::RIGHTS_FD_READ { + flags |= wasi_filesystem::DescriptorFlags::READ; + } + if rights & wasi::RIGHTS_FD_WRITE == wasi::RIGHTS_FD_WRITE { + flags |= wasi_filesystem::DescriptorFlags::WRITE; + } + if fdflags & wasi::FDFLAGS_SYNC == wasi::FDFLAGS_SYNC { + flags |= wasi_filesystem::DescriptorFlags::SYNC; + } + if fdflags & wasi::FDFLAGS_DSYNC == wasi::FDFLAGS_DSYNC { + flags |= wasi_filesystem::DescriptorFlags::DSYNC; + } + if fdflags & wasi::FDFLAGS_RSYNC == wasi::FDFLAGS_RSYNC { + flags |= wasi_filesystem::DescriptorFlags::RSYNC; + } + if fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND { + flags |= wasi_filesystem::DescriptorFlags::APPEND; + } + if fdflags & wasi::FDFLAGS_NONBLOCK == wasi::FDFLAGS_NONBLOCK { + flags |= wasi_filesystem::DescriptorFlags::NONBLOCK; + } + flags +} + impl From for Errno { #[inline(never)] // Disable inlining as this is bulky and relatively cold. fn from(err: wasi_filesystem::Errno) -> Errno { @@ -1390,7 +1502,7 @@ fn black_box(x: Errno) -> Errno { #[repr(C)] pub enum Descriptor { - Closed, + Closed(Option), File(File), Socket(wasi_tcp::Socket), Log, @@ -1422,6 +1534,9 @@ struct State { ndescriptors: u16, descriptors: MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>, + /// Points to the head of a free-list of closed file descriptors. + closed: Option, + /// Auxiliary storage to handle the `path_readlink` function. path_buf: UnsafeCell>, @@ -1451,7 +1566,7 @@ const fn command_data_size() -> usize { start -= size_of::() * MAX_DESCRIPTORS; // Remove miscellaneous metadata also stored in state. - start -= 5 * size_of::(); + start -= 7 * size_of::(); // Everything else is the `command_data` allocation. start @@ -1514,6 +1629,7 @@ impl State { buffer_ptr: Cell::new(null_mut()), buffer_len: Cell::new(0), ndescriptors: 0, + closed: None, descriptors: MaybeUninit::uninit(), path_buf: UnsafeCell::new(MaybeUninit::uninit()), command_data: MaybeUninit::uninit(), @@ -1529,7 +1645,7 @@ impl State { let descriptors = self.descriptors.as_mut_ptr(); let ndescriptors = usize::try_from(self.ndescriptors).unwrap(); if ndescriptors >= (*descriptors).len() { - return Err(ERRNO_INVAL); + return Err(ERRNO_NOMEM); } ptr::addr_of_mut!((*descriptors)[ndescriptors]).write(desc); self.ndescriptors += 1; @@ -1540,7 +1656,16 @@ impl State { fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { let index = usize::try_from(fd).unwrap(); if index < usize::try_from(self.ndescriptors).unwrap() { - unsafe { (*self.descriptors.as_ptr()).get(index).ok_or(ERRNO_BADF) } + unsafe { Ok(unwrap((*self.descriptors.as_ptr()).get(index))) } + } else { + Err(ERRNO_BADF) + } + } + + fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { + let index = usize::try_from(fd).unwrap(); + if index < usize::try_from(self.ndescriptors).unwrap() { + unsafe { Ok(unwrap((*self.descriptors.as_mut_ptr()).get_mut(index))) } } else { Err(ERRNO_BADF) } @@ -1550,7 +1675,7 @@ impl State { match self.get(fd)? { Descriptor::File(file) => Ok(file), Descriptor::Log | Descriptor::Socket(_) => Err(error), - Descriptor::Closed => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), } } @@ -1558,7 +1683,7 @@ impl State { match self.get(fd)? { Descriptor::Socket(socket) => Ok(*socket), Descriptor::Log | Descriptor::File(_) => Err(ERRNO_INVAL), - Descriptor::Closed => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), } } From 6f0e31c2e3a6c4e325b64fa2d01ec5813cf38353 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 7 Dec 2022 15:52:52 -0800 Subject: [PATCH 046/153] Add a `badf` error code. This pulls in https://github.com/WebAssembly/wasi-filesystem/pull/62. --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 24c1d372949d..01c5308233b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1429,6 +1429,7 @@ impl From for Errno { wasi_filesystem::Errno::Again => ERRNO_AGAIN, wasi_filesystem::Errno::Already => ERRNO_ALREADY, wasi_filesystem::Errno::Badmsg => ERRNO_BADMSG, + wasi_filesystem::Errno::Badf => ERRNO_BADF, wasi_filesystem::Errno::Busy => ERRNO_BUSY, wasi_filesystem::Errno::Canceled => ERRNO_CANCELED, wasi_filesystem::Errno::Child => ERRNO_CHILD, From 57b934c66395fcc7441eae9c4bd5b75e6b68a023 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Dec 2022 15:16:18 -0600 Subject: [PATCH 047/153] Fix prints in non-command builds (#19) * Fix prints in non-command builds When the `command` entrypoint isn't invoked, such as for non-command builds, then prints were not working as they would return `ERRNO_BADF` which is swallowed by at least Rust right now. This instead sets the initialization of `State` to reflect how fd 0 is an empty stdin stream, fd 1 goes to a `log` call, and fd 2 also goes to `log`. During `command` initialization fds 0 and 1 are overwritten with the provided input and for non-command builds this is the state permanently for the program. It's possible we can add further configuration hooks for this in the future, but this should get at least the initial state of non-command builds more workable. * Use BADF for reading log fds --- src/lib.rs | 140 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 43 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 01c5308233b8..42154852f413 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,15 +39,21 @@ pub unsafe extern "C" fn command( unreachable(); } State::with_mut(|state| { - state.push_desc(Descriptor::File(File { + // Initialization of `State` automatically fills in some dummy + // structures for fds 0, 1, and 2. Overwrite the stdin/stdout slots of 0 + // and 1 with actual files. + let descriptors = state.descriptors_mut(); + if descriptors.len() < 3 { + unreachable(); + } + descriptors[0] = Descriptor::File(File { fd: stdin, position: Cell::new(0), - }))?; - state.push_desc(Descriptor::File(File { + }); + descriptors[1] = Descriptor::File(File { fd: stdout, position: Cell::new(0), - }))?; - state.push_desc(Descriptor::Log)?; + }); state.args = Some(slice::from_raw_parts(args_ptr, args_len)); Ok(()) }); @@ -304,7 +310,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { Descriptor::File(file) => { wasi_filesystem::close(file.fd); } - Descriptor::Log => {} + Descriptor::StdoutLog | Descriptor::StderrLog | Descriptor::EmptyStdin => {} Descriptor::Socket(_) => unreachable(), Descriptor::Closed(_) => return Err(ERRNO_BADF), } @@ -379,7 +385,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } - Descriptor::Log => { + Descriptor::StdoutLog | Descriptor::StderrLog => { let fs_filetype = FILETYPE_UNKNOWN; let fs_flags = 0; let fs_rights_base = !RIGHTS_FD_READ; @@ -392,6 +398,19 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } + Descriptor::EmptyStdin => { + let fs_filetype = FILETYPE_UNKNOWN; + let fs_flags = 0; + let fs_rights_base = RIGHTS_FD_READ; + let fs_rights_inheriting = fs_rights_base; + stat.write(Fdstat { + fs_filetype, + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) + } // TODO: Handle socket case here once `wasi-tcp` has been fleshed out Descriptor::Socket(_) => unreachable(), Descriptor::Closed(_) => Err(ERRNO_BADF), @@ -585,23 +604,11 @@ pub unsafe extern "C" fn fd_pwrite( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - State::with(|state| match state.get(fd)? { - Descriptor::File(file) => { - let bytes = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), offset)?; - - *nwritten = bytes as usize; - Ok(()) - } - Descriptor::Log => { - let bytes = slice::from_raw_parts(ptr, len); - let context: [u8; 3] = [b'I', b'/', b'O']; - wasi_logging::log(wasi_logging::Level::Info, &context, bytes); - *nwritten = len; - Ok(()) - } - // TODO: Handle socket case here once `wasi-tcp` has been fleshed out - Descriptor::Socket(_) => unreachable(), - Descriptor::Closed(_) => Err(ERRNO_BADF), + State::with(|state| { + let file = state.get_seekable_file(fd)?; + let bytes = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), offset)?; + *nwritten = bytes as usize; + Ok(()) }) } @@ -631,7 +638,18 @@ pub unsafe extern "C" fn fd_read( state.register_buffer(ptr, len); let read_len = u32::try_from(len).unwrap(); - let file = state.get_file(fd)?; + let file = match state.get(fd)? { + Descriptor::File(f) => f, + Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { + return Err(ERRNO_BADF) + } + // TODO: Handle socket case here once `wasi-tcp` has been fleshed out + Descriptor::Socket(_) => unreachable(), + Descriptor::EmptyStdin => { + *nread = 0; + return Ok(()); + } + }; let data = wasi_filesystem::pread(file.fd, read_len, file.position.get())?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); @@ -756,7 +774,7 @@ pub unsafe extern "C" fn fd_write( file.position.set(file.position.get() + u64::from(bytes)); Ok(()) } - Descriptor::Log => { + Descriptor::StderrLog | Descriptor::StdoutLog => { let bytes = slice::from_raw_parts(ptr, len); let context: [u8; 3] = [b'I', b'/', b'O']; wasi_logging::log(wasi_logging::Level::Info, &context, bytes); @@ -765,6 +783,7 @@ pub unsafe extern "C" fn fd_write( } // TODO: Handle socket case here once `wasi-tcp` has been fleshed out Descriptor::Socket(_) => unreachable(), + Descriptor::EmptyStdin => Err(ERRNO_INVAL), Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -1506,7 +1525,20 @@ pub enum Descriptor { Closed(Option), File(File), Socket(wasi_tcp::Socket), - Log, + + /// Initial state of fd 0 when `State` is created, representing a standard + /// input that is empty as it hasn't been configured yet. This is the + /// permanent fd 0 marker if `command` is never called. + EmptyStdin, + + /// Initial state of fd 1 when `State` is created, representing that writes + /// to `fd_write` will go to a call to `log`. This is overwritten during + /// initialization in `command`. + StdoutLog, + + /// Same as `StdoutLog` except for stderr. This is not overwritten during + /// `command`. + StderrLog, } #[repr(C)] @@ -1625,7 +1657,7 @@ impl State { unreachable(); } let ret = (grew * PAGE_SIZE) as *mut RefCell; - unsafe { + let ret = unsafe { ret.write(RefCell::new(State { buffer_ptr: Cell::new(null_mut()), buffer_len: Cell::new(0), @@ -1638,7 +1670,17 @@ impl State { args: None, })); &*ret - } + }; + ret.try_borrow_mut() + .unwrap_or_else(|_| unreachable()) + .init(); + ret + } + + fn init(&mut self) { + self.push_desc(Descriptor::EmptyStdin).unwrap(); + self.push_desc(Descriptor::StdoutLog).unwrap(); + self.push_desc(Descriptor::StderrLog).unwrap(); } fn push_desc(&mut self, desc: Descriptor) -> Result { @@ -1654,37 +1696,49 @@ impl State { } } - fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { - let index = usize::try_from(fd).unwrap(); - if index < usize::try_from(self.ndescriptors).unwrap() { - unsafe { Ok(unwrap((*self.descriptors.as_ptr()).get(index))) } - } else { - Err(ERRNO_BADF) + fn descriptors(&self) -> &[Descriptor] { + unsafe { + core::slice::from_raw_parts( + self.descriptors.as_ptr().cast(), + usize::try_from(self.ndescriptors).unwrap(), + ) } } - fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { - let index = usize::try_from(fd).unwrap(); - if index < usize::try_from(self.ndescriptors).unwrap() { - unsafe { Ok(unwrap((*self.descriptors.as_mut_ptr()).get_mut(index))) } - } else { - Err(ERRNO_BADF) + fn descriptors_mut(&mut self) -> &mut [Descriptor] { + unsafe { + core::slice::from_raw_parts_mut( + self.descriptors.as_mut_ptr().cast(), + usize::try_from(self.ndescriptors).unwrap(), + ) } } + fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { + self.descriptors() + .get(usize::try_from(fd).unwrap()) + .ok_or(ERRNO_BADF) + } + + fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { + self.descriptors_mut() + .get_mut(usize::try_from(fd).unwrap()) + .ok_or(ERRNO_BADF) + } + fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { Descriptor::File(file) => Ok(file), - Descriptor::Log | Descriptor::Socket(_) => Err(error), Descriptor::Closed(_) => Err(ERRNO_BADF), + _ => Err(error), } } fn get_socket(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Socket(socket) => Ok(*socket), - Descriptor::Log | Descriptor::File(_) => Err(ERRNO_INVAL), Descriptor::Closed(_) => Err(ERRNO_BADF), + _ => Err(ERRNO_INVAL), } } From 0a54cdfabc089e26034dc7758e68230901c3d793 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 13 Dec 2022 11:11:45 -0600 Subject: [PATCH 048/153] Implement the `fd_readdir` function (#23) * Implement the `fd_readdir` function This roughly matches the implementation in `wasi-common` today where it uses the directory entry stream as a form of iterator and the `cookie` represents the `n`th iteration. Not exactly efficient if the `buf` provided to the hostcall is too small to hold many of the entries but there's not a whole lot else that can be done at this time. This also updates the WIT for `read-dir-entry` to return `option` instead of `dir-entry` to indicate EOF. * Fix host compile * Add a specific method to close a dir-entry-stream * Add a cache to avoid quadratic dirent behavior When a readdir call is truncated due to the output buffer not being large enough save the final state of the readdir iterator into `State` to possibly get reused on the next call to `readdir`. Some limits are put in place such as: * The next call to readdir must be for the same fd and the required cookie. * The dirent that didn't fit must have its full name fit within the path cache. * If `fd_close` is called after a readdir it clears the cache so a future `fd_readdir` doesn't actually resume now-stale state. There's a fair bit of trickiness here so I've attempted to structure things as "simply" as possible to hopefully reduce the chance there's an issue, but this is all untested so there's still likely an off-by-one or similar bug. --- src/lib.rs | 279 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 241 insertions(+), 38 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 42154852f413..173fd08cc351 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use crate::bindings::{ use core::arch::wasm32::unreachable; use core::cell::{Cell, RefCell, UnsafeCell}; use core::ffi::c_void; -use core::mem::{self, forget, size_of, MaybeUninit}; +use core::mem::{self, forget, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; @@ -303,6 +303,13 @@ pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> #[no_mangle] pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { State::with_mut(|state| { + // If there's a dirent cache entry for this file descriptor then drop + // it since the descriptor is being closed and future calls to + // `fd_readdir` should return an error. + if fd == state.dirent_cache.for_fd.get() { + drop(state.dirent_cache.stream.replace(None)); + } + let closed = state.closed; let desc = state.get_mut(fd)?; @@ -341,16 +348,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { let flags = wasi_filesystem::flags(file.fd)?; let type_ = wasi_filesystem::todo_type(file.fd)?; - let fs_filetype = match type_ { - wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, - wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, - wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, - wasi_filesystem::DescriptorType::Socket => FILETYPE_SOCKET_STREAM, - wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, - }; + let fs_filetype = type_.into(); let mut fs_flags = 0; let mut fs_rights_base = !0; @@ -462,19 +460,7 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { let file = state.get_file(fd)?; let stat = wasi_filesystem::stat(file.fd)?; - let filetype = match stat.type_ { - wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, - wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, - // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and - // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::DescriptorType::Socket => unreachable(), - wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, - // preview1 never had a FIFO code. - wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, - }; + let filetype = stat.type_.into(); *buf = Filestat { dev: stat.dev, ino: stat.ino, @@ -677,7 +663,181 @@ pub unsafe extern "C" fn fd_readdir( cookie: Dircookie, bufused: *mut Size, ) -> Errno { - unreachable() + let mut buf = core::slice::from_raw_parts_mut(buf, buf_len); + return State::with(|state| { + // First determine if there's an entry in the dirent cache to use. This + // is done to optimize the use case where a large directory is being + // used with a fixed-sized buffer to avoid re-invoking the `readdir` + // function and continuing to use the same iterator. + // + // This is a bit tricky since the reqeusted state in this function call + // must match the prior state of the dirent stream, if any, so that's + // all validated here as well. + // + // Note that for the duration of this function the `cookie` specifier is + // the `n`th iteration of the `readdir` stream return value. + let prev_stream = state.dirent_cache.stream.replace(None); + let stream = + if state.dirent_cache.for_fd.get() == fd && state.dirent_cache.cookie.get() == cookie { + prev_stream + } else { + None + }; + let mut iter; + match stream { + // All our checks passed and a dirent cache was available with a + // prior stream. Construct an iterator which will yield its first + // entry from cache and is additionally resuming at the `cookie` + // specified. + Some(stream) => { + iter = DirEntryIterator { + stream, + state, + cookie, + use_cache: true, + } + } + + // Either a dirent stream wasn't previously available, a different + // cookie was requested, or a brand new directory is now being read. + // In these situations fall back to resuming reading the directory + // from scratch, and the `cookie` value indicates how many items + // need skipping. + None => { + let dir = state.get_dir(fd)?; + iter = DirEntryIterator { + state, + cookie: 0, + use_cache: false, + stream: DirEntryStream(wasi_filesystem::readdir(dir.fd)?), + }; + + // Skip to the entry that is requested by the `cookie` + // parameter. + for _ in 0..cookie { + match iter.next() { + Some(Ok(_)) => {} + Some(Err(e)) => return Err(e), + None => return Ok(()), + } + } + } + }; + + while buf.len() > 0 { + let (dirent, name) = match iter.next() { + Some(Ok(pair)) => pair, + Some(Err(e)) => return Err(e), + None => break, + }; + + // Copy a `dirent` describing this entry into the destination `buf`, + // truncating it if it doesn't fit entirely. + let bytes = core::slice::from_raw_parts( + (&dirent as *const wasi::Dirent).cast::(), + size_of::(), + ); + let dirent_bytes_to_copy = buf.len().min(bytes.len()); + buf[..dirent_bytes_to_copy].copy_from_slice(&bytes[..dirent_bytes_to_copy]); + buf = &mut buf[dirent_bytes_to_copy..]; + + // Copy the name bytes into the output `buf`, truncating it if it + // doesn't fit. + // + // Note that this might be a 0-byte copy if the `dirent` was + // truncated or fit entirely into the destination. + let name_bytes_to_copy = buf.len().min(name.len()); + core::ptr::copy_nonoverlapping( + name.as_ptr().cast(), + buf.as_mut_ptr(), + name_bytes_to_copy, + ); + + buf = &mut buf[name_bytes_to_copy..]; + + // If the buffer is empty then that means the value may be + // truncated, so save the state of the iterator in our dirent cache + // and return. + // + // Note that `cookie - 1` is stored here since `iter.cookie` stores + // the address of the next item, and we're rewinding one item since + // the current item is truncated and will want to resume from that + // in the future. + // + // Additionally note that this caching step is skipped if the name + // to store doesn't actually fit in the dirent cache's path storage. + // In that case there's not much we can do and let the next call to + // `fd_readdir` start from scratch. + if buf.len() == 0 && name.len() <= DIRENT_CACHE { + let DirEntryIterator { stream, cookie, .. } = iter; + state.dirent_cache.stream.set(Some(stream)); + state.dirent_cache.for_fd.set(fd); + state.dirent_cache.cookie.set(cookie - 1); + state.dirent_cache.cached_dirent.set(dirent); + std::ptr::copy( + name.as_ptr().cast(), + (*state.dirent_cache.path_data.get()).as_mut_ptr(), + name.len(), + ); + break; + } + } + + *bufused = buf_len - buf.len(); + Ok(()) + }); + + struct DirEntryIterator<'a> { + state: &'a State, + use_cache: bool, + cookie: Dircookie, + stream: DirEntryStream, + } + + impl<'a> Iterator for DirEntryIterator<'a> { + // Note the usage of `UnsafeCell` here to indicate that the data can + // alias the storage within `state`. + type Item = Result<(wasi::Dirent, &'a [UnsafeCell]), Errno>; + + fn next(&mut self) -> Option { + self.cookie += 1; + + if self.use_cache { + self.use_cache = false; + return Some(unsafe { + let dirent = self.state.dirent_cache.cached_dirent.as_ptr().read(); + let ptr = (*(*self.state.dirent_cache.path_data.get()).as_ptr()) + .as_ptr() + .cast(); + let buffer = core::slice::from_raw_parts(ptr, dirent.d_namlen as usize); + Ok((dirent, buffer)) + }); + } + self.state + .register_buffer(self.state.path_buf.get().cast(), PATH_MAX); + let entry = match wasi_filesystem::read_dir_entry(self.stream.0) { + Ok(Some(entry)) => entry, + Ok(None) => return None, + Err(e) => return Some(Err(e.into())), + }; + + let wasi_filesystem::DirEntry { ino, type_, name } = entry; + let name = ManuallyDrop::new(name); + let dirent = wasi::Dirent { + d_next: self.cookie, + d_ino: ino.unwrap_or(0), + d_namlen: u32::try_from(name.len()).unwrap(), + d_type: type_.into(), + }; + // Extend the lifetime of `name` to the `self.state` lifetime for + // this iterator since the data for the name lives within state. + let name = unsafe { + assert_eq!(name.as_ptr(), self.state.path_buf.get().cast()); + core::slice::from_raw_parts(name.as_ptr().cast(), name.len()) + }; + Some(Ok((dirent, name))) + } + } } /// Atomically replace a file descriptor by renumbering another file descriptor. @@ -821,19 +981,7 @@ pub unsafe extern "C" fn path_filestat_get( State::with(|state| { let file = state.get_dir(fd)?; let stat = wasi_filesystem::stat_at(file.fd, at_flags, path)?; - let filetype = match stat.type_ { - wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, - wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, - // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and - // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::DescriptorType::Socket => unreachable(), - wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, - // preview1 never had a FIFO code. - wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, - }; + let filetype = stat.type_.into(); *buf = Filestat { dev: stat.dev, ino: stat.ino, @@ -1513,6 +1661,24 @@ impl From for Errno { } } +impl From for wasi::Filetype { + fn from(ty: wasi_filesystem::DescriptorType) -> wasi::Filetype { + match ty { + wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, + wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + // preview1 never had a FIFO code. + wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, + // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and + // FILETYPE_SOCKET_DGRAM. + wasi_filesystem::DescriptorType::Socket => unreachable(), + wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, + } + } +} + // A black box to prevent the optimizer from generating a lookup table // from the match above, which would require a static initializer. fn black_box(x: Errno) -> Errno { @@ -1543,6 +1709,7 @@ pub enum Descriptor { #[repr(C)] pub struct File { + /// The handle to the preview2 descriptor that this file is referencing. fd: wasi_filesystem::Descriptor, position: Cell, } @@ -1556,6 +1723,9 @@ const PATH_MAX: usize = 4096; const MAX_DESCRIPTORS: usize = 128; +/// Maximum number of bytes to cache for a `wasi::Dirent` plus its path name. +const DIRENT_CACHE: usize = 256; + struct State { /// Used by `register_buffer` to coordinate allocations with /// `cabi_import_realloc`. @@ -1582,6 +1752,26 @@ struct State { /// Arguments passed to the `command` entrypoint args: Option<&'static [WasmStr]>, + + /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path + /// name that didn't fit into the caller's buffer. + dirent_cache: DirentCache, +} + +struct DirentCache { + stream: Cell>, + for_fd: Cell, + cookie: Cell, + cached_dirent: Cell, + path_data: UnsafeCell>, +} + +struct DirEntryStream(wasi_filesystem::DirEntryStream); + +impl Drop for DirEntryStream { + fn drop(&mut self) { + wasi_filesystem::close_dir_entry_stream(self.0); + } } pub struct WasmStr { @@ -1597,6 +1787,7 @@ const fn command_data_size() -> usize { // fields. start -= PATH_MAX; start -= size_of::() * MAX_DESCRIPTORS; + start -= size_of::(); // Remove miscellaneous metadata also stored in state. start -= 7 * size_of::(); @@ -1668,6 +1859,18 @@ impl State { command_data: MaybeUninit::uninit(), command_data_next: 0, args: None, + dirent_cache: DirentCache { + stream: Cell::new(None), + for_fd: Cell::new(0), + cookie: Cell::new(0), + cached_dirent: Cell::new(wasi::Dirent { + d_next: 0, + d_ino: 0, + d_type: FILETYPE_UNKNOWN, + d_namlen: 0, + }), + path_data: UnsafeCell::new(MaybeUninit::uninit()), + }, })); &*ret }; From bf3d212ae952624882a13f450c31f42e18a20103 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 13 Dec 2022 10:48:43 -0800 Subject: [PATCH 049/153] Implement `fd_renumber`. (#21) * Implement `fd_renumber`. * Implement `Drop` for `Descriptor` to free backend resources. Instead of trying to remember to call `close` on file descriptors when they're replaced or removed, just have the `Drop` impl for `Descriptor`. * Use the local `unwrap_result` instead of `.unwrap()` This avoids static memory inits. --- src/lib.rs | 61 +++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 173fd08cc351..c3f7c05a4a2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use crate::bindings::{ use core::arch::wasm32::unreachable; use core::cell::{Cell, RefCell, UnsafeCell}; use core::ffi::c_void; -use core::mem::{self, forget, size_of, ManuallyDrop, MaybeUninit}; +use core::mem::{self, forget, replace, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; @@ -141,7 +141,7 @@ pub unsafe extern "C" fn cabi_export_realloc( let mut ret = null_mut::(); State::with_mut(|state| { let data = state.command_data.as_mut_ptr(); - let ptr = usize::try_from(state.command_data_next).unwrap(); + let ptr = unwrap_result(usize::try_from(state.command_data_next)); // "oom" as too much argument data tried to flow into the component. // Ideally this would have a better error message? @@ -312,16 +312,6 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { let closed = state.closed; let desc = state.get_mut(fd)?; - - match desc { - Descriptor::File(file) => { - wasi_filesystem::close(file.fd); - } - Descriptor::StdoutLog | Descriptor::StderrLog | Descriptor::EmptyStdin => {} - Descriptor::Socket(_) => unreachable(), - Descriptor::Closed(_) => return Err(ERRNO_BADF), - } - *desc = Descriptor::Closed(closed); state.closed = Some(fd); Ok(()) @@ -850,7 +840,17 @@ pub unsafe extern "C" fn fd_readdir( /// would disappear if `dup2()` were to be removed entirely. #[no_mangle] pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { - unreachable() + State::with_mut(|state| { + let closed = state.closed; + + let fd_desc = state.get_mut(fd)?; + let desc = replace(fd_desc, Descriptor::Closed(closed)); + + let to_desc = state.get_mut(to)?; + *to_desc = desc; + state.closed = Some(fd); + Ok(()) + }) } /// Move the offset of a file descriptor. @@ -1098,13 +1098,7 @@ pub unsafe extern "C" fn path_open( let fd = match state.closed { // No free fds; create a new one. - None => match state.push_desc(desc) { - Ok(new) => new, - Err(err) => { - wasi_filesystem::close(result); - return Err(err); - } - }, + None => state.push_desc(desc)?, // `recycle_fd` is a free fd. Some(recycle_fd) => { let recycle_desc = unwrap_result(state.get_mut(recycle_fd)); @@ -1707,6 +1701,17 @@ pub enum Descriptor { StderrLog, } +impl Drop for Descriptor { + fn drop(&mut self) { + match self { + Descriptor::File(file) => wasi_filesystem::close(file.fd), + Descriptor::StdoutLog | Descriptor::StderrLog | Descriptor::EmptyStdin => {} + Descriptor::Socket(_) => unreachable(), + Descriptor::Closed(_) => {} + } + } +} + #[repr(C)] pub struct File { /// The handle to the preview2 descriptor that this file is referencing. @@ -1881,15 +1886,15 @@ impl State { } fn init(&mut self) { - self.push_desc(Descriptor::EmptyStdin).unwrap(); - self.push_desc(Descriptor::StdoutLog).unwrap(); - self.push_desc(Descriptor::StderrLog).unwrap(); + unwrap_result(self.push_desc(Descriptor::EmptyStdin)); + unwrap_result(self.push_desc(Descriptor::StdoutLog)); + unwrap_result(self.push_desc(Descriptor::StderrLog)); } fn push_desc(&mut self, desc: Descriptor) -> Result { unsafe { let descriptors = self.descriptors.as_mut_ptr(); - let ndescriptors = usize::try_from(self.ndescriptors).unwrap(); + let ndescriptors = unwrap_result(usize::try_from(self.ndescriptors)); if ndescriptors >= (*descriptors).len() { return Err(ERRNO_NOMEM); } @@ -1903,7 +1908,7 @@ impl State { unsafe { core::slice::from_raw_parts( self.descriptors.as_ptr().cast(), - usize::try_from(self.ndescriptors).unwrap(), + unwrap_result(usize::try_from(self.ndescriptors)), ) } } @@ -1912,20 +1917,20 @@ impl State { unsafe { core::slice::from_raw_parts_mut( self.descriptors.as_mut_ptr().cast(), - usize::try_from(self.ndescriptors).unwrap(), + unwrap_result(usize::try_from(self.ndescriptors)), ) } } fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { self.descriptors() - .get(usize::try_from(fd).unwrap()) + .get(unwrap_result(usize::try_from(fd))) .ok_or(ERRNO_BADF) } fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { self.descriptors_mut() - .get_mut(usize::try_from(fd).unwrap()) + .get_mut(unwrap_result(usize::try_from(fd))) .ok_or(ERRNO_BADF) } From e0c00bd34dc4f55792e072b7cb0d85235185b974 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 15 Dec 2022 16:07:42 -0800 Subject: [PATCH 050/153] Don't print extra metadata in stdout/stderr, and implement proc_exit (#27) Add an API to implement proc_exit with, and implement it. --- src/lib.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3f7c05a4a2e..0506665b128d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_clocks, wasi_default_clocks, wasi_filesystem, wasi_logging, wasi_poll, wasi_random, - wasi_tcp, + wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_logging, wasi_poll, + wasi_random, wasi_tcp, }; use core::arch::wasm32::unreachable; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -534,7 +534,7 @@ pub unsafe extern "C" fn fd_pread( let len = (*iovs_ptr).buf_len; state.register_buffer(ptr, len); - let read_len = u32::try_from(len).unwrap(); + let read_len = unwrap_result(u32::try_from(len)); let file = state.get_file(fd)?; let data = wasi_filesystem::pread(file.fd, read_len, offset)?; assert_eq!(data.as_ptr(), ptr); @@ -613,7 +613,7 @@ pub unsafe extern "C" fn fd_read( state.register_buffer(ptr, len); - let read_len = u32::try_from(len).unwrap(); + let read_len = unwrap_result(u32::try_from(len)); let file = match state.get(fd)? { Descriptor::File(f) => f, Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { @@ -816,7 +816,7 @@ pub unsafe extern "C" fn fd_readdir( let dirent = wasi::Dirent { d_next: self.cookie, d_ino: ino.unwrap_or(0), - d_namlen: u32::try_from(name.len()).unwrap(), + d_namlen: unwrap_result(u32::try_from(name.len())), d_type: type_.into(), }; // Extend the lifetime of `name` to the `self.state` lifetime for @@ -921,21 +921,16 @@ pub unsafe extern "C" fn fd_write( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; + let bytes = slice::from_raw_parts(ptr, len); State::with(|state| match state.get(fd)? { Descriptor::File(file) => { - let bytes = wasi_filesystem::pwrite( - file.fd, - slice::from_raw_parts(ptr, len), - file.position.get(), - )?; - + let bytes = wasi_filesystem::pwrite(file.fd, bytes, file.position.get())?; *nwritten = bytes as usize; file.position.set(file.position.get() + u64::from(bytes)); Ok(()) } Descriptor::StderrLog | Descriptor::StdoutLog => { - let bytes = slice::from_raw_parts(ptr, len); let context: [u8; 3] = [b'I', b'/', b'O']; wasi_logging::log(wasi_logging::Level::Info, &context, bytes); *nwritten = len; @@ -1446,7 +1441,9 @@ pub unsafe extern "C" fn poll_oneoff( /// the environment. #[no_mangle] pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! { - unreachable() + let status = if rval == 0 { Ok(()) } else { Err(()) }; + wasi_exit::exit(status); // does not return + unreachable() // actually unreachable } /// Send a signal to the process of the calling thread. From 6dc3646f326f0c2c3f3b47ad1392824d3204d24f Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Sat, 17 Dec 2022 10:00:01 -0700 Subject: [PATCH 051/153] add tests for `wasi-random`, `wasi-clocks`, and stdin (#30) This required fleshing out the `wasi-clocks` host implementation a bit and adding a `read_vectored_at` implementation for `ReadPipe`. Signed-off-by: Joel Dice Signed-off-by: Joel Dice --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0506665b128d..16bf4d48e979 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1792,7 +1792,7 @@ const fn command_data_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 7 * size_of::(); + start -= 9 * size_of::(); // Everything else is the `command_data` allocation. start @@ -1802,7 +1802,7 @@ const fn command_data_size() -> usize { // mostly guarantees that it's not larger than one page which is relied upon // below. const _: () = { - let _size_assert: [(); PAGE_SIZE] = [(); size_of::()]; + let _size_assert: [(); PAGE_SIZE] = [(); size_of::>()]; }; #[allow(improper_ctypes)] From e20ab42597b087c672c42a16d297541a4fc550d3 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 22 Dec 2022 08:47:27 -0800 Subject: [PATCH 052/153] Add a way to get a random `u64`. (#36) This anticipates https://github.com/WebAssembly/wasi-random/pull/18. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 16bf4d48e979..f105f667f916 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1474,7 +1474,7 @@ pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { state.register_buffer(buf, buf_len); assert_eq!(buf_len as u32 as Size, buf_len); - let result = wasi_random::getrandom(buf_len as u32); + let result = wasi_random::get_random_bytes(buf_len as u32); assert_eq!(result.as_ptr(), buf); // The returned buffer's memory was allocated in `buf`, so don't separately From 36a0fa76a3f45969a7b8b6b66b8e36c03b2f9bd1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 23 Dec 2022 10:51:37 -0800 Subject: [PATCH 053/153] Fix `fd_renumber` to handle output fds that aren't previously allocated. (#38) This fixes the crates/test-programs/wasi-tests/src/bin/stdio.rs test in the Wasmtime testsuite. --- src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f105f667f916..28b399fbe96b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -843,10 +843,18 @@ pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { State::with_mut(|state| { let closed = state.closed; + // Ensure the table is big enough to contain `to`. Do this before + // looking up `fd` as it can fail due to `NOMEM`. + while Fd::from(state.ndescriptors) <= to { + let old_closed = state.closed; + let new_closed = state.push_desc(Descriptor::Closed(old_closed))?; + state.closed = Some(new_closed); + } + let fd_desc = state.get_mut(fd)?; let desc = replace(fd_desc, Descriptor::Closed(closed)); - let to_desc = state.get_mut(to)?; + let to_desc = unwrap_result(state.get_mut(to)); *to_desc = desc; state.closed = Some(fd); Ok(()) From 32c1a87a86e9a9fe3b2945c08d9a4fa9a93edd41 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 23 Dec 2022 08:41:07 -0700 Subject: [PATCH 054/153] add support for environment variables and preopens Per #31, we pass env vars and preopens via `command`, just like CLI arguments. Later, we plan to add another interface (e.g. `cli-reactor`) for reactors which need env vars and/or preopens, which will have a single function `initialize` that takes the same two parameters (but not stdio or CLI arg parameters). This also removes unused parts of `wasi-common` (e.g. args, and env vars, and preopen paths) which have been superceded by the `command` interface. Our eventual goal is to provide a more explicit interface for environment variables using WIT star imports such that the guest will import a zero-argument function (or value, when that is supported) for each variable it needs, allowing the host to statically verify that it can provide all those variables. However, star imports are not yet supported in `wit-bindgen`, and once they are, we'll likely still need a dynamic interface to support existing software. Note that I've added a `file_read` test which does not actually do anything yet, since not all the required host functions have been implemented. I plan to address that soon. Signed-off-by: Joel Dice --- src/lib.rs | 172 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 147 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 28b399fbe96b..6df2f9fd62e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,8 @@ pub unsafe extern "C" fn command( stdout: u32, args_ptr: *const WasmStr, args_len: usize, + env_vars: StrTupleList, + preopens: PreopenList, ) { // TODO: ideally turning off `command` would remove this import and the // `*.wit` metadata entirely but doing that ergonomically will likely @@ -42,19 +44,33 @@ pub unsafe extern "C" fn command( // Initialization of `State` automatically fills in some dummy // structures for fds 0, 1, and 2. Overwrite the stdin/stdout slots of 0 // and 1 with actual files. - let descriptors = state.descriptors_mut(); - if descriptors.len() < 3 { - unreachable(); + { + let descriptors = state.descriptors_mut(); + if descriptors.len() < 3 { + unreachable(); + } + descriptors[0] = Descriptor::File(File { + fd: stdin, + position: Cell::new(0), + }); + descriptors[1] = Descriptor::File(File { + fd: stdout, + position: Cell::new(0), + }); } - descriptors[0] = Descriptor::File(File { - fd: stdin, - position: Cell::new(0), - }); - descriptors[1] = Descriptor::File(File { - fd: stdout, - position: Cell::new(0), - }); state.args = Some(slice::from_raw_parts(args_ptr, args_len)); + state.env_vars = Some(slice::from_raw_parts(env_vars.base, env_vars.len)); + + let preopens = slice::from_raw_parts(preopens.base, preopens.len); + state.preopens = Some(preopens); + + for preopen in preopens { + unwrap_result(state.push_desc(Descriptor::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + }))); + } + Ok(()) }); @@ -121,6 +137,10 @@ pub unsafe extern "C" fn cabi_import_realloc( ptr } +fn align_to(ptr: usize, align: usize) -> usize { + (ptr + (align - 1)) & !(align - 1) +} + /// This allocator is only used for the `command` entrypoint. /// /// The implementation here is a bump allocator into `State::command_data` which @@ -141,14 +161,19 @@ pub unsafe extern "C" fn cabi_export_realloc( let mut ret = null_mut::(); State::with_mut(|state| { let data = state.command_data.as_mut_ptr(); - let ptr = unwrap_result(usize::try_from(state.command_data_next)); + let ptr = align_to( + unwrap_result(usize::try_from(state.command_data_next)), + align, + ); // "oom" as too much argument data tried to flow into the component. // Ideally this would have a better error message? if ptr + new_size > (*data).len() { unreachable(); } - state.command_data_next += new_size as u16; + state.command_data_next = (ptr + new_size) + .try_into() + .unwrap_or_else(|_| unreachable()); ret = (*data).as_mut_ptr().add(ptr); Ok(()) }); @@ -204,10 +229,30 @@ pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Siz /// The sizes of the buffers should match that returned by `environ_sizes_get`. #[no_mangle] pub unsafe extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { - // TODO: Use real env vars. - *environ = null_mut(); - let _ = environ_buf; - ERRNO_SUCCESS + State::with(|state| { + if let Some(list) = state.env_vars { + let mut offsets = environ; + let mut buffer = environ_buf; + for pair in list { + ptr::write(offsets, buffer); + offsets = offsets.add(1); + + ptr::copy_nonoverlapping(pair.key.ptr, buffer, pair.key.len); + buffer = buffer.add(pair.key.len); + + ptr::write(buffer, b'='); + buffer = buffer.add(1); + + ptr::copy_nonoverlapping(pair.value.ptr, buffer, pair.value.len); + buffer = buffer.add(pair.value.len); + + ptr::write(buffer, 0); + buffer = buffer.add(1); + } + } + + Ok(()) + }) } /// Return environment variable data sizes. @@ -216,10 +261,23 @@ pub unsafe extern "C" fn environ_sizes_get( environc: *mut Size, environ_buf_size: *mut Size, ) -> Errno { - // TODO: Use real env vars. - *environc = 0; - *environ_buf_size = 0; - ERRNO_SUCCESS + State::with(|state| { + if let Some(list) = state.env_vars { + *environc = list.len(); + *environ_buf_size = { + let mut sum = 0; + for pair in list { + sum += pair.key.len + pair.value.len + 2; + } + sum + }; + } else { + *environc = 0; + *environ_buf_size = 0; + } + + Ok(()) + }) } /// Return the resolution of a clock. @@ -545,16 +603,46 @@ pub unsafe extern "C" fn fd_pread( }) } +fn get_preopen(state: &State, fd: Fd) -> Option<&Preopen> { + state.preopens?.get(fd.checked_sub(3)? as usize) +} + /// Return a description of the given preopened file descriptor. #[no_mangle] pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { - unreachable() + State::with(|state| { + if let Some(preopen) = get_preopen(state, fd) { + buf.write(Prestat { + tag: 0, + u: PrestatU { + dir: PrestatDir { + pr_name_len: preopen.path.len, + }, + }, + }); + + Ok(()) + } else { + Err(ERRNO_BADF) + } + }) } /// Return a description of the given preopened file descriptor. #[no_mangle] pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { - unreachable() + State::with(|state| { + if let Some(preopen) = get_preopen(state, fd) { + if preopen.path.len < path_len as usize { + Err(ERRNO_NAMETOOLONG) + } else { + ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len); + Ok(()) + } + } else { + Err(ERRNO_NOTDIR) + } + }) } /// Write to a file descriptor, without using and updating the file descriptor's offset. @@ -660,7 +748,7 @@ pub unsafe extern "C" fn fd_readdir( // used with a fixed-sized buffer to avoid re-invoking the `readdir` // function and continuing to use the same iterator. // - // This is a bit tricky since the reqeusted state in this function call + // This is a bit tricky since the requested state in this function call // must match the prior state of the dirent stream, if any, so that's // all validated here as well. // @@ -1763,6 +1851,12 @@ struct State { /// Arguments passed to the `command` entrypoint args: Option<&'static [WasmStr]>, + /// Environment variables passed to the `command` entrypoint + env_vars: Option<&'static [StrTuple]>, + + /// Preopened directories passed to the `command` entrypoint + preopens: Option<&'static [Preopen]>, + /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. dirent_cache: DirentCache, @@ -1784,11 +1878,37 @@ impl Drop for DirEntryStream { } } +#[repr(C)] pub struct WasmStr { ptr: *const u8, len: usize, } +#[repr(C)] +pub struct StrTuple { + key: WasmStr, + value: WasmStr, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct StrTupleList { + base: *const StrTuple, + len: usize, +} + +#[repr(C)] +pub struct Preopen { + descriptor: u32, + path: WasmStr, +} + +#[repr(C)] +pub struct PreopenList { + base: *const Preopen, + len: usize, +} + const fn command_data_size() -> usize { // The total size of the struct should be a page, so start there let mut start = PAGE_SIZE; @@ -1800,7 +1920,7 @@ const fn command_data_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 9 * size_of::(); + start -= 14 * size_of::(); // Everything else is the `command_data` allocation. start @@ -1869,6 +1989,8 @@ impl State { command_data: MaybeUninit::uninit(), command_data_next: 0, args: None, + env_vars: None, + preopens: None, dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), From bad0f20d10114b444fadee7ef4f848ef718a8004 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 23 Dec 2022 11:11:51 -0700 Subject: [PATCH 055/153] implement more host filesystem functions The `file_read` test now passes. Signed-off-by: Joel Dice --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 6df2f9fd62e9..97c83c4818e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -969,6 +969,7 @@ pub unsafe extern "C" fn fd_seek( _ => return Err(ERRNO_INVAL), }; let result = wasi_filesystem::seek(file.fd, from)?; + file.position.set(result); *newoffset = result; Ok(()) }) From decb08c00dfbbdc79377c96b735084406cd6e7cd Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 23 Dec 2022 15:29:06 -0800 Subject: [PATCH 056/153] Add pseudo-streams. (#29) * Add pseudo-streams. This add a pseudo-stream type to the wasi-poll interface, and adds ways to obtain streams from command invocation and from files. In the future, it can support sockets too. With this, `command` takes streams for stdin/stdout, rather than filesystem descriptors. Streams support reading and writing, as well as skipping, repeated-element writing, and splicing from one stream to another. And there are `subscribe-*` functions to produce pseudo-futures from pseudo-streams, allowing them to be polled. This makes the polyfill somewhat more complex, but this is largely due to the polyfill being tied to the preview1 API. This replaces the `seek` and `tell` functions, and implemented `fd_seek` and `fd_tell` in terms of the polyfill's own position. Also, add a dedicated stderr API for writing to stderr in a way that tolerates strings that aren't necessarily expected to be newlines. And add a way to test whether stderr is a terminal. * Implement the host side of `poll_oneoff`. This implements pseudo-futures and subscription functions, and adds polling for streams. * Implement clock subscriptions. wasi.wit: - Remove the "timers" API from wasi-clocks, as it's now redundant with pseudo-future clock subscriptions. - Remove `subscribe-wall-clock`. Wall-clock timeouts were implemented by converting them to monotonic-clock timeouts anyway, so just make that explicit in the WASI API, and teach the polyfill how to convert wall-clock timeouts into monotonic-clock timeouts. - Move `subscribe-monotonic-clock` out of wasi-clocks and into wasi-poll, as it's closely tied to the pseudo-futures mechanism and the `poll-oneoff` implementation. - While here, fix `stream-read` and related functions to return an end-of-stream/file indicator. Code changes: - `default_wall_clock()` and `default_monotonic_clock()` now always create a new table entry, rather than holding a table index in the `WasiCtx` which could potentially dangle. - Add support for monotonic-clock poll subscriptions. - Say "wall clock" instead of "system clock" when we have a choice. * Remove the `OFlags::APPEND` flag, which is no longer used. --- src/lib.rs | 508 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 381 insertions(+), 127 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 97c83c4818e0..f8d99b45837f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ use core::mem::{self, forget, replace, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, copy_nonoverlapping, null_mut}; use core::slice; use wasi::*; -use wasi_poll::WasiFuture; +use wasi_poll::{WasiFuture, WasiStream}; mod bindings { wit_bindgen_guest_rust::generate!({ @@ -27,8 +27,8 @@ mod bindings { #[no_mangle] pub unsafe extern "C" fn command( - stdin: u32, - stdout: u32, + stdin: WasiStream, + stdout: WasiStream, args_ptr: *const WasmStr, args_len: usize, env_vars: StrTupleList, @@ -40,6 +40,7 @@ pub unsafe extern "C" fn command( if !cfg!(feature = "command") { unreachable(); } + State::with_mut(|state| { // Initialization of `State` automatically fills in some dummy // structures for fds 0, 1, and 2. Overwrite the stdin/stdout slots of 0 @@ -49,13 +50,15 @@ pub unsafe extern "C" fn command( if descriptors.len() < 3 { unreachable(); } - descriptors[0] = Descriptor::File(File { - fd: stdin, - position: Cell::new(0), + descriptors[0] = Descriptor::Streams(Streams { + input: Cell::new(Some(stdin)), + output: Cell::new(None), + type_: StreamType::Unknown, }); - descriptors[1] = Descriptor::File(File { - fd: stdout, - position: Cell::new(0), + descriptors[1] = Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(Some(stdout)), + type_: StreamType::Unknown, }); } state.args = Some(slice::from_raw_parts(args_ptr, args_len)); @@ -65,9 +68,13 @@ pub unsafe extern "C" fn command( state.preopens = Some(preopens); for preopen in preopens { - unwrap_result(state.push_desc(Descriptor::File(File { - fd: preopen.descriptor, - position: Cell::new(0), + unwrap_result(state.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + }), }))); } @@ -286,20 +293,23 @@ pub unsafe extern "C" fn environ_sizes_get( /// Note: This is similar to `clock_getres` in POSIX. #[no_mangle] pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno { - match id { - CLOCKID_MONOTONIC => { - let res = wasi_clocks::monotonic_clock_resolution( - wasi_default_clocks::default_monotonic_clock(), - ); - *resolution = res; - } - CLOCKID_REALTIME => { - let res = wasi_clocks::wall_clock_resolution(wasi_default_clocks::default_wall_clock()); - *resolution = u64::from(res.nanoseconds) + res.seconds * 1_000_000_000; + State::with(|state| { + match id { + CLOCKID_MONOTONIC => { + let res = wasi_clocks::monotonic_clock_resolution(state.default_monotonic_clock()); + *resolution = res; + } + CLOCKID_REALTIME => { + let res = wasi_clocks::wall_clock_resolution(state.default_wall_clock()); + *resolution = u64::from(res.nanoseconds) + .checked_add(res.seconds) + .and_then(|secs| secs.checked_mul(1_000_000_000)) + .ok_or(ERRNO_OVERFLOW)?; + } + _ => unreachable(), } - _ => unreachable(), - } - ERRNO_SUCCESS + Ok(()) + }) } /// Return the time value of a clock. @@ -310,18 +320,22 @@ pub unsafe extern "C" fn clock_time_get( _precision: Timestamp, time: &mut Timestamp, ) -> Errno { - match id { - CLOCKID_MONOTONIC => { - *time = - wasi_clocks::monotonic_clock_now(wasi_default_clocks::default_monotonic_clock()); - } - CLOCKID_REALTIME => { - let res = wasi_clocks::wall_clock_now(wasi_default_clocks::default_wall_clock()); - *time = u64::from(res.nanoseconds) + res.seconds * 1_000_000_000; + State::with(|state| { + match id { + CLOCKID_MONOTONIC => { + *time = wasi_clocks::monotonic_clock_now(state.default_monotonic_clock()); + } + CLOCKID_REALTIME => { + let res = wasi_clocks::wall_clock_now(state.default_wall_clock()); + *time = u64::from(res.nanoseconds) + .checked_add(res.seconds) + .and_then(|secs| secs.checked_mul(1_000_000_000)) + .ok_or(ERRNO_OVERFLOW)?; + } + _ => unreachable(), } - _ => unreachable(), - } - ERRNO_SUCCESS + Ok(()) + }) } /// Provide file advisory information on a file descriptor. @@ -392,7 +406,10 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { State::with(|state| match state.get(fd)? { - Descriptor::File(file) => { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => { let flags = wasi_filesystem::flags(file.fd)?; let type_ = wasi_filesystem::todo_type(file.fd)?; @@ -406,9 +423,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { if !flags.contains(wasi_filesystem::DescriptorFlags::WRITE) { fs_rights_base &= !RIGHTS_FD_WRITE; } - if flags.contains(wasi_filesystem::DescriptorFlags::APPEND) { - fs_flags |= FDFLAGS_APPEND; - } if flags.contains(wasi_filesystem::DescriptorFlags::DSYNC) { fs_flags |= FDFLAGS_DSYNC; } @@ -444,7 +458,39 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } - Descriptor::EmptyStdin => { + Descriptor::Streams(Streams { + input, + output, + type_: StreamType::Socket(_), + }) + | Descriptor::Streams(Streams { + input, + output, + type_: StreamType::Unknown, + }) => { + let fs_filetype = FILETYPE_UNKNOWN; + let fs_flags = 0; + let mut fs_rights_base = 0; + if input.get().is_some() { + fs_rights_base |= RIGHTS_FD_READ; + } + if output.get().is_some() { + fs_rights_base |= RIGHTS_FD_WRITE; + } + let fs_rights_inheriting = fs_rights_base; + stat.write(Fdstat { + fs_filetype, + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) + } + Descriptor::Streams(Streams { + input, + output, + type_: StreamType::EmptyStdin, + }) => { let fs_filetype = FILETYPE_UNKNOWN; let fs_flags = 0; let fs_rights_base = RIGHTS_FD_READ; @@ -457,8 +503,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } - // TODO: Handle socket case here once `wasi-tcp` has been fleshed out - Descriptor::Socket(_) => unreachable(), Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -468,9 +512,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { let mut new_flags = wasi_filesystem::DescriptorFlags::empty(); - if flags & FDFLAGS_APPEND == FDFLAGS_APPEND { - new_flags |= wasi_filesystem::DescriptorFlags::APPEND; - } if flags & FDFLAGS_DSYNC == FDFLAGS_DSYNC { new_flags |= wasi_filesystem::DescriptorFlags::DSYNC; } @@ -594,12 +635,18 @@ pub unsafe extern "C" fn fd_pread( let read_len = unwrap_result(u32::try_from(len)); let file = state.get_file(fd)?; - let data = wasi_filesystem::pread(file.fd, read_len, offset)?; + let (data, end) = wasi_filesystem::pread(file.fd, read_len, offset)?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); - *nread = data.len(); + + let len = data.len(); forget(data); - Ok(()) + if !end && len == 0 { + Err(ERRNO_INTR) + } else { + *nread = len; + Ok(()) + } }) } @@ -695,32 +742,42 @@ pub unsafe extern "C" fn fd_read( return ERRNO_SUCCESS; } - State::with(|state| { - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + State::with(|state| { state.register_buffer(ptr, len); - let read_len = unwrap_result(u32::try_from(len)); - let file = match state.get(fd)? { - Descriptor::File(f) => f, - Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { - return Err(ERRNO_BADF) + match state.get(fd)? { + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_read_stream()?; + + let read_len = unwrap_result(u32::try_from(len)); + let wasi_stream = streams.get_read_stream()?; + let (data, end) = + wasi_poll::read_stream(wasi_stream, read_len).map_err(|_| ERRNO_IO)?; + + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + + // If this is a file, keep the current-position pointer up to date. + if let StreamType::File(file) = &streams.type_ { + file.position.set(file.position.get() + data.len() as u64); + } + + let len = data.len(); + forget(data); + if !end && len == 0 { + Err(ERRNO_INTR) + } else { + *nread = len; + Ok(()) + } } - // TODO: Handle socket case here once `wasi-tcp` has been fleshed out - Descriptor::Socket(_) => unreachable(), - Descriptor::EmptyStdin => { - *nread = 0; - return Ok(()); + Descriptor::StdoutLog | Descriptor::StderrLog | Descriptor::Closed(_) => { + Err(ERRNO_BADF) } - }; - let data = wasi_filesystem::pread(file.fd, read_len, file.position.get())?; - assert_eq!(data.as_ptr(), ptr); - assert!(data.len() <= len); - *nread = data.len(); - file.position.set(file.position.get() + data.len() as u64); - forget(data); - Ok(()) + } }) } @@ -959,19 +1016,26 @@ pub unsafe extern "C" fn fd_seek( newoffset: *mut Filesize, ) -> Errno { State::with(|state| { - let file = state.get_seekable_file(fd)?; - // It's ok to cast these indices; the WASI API will fail if - // the resulting values are out of range. - let from = match whence { - WHENCE_SET => wasi_filesystem::SeekFrom::Set(offset as _), - WHENCE_CUR => wasi_filesystem::SeekFrom::Cur(offset), - WHENCE_END => wasi_filesystem::SeekFrom::End(offset as _), - _ => return Err(ERRNO_INVAL), - }; - let result = wasi_filesystem::seek(file.fd, from)?; - file.position.set(result); - *newoffset = result; - Ok(()) + let stream = state.get_seekable_stream(fd)?; + + // Seeking only works on files. + if let StreamType::File(file) = &stream.type_ { + // It's ok to cast these indices; the WASI API will fail if + // the resulting values are out of range. + let from = match whence { + WHENCE_SET => offset, + WHENCE_CUR => (file.position.get() as i64).wrapping_add(offset), + WHENCE_END => (wasi_filesystem::stat(file.fd)?.size as i64) + offset, + _ => return Err(ERRNO_INVAL), + }; + stream.input.set(None); + stream.output.set(None); + file.position.set(from as u64); + *newoffset = from as u64; + Ok(()) + } else { + Err(ERRNO_SPIPE) + } }) } @@ -992,7 +1056,7 @@ pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { State::with(|state| { let file = state.get_seekable_file(fd)?; - *offset = wasi_filesystem::tell(file.fd)?; + *offset = file.position.get() as Filesize; Ok(()) }) } @@ -1021,10 +1085,16 @@ pub unsafe extern "C" fn fd_write( let bytes = slice::from_raw_parts(ptr, len); State::with(|state| match state.get(fd)? { - Descriptor::File(file) => { - let bytes = wasi_filesystem::pwrite(file.fd, bytes, file.position.get())?; + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_write_stream()?; + let bytes = wasi_poll::write_stream(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; + + // If this is a file, keep the current-position pointer up to date. + if let StreamType::File(file) = &streams.type_ { + file.position.set(file.position.get() + u64::from(bytes)); + } + *nwritten = bytes as usize; - file.position.set(file.position.get() + u64::from(bytes)); Ok(()) } Descriptor::StderrLog | Descriptor::StdoutLog => { @@ -1033,9 +1103,6 @@ pub unsafe extern "C" fn fd_write( *nwritten = len; Ok(()) } - // TODO: Handle socket case here once `wasi-tcp` has been fleshed out - Descriptor::Socket(_) => unreachable(), - Descriptor::EmptyStdin => Err(ERRNO_INVAL), Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -1183,9 +1250,13 @@ pub unsafe extern "C" fn path_open( let file = state.get_dir(fd)?; let result = wasi_filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; - let desc = Descriptor::File(File { - fd: result, - position: Cell::new(0), + let desc = Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: result, + position: Cell::new(0), + }), }); let fd = match state.closed { @@ -1410,35 +1481,66 @@ pub unsafe extern "C" fn poll_oneoff( }; for subscription in subscriptions { + const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); + const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); + const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); futures.push(match subscription.u.tag { - 0 => { + EVENTTYPE_CLOCK => { let clock = &subscription.u.u.clock; let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) == 0; match clock.id { - CLOCKID_REALTIME => wasi_clocks::subscribe_wall_clock( - wasi_clocks::Datetime { - seconds: unwrap((clock.timeout / 1_000_000_000).try_into().ok()), - nanoseconds: unwrap( - (clock.timeout % 1_000_000_000).try_into().ok(), - ), - }, - absolute, - ), + CLOCKID_REALTIME => { + let timeout = if absolute { + // Convert `clock.timeout` to `Datetime`. + let mut datetime = wasi_clocks::Datetime { + seconds: clock.timeout / 1_000_000_000, + nanoseconds: (clock.timeout % 1_000_000_000) as _, + }; + + // Subtract `now`. + let now = wasi_clocks::wall_clock_now(state.default_wall_clock()); + datetime.seconds -= now.seconds; + if datetime.nanoseconds < now.nanoseconds { + datetime.seconds -= 1; + datetime.nanoseconds += 1_000_000_000; + } + datetime.nanoseconds -= now.nanoseconds; + + // Convert to nanoseconds. + let nanos = datetime + .seconds + .checked_mul(1_000_000_000) + .ok_or(ERRNO_OVERFLOW)?; + nanos + .checked_add(datetime.nanoseconds.into()) + .ok_or(ERRNO_OVERFLOW)? + } else { + clock.timeout + }; - CLOCKID_MONOTONIC => { - wasi_clocks::subscribe_monotonic_clock(clock.timeout, absolute) + wasi_poll::subscribe_monotonic_clock( + state.default_monotonic_clock(), + timeout, + false, + ) } + CLOCKID_MONOTONIC => wasi_poll::subscribe_monotonic_clock( + state.default_monotonic_clock(), + clock.timeout, + absolute, + ), + _ => return Err(ERRNO_INVAL), } } - 1 => wasi_tcp::subscribe_read( - state.get_socket(subscription.u.u.fd_read.file_descriptor)?, + EVENTTYPE_FD_READ => wasi_poll::subscribe_read( + state.get_read_stream(subscription.u.u.fd_read.file_descriptor)?, ), - 2 => wasi_tcp::subscribe_write( - state.get_socket(subscription.u.u.fd_write.file_descriptor)?, + EVENTTYPE_FD_WRITE => wasi_poll::subscribe_write( + state.get_write_stream(subscription.u.u.fd_write.file_descriptor)?, ), _ => return Err(ERRNO_INVAL), @@ -1663,9 +1765,6 @@ fn flags_from_descriptor_flags( if fdflags & wasi::FDFLAGS_RSYNC == wasi::FDFLAGS_RSYNC { flags |= wasi_filesystem::DescriptorFlags::RSYNC; } - if fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND { - flags |= wasi_filesystem::DescriptorFlags::APPEND; - } if fdflags & wasi::FDFLAGS_NONBLOCK == wasi::FDFLAGS_NONBLOCK { flags |= wasi_filesystem::DescriptorFlags::NONBLOCK; } @@ -1775,15 +1874,13 @@ fn black_box(x: Errno) -> Errno { } #[repr(C)] -pub enum Descriptor { +enum Descriptor { + /// A closed descriptor, holding a reference to the previous closed + /// descriptor to support reusing them. Closed(Option), - File(File), - Socket(wasi_tcp::Socket), - /// Initial state of fd 0 when `State` is created, representing a standard - /// input that is empty as it hasn't been configured yet. This is the - /// permanent fd 0 marker if `command` is never called. - EmptyStdin, + /// Input and/or output wasi-streams, along with stream metadata. + Streams(Streams), /// Initial state of fd 1 when `State` is created, representing that writes /// to `fd_write` will go to a call to `log`. This is overwritten during @@ -1795,21 +1892,99 @@ pub enum Descriptor { StderrLog, } +/// Input and/or output wasi-streams, along with a stream type that +/// identifies what kind of stream they are and possibly supporting +/// type-specific operations like seeking. +struct Streams { + /// The output stream, if present. + input: Cell>, + + /// The input stream, if present. + output: Cell>, + + /// Information about the source of the stream. + type_: StreamType, +} + +impl Streams { + /// Return the input stream, initializing it on the fly if needed. + fn get_read_stream(&self) -> Result { + match &self.input.get() { + Some(wasi_stream) => Ok(*wasi_stream), + None => match &self.type_ { + // For files, we may have adjusted the position for seeking, so + // create a new stream. + StreamType::File(file) => { + let input = wasi_filesystem::read_via_stream(file.fd, file.position.get())?; + self.input.set(Some(input)); + Ok(input) + } + _ => Err(ERRNO_BADF), + }, + } + } + + /// Return the output stream, initializing it on the fly if needed. + fn get_write_stream(&self) -> Result { + match &self.output.get() { + Some(wasi_stream) => Ok(*wasi_stream), + None => match &self.type_ { + // For files, we may have adjusted the position for seeking, so + // create a new stream. + StreamType::File(file) => { + let output = wasi_filesystem::write_via_stream(file.fd, file.position.get())?; + self.output.set(Some(output)); + Ok(output) + } + _ => Err(ERRNO_BADF), + }, + } + } +} + +#[allow(dead_code)] // until Socket is implemented +enum StreamType { + /// It's a valid stream but we don't know where it comes from. + Unknown, + + /// A stdin source containing no bytes. + EmptyStdin, + + /// Streaming data with a file. + File(File), + + /// Streaming data with a socket. + Socket(wasi_tcp::Socket), +} + impl Drop for Descriptor { fn drop(&mut self) { match self { - Descriptor::File(file) => wasi_filesystem::close(file.fd), - Descriptor::StdoutLog | Descriptor::StderrLog | Descriptor::EmptyStdin => {} - Descriptor::Socket(_) => unreachable(), + Descriptor::Streams(stream) => { + if let Some(input) = stream.input.get() { + wasi_poll::drop_stream(input); + } + if let Some(output) = stream.output.get() { + wasi_poll::drop_stream(output); + } + match &stream.type_ { + StreamType::File(file) => wasi_filesystem::close(file.fd), + StreamType::Socket(_) => unreachable(), + StreamType::EmptyStdin | StreamType::Unknown => {} + } + } + Descriptor::StdoutLog | Descriptor::StderrLog => {} Descriptor::Closed(_) => {} } } } #[repr(C)] -pub struct File { +struct File { /// The handle to the preview2 descriptor that this file is referencing. fd: wasi_filesystem::Descriptor, + + /// The current-position pointer. position: Cell, } @@ -1861,6 +2036,12 @@ struct State { /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. dirent_cache: DirentCache, + + /// The clock handle for `CLOCKID_MONOTONIC`. + default_monotonic_clock: Cell>, + + /// The clock handle for `CLOCKID_REALTIME`. + default_wall_clock: Cell>, } struct DirentCache { @@ -1921,7 +2102,7 @@ const fn command_data_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 14 * size_of::(); + start -= 17 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2004,6 +2185,8 @@ impl State { }), path_data: UnsafeCell::new(MaybeUninit::uninit()), }, + default_monotonic_clock: Cell::new(None), + default_wall_clock: Cell::new(None), })); &*ret }; @@ -2014,7 +2197,11 @@ impl State { } fn init(&mut self) { - unwrap_result(self.push_desc(Descriptor::EmptyStdin)); + unwrap_result(self.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::Unknown, + }))); unwrap_result(self.push_desc(Descriptor::StdoutLog)); unwrap_result(self.push_desc(Descriptor::StderrLog)); } @@ -2062,17 +2249,32 @@ impl State { .ok_or(ERRNO_BADF) } + fn get_stream_with_error(&self, fd: Fd, error: Errno) -> Result<&Streams, Errno> { + match self.get(fd)? { + Descriptor::Streams(streams) => Ok(streams), + Descriptor::Closed(_) => Err(ERRNO_BADF), + _ => Err(error), + } + } + fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { - Descriptor::File(file) => Ok(file), + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => Ok(file), Descriptor::Closed(_) => Err(ERRNO_BADF), _ => Err(error), } } + #[allow(dead_code)] // until Socket is implemented fn get_socket(&self, fd: Fd) -> Result { match self.get(fd)? { - Descriptor::Socket(socket) => Ok(*socket), + Descriptor::Streams(Streams { + type_: StreamType::Socket(socket), + .. + }) => Ok(*socket), Descriptor::Closed(_) => Err(ERRNO_BADF), _ => Err(ERRNO_INVAL), } @@ -2090,10 +2292,62 @@ impl State { self.get_file_with_error(fd, ERRNO_SPIPE) } + fn get_seekable_stream(&self, fd: Fd) -> Result<&Streams, Errno> { + self.get_stream_with_error(fd, ERRNO_SPIPE) + } + + fn get_read_stream(&self, fd: Fd) -> Result { + match self.get(fd)? { + Descriptor::Streams(streams) => streams.get_read_stream(), + Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { + Err(ERRNO_BADF) + } + } + } + + fn get_write_stream(&self, fd: Fd) -> Result { + match self.get(fd)? { + Descriptor::Streams(streams) => streams.get_write_stream(), + Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { + Err(ERRNO_BADF) + } + } + } + /// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy /// the next request. fn register_buffer(&self, buf: *mut u8, buf_len: usize) { self.buffer_ptr.set(buf); self.buffer_len.set(buf_len); } + + /// Return a handle to the default wall clock, creating one if we + /// don't already have one. + fn default_wall_clock(&self) -> Fd { + match self.default_wall_clock.get() { + Some(fd) => fd, + None => self.init_default_wall_clock(), + } + } + + fn init_default_wall_clock(&self) -> Fd { + let clock = wasi_default_clocks::default_wall_clock(); + self.default_wall_clock.set(Some(clock)); + clock + } + + /// Return a handle to the default monotonic clock, creating one if we + /// don't already have one. + fn default_monotonic_clock(&self) -> Fd { + match self.default_monotonic_clock.get() { + Some(fd) => fd, + None => self.init_default_monotonic_clock(), + } + } + + fn init_default_monotonic_clock(&self) -> Fd { + let clock = wasi_default_clocks::default_monotonic_clock(); + self.default_monotonic_clock.set(Some(clock)); + clock + } } From 21ba68b41bf287965c50d62e1aaf0bf0e11a0d4f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 23 Dec 2022 20:00:48 -0800 Subject: [PATCH 057/153] Implement `command` exit statuses. (#40) Add a `result` return type to `command` so that it can indicate success or failure. The idea here is that this isn't a full `i32` return value because the meaning of return values isn't portable across platforms. Also, Typed Main is a better long-term answer for users that want rich error return values from commands. --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f8d99b45837f..5ad723e77266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ pub unsafe extern "C" fn command( args_len: usize, env_vars: StrTupleList, preopens: PreopenList, -) { +) -> Result<(), ()> { // TODO: ideally turning off `command` would remove this import and the // `*.wit` metadata entirely but doing that ergonomically will likely // require some form of `use` to avoid duplicating lots of `*.wit` bits. @@ -86,6 +86,7 @@ pub unsafe extern "C" fn command( fn _start(); } _start(); + Ok(()) } // We're avoiding static initializers, so replace the standard assert macros From 359a7964f9d1957e8463944fed2c58a46964d403 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 4 Jan 2023 10:27:29 -0700 Subject: [PATCH 058/153] implement `wasi-filesystem::readdir` and related functions (#45) * implement `wasi-filesystem::readdir` and related functions This adds a `directory_list` test and provides the required host implementation. I've also added a file length check to the `file_read` test, just to cover a bit more of the API. Signed-off-by: Joel Dice * fix memory corruption in `fd_readdir` polyfill We were copying `name.len() * 256` bytes instead of just `name.len()` bytes, which was overwriting other parts of `State` and causing untold havoc. Signed-off-by: Joel Dice * check type of entry in `Table::delete` Signed-off-by: Joel Dice Signed-off-by: Joel Dice --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5ad723e77266..d6c0a085f150 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -911,8 +911,8 @@ pub unsafe extern "C" fn fd_readdir( state.dirent_cache.cookie.set(cookie - 1); state.dirent_cache.cached_dirent.set(dirent); std::ptr::copy( - name.as_ptr().cast(), - (*state.dirent_cache.path_data.get()).as_mut_ptr(), + name.as_ptr().cast::(), + (*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8, name.len(), ); break; From 3a49d68588ea3d5d7b3cf1862842840643a11edc Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 6 Jan 2023 10:52:53 -0600 Subject: [PATCH 059/153] Minor cleanups (#49) * Use `use` imports consistently. * Rename `flags_from_descriptor_flags` to `descriptor_flags_from_flags`. * Rename `black_box` to `obscure`. * Make some comments be doc comments. * Use a hyphen for compound adjectives. * Delete an unused variable. * Update the name of `change-file-permissions-at`. * Use generated typedefs instead of hard-coding u64. * Use core::hint::black_box now that it's stabilized. --- src/lib.rs | 81 +++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6c0a085f150..4cf4a8289175 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,13 @@ use crate::bindings::{ wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_logging, wasi_poll, wasi_random, wasi_tcp, }; -use core::arch::wasm32::unreachable; +use core::arch::wasm32::{self, unreachable}; use core::cell::{Cell, RefCell, UnsafeCell}; +use core::cmp::min; use core::ffi::c_void; -use core::mem::{self, forget, replace, size_of, ManuallyDrop, MaybeUninit}; -use core::ptr::{self, copy_nonoverlapping, null_mut}; +use core::hint::black_box; +use core::mem::{align_of, forget, replace, size_of, ManuallyDrop, MaybeUninit}; +use core::ptr::{self, null_mut}; use core::slice; use wasi::*; use wasi_poll::{WasiFuture, WasiStream}; @@ -302,7 +304,7 @@ pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errn } CLOCKID_REALTIME => { let res = wasi_clocks::wall_clock_resolution(state.default_wall_clock()); - *resolution = u64::from(res.nanoseconds) + *resolution = Timestamp::from(res.nanoseconds) .checked_add(res.seconds) .and_then(|secs| secs.checked_mul(1_000_000_000)) .ok_or(ERRNO_OVERFLOW)?; @@ -328,7 +330,7 @@ pub unsafe extern "C" fn clock_time_get( } CLOCKID_REALTIME => { let res = wasi_clocks::wall_clock_now(state.default_wall_clock()); - *time = u64::from(res.nanoseconds) + *time = Timestamp::from(res.nanoseconds) .checked_add(res.seconds) .and_then(|secs| secs.checked_mul(1_000_000_000)) .ok_or(ERRNO_OVERFLOW)?; @@ -763,7 +765,8 @@ pub unsafe extern "C" fn fd_read( // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { - file.position.set(file.position.get() + data.len() as u64); + file.position + .set(file.position.get() + data.len() as wasi_filesystem::Filesize); } let len = data.len(); @@ -799,7 +802,7 @@ pub unsafe extern "C" fn fd_readdir( cookie: Dircookie, bufused: *mut Size, ) -> Errno { - let mut buf = core::slice::from_raw_parts_mut(buf, buf_len); + let mut buf = slice::from_raw_parts_mut(buf, buf_len); return State::with(|state| { // First determine if there's an entry in the dirent cache to use. This // is done to optimize the use case where a large directory is being @@ -869,7 +872,7 @@ pub unsafe extern "C" fn fd_readdir( // Copy a `dirent` describing this entry into the destination `buf`, // truncating it if it doesn't fit entirely. - let bytes = core::slice::from_raw_parts( + let bytes = slice::from_raw_parts( (&dirent as *const wasi::Dirent).cast::(), size_of::(), ); @@ -883,11 +886,7 @@ pub unsafe extern "C" fn fd_readdir( // Note that this might be a 0-byte copy if the `dirent` was // truncated or fit entirely into the destination. let name_bytes_to_copy = buf.len().min(name.len()); - core::ptr::copy_nonoverlapping( - name.as_ptr().cast(), - buf.as_mut_ptr(), - name_bytes_to_copy, - ); + ptr::copy_nonoverlapping(name.as_ptr().cast(), buf.as_mut_ptr(), name_bytes_to_copy); buf = &mut buf[name_bytes_to_copy..]; @@ -910,7 +909,7 @@ pub unsafe extern "C" fn fd_readdir( state.dirent_cache.for_fd.set(fd); state.dirent_cache.cookie.set(cookie - 1); state.dirent_cache.cached_dirent.set(dirent); - std::ptr::copy( + ptr::copy( name.as_ptr().cast::(), (*state.dirent_cache.path_data.get()).as_mut_ptr() as *mut u8, name.len(), @@ -945,7 +944,7 @@ pub unsafe extern "C" fn fd_readdir( let ptr = (*(*self.state.dirent_cache.path_data.get()).as_ptr()) .as_ptr() .cast(); - let buffer = core::slice::from_raw_parts(ptr, dirent.d_namlen as usize); + let buffer = slice::from_raw_parts(ptr, dirent.d_namlen as usize); Ok((dirent, buffer)) }); } @@ -969,7 +968,7 @@ pub unsafe extern "C" fn fd_readdir( // this iterator since the data for the name lives within state. let name = unsafe { assert_eq!(name.as_ptr(), self.state.path_buf.get().cast()); - core::slice::from_raw_parts(name.as_ptr().cast(), name.len()) + slice::from_raw_parts(name.as_ptr().cast(), name.len()) }; Some(Ok((dirent, name))) } @@ -1031,8 +1030,8 @@ pub unsafe extern "C" fn fd_seek( }; stream.input.set(None); stream.output.set(None); - file.position.set(from as u64); - *newoffset = from as u64; + file.position.set(from as wasi_filesystem::Filesize); + *newoffset = from as wasi_filesystem::Filesize; Ok(()) } else { Err(ERRNO_SPIPE) @@ -1092,7 +1091,8 @@ pub unsafe extern "C" fn fd_write( // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { - file.position.set(file.position.get() + u64::from(bytes)); + file.position + .set(file.position.get() + wasi_filesystem::Filesize::from(bytes)); } *nwritten = bytes as usize; @@ -1243,12 +1243,10 @@ pub unsafe extern "C" fn path_open( let path = slice::from_raw_parts(path_ptr, path_len); let at_flags = at_flags_from_lookupflags(dirflags); let o_flags = o_flags_from_oflags(oflags); - let flags = flags_from_descriptor_flags(fs_rights_base, fdflags); + let flags = descriptor_flags_from_flags(fs_rights_base, fdflags); let mode = wasi_filesystem::Mode::READABLE | wasi_filesystem::Mode::WRITEABLE; State::with_mut(|state| { - let desc = state.get_mut(fd)?; - let file = state.get_dir(fd)?; let result = wasi_filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; let desc = Descriptor::Streams(Streams { @@ -1316,8 +1314,8 @@ pub unsafe extern "C" fn path_readlink( if use_state_buf { // Preview1 follows POSIX in truncating the returned path if it // doesn't fit. - let len = core::cmp::min(path.len(), buf_len); - copy_nonoverlapping(path.as_ptr().cast(), buf, len); + let len = min(path.len(), buf_len); + ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len); } // The returned string's memory was allocated in `buf`, so don't separately @@ -1429,6 +1427,8 @@ impl From for Errno { use wasi_tcp::Error::*; match error { + // Use a black box to prevent the optimizer from generating a + // lookup table, which would require a static initializer. ConnectionAborted => black_box(ERRNO_CONNABORTED), ConnectionRefused => ERRNO_CONNREFUSED, ConnectionReset => ERRNO_CONNRESET, @@ -1456,13 +1456,13 @@ pub unsafe extern "C" fn poll_oneoff( // and the other to store the bool results. // // First, we assert that this is possible: - assert!(mem::align_of::() >= mem::align_of::()); - assert!(mem::align_of::() >= mem::align_of::()); + assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); assert!( - unwrap(nsubscriptions.checked_mul(mem::size_of::())) + unwrap(nsubscriptions.checked_mul(size_of::())) > unwrap( - unwrap(nsubscriptions.checked_mul(mem::size_of::())) - .checked_add(unwrap(nsubscriptions.checked_mul(mem::size_of::()))) + unwrap(nsubscriptions.checked_mul(size_of::())) + .checked_add(unwrap(nsubscriptions.checked_mul(size_of::()))) ) ); @@ -1472,7 +1472,7 @@ pub unsafe extern "C" fn poll_oneoff( State::with(|state| { state.register_buffer( results, - unwrap(nsubscriptions.checked_mul(mem::size_of::())), + unwrap(nsubscriptions.checked_mul(size_of::())), ); let mut futures = Futures { @@ -1552,7 +1552,7 @@ pub unsafe extern "C" fn poll_oneoff( assert!(vec.len() == nsubscriptions); assert_eq!(vec.as_ptr(), results); - mem::forget(vec); + forget(vec); drop(futures); @@ -1746,7 +1746,7 @@ fn o_flags_from_oflags(flags: Oflags) -> wasi_filesystem::OFlags { o_flags } -fn flags_from_descriptor_flags( +fn descriptor_flags_from_flags( rights: Rights, fdflags: Fdflags, ) -> wasi_filesystem::DescriptorFlags { @@ -1776,6 +1776,8 @@ impl From for Errno { #[inline(never)] // Disable inlining as this is bulky and relatively cold. fn from(err: wasi_filesystem::Errno) -> Errno { match err { + // Use a black box to prevent the optimizer from generating a + // lookup table, which would require a static initializer. wasi_filesystem::Errno::Toobig => black_box(ERRNO_2BIG), wasi_filesystem::Errno::Access => ERRNO_ACCES, wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, @@ -1867,13 +1869,6 @@ impl From for wasi::Filetype { } } -// A black box to prevent the optimizer from generating a lookup table -// from the match above, which would require a static initializer. -fn black_box(x: Errno) -> Errno { - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - x -} - #[repr(C)] enum Descriptor { /// A closed descriptor, holding a reference to the previous closed @@ -1986,7 +1981,7 @@ struct File { fd: wasi_filesystem::Descriptor, /// The current-position pointer. - position: Cell, + position: Cell, } const PAGE_SIZE: usize = 65536; @@ -2156,7 +2151,7 @@ impl State { #[cold] fn new() -> &'static RefCell { - let grew = core::arch::wasm32::memory_grow(0, 1); + let grew = wasm32::memory_grow(0, 1); if grew == usize::MAX { unreachable(); } @@ -2222,7 +2217,7 @@ impl State { fn descriptors(&self) -> &[Descriptor] { unsafe { - core::slice::from_raw_parts( + slice::from_raw_parts( self.descriptors.as_ptr().cast(), unwrap_result(usize::try_from(self.ndescriptors)), ) @@ -2231,7 +2226,7 @@ impl State { fn descriptors_mut(&mut self) -> &mut [Descriptor] { unsafe { - core::slice::from_raw_parts_mut( + slice::from_raw_parts_mut( self.descriptors.as_mut_ptr().cast(), unwrap_result(usize::try_from(self.ndescriptors)), ) From 72feade7d9234e6848ee1ca79959f548aa9398f8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 9 Jan 2023 11:13:03 -0600 Subject: [PATCH 060/153] Try to detect memory corruption with "magic" canaries (#52) After two separate rounds of memory corruption now happening it's probably best to place some protections in place to try to detect when this happens earlier rather than much later after the fact. --- src/lib.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4cf4a8289175..9626de8fd5eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ pub unsafe extern "C" fn command( args_len: usize, env_vars: StrTupleList, preopens: PreopenList, -) -> Result<(), ()> { +) -> u32 { // TODO: ideally turning off `command` would remove this import and the // `*.wit` metadata entirely but doing that ergonomically will likely // require some form of `use` to avoid duplicating lots of `*.wit` bits. @@ -88,7 +88,7 @@ pub unsafe extern "C" fn command( fn _start(); } _start(); - Ok(()) + 0 } // We're avoiding static initializers, so replace the standard assert macros @@ -1996,7 +1996,15 @@ const MAX_DESCRIPTORS: usize = 128; /// Maximum number of bytes to cache for a `wasi::Dirent` plus its path name. const DIRENT_CACHE: usize = 256; +/// A canary value to detect memory corruption within `State`. +const MAGIC: u32 = u32::from_le_bytes(*b"ugh!"); + +#[repr(C)] // used for now to keep magic1 and magic2 at the start and end struct State { + /// A canary constant value located at the beginning of this structure to + /// try to catch memory corruption coming from the bottom. + magic1: u32, + /// Used by `register_buffer` to coordinate allocations with /// `cabi_import_realloc`. buffer_ptr: Cell<*mut u8>, @@ -2038,6 +2046,10 @@ struct State { /// The clock handle for `CLOCKID_REALTIME`. default_wall_clock: Cell>, + + /// Another canary constant located at the end of the structure to catch + /// memory corruption coming from the bottom. + magic2: u32, } struct DirentCache { @@ -2098,7 +2110,7 @@ const fn command_data_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 17 * size_of::(); + start -= 21 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2121,6 +2133,8 @@ impl State { fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno { let ptr = State::ptr(); let ptr = ptr.try_borrow().unwrap_or_else(|_| unreachable()); + assert_eq!(ptr.magic1, MAGIC); + assert_eq!(ptr.magic2, MAGIC); let ret = f(&*ptr); match ret { Ok(()) => ERRNO_SUCCESS, @@ -2131,6 +2145,8 @@ impl State { fn with_mut(f: impl FnOnce(&mut State) -> Result<(), Errno>) -> Errno { let ptr = State::ptr(); let mut ptr = ptr.try_borrow_mut().unwrap_or_else(|_| unreachable()); + assert_eq!(ptr.magic1, MAGIC); + assert_eq!(ptr.magic2, MAGIC); let ret = f(&mut *ptr); match ret { Ok(()) => ERRNO_SUCCESS, @@ -2158,6 +2174,8 @@ impl State { let ret = (grew * PAGE_SIZE) as *mut RefCell; let ret = unsafe { ret.write(RefCell::new(State { + magic1: MAGIC, + magic2: MAGIC, buffer_ptr: Cell::new(null_mut()), buffer_len: Cell::new(0), ndescriptors: 0, From 49cd5e4efb628fca8eaf2e27d516f23ab489c857 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 9 Jan 2023 12:22:56 -0800 Subject: [PATCH 061/153] Implement file append functionality. (#50) * Implement file append functionality. - In the preview1-to-preview2 polyfill, using `append_via_stream`. - In the host implementation, using a new system-interface `FileIoExt::append` function. * Add a basic testcase. --- src/lib.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9626de8fd5eb..9714469bb6be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,7 @@ pub unsafe extern "C" fn command( type_: StreamType::File(File { fd: preopen.descriptor, position: Cell::new(0), + append: false, }), }))); } @@ -1091,8 +1092,13 @@ pub unsafe extern "C" fn fd_write( // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { - file.position - .set(file.position.get() + wasi_filesystem::Filesize::from(bytes)); + // But don't update if we're in append mode. Strictly speaking, + // we should set the position to the new end of the file, but + // we don't have an API to do that atomically. + if !file.append { + file.position + .set(file.position.get() + wasi_filesystem::Filesize::from(bytes)); + } } *nwritten = bytes as usize; @@ -1245,6 +1251,7 @@ pub unsafe extern "C" fn path_open( let o_flags = o_flags_from_oflags(oflags); let flags = descriptor_flags_from_flags(fs_rights_base, fdflags); let mode = wasi_filesystem::Mode::READABLE | wasi_filesystem::Mode::WRITEABLE; + let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; State::with_mut(|state| { let file = state.get_dir(fd)?; @@ -1255,6 +1262,7 @@ pub unsafe extern "C" fn path_open( type_: StreamType::File(File { fd: result, position: Cell::new(0), + append, }), }); @@ -1928,7 +1936,11 @@ impl Streams { // For files, we may have adjusted the position for seeking, so // create a new stream. StreamType::File(file) => { - let output = wasi_filesystem::write_via_stream(file.fd, file.position.get())?; + let output = if file.append { + wasi_filesystem::append_via_stream(file.fd)? + } else { + wasi_filesystem::write_via_stream(file.fd, file.position.get())? + }; self.output.set(Some(output)); Ok(output) } @@ -1982,6 +1994,9 @@ struct File { /// The current-position pointer. position: Cell, + + /// In append mode, all writes append to the file. + append: bool, } const PAGE_SIZE: usize = 65536; From e34b6d90fdd5c73f17d40a8a1e4d8fe77803ce14 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 27 Dec 2022 15:56:14 -0800 Subject: [PATCH 062/153] Fix some bugs turned up in the Wasmtime testsuite. --- src/lib.rs | 125 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9714469bb6be..f10a05f42609 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1468,7 +1468,7 @@ pub unsafe extern "C" fn poll_oneoff( assert!(align_of::() >= align_of::()); assert!( unwrap(nsubscriptions.checked_mul(size_of::())) - > unwrap( + >= unwrap( unwrap(nsubscriptions.checked_mul(size_of::())) .checked_add(unwrap(nsubscriptions.checked_mul(size_of::()))) ) @@ -1477,6 +1477,11 @@ pub unsafe extern "C" fn poll_oneoff( let futures = out as *mut c_void as *mut WasiFuture; let results = futures.add(nsubscriptions) as *mut c_void as *mut u8; + // Indefinite sleeping is not supported in preview1. + if nsubscriptions == 0 { + return ERRNO_INVAL; + } + State::with(|state| { state.register_buffer( results, @@ -1496,7 +1501,8 @@ pub unsafe extern "C" fn poll_oneoff( futures.push(match subscription.u.tag { EVENTTYPE_CLOCK => { let clock = &subscription.u.u.clock; - let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) == 0; + let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) + == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME; match clock.id { CLOCKID_REALTIME => { let timeout = if absolute { @@ -1587,41 +1593,94 @@ pub unsafe extern "C" fn poll_oneoff( 1 => { type_ = EVENTTYPE_FD_READ; - match wasi_tcp::bytes_readable(subscription.u.u.fd_read.file_descriptor) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.nbytes; - flags = if result.is_closed { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } + let desc = unwrap_result(state.get(subscription.u.u.fd_read.file_descriptor)); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::File(file) => match wasi_filesystem::stat(file.fd) { + Ok(stat) => { + error = ERRNO_SUCCESS; + nbytes = stat.size.saturating_sub(file.position.get()); + flags = if nbytes == 0 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 1; + flags = 0; + } + }, + StreamType::Socket(_) => unreachable(), // TODO + /* + StreamType::Socket(_) => match wasi_tcp::bytes_readable(todo!()) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.nbytes; + flags = if result.is_closed { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + }, + */ + StreamType::EmptyStdin => { + error = ERRNO_SUCCESS; + nbytes = 0; + flags = EVENTRWFLAGS_FD_READWRITE_HANGUP; + } + StreamType::Unknown => { + error = ERRNO_SUCCESS; + nbytes = 1; + flags = 0; + } + }, + _ => unreachable(), } } - 2 => { type_ = EVENTTYPE_FD_WRITE; - match wasi_tcp::bytes_writable(subscription.u.u.fd_write.file_descriptor) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.nbytes; - flags = if result.is_closed { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } + let desc = unwrap_result(state.get(subscription.u.u.fd_read.file_descriptor)); + match desc { + Descriptor::Streams(streams) => match streams.type_ { + StreamType::File(_) | StreamType::Unknown => { + error = ERRNO_SUCCESS; + nbytes = 1; + flags = 0; + } + StreamType::Socket(_) => unreachable(), // TODO + /* + StreamType::Socket(_) => match wasi_tcp::bytes_writable(todo!()) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.nbytes; + flags = if result.is_closed { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + }, + */ + StreamType::EmptyStdin => { + error = ERRNO_BADF; + nbytes = 0; + flags = 0; + } + }, + _ => unreachable(), } } From e5ee34fada96d965055f45ad69c20c00a030ab19 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 18 Jan 2023 06:13:43 -0800 Subject: [PATCH 063/153] Allocate bool results at the end of the events buffer. --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f10a05f42609..e3ffcdda3cfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1474,8 +1474,10 @@ pub unsafe extern "C" fn poll_oneoff( ) ); + // Store the future handles at the beginning, and the bool results at the + // end, so that we don't clobber the bool results when writting the events. let futures = out as *mut c_void as *mut WasiFuture; - let results = futures.add(nsubscriptions) as *mut c_void as *mut u8; + let results = out.add(nsubscriptions).cast::().sub(nsubscriptions); // Indefinite sleeping is not supported in preview1. if nsubscriptions == 0 { From 6a41baf306168ca46a91d99be1326344874b372d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 18 Jan 2023 06:13:52 -0800 Subject: [PATCH 064/153] Don't fail poll for per-fd failures. When a fd is not pollable, report it as an immediately-ready future, rather than as a poll failure. --- src/lib.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e3ffcdda3cfc..93bdc3468cda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1552,13 +1552,35 @@ pub unsafe extern "C" fn poll_oneoff( } } - EVENTTYPE_FD_READ => wasi_poll::subscribe_read( - state.get_read_stream(subscription.u.u.fd_read.file_descriptor)?, - ), + EVENTTYPE_FD_READ => { + match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { + Ok(stream) => wasi_poll::subscribe_read(stream), + // If the file descriptor isn't a stream, request a + // future which completes immediately so that it'll + // immediately fail. + Err(ERRNO_BADF) => wasi_poll::subscribe_monotonic_clock( + state.default_monotonic_clock(), + 0, + false, + ), + Err(e) => return Err(e), + } + } - EVENTTYPE_FD_WRITE => wasi_poll::subscribe_write( - state.get_write_stream(subscription.u.u.fd_write.file_descriptor)?, - ), + EVENTTYPE_FD_WRITE => { + match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) { + Ok(stream) => wasi_poll::subscribe_write(stream), + // If the file descriptor isn't a stream, request a + // future which completes immediately so that it'll + // immediately fail. + Err(ERRNO_BADF) => wasi_poll::subscribe_monotonic_clock( + state.default_monotonic_clock(), + 0, + false, + ), + Err(e) => return Err(e), + } + } _ => return Err(ERRNO_INVAL), }); From 902ca94d7595e816051e103d9962c3cf06effcee Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 10 Jan 2023 17:41:15 -0800 Subject: [PATCH 065/153] Implement `sync` and `datasync` on files and directories. On Windows, there doesn't appear to be a way to sync a directory, to ensure that the directory entry for a file is sync'd. So for now, just silently succeed. I've opened WebAssembly/wasi-filesystem#79 to track this at the spec level. --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 93bdc3468cda..5fffd8441cfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -847,14 +847,14 @@ pub unsafe extern "C" fn fd_readdir( let dir = state.get_dir(fd)?; iter = DirEntryIterator { state, - cookie: 0, + cookie: wasi::DIRCOOKIE_START, use_cache: false, stream: DirEntryStream(wasi_filesystem::readdir(dir.fd)?), }; // Skip to the entry that is requested by the `cookie` // parameter. - for _ in 0..cookie { + for _ in wasi::DIRCOOKIE_START..cookie { match iter.next() { Some(Ok(_)) => {} Some(Err(e)) => return Err(e), @@ -2288,7 +2288,7 @@ impl State { dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), - cookie: Cell::new(0), + cookie: Cell::new(wasi::DIRCOOKIE_START), cached_dirent: Cell::new(wasi::Dirent { d_next: 0, d_ino: 0, From 66e39bb34c54b0a2a411767cbd4b5d865dd03fcc Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 18 Jan 2023 16:18:52 -0800 Subject: [PATCH 066/153] Add `eprintln`, `unreachable`, and other macros. (#62) Add a `byte-array` proc-macro crate for converting strings into byte arrays that don't use static initializers, and use it to implement `eprintln`, an `unreachable` that prints the line number, and other macros. --- src/lib.rs | 86 ++++++++++++++++++++++----------------------------- src/macros.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 49 deletions(-) create mode 100644 src/macros.rs diff --git a/src/lib.rs b/src/lib.rs index 5fffd8441cfe..334d1bebd347 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ use crate::bindings::{ wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_logging, wasi_poll, wasi_random, wasi_tcp, }; -use core::arch::wasm32::{self, unreachable}; +use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; use core::cmp::min; use core::ffi::c_void; @@ -15,6 +15,9 @@ use core::slice; use wasi::*; use wasi_poll::{WasiFuture, WasiStream}; +#[macro_use] +mod macros; + mod bindings { wit_bindgen_guest_rust::generate!({ path: "wit/wasi.wit", @@ -40,7 +43,7 @@ pub unsafe extern "C" fn command( // `*.wit` metadata entirely but doing that ergonomically will likely // require some form of `use` to avoid duplicating lots of `*.wit` bits. if !cfg!(feature = "command") { - unreachable(); + unreachable!("`command` called without the \"command\" feature"); } State::with_mut(|state| { @@ -50,7 +53,7 @@ pub unsafe extern "C" fn command( { let descriptors = state.descriptors_mut(); if descriptors.len() < 3 { - unreachable(); + unreachable!("insufficient memory for stdio descriptors"); } descriptors[0] = Descriptor::Streams(Streams { input: Cell::new(Some(stdin)), @@ -92,26 +95,11 @@ pub unsafe extern "C" fn command( 0 } -// We're avoiding static initializers, so replace the standard assert macros -// with simpler implementations. -macro_rules! assert { - ($cond:expr $(,)?) => { - if !$cond { - unreachable() - } - }; -} -macro_rules! assert_eq { - ($left:expr, $right:expr $(,)?) => { - assert!($left == $right); - }; -} - fn unwrap(maybe: Option) -> T { if let Some(value) = maybe { value } else { - unreachable() + unreachable!("unwrap failed") } } @@ -119,7 +107,7 @@ fn unwrap_result(result: Result) -> T { if let Ok(value) = result { value } else { - unreachable() + unreachable!("unwrap result failed") } } @@ -131,17 +119,17 @@ pub unsafe extern "C" fn cabi_import_realloc( new_size: usize, ) -> *mut u8 { if !old_ptr.is_null() || old_size != 0 { - unreachable(); + unreachable!(); } let mut ptr = null_mut::(); State::with(|state| { ptr = state.buffer_ptr.replace(null_mut()); if ptr.is_null() { - unreachable(); + unreachable!(); } let len = state.buffer_len.replace(0); if len < new_size { - unreachable(); + unreachable!(); } Ok(()) }); @@ -167,7 +155,7 @@ pub unsafe extern "C" fn cabi_export_realloc( new_size: usize, ) -> *mut u8 { if !old_ptr.is_null() || old_size != 0 { - unreachable(); + unreachable!(); } let mut ret = null_mut::(); State::with_mut(|state| { @@ -180,11 +168,11 @@ pub unsafe extern "C" fn cabi_export_realloc( // "oom" as too much argument data tried to flow into the component. // Ideally this would have a better error message? if ptr + new_size > (*data).len() { - unreachable(); + unreachable!("out of memory"); } state.command_data_next = (ptr + new_size) .try_into() - .unwrap_or_else(|_| unreachable()); + .unwrap_or_else(|_| unreachable!()); ret = (*data).as_mut_ptr().add(ptr); Ok(()) }); @@ -310,7 +298,7 @@ pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errn .and_then(|secs| secs.checked_mul(1_000_000_000)) .ok_or(ERRNO_OVERFLOW)?; } - _ => unreachable(), + _ => unreachable!(), } Ok(()) }) @@ -336,7 +324,7 @@ pub unsafe extern "C" fn clock_time_get( .and_then(|secs| secs.checked_mul(1_000_000_000)) .ok_or(ERRNO_OVERFLOW)?; } - _ => unreachable(), + _ => unreachable!(), } Ok(()) }) @@ -371,7 +359,7 @@ pub unsafe extern "C" fn fd_advise( /// Note: This is similar to `posix_fallocate` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { - unreachable() + unreachable!("fd_allocate") } /// Close a file descriptor. @@ -544,7 +532,7 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( fs_rights_base: Rights, fs_rights_inheriting: Rights, ) -> Errno { - unreachable() + unreachable!() } /// Return the attributes of an open file. @@ -1105,7 +1093,7 @@ pub unsafe extern "C" fn fd_write( Ok(()) } Descriptor::StderrLog | Descriptor::StdoutLog => { - let context: [u8; 3] = [b'I', b'/', b'O']; + let context: [u8; 3] = byte_array::str!("I/O"); wasi_logging::log(wasi_logging::Level::Info, &context, bytes); *nwritten = len; Ok(()) @@ -1274,7 +1262,7 @@ pub unsafe extern "C" fn path_open( let recycle_desc = unwrap_result(state.get_mut(recycle_fd)); let next_closed = match recycle_desc { Descriptor::Closed(next) => *next, - _ => unreachable(), + _ => unreachable!(), }; *recycle_desc = desc; state.closed = next_closed; @@ -1588,7 +1576,7 @@ pub unsafe extern "C" fn poll_oneoff( let vec = wasi_poll::poll_oneoff(slice::from_raw_parts(futures.pointer, futures.length)); - assert!(vec.len() == nsubscriptions); + assert_eq!(vec.len(), nsubscriptions); assert_eq!(vec.as_ptr(), results); forget(vec); @@ -1636,7 +1624,7 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } }, - StreamType::Socket(_) => unreachable(), // TODO + StreamType::Socket(_) => unreachable!(), // TODO /* StreamType::Socket(_) => match wasi_tcp::bytes_readable(todo!()) { Ok(result) => { @@ -1666,7 +1654,7 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } }, - _ => unreachable(), + _ => unreachable!(), } } 2 => { @@ -1679,7 +1667,7 @@ pub unsafe extern "C" fn poll_oneoff( nbytes = 1; flags = 0; } - StreamType::Socket(_) => unreachable(), // TODO + StreamType::Socket(_) => unreachable!(), // TODO /* StreamType::Socket(_) => match wasi_tcp::bytes_writable(todo!()) { Ok(result) => { @@ -1704,11 +1692,11 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } }, - _ => unreachable(), + _ => unreachable!(), } } - _ => unreachable(), + _ => unreachable!(), } *out.add(count) = Event { @@ -1734,14 +1722,14 @@ pub unsafe extern "C" fn poll_oneoff( pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! { let status = if rval == 0 { Ok(()) } else { Err(()) }; wasi_exit::exit(status); // does not return - unreachable() // actually unreachable + unreachable!("host exit implementation didn't exit!") // actually unreachable } /// Send a signal to the process of the calling thread. /// Note: This is similar to `raise` in POSIX. #[no_mangle] pub unsafe extern "C" fn proc_raise(sig: Signal) -> Errno { - unreachable() + unreachable!() } /// Temporarily yield execution of the calling thread. @@ -1788,7 +1776,7 @@ pub unsafe extern "C" fn sock_recv( ro_datalen: *mut Size, ro_flags: *mut Roflags, ) -> Errno { - unreachable() + unreachable!() } /// Send a message on a socket. @@ -1802,14 +1790,14 @@ pub unsafe extern "C" fn sock_send( si_flags: Siflags, so_datalen: *mut Size, ) -> Errno { - unreachable() + unreachable!() } /// Shut down socket send and receive channels. /// Note: This is similar to `shutdown` in POSIX. #[no_mangle] pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { - unreachable() + unreachable!() } fn at_flags_from_lookupflags(flags: Lookupflags) -> wasi_filesystem::AtFlags { @@ -1953,7 +1941,7 @@ impl From for wasi::Filetype { wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::DescriptorType::Socket => unreachable(), + wasi_filesystem::DescriptorType::Socket => unreachable!(), wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, } @@ -2060,7 +2048,7 @@ impl Drop for Descriptor { } match &stream.type_ { StreamType::File(file) => wasi_filesystem::close(file.fd), - StreamType::Socket(_) => unreachable(), + StreamType::Socket(_) => unreachable!(), StreamType::EmptyStdin | StreamType::Unknown => {} } } @@ -2230,7 +2218,7 @@ extern "C" { impl State { fn with(f: impl FnOnce(&State) -> Result<(), Errno>) -> Errno { let ptr = State::ptr(); - let ptr = ptr.try_borrow().unwrap_or_else(|_| unreachable()); + let ptr = ptr.try_borrow().unwrap_or_else(|_| unreachable!()); assert_eq!(ptr.magic1, MAGIC); assert_eq!(ptr.magic2, MAGIC); let ret = f(&*ptr); @@ -2242,7 +2230,7 @@ impl State { fn with_mut(f: impl FnOnce(&mut State) -> Result<(), Errno>) -> Errno { let ptr = State::ptr(); - let mut ptr = ptr.try_borrow_mut().unwrap_or_else(|_| unreachable()); + let mut ptr = ptr.try_borrow_mut().unwrap_or_else(|_| unreachable!()); assert_eq!(ptr.magic1, MAGIC); assert_eq!(ptr.magic2, MAGIC); let ret = f(&mut *ptr); @@ -2267,7 +2255,7 @@ impl State { fn new() -> &'static RefCell { let grew = wasm32::memory_grow(0, 1); if grew == usize::MAX { - unreachable(); + unreachable!(); } let ret = (grew * PAGE_SIZE) as *mut RefCell; let ret = unsafe { @@ -2303,7 +2291,7 @@ impl State { &*ret }; ret.try_borrow_mut() - .unwrap_or_else(|_| unreachable()) + .unwrap_or_else(|_| unreachable!()) .init(); ret } diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 000000000000..608b0d65e5a0 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,76 @@ +//! Minimal versions of standard-library panicking and printing macros. +//! +//! We're avoiding static initializers, so we can't have things like string +//! literals. Replace the standard assert macros with simpler implementations. + +/// A minimal `eprint` for debugging. +#[allow(unused_macros)] +macro_rules! eprint { + ($arg:tt) => {{ + // We have to expand string literals into byte arrays to prevent them + // from getting statically initialized. + let message = byte_array::str!($arg); + crate::bindings::wasi_stderr::print(&message); + }}; +} + +/// A minimal `eprintln` for debugging. +#[allow(unused_macros)] +macro_rules! eprintln { + ($arg:tt) => {{ + // We have to expand string literals into byte arrays to prevent them + // from getting statically initialized. + let message = byte_array::str_nl!($arg); + crate::bindings::wasi_stderr::print(&message); + }}; +} + +pub(crate) fn eprint_u32(x: u32) { + if x == 0 { + eprint!("0"); + } else { + eprint_u32_impl(x) + } + + fn eprint_u32_impl(x: u32) { + if x != 0 { + eprint_u32_impl(x / 10); + + let digit = [b'0' + ((x % 10) as u8)]; + crate::bindings::wasi_stderr::print(&digit); + } + } +} + +/// A minimal `unreachable`. +macro_rules! unreachable { + () => {{ + eprint!("unreachable executed at line "); + crate::macros::eprint_u32(line!()); + wasm32::unreachable() + }}; + + ($arg:tt) => {{ + eprint!("unreachable executed at line "); + crate::macros::eprint_u32(line!()); + eprint!(": "); + eprintln!($arg); + wasm32::unreachable() + }}; +} + +/// A minimal `assert`. +macro_rules! assert { + ($cond:expr $(,)?) => { + if !$cond { + unreachable!("assertion failed") + } + }; +} + +/// A minimal `assert_eq`. +macro_rules! assert_eq { + ($left:expr, $right:expr $(,)?) => { + assert!($left == $right); + }; +} From 528f095d337a6a480b15b5f8d85b732e304bb9e5 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 20 Jan 2023 16:58:10 -0800 Subject: [PATCH 067/153] Use the `wasi-stderr` API for stderr (#59) Use the dedicated `wasi-stderr` API for stderr, instead of `wasi-logging`. Fixes #57. --- src/lib.rs | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 334d1bebd347..966247679574 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use crate::bindings::{ wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_logging, wasi_poll, - wasi_random, wasi_tcp, + wasi_random, wasi_stderr, wasi_tcp, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -437,7 +437,7 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } - Descriptor::StdoutLog | Descriptor::StderrLog => { + Descriptor::Stderr => { let fs_filetype = FILETYPE_UNKNOWN; let fs_flags = 0; let fs_rights_base = !RIGHTS_FD_READ; @@ -767,9 +767,7 @@ pub unsafe extern "C" fn fd_read( Ok(()) } } - Descriptor::StdoutLog | Descriptor::StderrLog | Descriptor::Closed(_) => { - Err(ERRNO_BADF) - } + Descriptor::Stderr | Descriptor::Closed(_) => Err(ERRNO_BADF), } }) } @@ -1092,9 +1090,8 @@ pub unsafe extern "C" fn fd_write( *nwritten = bytes as usize; Ok(()) } - Descriptor::StderrLog | Descriptor::StdoutLog => { - let context: [u8; 3] = byte_array::str!("I/O"); - wasi_logging::log(wasi_logging::Level::Info, &context, bytes); + Descriptor::Stderr => { + wasi_stderr::print(bytes); *nwritten = len; Ok(()) } @@ -1957,14 +1954,8 @@ enum Descriptor { /// Input and/or output wasi-streams, along with stream metadata. Streams(Streams), - /// Initial state of fd 1 when `State` is created, representing that writes - /// to `fd_write` will go to a call to `log`. This is overwritten during - /// initialization in `command`. - StdoutLog, - - /// Same as `StdoutLog` except for stderr. This is not overwritten during - /// `command`. - StderrLog, + /// Writes to `fd_write` will go to the `wasi-stderr` API. + Stderr, } /// Input and/or output wasi-streams, along with a stream type that @@ -2052,7 +2043,7 @@ impl Drop for Descriptor { StreamType::EmptyStdin | StreamType::Unknown => {} } } - Descriptor::StdoutLog | Descriptor::StderrLog => {} + Descriptor::Stderr => {} Descriptor::Closed(_) => {} } } @@ -2297,13 +2288,18 @@ impl State { } fn init(&mut self) { + // Set up a default stdin. This will be overridden when `command` + // is called. unwrap_result(self.push_desc(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), type_: StreamType::Unknown, }))); - unwrap_result(self.push_desc(Descriptor::StdoutLog)); - unwrap_result(self.push_desc(Descriptor::StderrLog)); + // Set up a default stdout, writing to the stderr device. This will + // be overridden when `command` is called. + unwrap_result(self.push_desc(Descriptor::Stderr)); + // Set up a default stderr. + unwrap_result(self.push_desc(Descriptor::Stderr)); } fn push_desc(&mut self, desc: Descriptor) -> Result { @@ -2399,18 +2395,14 @@ impl State { fn get_read_stream(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_read_stream(), - Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { - Err(ERRNO_BADF) - } + Descriptor::Closed(_) | Descriptor::Stderr => Err(ERRNO_BADF), } } fn get_write_stream(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_write_stream(), - Descriptor::Closed(_) | Descriptor::StdoutLog | Descriptor::StderrLog => { - Err(ERRNO_BADF) - } + Descriptor::Closed(_) | Descriptor::Stderr => Err(ERRNO_BADF), } } From 9b10c0b90a78a638ba0d70aa5291deda0af0f6f7 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 20 Jan 2023 16:58:28 -0800 Subject: [PATCH 068/153] Change the filesystem timestamp to seconds+nanoseconds. (#55) This corresponds to https://github.com/WebAssembly/wasi-filesystem/pull/76. --- src/lib.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 966247679574..d10befa44636 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -548,9 +548,9 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { filetype, nlink: stat.nlink, size: stat.size, - atim: stat.atim, - mtim: stat.mtim, - ctim: stat.ctim, + atim: datetime_to_timestamp(stat.atim), + mtim: datetime_to_timestamp(stat.mtim), + ctim: datetime_to_timestamp(stat.ctim), }; Ok(()) }) @@ -580,7 +580,10 @@ pub unsafe extern "C" fn fd_filestat_set_times( if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { wasi_filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { - wasi_filesystem::NewTimestamp::Timestamp(atim) + wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + seconds: atim / 1_000_000_000, + nanoseconds: (atim % 1_000_000_000) as _, + }) } else { wasi_filesystem::NewTimestamp::NoChange }; @@ -588,7 +591,10 @@ pub unsafe extern "C" fn fd_filestat_set_times( if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { wasi_filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { - wasi_filesystem::NewTimestamp::Timestamp(mtim) + wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + seconds: mtim / 1_000_000_000, + nanoseconds: (mtim % 1_000_000_000) as _, + }) } else { wasi_filesystem::NewTimestamp::NoChange }; @@ -1139,9 +1145,9 @@ pub unsafe extern "C" fn path_filestat_get( filetype, nlink: stat.nlink, size: stat.size, - atim: stat.atim, - mtim: stat.mtim, - ctim: stat.ctim, + atim: datetime_to_timestamp(stat.atim), + mtim: datetime_to_timestamp(stat.mtim), + ctim: datetime_to_timestamp(stat.ctim), }; Ok(()) }) @@ -1163,7 +1169,10 @@ pub unsafe extern "C" fn path_filestat_set_times( if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { wasi_filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { - wasi_filesystem::NewTimestamp::Timestamp(atim) + wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + seconds: atim / 1_000_000_000, + nanoseconds: (atim % 1_000_000_000) as _, + }) } else { wasi_filesystem::NewTimestamp::NoChange }; @@ -1171,7 +1180,10 @@ pub unsafe extern "C" fn path_filestat_set_times( if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { wasi_filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { - wasi_filesystem::NewTimestamp::Timestamp(mtim) + wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + seconds: mtim / 1_000_000_000, + nanoseconds: (mtim % 1_000_000_000) as _, + }) } else { wasi_filesystem::NewTimestamp::NoChange }; @@ -1797,6 +1809,10 @@ pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable!() } +fn datetime_to_timestamp(datetime: wasi_filesystem::Datetime) -> Timestamp { + u64::from(datetime.nanoseconds).saturating_add(datetime.seconds.saturating_mul(1_000_000_000)) +} + fn at_flags_from_lookupflags(flags: Lookupflags) -> wasi_filesystem::AtFlags { if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW { wasi_filesystem::AtFlags::SYMLINK_FOLLOW From 710cc4ce82fa91ae44c9d022fcca99d5b3d83080 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 10:25:21 -0600 Subject: [PATCH 069/153] Update wasmtime and wit-bindgen (#68) * Update wasmtime and wit-bindgen This commit updates the `wasmtime` dependency and the `wit-bindgen-guest-rust` dependencies to namely update the `wit-parser` and component underlying implementations. This pulls in bytecodealliance/wasm-tools#867 which is the implementation of `use` for WIT. This does not currently break apart the one large `wasi.wit` file, instead just gets tests working. * Split apart `wasi.wit` into multiple `*.wit` files This commit refactors the monolithic `wasi.wit` file into multiple files. I've taken some liberties in naming here so suggestions are definitely welcome! I've resolved some TODO annotations as well about `use`-ing types between interfaces. There are two issues remaining, however: * The `wasi-command` world still hardcodes `u32` because `world` items don't support `use` just yet. That isn't a hard limitation of WIT, however, it's just a temporary limitation of the implementation. * The `wasi-clocks` interface defines the `wasi-future` type. This is due to a cyclic dependency where `wasi-future` is defined within `wasi-poll` which depends on `wasi-clocks` for types. I've left this to a future refactoring to probably have a `types` interface of some form somewhere. * Update patch for wasm-tools * Switch to wasmtime upstream * Update CI for build * Remove patch overrides * Update names of uploaded files --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d10befa44636..12f3f276b9cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ mod macros; mod bindings { wit_bindgen_guest_rust::generate!({ - path: "wit/wasi.wit", + world: "wasi-command", no_std, raw_strings, unchecked, From 556cea39e680335ef3d2d7a7fb69fe95fadc3be5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 26 Jan 2023 13:54:03 -0600 Subject: [PATCH 070/153] Use a more targeted means of specifying link flags (#71) * Use a more targeted means of specifying link flags I had forgotten earlier that this could be done with build scripts so do that in the adapter's build script rather than as auxiliary rust flags. * Remove `.cargo/config.toml` file This now only serves the purpose to enable bulk-memory which is relatively minor. This removes the file for now and wasm features can always be reenabled at a later date if file size is truly an issue. --- build.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.rs b/build.rs index 0f3884b4bfe8..b5f7ccd30bb8 100644 --- a/build.rs +++ b/build.rs @@ -13,6 +13,13 @@ fn main() { "cargo:rustc-link-search=native={}", out_dir.to_str().unwrap() ); + + // Some specific flags to `wasm-ld` to inform the shape of this adapter. + // Notably we're importing memory from the main module and additionally our + // own module has no stack at all since it's specifically allocated at + // startup. + println!("cargo:rustc-link-arg=--import-memory"); + println!("cargo:rustc-link-arg=-zstack-size=0"); } /// This function will produce a wasm module which is itself an object file From a25f3f6ba85e53b92d6362cddffdef64835c2604 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 26 Jan 2023 13:54:18 -0600 Subject: [PATCH 071/153] Fix the non-command build (#72) This commit fixes the non-command build of the WASI adapter by adding a new `wasi.wit` which only has the imports and nothing else. --- src/lib.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 12f3f276b9cd..fe62d1958f0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ use wasi_poll::{WasiFuture, WasiStream}; mod macros; mod bindings { + #[cfg(feature = "command")] wit_bindgen_guest_rust::generate!({ world: "wasi-command", no_std, @@ -28,9 +29,18 @@ mod bindings { // manually below instead skip: ["command"], }); + + #[cfg(not(feature = "command"))] + wit_bindgen_guest_rust::generate!({ + world: "wasi", + no_std, + raw_strings, + unchecked, + }); } #[no_mangle] +#[cfg(feature = "command")] pub unsafe extern "C" fn command( stdin: WasiStream, stdout: WasiStream, @@ -39,13 +49,6 @@ pub unsafe extern "C" fn command( env_vars: StrTupleList, preopens: PreopenList, ) -> u32 { - // TODO: ideally turning off `command` would remove this import and the - // `*.wit` metadata entirely but doing that ergonomically will likely - // require some form of `use` to avoid duplicating lots of `*.wit` bits. - if !cfg!(feature = "command") { - unreachable!("`command` called without the \"command\" feature"); - } - State::with_mut(|state| { // Initialization of `State` automatically fills in some dummy // structures for fds 0, 1, and 2. Overwrite the stdin/stdout slots of 0 From 56048d445805c5b82a1a35531dc680d35b9efe84 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 30 Jan 2023 11:17:44 -0800 Subject: [PATCH 072/153] Rename `wasi-future` to `waitable`. (#70) * Rename `wasi-future` to `waitable`. You can wait on `waitable`s multiple times, which differs from proper futures, which you can only wait on once. * Rename `waitable` to `pollable`. Because the function they're passed to is `poll-oneoff`. --- src/lib.rs | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fe62d1958f0a..973ff6f70038 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ use core::mem::{align_of, forget, replace, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, null_mut}; use core::slice; use wasi::*; -use wasi_poll::{WasiFuture, WasiStream}; +use wasi_poll::{Pollable, WasiStream}; #[macro_use] mod macros; @@ -1408,24 +1408,24 @@ pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: }) } -struct Futures { - pointer: *mut WasiFuture, +struct Pollables { + pointer: *mut Pollable, index: usize, length: usize, } -impl Futures { - unsafe fn push(&mut self, future: WasiFuture) { +impl Pollables { + unsafe fn push(&mut self, pollable: Pollable) { assert!(self.index < self.length); - *self.pointer.add(self.index) = future; + *self.pointer.add(self.index) = pollable; self.index += 1; } } -impl Drop for Futures { +impl Drop for Pollables { fn drop(&mut self) { for i in 0..self.index { - wasi_poll::drop_future(unsafe { *self.pointer.add(i) }) + wasi_poll::drop_pollable(unsafe { *self.pointer.add(i) }) } } } @@ -1460,23 +1460,24 @@ pub unsafe extern "C" fn poll_oneoff( let subscriptions = slice::from_raw_parts(r#in, nsubscriptions); - // We're going to split the `nevents` buffer into two non-overlapping buffers: one to store the future handles, - // and the other to store the bool results. + // We're going to split the `nevents` buffer into two non-overlapping + // buffers: one to store the pollable handles, and the other to store + // the bool results. // // First, we assert that this is possible: - assert!(align_of::() >= align_of::()); - assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); assert!( unwrap(nsubscriptions.checked_mul(size_of::())) >= unwrap( - unwrap(nsubscriptions.checked_mul(size_of::())) + unwrap(nsubscriptions.checked_mul(size_of::())) .checked_add(unwrap(nsubscriptions.checked_mul(size_of::()))) ) ); - // Store the future handles at the beginning, and the bool results at the + // Store the pollable handles at the beginning, and the bool results at the // end, so that we don't clobber the bool results when writting the events. - let futures = out as *mut c_void as *mut WasiFuture; + let pollables = out as *mut c_void as *mut Pollable; let results = out.add(nsubscriptions).cast::().sub(nsubscriptions); // Indefinite sleeping is not supported in preview1. @@ -1490,8 +1491,8 @@ pub unsafe extern "C" fn poll_oneoff( unwrap(nsubscriptions.checked_mul(size_of::())), ); - let mut futures = Futures { - pointer: futures, + let mut pollables = Pollables { + pointer: pollables, index: 0, length: nsubscriptions, }; @@ -1500,7 +1501,7 @@ pub unsafe extern "C" fn poll_oneoff( const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); - futures.push(match subscription.u.tag { + pollables.push(match subscription.u.tag { EVENTTYPE_CLOCK => { let clock = &subscription.u.u.clock; let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) @@ -1556,7 +1557,7 @@ pub unsafe extern "C" fn poll_oneoff( match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { Ok(stream) => wasi_poll::subscribe_read(stream), // If the file descriptor isn't a stream, request a - // future which completes immediately so that it'll + // pollable which completes immediately so that it'll // immediately fail. Err(ERRNO_BADF) => wasi_poll::subscribe_monotonic_clock( state.default_monotonic_clock(), @@ -1571,7 +1572,7 @@ pub unsafe extern "C" fn poll_oneoff( match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) { Ok(stream) => wasi_poll::subscribe_write(stream), // If the file descriptor isn't a stream, request a - // future which completes immediately so that it'll + // pollable which completes immediately so that it'll // immediately fail. Err(ERRNO_BADF) => wasi_poll::subscribe_monotonic_clock( state.default_monotonic_clock(), @@ -1586,13 +1587,14 @@ pub unsafe extern "C" fn poll_oneoff( }); } - let vec = wasi_poll::poll_oneoff(slice::from_raw_parts(futures.pointer, futures.length)); + let vec = + wasi_poll::poll_oneoff(slice::from_raw_parts(pollables.pointer, pollables.length)); assert_eq!(vec.len(), nsubscriptions); assert_eq!(vec.as_ptr(), results); forget(vec); - drop(futures); + drop(pollables); let ready = subscriptions .iter() From 1c79bde9b272a2760ef75a3845d0284dc7478b1d Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 1 Feb 2023 12:55:03 -0700 Subject: [PATCH 073/153] use main module's `cabi_realloc` instead of `memory.grow` This depends on https://github.com/bytecodealliance/wasm-tools/pull/900, which will polyfill an implementation if the main module does not export it. Signed-off-by: Joel Dice --- src/lib.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 973ff6f70038..2878b517ebb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,15 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_logging, wasi_poll, - wasi_random, wasi_stderr, wasi_tcp, + wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_poll, wasi_random, + wasi_stderr, wasi_tcp, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; use core::cmp::min; use core::ffi::c_void; use core::hint::black_box; -use core::mem::{align_of, forget, replace, size_of, ManuallyDrop, MaybeUninit}; +use core::mem::{self, align_of, forget, replace, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, null_mut}; use core::slice; use wasi::*; @@ -149,7 +149,7 @@ fn align_to(ptr: usize, align: usize) -> usize { /// traps when it runs out of data. This means that the total size of /// arguments/env/etc coming into a component is bounded by the current 64k /// (ish) limit. That's just an implementation limit though which can be lifted -/// by dynamically calling `memory.grow` as necessary for more data. +/// by dynamically calling the main module's allocator as necessary for more data. #[no_mangle] pub unsafe extern "C" fn cabi_export_realloc( old_ptr: *mut u8, @@ -2265,11 +2265,25 @@ impl State { #[cold] fn new() -> &'static RefCell { - let grew = wasm32::memory_grow(0, 1); - if grew == usize::MAX { - unreachable!(); + #[link(wasm_import_module = "__main_module__")] + extern "C" { + fn cabi_realloc( + old_ptr: *mut u8, + old_len: usize, + align: usize, + new_len: usize, + ) -> *mut u8; } - let ret = (grew * PAGE_SIZE) as *mut RefCell; + + let ret = unsafe { + cabi_realloc( + ptr::null_mut(), + 0, + mem::align_of::>(), + mem::size_of::>(), + ) as *mut RefCell + }; + let ret = unsafe { ret.write(RefCell::new(State { magic1: MAGIC, From 3c4e2ff0b8ba0790f864b60473eccc38a3ad5a04 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 2 Feb 2023 11:14:00 -0800 Subject: [PATCH 074/153] Add basic TCP socket APIs. (#33) * Add basic TCP socket APIs. This is based on @npmccullum's [wasi-snapshot-preview2 draft] which is in turn based on the [wasi-sockets proposal], though for simplify for now it omits UDP sockets and some other features. It's also based on the [pseudo-streams PR]; so that should proceed first before this. [draft wasi-snapshot-preview2]: https://github.com/npmccallum/wasi-snapshot-preview2 [wasi-sockets proposal]: https://github.com/WebAssembly/wasi-sockets [pseudo-streams PR]: https://github.com/bytecodealliance/preview2-prototyping/pull/29 --- src/lib.rs | 115 +++++++++++++++++++---------------------------------- 1 file changed, 41 insertions(+), 74 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2878b517ebb3..f487120b5254 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1430,9 +1430,9 @@ impl Drop for Pollables { } } -impl From for Errno { - fn from(error: wasi_tcp::Error) -> Errno { - use wasi_tcp::Error::*; +impl From for Errno { + fn from(error: wasi_tcp::Errno) -> Errno { + use wasi_tcp::Errno::*; match error { // Use a black box to prevent the optimizer from generating a @@ -1638,25 +1638,24 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } }, - StreamType::Socket(_) => unreachable!(), // TODO - /* - StreamType::Socket(_) => match wasi_tcp::bytes_readable(todo!()) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.nbytes; - flags = if result.is_closed { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; + StreamType::Socket(connection) => { + match wasi_tcp::bytes_readable(*connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - }, - */ + } StreamType::EmptyStdin => { error = ERRNO_SUCCESS; nbytes = 0; @@ -1681,25 +1680,24 @@ pub unsafe extern "C" fn poll_oneoff( nbytes = 1; flags = 0; } - StreamType::Socket(_) => unreachable!(), // TODO - /* - StreamType::Socket(_) => match wasi_tcp::bytes_writable(todo!()) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.nbytes; - flags = if result.is_closed { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; + StreamType::Socket(connection) => { + match wasi_tcp::bytes_writable(connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - }, - */ + } StreamType::EmptyStdin => { error = ERRNO_BADF; nbytes = 0; @@ -1875,55 +1873,29 @@ impl From for Errno { match err { // Use a black box to prevent the optimizer from generating a // lookup table, which would require a static initializer. - wasi_filesystem::Errno::Toobig => black_box(ERRNO_2BIG), - wasi_filesystem::Errno::Access => ERRNO_ACCES, - wasi_filesystem::Errno::Addrinuse => ERRNO_ADDRINUSE, - wasi_filesystem::Errno::Addrnotavail => ERRNO_ADDRNOTAVAIL, - wasi_filesystem::Errno::Afnosupport => ERRNO_AFNOSUPPORT, + wasi_filesystem::Errno::Access => black_box(ERRNO_ACCES), wasi_filesystem::Errno::Again => ERRNO_AGAIN, wasi_filesystem::Errno::Already => ERRNO_ALREADY, - wasi_filesystem::Errno::Badmsg => ERRNO_BADMSG, wasi_filesystem::Errno::Badf => ERRNO_BADF, wasi_filesystem::Errno::Busy => ERRNO_BUSY, - wasi_filesystem::Errno::Canceled => ERRNO_CANCELED, - wasi_filesystem::Errno::Child => ERRNO_CHILD, - wasi_filesystem::Errno::Connaborted => ERRNO_CONNABORTED, - wasi_filesystem::Errno::Connrefused => ERRNO_CONNREFUSED, - wasi_filesystem::Errno::Connreset => ERRNO_CONNRESET, wasi_filesystem::Errno::Deadlk => ERRNO_DEADLK, - wasi_filesystem::Errno::Destaddrreq => ERRNO_DESTADDRREQ, wasi_filesystem::Errno::Dquot => ERRNO_DQUOT, wasi_filesystem::Errno::Exist => ERRNO_EXIST, - wasi_filesystem::Errno::Fault => ERRNO_FAULT, wasi_filesystem::Errno::Fbig => ERRNO_FBIG, - wasi_filesystem::Errno::Hostunreach => ERRNO_HOSTUNREACH, - wasi_filesystem::Errno::Idrm => ERRNO_IDRM, wasi_filesystem::Errno::Ilseq => ERRNO_ILSEQ, wasi_filesystem::Errno::Inprogress => ERRNO_INPROGRESS, wasi_filesystem::Errno::Intr => ERRNO_INTR, wasi_filesystem::Errno::Inval => ERRNO_INVAL, wasi_filesystem::Errno::Io => ERRNO_IO, - wasi_filesystem::Errno::Isconn => ERRNO_ISCONN, wasi_filesystem::Errno::Isdir => ERRNO_ISDIR, wasi_filesystem::Errno::Loop => ERRNO_LOOP, - wasi_filesystem::Errno::Mfile => ERRNO_MFILE, wasi_filesystem::Errno::Mlink => ERRNO_MLINK, wasi_filesystem::Errno::Msgsize => ERRNO_MSGSIZE, - wasi_filesystem::Errno::Multihop => ERRNO_MULTIHOP, wasi_filesystem::Errno::Nametoolong => ERRNO_NAMETOOLONG, - wasi_filesystem::Errno::Netdown => ERRNO_NETDOWN, - wasi_filesystem::Errno::Netreset => ERRNO_NETRESET, - wasi_filesystem::Errno::Netunreach => ERRNO_NETUNREACH, - wasi_filesystem::Errno::Nfile => ERRNO_NFILE, - wasi_filesystem::Errno::Nobufs => ERRNO_NOBUFS, wasi_filesystem::Errno::Nodev => ERRNO_NODEV, wasi_filesystem::Errno::Noent => ERRNO_NOENT, - wasi_filesystem::Errno::Noexec => ERRNO_NOEXEC, wasi_filesystem::Errno::Nolck => ERRNO_NOLCK, - wasi_filesystem::Errno::Nolink => ERRNO_NOLINK, wasi_filesystem::Errno::Nomem => ERRNO_NOMEM, - wasi_filesystem::Errno::Nomsg => ERRNO_NOMSG, - wasi_filesystem::Errno::Noprotoopt => ERRNO_NOPROTOOPT, wasi_filesystem::Errno::Nospc => ERRNO_NOSPC, wasi_filesystem::Errno::Nosys => ERRNO_NOSYS, wasi_filesystem::Errno::Notdir => ERRNO_NOTDIR, @@ -1933,15 +1905,10 @@ impl From for Errno { wasi_filesystem::Errno::Notty => ERRNO_NOTTY, wasi_filesystem::Errno::Nxio => ERRNO_NXIO, wasi_filesystem::Errno::Overflow => ERRNO_OVERFLOW, - wasi_filesystem::Errno::Ownerdead => ERRNO_OWNERDEAD, wasi_filesystem::Errno::Perm => ERRNO_PERM, wasi_filesystem::Errno::Pipe => ERRNO_PIPE, - wasi_filesystem::Errno::Range => ERRNO_RANGE, wasi_filesystem::Errno::Rofs => ERRNO_ROFS, wasi_filesystem::Errno::Spipe => ERRNO_SPIPE, - wasi_filesystem::Errno::Srch => ERRNO_SRCH, - wasi_filesystem::Errno::Stale => ERRNO_STALE, - wasi_filesystem::Errno::Timedout => ERRNO_TIMEDOUT, wasi_filesystem::Errno::Txtbsy => ERRNO_TXTBSY, wasi_filesystem::Errno::Xdev => ERRNO_XDEV, } @@ -2044,8 +2011,8 @@ enum StreamType { /// Streaming data with a file. File(File), - /// Streaming data with a socket. - Socket(wasi_tcp::Socket), + /// Streaming data with a socket connection. + Socket(wasi_tcp::Connection), } impl Drop for Descriptor { @@ -2400,7 +2367,7 @@ impl State { } #[allow(dead_code)] // until Socket is implemented - fn get_socket(&self, fd: Fd) -> Result { + fn get_socket(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(Streams { type_: StreamType::Socket(socket), From b81c32154bb44c5b8dfad7c8445f3d0cbcb91f00 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 3 Feb 2023 16:15:39 -0800 Subject: [PATCH 075/153] Add the preview1 `sock_accept` function declaration to the polyfill. (#77) --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f487120b5254..e159d3f07c24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1776,6 +1776,13 @@ pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { }) } +/// Accept a new incoming connection. +/// Note: This is similar to `accept` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn sock_accept(fd: Fd, flags: Fdflags, connection: *mut Fd) -> Errno { + unreachable!() +} + /// Receive a message from a socket. /// Note: This is similar to `recv` in POSIX, though it also supports reading /// the data into multiple buffers in the manner of `readv`. From 60178d0fead1b9538ba1a0c20dcb4a09708a5c0f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Feb 2023 05:43:51 -0800 Subject: [PATCH 076/153] Split wasi-stream into separate input and output stream types. (#73) * Split wasi-stream into separate input and output stream types. This syncs the prototype's streams with the upstream wasi-io repo. Streams are unidirectional, so this allows us to statically describe whether something is an input stream or an output stream in an interface. This differs a little from the component model async streams, which don't have separate input and output streams, but it does partially reflect how the component model async design differentiates between input streams in type signatures, which are passed in as arguments, and output streams, which are returned as return values. * Fix compilation on Windows. --- src/lib.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e159d3f07c24..05aaa2c65368 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_poll, wasi_random, + wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_poll, wasi_random, wasi_stderr, wasi_tcp, }; use core::arch::wasm32; @@ -13,7 +13,7 @@ use core::mem::{self, align_of, forget, replace, size_of, ManuallyDrop, MaybeUni use core::ptr::{self, null_mut}; use core::slice; use wasi::*; -use wasi_poll::{Pollable, WasiStream}; +use wasi_poll::{InputStream, OutputStream, Pollable}; #[macro_use] mod macros; @@ -42,8 +42,8 @@ mod bindings { #[no_mangle] #[cfg(feature = "command")] pub unsafe extern "C" fn command( - stdin: WasiStream, - stdout: WasiStream, + stdin: InputStream, + stdout: OutputStream, args_ptr: *const WasmStr, args_len: usize, env_vars: StrTupleList, @@ -753,10 +753,9 @@ pub unsafe extern "C" fn fd_read( Descriptor::Streams(streams) => { let wasi_stream = streams.get_read_stream()?; - let read_len = unwrap_result(u32::try_from(len)); + let read_len = unwrap_result(u64::try_from(len)); let wasi_stream = streams.get_read_stream()?; - let (data, end) = - wasi_poll::read_stream(wasi_stream, read_len).map_err(|_| ERRNO_IO)?; + let (data, end) = wasi_io::read(wasi_stream, read_len).map_err(|_| ERRNO_IO)?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); @@ -1083,7 +1082,7 @@ pub unsafe extern "C" fn fd_write( State::with(|state| match state.get(fd)? { Descriptor::Streams(streams) => { let wasi_stream = streams.get_write_stream()?; - let bytes = wasi_poll::write_stream(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; + let bytes = wasi_io::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { @@ -1958,10 +1957,10 @@ enum Descriptor { /// type-specific operations like seeking. struct Streams { /// The output stream, if present. - input: Cell>, + input: Cell>, /// The input stream, if present. - output: Cell>, + output: Cell>, /// Information about the source of the stream. type_: StreamType, @@ -1969,7 +1968,7 @@ struct Streams { impl Streams { /// Return the input stream, initializing it on the fly if needed. - fn get_read_stream(&self) -> Result { + fn get_read_stream(&self) -> Result { match &self.input.get() { Some(wasi_stream) => Ok(*wasi_stream), None => match &self.type_ { @@ -1986,7 +1985,7 @@ impl Streams { } /// Return the output stream, initializing it on the fly if needed. - fn get_write_stream(&self) -> Result { + fn get_write_stream(&self) -> Result { match &self.output.get() { Some(wasi_stream) => Ok(*wasi_stream), None => match &self.type_ { @@ -2027,10 +2026,10 @@ impl Drop for Descriptor { match self { Descriptor::Streams(stream) => { if let Some(input) = stream.input.get() { - wasi_poll::drop_stream(input); + wasi_io::drop_input_stream(input); } if let Some(output) = stream.output.get() { - wasi_poll::drop_stream(output); + wasi_io::drop_output_stream(output); } match &stream.type_ { StreamType::File(file) => wasi_filesystem::close(file.fd), @@ -2401,14 +2400,14 @@ impl State { self.get_stream_with_error(fd, ERRNO_SPIPE) } - fn get_read_stream(&self, fd: Fd) -> Result { + fn get_read_stream(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_read_stream(), Descriptor::Closed(_) | Descriptor::Stderr => Err(ERRNO_BADF), } } - fn get_write_stream(&self, fd: Fd) -> Result { + fn get_write_stream(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_write_stream(), Descriptor::Closed(_) | Descriptor::Stderr => Err(ERRNO_BADF), From b80819720e933c5a5c9583a8b3fa58fd26ab3f39 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 6 Feb 2023 13:41:38 -0800 Subject: [PATCH 077/153] polyfill: fill in fdflags_append from File::append --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 05aaa2c65368..2cd06818576a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -430,6 +430,9 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { if flags.contains(wasi_filesystem::DescriptorFlags::SYNC) { fs_flags |= FDFLAGS_SYNC; } + if file.append { + fs_flags |= FDFLAGS_APPEND; + } let fs_rights_inheriting = fs_rights_base; stat.write(Fdstat { From b1f53095718e310c74272a62598030e6b474c07e Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 13 Feb 2023 11:53:45 -0700 Subject: [PATCH 078/153] short-circuit reentrance when allocating stack and `State` (#83) * short-circuit reentrance when allocating stack and `State` Per https://github.com/bytecodealliance/wasm-tools/pull/919, `wit-component` needs to lazily allocate the adapter stack to avoid premature or infinite reentrance from the main module to the adapter. This means adding an additional `allocation_state` global variable and using it to determine when to return early from an exported function, e.g. because we're in the process of either allocating the stack or allocating `State`. This requires an updated `wit-component` dependency once https://github.com/bytecodealliance/wasm-tools/pull/919 has been merged. Fixes #78 Signed-off-by: Joel Dice * remove redundant unsafe blocks Signed-off-by: Joel Dice * update dependencies This brings us up-to-date with wasi-tools, wit-bindgen, and the latest component ABI. Signed-off-by: Joel Dice --------- Signed-off-by: Joel Dice --- build.rs | 151 ++++++++++++++++++++++++++++------------------ src/lib.rs | 173 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 205 insertions(+), 119 deletions(-) diff --git a/build.rs b/build.rs index b5f7ccd30bb8..6b0dc9652b25 100644 --- a/build.rs +++ b/build.rs @@ -28,18 +28,18 @@ fn main() { /// ```rust /// std::arch::global_asm!( /// " -/// .globaltype internal_global_ptr, i32 -/// internal_global_ptr: +/// .globaltype internal_state_ptr, i32 +/// internal_state_ptr: /// " /// ); /// /// #[no_mangle] -/// extern "C" fn get_global_ptr() -> *mut u8 { +/// extern "C" fn get_state_ptr() -> *mut u8 { /// unsafe { /// let ret: *mut u8; /// std::arch::asm!( /// " -/// global.get internal_global_ptr +/// global.get internal_state_ptr /// ", /// out(local) ret, /// options(nostack, readonly) @@ -49,18 +49,20 @@ fn main() { /// } /// /// #[no_mangle] -/// extern "C" fn set_global_ptr(val: *mut u8) { +/// extern "C" fn set_state_ptr(val: *mut u8) { /// unsafe { /// std::arch::asm!( /// " /// local.get {} -/// global.set internal_global_ptr +/// global.set internal_state_ptr /// ", /// in(local) val, /// options(nostack, readonly) /// ); /// } /// } +/// +/// // And likewise for `allocation_state`, `get_allocation_state`, and `set_allocation_state` /// ``` /// /// The main trickiness here is getting the `reloc.CODE` and `linking` sections @@ -80,10 +82,21 @@ fn build_raw_intrinsics() -> Vec { let mut funcs = FunctionSection::new(); funcs.function(0); funcs.function(1); + funcs.function(0); + funcs.function(1); module.section(&funcs); // Declare the globals. let mut globals = GlobalSection::new(); + // internal_state_ptr + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + }, + &ConstExpr::i32_const(0), + ); + // allocation_state globals.global( GlobalType { val_type: ValType::I32, @@ -98,50 +111,45 @@ fn build_raw_intrinsics() -> Vec { // later. At this time `wasm-encoder` doesn't give enough functionality to // use the high-level APIs. so everything is done manually here. // - // First the function body is created and then it's appended into a code - // section. + // First the function bodies are created and then they're appended into a + // code section. let mut code = Vec::new(); - 2u32.encode(&mut code); + 4u32.encode(&mut code); // number of functions - // get_global_ptr - let global_offset0 = { - let mut body = Vec::new(); - 0u32.encode(&mut body); // no locals - let global_offset = body.len() + 1; - // global.get 0 ;; but with maximal encoding of the 0 - body.extend_from_slice(&[0x23, 0x80, 0x80, 0x80, 0x80, 0x00]); - End.encode(&mut body); - body.len().encode(&mut code); // length of the function - let offset = code.len() + global_offset; - code.extend_from_slice(&body); // the function itself - offset - }; + let global_get = 0x23; + let global_set = 0x24; + + let encode = |code: &mut _, global, instruction| { + assert!(global < 0x7F); - // set_global_ptr - let global_offset1 = { let mut body = Vec::new(); 0u32.encode(&mut body); // no locals - LocalGet(0).encode(&mut body); + if instruction == global_set { + LocalGet(0).encode(&mut body); + } let global_offset = body.len() + 1; - // global.set 0 ;; but with maximal encoding of the 0 - body.extend_from_slice(&[0x24, 0x80, 0x80, 0x80, 0x80, 0x00]); + // global.get $global ;; but with maximal encoding of $global + body.extend_from_slice(&[instruction, 0x80u8 + global, 0x80, 0x80, 0x80, 0x00]); End.encode(&mut body); - body.len().encode(&mut code); // length of the function + body.len().encode(code); // length of the function let offset = code.len() + global_offset; code.extend_from_slice(&body); // the function itself offset }; + let internal_state_ptr_ref1 = encode(&mut code, 0, global_get); // get_state_ptr + let internal_state_ptr_ref2 = encode(&mut code, 0, global_set); // set_state_ptr + let allocation_state_ref1 = encode(&mut code, 1, global_get); // get_allocation_state + let allocation_state_ref2 = encode(&mut code, 1, global_set); // set_allocation_state + module.section(&RawSection { id: SectionId::Code as u8, data: &code, }); - // Here the linking section is constructed. There are two symbols described - // here, one for the function that we injected and one for the global - // that was injected. The injected global here is referenced in the - // relocations below. + // Here the linking section is constructed. There is one symbol for each function and global. The injected + // globals here are referenced in the relocations below. // // More information about this format is at // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md @@ -151,22 +159,37 @@ fn build_raw_intrinsics() -> Vec { linking.push(0x08); // `WASM_SYMBOL_TABLE` let mut subsection = Vec::new(); - 3u32.encode(&mut subsection); // 3 symbols (2 functions + 1 global) + 6u32.encode(&mut subsection); // 6 symbols (4 functions + 2 globals) subsection.push(0x00); // SYMTAB_FUNCTION 0x00.encode(&mut subsection); // flags 0u32.encode(&mut subsection); // function index - "get_global_ptr".encode(&mut subsection); // symbol name + "get_state_ptr".encode(&mut subsection); // symbol name subsection.push(0x00); // SYMTAB_FUNCTION 0x00.encode(&mut subsection); // flags 1u32.encode(&mut subsection); // function index - "set_global_ptr".encode(&mut subsection); // symbol name + "set_state_ptr".encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 2u32.encode(&mut subsection); // function index + "get_allocation_state".encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 3u32.encode(&mut subsection); // function index + "set_allocation_state".encode(&mut subsection); // symbol name subsection.push(0x02); // SYMTAB_GLOBAL - 0x02.encode(&mut subsection); // flags + 0x02.encode(&mut subsection); // flags (WASM_SYM_BINDING_LOCAL) 0u32.encode(&mut subsection); // global index - "internal_global_ptr".encode(&mut subsection); // symbol name + "internal_state_ptr".encode(&mut subsection); // symbol name + + subsection.push(0x02); // SYMTAB_GLOBAL + 0x00.encode(&mut subsection); // flags + 1u32.encode(&mut subsection); // global index + "allocation_state".encode(&mut subsection); // symbol name subsection.encode(&mut linking); module.section(&CustomSection { @@ -175,20 +198,28 @@ fn build_raw_intrinsics() -> Vec { }); } - // A `reloc.CODE` section is appended here with two relocations for the - // two `global`-referencing instructions that were added. + // A `reloc.CODE` section is appended here with relocations for the + // `global`-referencing instructions that were added. { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 2u32.encode(&mut reloc); // 2 relocations + 4u32.encode(&mut reloc); // 4 relocations + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + internal_state_ptr_ref1.encode(&mut reloc); // offset + 4u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset0.encode(&mut reloc); // offset - 2u32.encode(&mut reloc); // symbol index + internal_state_ptr_ref2.encode(&mut reloc); // offset + 4u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB - global_offset1.encode(&mut reloc); // offset - 2u32.encode(&mut reloc); // symbol index + allocation_state_ref1.encode(&mut reloc); // offset + 5u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + allocation_state_ref2.encode(&mut reloc); // offset + 5u32.encode(&mut reloc); // symbol index module.section(&CustomSection { name: "reloc.CODE", @@ -219,15 +250,19 @@ fn build_archive(wasm: &[u8]) -> Vec { // the entire archive, for which object has the symbol // * N nul-delimited strings for each symbol // - // Here we're building an archive with just one symbol so it's a bit + // Here we're building an archive with just a few symbols so it's a bit // easier. Note though we don't know the offset of our `intrinsics.o` up // front so it's left as 0 for now and filled in later. let mut symbol_table = Vec::new(); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 2))); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); - symbol_table.extend_from_slice(b"get_global_ptr\0"); - symbol_table.extend_from_slice(b"set_global_ptr\0"); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 5))); + for _ in 0..5 { + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + } + symbol_table.extend_from_slice(b"get_state_ptr\0"); + symbol_table.extend_from_slice(b"set_state_ptr\0"); + symbol_table.extend_from_slice(b"get_allocation_state\0"); + symbol_table.extend_from_slice(b"set_allocation_state\0"); + symbol_table.extend_from_slice(b"allocation_state\0"); archive.extend_from_slice(bytes_of(&object::archive::Header { name: *b"/ ", @@ -244,7 +279,7 @@ fn build_archive(wasm: &[u8]) -> Vec { let symtab_offset = archive.len(); archive.extend_from_slice(&symbol_table); - // All archive memberes must start on even offsets + // All archive members must start on even offsets if archive.len() & 1 == 1 { archive.push(0x00); } @@ -252,14 +287,12 @@ fn build_archive(wasm: &[u8]) -> Vec { // Now that we have the starting offset of the `intrinsics.o` file go back // and fill in the offset within the symbol table generated earlier. let member_offset = archive.len(); - archive[symtab_offset + 4..][..4].copy_from_slice(bytes_of(&U32Bytes::new( - BigEndian, - member_offset.try_into().unwrap(), - ))); - archive[symtab_offset + 8..][..4].copy_from_slice(bytes_of(&U32Bytes::new( - BigEndian, - member_offset.try_into().unwrap(), - ))); + for index in 1..6 { + archive[symtab_offset + (index * 4)..][..4].copy_from_slice(bytes_of(&U32Bytes::new( + BigEndian, + member_offset.try_into().unwrap(), + ))); + } archive.extend_from_slice(object::bytes_of(&object::archive::Header { name: *b"intrinsics.o ", diff --git a/src/lib.rs b/src/lib.rs index 2cd06818576a..a319ba40dd40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,23 +263,32 @@ pub unsafe extern "C" fn environ_sizes_get( environc: *mut Size, environ_buf_size: *mut Size, ) -> Errno { - State::with(|state| { - if let Some(list) = state.env_vars { - *environc = list.len(); - *environ_buf_size = { - let mut sum = 0; - for pair in list { - sum += pair.key.len + pair.value.len + 2; - } - sum - }; - } else { - *environc = 0; - *environ_buf_size = 0; - } + if matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + State::with(|state| { + if let Some(list) = state.env_vars { + *environc = list.len(); + *environ_buf_size = { + let mut sum = 0; + for pair in list { + sum += pair.key.len + pair.value.len + 2; + } + sum + }; + } else { + *environc = 0; + *environ_buf_size = 0; + } - Ok(()) - }) + Ok(()) + }) + } else { + *environc = 0; + *environ_buf_size = 0; + ERRNO_SUCCESS + } } /// Return the resolution of a clock. @@ -315,22 +324,30 @@ pub unsafe extern "C" fn clock_time_get( _precision: Timestamp, time: &mut Timestamp, ) -> Errno { - State::with(|state| { - match id { - CLOCKID_MONOTONIC => { - *time = wasi_clocks::monotonic_clock_now(state.default_monotonic_clock()); - } - CLOCKID_REALTIME => { - let res = wasi_clocks::wall_clock_now(state.default_wall_clock()); - *time = Timestamp::from(res.nanoseconds) - .checked_add(res.seconds) - .and_then(|secs| secs.checked_mul(1_000_000_000)) - .ok_or(ERRNO_OVERFLOW)?; + if matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + State::with(|state| { + match id { + CLOCKID_MONOTONIC => { + *time = wasi_clocks::monotonic_clock_now(state.default_monotonic_clock()); + } + CLOCKID_REALTIME => { + let res = wasi_clocks::wall_clock_now(state.default_wall_clock()); + *time = Timestamp::from(res.nanoseconds) + .checked_add(res.seconds) + .and_then(|secs| secs.checked_mul(1_000_000_000)) + .ok_or(ERRNO_OVERFLOW)?; + } + _ => unreachable!(), } - _ => unreachable!(), - } - Ok(()) - }) + Ok(()) + }) + } else { + *time = Timestamp::from(0u64); + ERRNO_SUCCESS + } } /// Provide file advisory information on a file descriptor. @@ -661,22 +678,29 @@ fn get_preopen(state: &State, fd: Fd) -> Option<&Preopen> { /// Return a description of the given preopened file descriptor. #[no_mangle] pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { - State::with(|state| { - if let Some(preopen) = get_preopen(state, fd) { - buf.write(Prestat { - tag: 0, - u: PrestatU { - dir: PrestatDir { - pr_name_len: preopen.path.len, + if matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + State::with(|state| { + if let Some(preopen) = get_preopen(state, fd) { + buf.write(Prestat { + tag: 0, + u: PrestatU { + dir: PrestatDir { + pr_name_len: preopen.path.len, + }, }, - }, - }); + }); - Ok(()) - } else { - Err(ERRNO_BADF) - } - }) + Ok(()) + } else { + Err(ERRNO_BADF) + } + }) + } else { + ERRNO_BADF + } } /// Return a description of the given preopened file descriptor. @@ -1445,7 +1469,8 @@ impl From for Errno { HostUnreachable => ERRNO_HOSTUNREACH, NetworkDown => ERRNO_NETDOWN, NetworkUnreachable => ERRNO_NETUNREACH, - Timeout => ERRNO_TIMEDOUT, + Timedout => ERRNO_TIMEDOUT, + _ => unreachable!(), } } } @@ -1763,19 +1788,26 @@ pub unsafe extern "C" fn sched_yield() -> Errno { /// number generator, rather than to provide the random data directly. #[no_mangle] pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { - State::with(|state| { - state.register_buffer(buf, buf_len); + if matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + State::with(|state| { + state.register_buffer(buf, buf_len); - assert_eq!(buf_len as u32 as Size, buf_len); - let result = wasi_random::get_random_bytes(buf_len as u32); - assert_eq!(result.as_ptr(), buf); + assert_eq!(buf_len as u32 as Size, buf_len); + let result = wasi_random::get_random_bytes(buf_len as u32); + assert_eq!(result.as_ptr(), buf); - // The returned buffer's memory was allocated in `buf`, so don't separately - // free it. - forget(result); + // The returned buffer's memory was allocated in `buf`, so don't separately + // free it. + forget(result); - Ok(()) - }) + Ok(()) + }) + } else { + ERRNO_SUCCESS + } } /// Accept a new incoming connection. @@ -2197,10 +2229,22 @@ const _: () = { let _size_assert: [(); PAGE_SIZE] = [(); size_of::>()]; }; +#[allow(unused)] +#[repr(i32)] +enum AllocationState { + StackUnallocated, + StackAllocating, + StackAllocated, + StateAllocating, + StateAllocated, +} + #[allow(improper_ctypes)] extern "C" { - fn get_global_ptr() -> *const RefCell; - fn set_global_ptr(a: *const RefCell); + fn get_state_ptr() -> *const RefCell; + fn set_state_ptr(state: *const RefCell); + fn get_allocation_state() -> AllocationState; + fn set_allocation_state(state: AllocationState); } impl State { @@ -2230,10 +2274,10 @@ impl State { fn ptr() -> &'static RefCell { unsafe { - let mut ptr = get_global_ptr(); + let mut ptr = get_state_ptr(); if ptr.is_null() { ptr = State::new(); - set_global_ptr(ptr); + set_state_ptr(ptr); } &*ptr } @@ -2251,6 +2295,13 @@ impl State { ) -> *mut u8; } + assert!(matches!( + unsafe { get_allocation_state() }, + AllocationState::StackAllocated + )); + + unsafe { set_allocation_state(AllocationState::StateAllocating) }; + let ret = unsafe { cabi_realloc( ptr::null_mut(), @@ -2260,6 +2311,8 @@ impl State { ) as *mut RefCell }; + unsafe { set_allocation_state(AllocationState::StateAllocated) }; + let ret = unsafe { ret.write(RefCell::new(State { magic1: MAGIC, From 0d9bc47e412c5c46928e243e7055e00c66f73308 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 13 Feb 2023 16:48:22 -0600 Subject: [PATCH 079/153] Move to crates.io/releases of dependencies (#84) * Use precompiled `wit-bindgen` binaries from CI * Use a release branch of Wasmtime for 6.0.0 * Use `wit-bindgen`-the-crate from crates.io --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a319ba40dd40..47d2631fa93d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ mod macros; mod bindings { #[cfg(feature = "command")] - wit_bindgen_guest_rust::generate!({ + wit_bindgen::generate!({ world: "wasi-command", no_std, raw_strings, @@ -31,7 +31,7 @@ mod bindings { }); #[cfg(not(feature = "command"))] - wit_bindgen_guest_rust::generate!({ + wit_bindgen::generate!({ world: "wasi", no_std, raw_strings, From 11e24c2e236eabbc5e5537b94a52d0c5fbd9bca3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 14 Feb 2023 17:40:15 -0800 Subject: [PATCH 080/153] replace `fn unwrap` and `fn unwrap_result` with postfix `.trapping_unwrap()` (#86) this is just aesthetic, but prefixed unwraps are a lot harder on the eyes than postfixed --- src/lib.rs | 109 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 42 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 47d2631fa93d..0b45df59238e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,15 +76,17 @@ pub unsafe extern "C" fn command( state.preopens = Some(preopens); for preopen in preopens { - unwrap_result(state.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::File(File { - fd: preopen.descriptor, - position: Cell::new(0), - append: false, - }), - }))); + state + .push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + append: false, + }), + })) + .trapping_unwrap(); } Ok(()) @@ -98,19 +100,28 @@ pub unsafe extern "C" fn command( 0 } -fn unwrap(maybe: Option) -> T { - if let Some(value) = maybe { - value - } else { - unreachable!("unwrap failed") +// The unwrap/expect methods in std pull panic when they fail, which pulls +// in unwinding machinery that we can't use in the adapter. Instead, use this +// extension trait to get postfixed upwrap on Option and Result. +trait TrappingUnwrap { + fn trapping_unwrap(self) -> T; +} + +impl TrappingUnwrap for Option { + fn trapping_unwrap(self) -> T { + match self { + Some(t) => t, + None => unreachable!(), + } } } -fn unwrap_result(result: Result) -> T { - if let Ok(value) = result { - value - } else { - unreachable!("unwrap result failed") +impl TrappingUnwrap for Result { + fn trapping_unwrap(self) -> T { + match self { + Ok(t) => t, + Err(_) => unreachable!(), + } } } @@ -164,7 +175,7 @@ pub unsafe extern "C" fn cabi_export_realloc( State::with_mut(|state| { let data = state.command_data.as_mut_ptr(); let ptr = align_to( - unwrap_result(usize::try_from(state.command_data_next)), + usize::try_from(state.command_data_next).trapping_unwrap(), align, ); @@ -654,7 +665,7 @@ pub unsafe extern "C" fn fd_pread( let len = (*iovs_ptr).buf_len; state.register_buffer(ptr, len); - let read_len = unwrap_result(u32::try_from(len)); + let read_len = u32::try_from(len).trapping_unwrap(); let file = state.get_file(fd)?; let (data, end) = wasi_filesystem::pread(file.fd, read_len, offset)?; assert_eq!(data.as_ptr(), ptr); @@ -780,7 +791,7 @@ pub unsafe extern "C" fn fd_read( Descriptor::Streams(streams) => { let wasi_stream = streams.get_read_stream()?; - let read_len = unwrap_result(u64::try_from(len)); + let read_len = u64::try_from(len).trapping_unwrap(); let wasi_stream = streams.get_read_stream()?; let (data, end) = wasi_io::read(wasi_stream, read_len).map_err(|_| ERRNO_IO)?; @@ -983,7 +994,7 @@ pub unsafe extern "C" fn fd_readdir( let dirent = wasi::Dirent { d_next: self.cookie, d_ino: ino.unwrap_or(0), - d_namlen: unwrap_result(u32::try_from(name.len())), + d_namlen: u32::try_from(name.len()).trapping_unwrap(), d_type: type_.into(), }; // Extend the lifetime of `name` to the `self.state` lifetime for @@ -1021,7 +1032,7 @@ pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { let fd_desc = state.get_mut(fd)?; let desc = replace(fd_desc, Descriptor::Closed(closed)); - let to_desc = unwrap_result(state.get_mut(to)); + let to_desc = state.get_mut(to).trapping_unwrap(); *to_desc = desc; state.closed = Some(fd); Ok(()) @@ -1297,7 +1308,7 @@ pub unsafe extern "C" fn path_open( None => state.push_desc(desc)?, // `recycle_fd` is a free fd. Some(recycle_fd) => { - let recycle_desc = unwrap_result(state.get_mut(recycle_fd)); + let recycle_desc = state.get_mut(recycle_fd).trapping_unwrap(); let next_closed = match recycle_desc { Descriptor::Closed(next) => *next, _ => unreachable!(), @@ -1495,11 +1506,18 @@ pub unsafe extern "C" fn poll_oneoff( assert!(align_of::() >= align_of::()); assert!(align_of::() >= align_of::()); assert!( - unwrap(nsubscriptions.checked_mul(size_of::())) - >= unwrap( - unwrap(nsubscriptions.checked_mul(size_of::())) - .checked_add(unwrap(nsubscriptions.checked_mul(size_of::()))) - ) + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap() + >= nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap() + .checked_add( + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap() + ) + .trapping_unwrap() ); // Store the pollable handles at the beginning, and the bool results at the @@ -1515,7 +1533,9 @@ pub unsafe extern "C" fn poll_oneoff( State::with(|state| { state.register_buffer( results, - unwrap(nsubscriptions.checked_mul(size_of::())), + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap(), ); let mut pollables = Pollables { @@ -1646,7 +1666,9 @@ pub unsafe extern "C" fn poll_oneoff( 1 => { type_ = EVENTTYPE_FD_READ; - let desc = unwrap_result(state.get(subscription.u.u.fd_read.file_descriptor)); + let desc = state + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match &streams.type_ { StreamType::File(file) => match wasi_filesystem::stat(file.fd) { @@ -1699,7 +1721,9 @@ pub unsafe extern "C" fn poll_oneoff( } 2 => { type_ = EVENTTYPE_FD_WRITE; - let desc = unwrap_result(state.get(subscription.u.u.fd_read.file_descriptor)); + let desc = state + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match streams.type_ { StreamType::File(_) | StreamType::Unknown => { @@ -2354,22 +2378,23 @@ impl State { fn init(&mut self) { // Set up a default stdin. This will be overridden when `command` // is called. - unwrap_result(self.push_desc(Descriptor::Streams(Streams { + self.push_desc(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), type_: StreamType::Unknown, - }))); + })) + .trapping_unwrap(); // Set up a default stdout, writing to the stderr device. This will // be overridden when `command` is called. - unwrap_result(self.push_desc(Descriptor::Stderr)); + self.push_desc(Descriptor::Stderr).trapping_unwrap(); // Set up a default stderr. - unwrap_result(self.push_desc(Descriptor::Stderr)); + self.push_desc(Descriptor::Stderr).trapping_unwrap(); } fn push_desc(&mut self, desc: Descriptor) -> Result { unsafe { let descriptors = self.descriptors.as_mut_ptr(); - let ndescriptors = unwrap_result(usize::try_from(self.ndescriptors)); + let ndescriptors = usize::try_from(self.ndescriptors).trapping_unwrap(); if ndescriptors >= (*descriptors).len() { return Err(ERRNO_NOMEM); } @@ -2383,7 +2408,7 @@ impl State { unsafe { slice::from_raw_parts( self.descriptors.as_ptr().cast(), - unwrap_result(usize::try_from(self.ndescriptors)), + usize::try_from(self.ndescriptors).trapping_unwrap(), ) } } @@ -2392,20 +2417,20 @@ impl State { unsafe { slice::from_raw_parts_mut( self.descriptors.as_mut_ptr().cast(), - unwrap_result(usize::try_from(self.ndescriptors)), + usize::try_from(self.ndescriptors).trapping_unwrap(), ) } } fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { self.descriptors() - .get(unwrap_result(usize::try_from(fd))) + .get(usize::try_from(fd).trapping_unwrap()) .ok_or(ERRNO_BADF) } fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { self.descriptors_mut() - .get_mut(unwrap_result(usize::try_from(fd))) + .get_mut(usize::try_from(fd).trapping_unwrap()) .ok_or(ERRNO_BADF) } From fb8997a2a601ca86f91900d43fb8d5c9c9eaa6dd Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 15 Feb 2023 06:30:19 -0800 Subject: [PATCH 081/153] Update wasi-filesystem, wasi-clocks, wasi-io, and wasi-poll. This updates to the latest versions of the wit files in the proposal repos, and updates the polyfill and host implementations accordingly. The changes here are just some renames and reorganizations. --- src/lib.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0b45df59238e..4b05a109fc8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_clocks, wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_poll, wasi_random, - wasi_stderr, wasi_tcp, + wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_monotonic_clock, wasi_poll, + wasi_random, wasi_stderr, wasi_tcp, wasi_wall_clock, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -13,7 +13,8 @@ use core::mem::{self, align_of, forget, replace, size_of, ManuallyDrop, MaybeUni use core::ptr::{self, null_mut}; use core::slice; use wasi::*; -use wasi_poll::{InputStream, OutputStream, Pollable}; +use wasi_io::{InputStream, OutputStream}; +use wasi_poll::Pollable; #[macro_use] mod macros; @@ -311,11 +312,11 @@ pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errn State::with(|state| { match id { CLOCKID_MONOTONIC => { - let res = wasi_clocks::monotonic_clock_resolution(state.default_monotonic_clock()); + let res = wasi_monotonic_clock::resolution(state.default_monotonic_clock()); *resolution = res; } CLOCKID_REALTIME => { - let res = wasi_clocks::wall_clock_resolution(state.default_wall_clock()); + let res = wasi_wall_clock::resolution(state.default_wall_clock()); *resolution = Timestamp::from(res.nanoseconds) .checked_add(res.seconds) .and_then(|secs| secs.checked_mul(1_000_000_000)) @@ -342,10 +343,10 @@ pub unsafe extern "C" fn clock_time_get( State::with(|state| { match id { CLOCKID_MONOTONIC => { - *time = wasi_clocks::monotonic_clock_now(state.default_monotonic_clock()); + *time = wasi_monotonic_clock::now(state.default_monotonic_clock()); } CLOCKID_REALTIME => { - let res = wasi_clocks::wall_clock_now(state.default_wall_clock()); + let res = wasi_wall_clock::now(state.default_wall_clock()); *time = Timestamp::from(res.nanoseconds) .checked_add(res.seconds) .and_then(|secs| secs.checked_mul(1_000_000_000)) @@ -665,9 +666,8 @@ pub unsafe extern "C" fn fd_pread( let len = (*iovs_ptr).buf_len; state.register_buffer(ptr, len); - let read_len = u32::try_from(len).trapping_unwrap(); let file = state.get_file(fd)?; - let (data, end) = wasi_filesystem::pread(file.fd, read_len, offset)?; + let (data, end) = wasi_filesystem::pread(file.fd, len as u64, offset)?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); @@ -1557,13 +1557,13 @@ pub unsafe extern "C" fn poll_oneoff( CLOCKID_REALTIME => { let timeout = if absolute { // Convert `clock.timeout` to `Datetime`. - let mut datetime = wasi_clocks::Datetime { + let mut datetime = wasi_wall_clock::Datetime { seconds: clock.timeout / 1_000_000_000, nanoseconds: (clock.timeout % 1_000_000_000) as _, }; // Subtract `now`. - let now = wasi_clocks::wall_clock_now(state.default_wall_clock()); + let now = wasi_wall_clock::now(state.default_wall_clock()); datetime.seconds -= now.seconds; if datetime.nanoseconds < now.nanoseconds { datetime.seconds -= 1; @@ -1583,14 +1583,14 @@ pub unsafe extern "C" fn poll_oneoff( clock.timeout }; - wasi_poll::subscribe_monotonic_clock( + wasi_monotonic_clock::subscribe( state.default_monotonic_clock(), timeout, false, ) } - CLOCKID_MONOTONIC => wasi_poll::subscribe_monotonic_clock( + CLOCKID_MONOTONIC => wasi_monotonic_clock::subscribe( state.default_monotonic_clock(), clock.timeout, absolute, @@ -1602,11 +1602,11 @@ pub unsafe extern "C" fn poll_oneoff( EVENTTYPE_FD_READ => { match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { - Ok(stream) => wasi_poll::subscribe_read(stream), + Ok(stream) => wasi_io::subscribe_read(stream), // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll // immediately fail. - Err(ERRNO_BADF) => wasi_poll::subscribe_monotonic_clock( + Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( state.default_monotonic_clock(), 0, false, @@ -1617,11 +1617,11 @@ pub unsafe extern "C" fn poll_oneoff( EVENTTYPE_FD_WRITE => { match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) { - Ok(stream) => wasi_poll::subscribe_write(stream), + Ok(stream) => wasi_io::subscribe(stream), // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll // immediately fail. - Err(ERRNO_BADF) => wasi_poll::subscribe_monotonic_clock( + Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( state.default_monotonic_clock(), 0, false, @@ -2091,7 +2091,7 @@ impl Drop for Descriptor { wasi_io::drop_output_stream(output); } match &stream.type_ { - StreamType::File(file) => wasi_filesystem::close(file.fd), + StreamType::File(file) => wasi_filesystem::drop_descriptor(file.fd), StreamType::Socket(_) => unreachable!(), StreamType::EmptyStdin | StreamType::Unknown => {} } @@ -2194,7 +2194,7 @@ struct DirEntryStream(wasi_filesystem::DirEntryStream); impl Drop for DirEntryStream { fn drop(&mut self) { - wasi_filesystem::close_dir_entry_stream(self.0); + wasi_filesystem::drop_dir_entry_stream(self.0); } } From 7989053e019f5ee8803117908dbf561d999b90c3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 17 Feb 2023 07:14:03 -0800 Subject: [PATCH 082/153] Refactor adapter's allocators (#88) * create a reusable BumpArena, rename command_data to long_lived * use ImportAlloc::with_buffer to register a bump arena for each import func call * import alloc just hands out buffer, nothing more only improvement over prior implementation is this one makes sure alignment is followed * make merging easier... * bumparena is now a singleton so it can own the memory area --- src/lib.rs | 595 +++++++++++++++++++++++++++++------------------------ 1 file changed, 323 insertions(+), 272 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4b05a109fc8d..df5364c98c40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,7 @@ impl TrappingUnwrap for Result { pub unsafe extern "C" fn cabi_import_realloc( old_ptr: *mut u8, old_size: usize, - _align: usize, + align: usize, new_size: usize, ) -> *mut u8 { if !old_ptr.is_null() || old_size != 0 { @@ -138,23 +138,83 @@ pub unsafe extern "C" fn cabi_import_realloc( } let mut ptr = null_mut::(); State::with(|state| { - ptr = state.buffer_ptr.replace(null_mut()); - if ptr.is_null() { - unreachable!(); - } - let len = state.buffer_len.replace(0); - if len < new_size { - unreachable!(); - } + ptr = state.import_alloc.alloc(align, new_size); Ok(()) }); ptr } +/// Bump-allocated memory arena. This is a singleton - the +/// memory will be sized according to `bump_arena_size()`. +struct BumpArena { + data: MaybeUninit<[u8; bump_arena_size()]>, + position: Cell, +} + +impl BumpArena { + fn new() -> Self { + BumpArena { + data: MaybeUninit::uninit(), + position: Cell::new(0), + } + } + fn alloc(&self, align: usize, size: usize) -> *mut u8 { + let start = self.data.as_ptr() as usize; + let next = start + self.position.get(); + let alloc = align_to(next, align); + let offset = alloc - start; + if offset + size > bump_arena_size() { + unreachable!("out of memory"); + } + self.position.set(offset + size); + alloc as *mut u8 + } +} fn align_to(ptr: usize, align: usize) -> usize { (ptr + (align - 1)) & !(align - 1) } +struct ImportAlloc { + buffer: Cell<*mut u8>, + len: Cell, +} + +impl ImportAlloc { + fn new() -> Self { + ImportAlloc { + buffer: Cell::new(std::ptr::null_mut()), + len: Cell::new(0), + } + } + + fn with_buffer(&self, buffer: *mut u8, len: usize, f: impl FnOnce() -> T) -> T { + let prev = self.buffer.replace(buffer); + if !prev.is_null() { + unreachable!() + } + self.len.set(len); + let r = f(); + self.buffer.set(std::ptr::null_mut()); + r + } + + fn alloc(&self, align: usize, size: usize) -> *mut u8 { + let buffer = self.buffer.get(); + if buffer.is_null() { + unreachable!("buffer not provided, or already used") + } + let buffer = buffer as usize; + let alloc = align_to(buffer, align); + if alloc.checked_add(size).trapping_unwrap() + > buffer.checked_add(self.len.get()).trapping_unwrap() + { + unreachable!("out of memory") + } + self.buffer.set(std::ptr::null_mut()); + alloc as *mut u8 + } +} + /// This allocator is only used for the `command` entrypoint. /// /// The implementation here is a bump allocator into `State::command_data` which @@ -174,21 +234,7 @@ pub unsafe extern "C" fn cabi_export_realloc( } let mut ret = null_mut::(); State::with_mut(|state| { - let data = state.command_data.as_mut_ptr(); - let ptr = align_to( - usize::try_from(state.command_data_next).trapping_unwrap(), - align, - ); - - // "oom" as too much argument data tried to flow into the component. - // Ideally this would have a better error message? - if ptr + new_size > (*data).len() { - unreachable!("out of memory"); - } - state.command_data_next = (ptr + new_size) - .try_into() - .unwrap_or_else(|_| unreachable!()); - ret = (*data).as_mut_ptr().add(ptr); + ret = state.long_lived_arena.alloc(align, new_size); Ok(()) }); ret @@ -664,10 +710,11 @@ pub unsafe extern "C" fn fd_pread( State::with(|state| { let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - state.register_buffer(ptr, len); let file = state.get_file(fd)?; - let (data, end) = wasi_filesystem::pread(file.fd, len as u64, offset)?; + let (data, end) = state.import_alloc.with_buffer(ptr, len, || { + wasi_filesystem::pread(file.fd, len as u64, offset) + })?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); @@ -785,15 +832,16 @@ pub unsafe extern "C" fn fd_read( let len = (*iovs_ptr).buf_len; State::with(|state| { - state.register_buffer(ptr, len); - match state.get(fd)? { Descriptor::Streams(streams) => { let wasi_stream = streams.get_read_stream()?; let read_len = u64::try_from(len).trapping_unwrap(); let wasi_stream = streams.get_read_stream()?; - let (data, end) = wasi_io::read(wasi_stream, read_len).map_err(|_| ERRNO_IO)?; + let (data, end) = state + .import_alloc + .with_buffer(ptr, len, || wasi_io::read(wasi_stream, read_len)) + .map_err(|_| ERRNO_IO)?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); @@ -981,9 +1029,12 @@ pub unsafe extern "C" fn fd_readdir( Ok((dirent, buffer)) }); } - self.state - .register_buffer(self.state.path_buf.get().cast(), PATH_MAX); - let entry = match wasi_filesystem::read_dir_entry(self.stream.0) { + let entry = self.state.import_alloc.with_buffer( + self.state.path_buf.get().cast(), + PATH_MAX, + || wasi_filesystem::read_dir_entry(self.stream.0), + ); + let entry = match entry { Ok(Some(entry)) => entry, Ok(None) => return None, Err(e) => return Some(Err(e.into())), @@ -1343,14 +1394,18 @@ pub unsafe extern "C" fn path_readlink( // so instead we handle this case specially. let use_state_buf = buf_len < PATH_MAX; - if use_state_buf { - state.register_buffer(state.path_buf.get().cast(), PATH_MAX); - } else { - state.register_buffer(buf, buf_len); - } - let file = state.get_dir(fd)?; - let path = wasi_filesystem::readlink_at(file.fd, path)?; + let path = if use_state_buf { + state + .import_alloc + .with_buffer(state.path_buf.get().cast(), PATH_MAX, || { + wasi_filesystem::readlink_at(file.fd, path) + })? + } else { + state + .import_alloc + .with_buffer(buf, buf_len, || wasi_filesystem::readlink_at(file.fd, path))? + }; assert_eq!(path.as_ptr(), buf); assert!(path.len() <= buf_len); @@ -1531,250 +1586,257 @@ pub unsafe extern "C" fn poll_oneoff( } State::with(|state| { - state.register_buffer( + state.import_alloc.with_buffer( results, nsubscriptions .checked_mul(size_of::()) .trapping_unwrap(), - ); + || { + let mut pollables = Pollables { + pointer: pollables, + index: 0, + length: nsubscriptions, + }; - let mut pollables = Pollables { - pointer: pollables, - index: 0, - length: nsubscriptions, - }; + for subscription in subscriptions { + const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); + const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); + const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); + pollables.push(match subscription.u.tag { + EVENTTYPE_CLOCK => { + let clock = &subscription.u.u.clock; + let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) + == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME; + match clock.id { + CLOCKID_REALTIME => { + let timeout = if absolute { + // Convert `clock.timeout` to `Datetime`. + let mut datetime = wasi_wall_clock::Datetime { + seconds: clock.timeout / 1_000_000_000, + nanoseconds: (clock.timeout % 1_000_000_000) as _, + }; - for subscription in subscriptions { - const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); - const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); - const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); - pollables.push(match subscription.u.tag { - EVENTTYPE_CLOCK => { - let clock = &subscription.u.u.clock; - let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) - == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME; - match clock.id { - CLOCKID_REALTIME => { - let timeout = if absolute { - // Convert `clock.timeout` to `Datetime`. - let mut datetime = wasi_wall_clock::Datetime { - seconds: clock.timeout / 1_000_000_000, - nanoseconds: (clock.timeout % 1_000_000_000) as _, - }; - - // Subtract `now`. - let now = wasi_wall_clock::now(state.default_wall_clock()); - datetime.seconds -= now.seconds; - if datetime.nanoseconds < now.nanoseconds { - datetime.seconds -= 1; - datetime.nanoseconds += 1_000_000_000; + // Subtract `now`. + let now = wasi_wall_clock::now(state.default_wall_clock()); + datetime.seconds -= now.seconds; + if datetime.nanoseconds < now.nanoseconds { + datetime.seconds -= 1; + datetime.nanoseconds += 1_000_000_000; + } + datetime.nanoseconds -= now.nanoseconds; + + // Convert to nanoseconds. + let nanos = datetime + .seconds + .checked_mul(1_000_000_000) + .ok_or(ERRNO_OVERFLOW)?; + nanos + .checked_add(datetime.nanoseconds.into()) + .ok_or(ERRNO_OVERFLOW)? + } else { + clock.timeout + }; + + wasi_monotonic_clock::subscribe( + state.default_monotonic_clock(), + timeout, + false, + ) } - datetime.nanoseconds -= now.nanoseconds; - - // Convert to nanoseconds. - let nanos = datetime - .seconds - .checked_mul(1_000_000_000) - .ok_or(ERRNO_OVERFLOW)?; - nanos - .checked_add(datetime.nanoseconds.into()) - .ok_or(ERRNO_OVERFLOW)? - } else { - clock.timeout - }; - - wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), - timeout, - false, - ) - } - CLOCKID_MONOTONIC => wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), - clock.timeout, - absolute, - ), + CLOCKID_MONOTONIC => wasi_monotonic_clock::subscribe( + state.default_monotonic_clock(), + clock.timeout, + absolute, + ), - _ => return Err(ERRNO_INVAL), - } - } + _ => return Err(ERRNO_INVAL), + } + } - EVENTTYPE_FD_READ => { - match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { - Ok(stream) => wasi_io::subscribe_read(stream), - // If the file descriptor isn't a stream, request a - // pollable which completes immediately so that it'll - // immediately fail. - Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), - 0, - false, - ), - Err(e) => return Err(e), - } - } + EVENTTYPE_FD_READ => { + match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { + Ok(stream) => wasi_io::subscribe_read(stream), + // If the file descriptor isn't a stream, request a + // pollable which completes immediately so that it'll + // immediately fail. + Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( + state.default_monotonic_clock(), + 0, + false, + ), + Err(e) => return Err(e), + } + } - EVENTTYPE_FD_WRITE => { - match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) { - Ok(stream) => wasi_io::subscribe(stream), - // If the file descriptor isn't a stream, request a - // pollable which completes immediately so that it'll - // immediately fail. - Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), - 0, - false, - ), - Err(e) => return Err(e), - } - } + EVENTTYPE_FD_WRITE => { + match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) + { + Ok(stream) => wasi_io::subscribe(stream), + // If the file descriptor isn't a stream, request a + // pollable which completes immediately so that it'll + // immediately fail. + Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( + state.default_monotonic_clock(), + 0, + false, + ), + Err(e) => return Err(e), + } + } - _ => return Err(ERRNO_INVAL), - }); - } + _ => return Err(ERRNO_INVAL), + }); + } - let vec = - wasi_poll::poll_oneoff(slice::from_raw_parts(pollables.pointer, pollables.length)); + let vec = wasi_poll::poll_oneoff(slice::from_raw_parts( + pollables.pointer, + pollables.length, + )); - assert_eq!(vec.len(), nsubscriptions); - assert_eq!(vec.as_ptr(), results); - forget(vec); + assert_eq!(vec.len(), nsubscriptions); + assert_eq!(vec.as_ptr(), results); + forget(vec); - drop(pollables); + drop(pollables); - let ready = subscriptions - .iter() - .enumerate() - .filter_map(|(i, s)| (*results.add(i) != 0).then_some(s)); + let ready = subscriptions + .iter() + .enumerate() + .filter_map(|(i, s)| (*results.add(i) != 0).then_some(s)); - let mut count = 0; + let mut count = 0; - for subscription in ready { - let error; - let type_; - let nbytes; - let flags; + for subscription in ready { + let error; + let type_; + let nbytes; + let flags; - match subscription.u.tag { - 0 => { - error = ERRNO_SUCCESS; - type_ = EVENTTYPE_CLOCK; - nbytes = 0; - flags = 0; - } + match subscription.u.tag { + 0 => { + error = ERRNO_SUCCESS; + type_ = EVENTTYPE_CLOCK; + nbytes = 0; + flags = 0; + } - 1 => { - type_ = EVENTTYPE_FD_READ; - let desc = state - .get(subscription.u.u.fd_read.file_descriptor) - .trapping_unwrap(); - match desc { - Descriptor::Streams(streams) => match &streams.type_ { - StreamType::File(file) => match wasi_filesystem::stat(file.fd) { - Ok(stat) => { - error = ERRNO_SUCCESS; - nbytes = stat.size.saturating_sub(file.position.get()); - flags = if nbytes == 0 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 1; - flags = 0; - } - }, - StreamType::Socket(connection) => { - match wasi_tcp::bytes_readable(*connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; + 1 => { + type_ = EVENTTYPE_FD_READ; + let desc = state + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::File(file) => { + match wasi_filesystem::stat(file.fd) { + Ok(stat) => { + error = ERRNO_SUCCESS; + nbytes = + stat.size.saturating_sub(file.position.get()); + flags = if nbytes == 0 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 1; + flags = 0; + } + } + } + StreamType::Socket(connection) => { + match wasi_tcp::bytes_readable(*connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } } - Err(e) => { - error = e.into(); + StreamType::EmptyStdin => { + error = ERRNO_SUCCESS; nbytes = 0; + flags = EVENTRWFLAGS_FD_READWRITE_HANGUP; + } + StreamType::Unknown => { + error = ERRNO_SUCCESS; + nbytes = 1; flags = 0; } - } - } - StreamType::EmptyStdin => { - error = ERRNO_SUCCESS; - nbytes = 0; - flags = EVENTRWFLAGS_FD_READWRITE_HANGUP; - } - StreamType::Unknown => { - error = ERRNO_SUCCESS; - nbytes = 1; - flags = 0; + }, + _ => unreachable!(), } - }, - _ => unreachable!(), - } - } - 2 => { - type_ = EVENTTYPE_FD_WRITE; - let desc = state - .get(subscription.u.u.fd_read.file_descriptor) - .trapping_unwrap(); - match desc { - Descriptor::Streams(streams) => match streams.type_ { - StreamType::File(_) | StreamType::Unknown => { - error = ERRNO_SUCCESS; - nbytes = 1; - flags = 0; - } - StreamType::Socket(connection) => { - match wasi_tcp::bytes_writable(connection) { - Ok(result) => { + } + 2 => { + type_ = EVENTTYPE_FD_WRITE; + let desc = state + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match streams.type_ { + StreamType::File(_) | StreamType::Unknown => { error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; + nbytes = 1; + flags = 0; + } + StreamType::Socket(connection) => { + match wasi_tcp::bytes_writable(connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } } - Err(e) => { - error = e.into(); + StreamType::EmptyStdin => { + error = ERRNO_BADF; nbytes = 0; flags = 0; } - } + }, + _ => unreachable!(), } - StreamType::EmptyStdin => { - error = ERRNO_BADF; - nbytes = 0; - flags = 0; - } - }, + } + _ => unreachable!(), } - } - - _ => unreachable!(), - } - *out.add(count) = Event { - userdata: subscription.userdata, - error, - type_, - fd_readwrite: EventFdReadwrite { nbytes, flags }, - }; + *out.add(count) = Event { + userdata: subscription.userdata, + error, + type_, + fd_readwrite: EventFdReadwrite { nbytes, flags }, + }; - count += 1; - } + count += 1; + } - *nevents = count; + *nevents = count; - Ok(()) + Ok(()) + }, + ) }) } @@ -1817,10 +1879,10 @@ pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { AllocationState::StackAllocated | AllocationState::StateAllocated ) { State::with(|state| { - state.register_buffer(buf, buf_len); - assert_eq!(buf_len as u32 as Size, buf_len); - let result = wasi_random::get_random_bytes(buf_len as u32); + let result = state.import_alloc.with_buffer(buf, buf_len, || { + wasi_random::get_random_bytes(buf_len as u32) + }); assert_eq!(result.as_ptr(), buf); // The returned buffer's memory was allocated in `buf`, so don't separately @@ -2135,10 +2197,8 @@ struct State { /// try to catch memory corruption coming from the bottom. magic1: u32, - /// Used by `register_buffer` to coordinate allocations with - /// `cabi_import_realloc`. - buffer_ptr: Cell<*mut u8>, - buffer_len: Cell, + /// Used to coordinate allocations of `cabi_import_realloc` + import_alloc: ImportAlloc, /// Storage of mapping from preview1 file descriptors to preview2 file /// descriptors. @@ -2151,12 +2211,12 @@ struct State { /// Auxiliary storage to handle the `path_readlink` function. path_buf: UnsafeCell>, - /// Storage area for data passed to the `command` entrypoint. The - /// `command_data` is a block of memory which is dynamically allocated from - /// in `cabi_export_realloc`. The `command_data_next` is the - /// bump-allocated-pointer of where to allocate from next. - command_data: MaybeUninit<[u8; command_data_size()]>, - command_data_next: u16, + /// Long-lived bump allocated memory arena. + /// + /// This is used for the cabi_export_realloc to allocate data passed to the + /// `command` entrypoint. Allocations in this arena are safe to use for + /// the lifetime of the State struct. + long_lived_arena: BumpArena, /// Arguments passed to the `command` entrypoint args: Option<&'static [WasmStr]>, @@ -2229,7 +2289,7 @@ pub struct PreopenList { len: usize, } -const fn command_data_size() -> usize { +const fn bump_arena_size() -> usize { // The total size of the struct should be a page, so start there let mut start = PAGE_SIZE; @@ -2240,7 +2300,7 @@ const fn command_data_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 21 * size_of::(); + start -= 22 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2341,14 +2401,12 @@ impl State { ret.write(RefCell::new(State { magic1: MAGIC, magic2: MAGIC, - buffer_ptr: Cell::new(null_mut()), - buffer_len: Cell::new(0), + import_alloc: ImportAlloc::new(), ndescriptors: 0, closed: None, descriptors: MaybeUninit::uninit(), path_buf: UnsafeCell::new(MaybeUninit::uninit()), - command_data: MaybeUninit::uninit(), - command_data_next: 0, + long_lived_arena: BumpArena::new(), args: None, env_vars: None, preopens: None, @@ -2495,13 +2553,6 @@ impl State { } } - /// Register `buf` and `buf_len` to be used by `cabi_realloc` to satisfy - /// the next request. - fn register_buffer(&self, buf: *mut u8, buf_len: usize) { - self.buffer_ptr.set(buf); - self.buffer_len.set(buf_len); - } - /// Return a handle to the default wall clock, creating one if we /// don't already have one. fn default_wall_clock(&self) -> Fd { From 505ed0b9e809dfc30034284f49a0b0dccadf0762 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 8 Feb 2023 18:39:31 -0800 Subject: [PATCH 083/153] get environment and preopens through import functions --- src/lib.rs | 183 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 108 insertions(+), 75 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index df5364c98c40..70cbbd69b3f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ mod bindings { unchecked, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["command"], + skip: ["command", "get-preopens", "get-environment"], }); #[cfg(not(feature = "command"))] @@ -37,6 +37,7 @@ mod bindings { no_std, raw_strings, unchecked, + skip: ["get-preopens", "get-environment"], }); } @@ -47,8 +48,6 @@ pub unsafe extern "C" fn command( stdout: OutputStream, args_ptr: *const WasmStr, args_len: usize, - env_vars: StrTupleList, - preopens: PreopenList, ) -> u32 { State::with_mut(|state| { // Initialization of `State` automatically fills in some dummy @@ -71,24 +70,6 @@ pub unsafe extern "C" fn command( }); } state.args = Some(slice::from_raw_parts(args_ptr, args_len)); - state.env_vars = Some(slice::from_raw_parts(env_vars.base, env_vars.len)); - - let preopens = slice::from_raw_parts(preopens.base, preopens.len); - state.preopens = Some(preopens); - - for preopen in preopens { - state - .push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::File(File { - fd: preopen.descriptor, - position: Cell::new(0), - append: false, - }), - })) - .trapping_unwrap(); - } Ok(()) }); @@ -290,25 +271,23 @@ pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Siz #[no_mangle] pub unsafe extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { State::with(|state| { - if let Some(list) = state.env_vars { - let mut offsets = environ; - let mut buffer = environ_buf; - for pair in list { - ptr::write(offsets, buffer); - offsets = offsets.add(1); + let mut offsets = environ; + let mut buffer = environ_buf; + for var in state.get_environment() { + ptr::write(offsets, buffer); + offsets = offsets.add(1); - ptr::copy_nonoverlapping(pair.key.ptr, buffer, pair.key.len); - buffer = buffer.add(pair.key.len); + ptr::copy_nonoverlapping(var.key.ptr, buffer, var.key.len); + buffer = buffer.add(var.key.len); - ptr::write(buffer, b'='); - buffer = buffer.add(1); + ptr::write(buffer, b'='); + buffer = buffer.add(1); - ptr::copy_nonoverlapping(pair.value.ptr, buffer, pair.value.len); - buffer = buffer.add(pair.value.len); + ptr::copy_nonoverlapping(var.value.ptr, buffer, var.value.len); + buffer = buffer.add(var.value.len); - ptr::write(buffer, 0); - buffer = buffer.add(1); - } + ptr::write(buffer, 0); + buffer = buffer.add(1); } Ok(()) @@ -326,19 +305,15 @@ pub unsafe extern "C" fn environ_sizes_get( AllocationState::StackAllocated | AllocationState::StateAllocated ) { State::with(|state| { - if let Some(list) = state.env_vars { - *environc = list.len(); - *environ_buf_size = { - let mut sum = 0; - for pair in list { - sum += pair.key.len + pair.value.len + 2; - } - sum - }; - } else { - *environc = 0; - *environ_buf_size = 0; - } + let vars = state.get_environment(); + *environc = vars.len(); + *environ_buf_size = { + let mut sum = 0; + for var in vars { + sum += var.key.len + var.value.len + 2; + } + sum + }; Ok(()) }) @@ -729,10 +704,6 @@ pub unsafe extern "C" fn fd_pread( }) } -fn get_preopen(state: &State, fd: Fd) -> Option<&Preopen> { - state.preopens?.get(fd.checked_sub(3)? as usize) -} - /// Return a description of the given preopened file descriptor. #[no_mangle] pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { @@ -741,7 +712,7 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { AllocationState::StackAllocated | AllocationState::StateAllocated ) { State::with(|state| { - if let Some(preopen) = get_preopen(state, fd) { + if let Some(preopen) = state.get_preopen(fd) { buf.write(Prestat { tag: 0, u: PrestatU { @@ -765,7 +736,7 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { State::with(|state| { - if let Some(preopen) = get_preopen(state, fd) { + if let Some(preopen) = state.get_preopen(fd) { if preopen.path.len < path_len as usize { Err(ERRNO_NAMETOOLONG) } else { @@ -1074,7 +1045,7 @@ pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { // Ensure the table is big enough to contain `to`. Do this before // looking up `fd` as it can fail due to `NOMEM`. - while Fd::from(state.ndescriptors) <= to { + while Fd::from(state.ndescriptors.get()) <= to { let old_closed = state.closed; let new_closed = state.push_desc(Descriptor::Closed(old_closed))?; state.closed = Some(new_closed); @@ -2202,8 +2173,8 @@ struct State { /// Storage of mapping from preview1 file descriptors to preview2 file /// descriptors. - ndescriptors: u16, - descriptors: MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>, + ndescriptors: Cell, + descriptors: UnsafeCell>, /// Points to the head of a free-list of closed file descriptors. closed: Option, @@ -2221,11 +2192,11 @@ struct State { /// Arguments passed to the `command` entrypoint args: Option<&'static [WasmStr]>, - /// Environment variables passed to the `command` entrypoint - env_vars: Option<&'static [StrTuple]>, + /// Environment variables + env_vars: Cell>, - /// Preopened directories passed to the `command` entrypoint - preopens: Option<&'static [Preopen]>, + /// Preopened directories + preopens: Cell>, /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. @@ -2402,14 +2373,14 @@ impl State { magic1: MAGIC, magic2: MAGIC, import_alloc: ImportAlloc::new(), - ndescriptors: 0, closed: None, - descriptors: MaybeUninit::uninit(), + ndescriptors: Cell::new(0), + descriptors: UnsafeCell::new(MaybeUninit::uninit()), path_buf: UnsafeCell::new(MaybeUninit::uninit()), long_lived_arena: BumpArena::new(), args: None, - env_vars: None, - preopens: None, + env_vars: Cell::new(None), + preopens: Cell::new(None), dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), @@ -2449,24 +2420,25 @@ impl State { self.push_desc(Descriptor::Stderr).trapping_unwrap(); } - fn push_desc(&mut self, desc: Descriptor) -> Result { + fn push_desc(&self, desc: Descriptor) -> Result { unsafe { - let descriptors = self.descriptors.as_mut_ptr(); - let ndescriptors = usize::try_from(self.ndescriptors).trapping_unwrap(); + let descriptors = (*self.descriptors.get()).as_mut_ptr(); + let ndescriptors = usize::try_from(self.ndescriptors.get()).trapping_unwrap(); if ndescriptors >= (*descriptors).len() { return Err(ERRNO_NOMEM); } ptr::addr_of_mut!((*descriptors)[ndescriptors]).write(desc); - self.ndescriptors += 1; - Ok(Fd::from(self.ndescriptors - 1)) + self.ndescriptors + .set(u16::try_from(ndescriptors + 1).trapping_unwrap()); + Ok(Fd::from(u32::try_from(ndescriptors).trapping_unwrap())) } } fn descriptors(&self) -> &[Descriptor] { unsafe { slice::from_raw_parts( - self.descriptors.as_ptr().cast(), - usize::try_from(self.ndescriptors).trapping_unwrap(), + (*self.descriptors.get()).as_ptr().cast(), + usize::try_from(self.ndescriptors.get()).trapping_unwrap(), ) } } @@ -2474,8 +2446,8 @@ impl State { fn descriptors_mut(&mut self) -> &mut [Descriptor] { unsafe { slice::from_raw_parts_mut( - self.descriptors.as_mut_ptr().cast(), - usize::try_from(self.ndescriptors).trapping_unwrap(), + (*self.descriptors.get()).as_mut_ptr().cast(), + usize::try_from(self.ndescriptors.get()).trapping_unwrap(), ) } } @@ -2582,4 +2554,65 @@ impl State { self.default_monotonic_clock.set(Some(clock)); clock } + + fn get_environment(&self) -> &[StrTuple] { + if self.env_vars.get().is_none() { + #[link(wasm_import_module = "wasi-environment")] + extern "C" { + #[link_name = "get-environment"] + fn get_environment_import(rval: *mut StrTupleList); + } + let mut list = StrTupleList { + base: std::ptr::null(), + len: 0, + }; + /* TODO alloc behavior set to arena for following call: */ + unsafe { get_environment_import(&mut list as *mut _) }; + self.env_vars.set(Some(unsafe { + /* allocation comes from long lived arena, so it is safe to + * cast this to a &'static slice: */ + std::slice::from_raw_parts(list.base, list.len) + })); + } + self.env_vars.get().trapping_unwrap() + } + + fn get_preopens(&self) -> &[Preopen] { + if self.env_vars.get().is_none() { + #[link(wasm_import_module = "wasi-filesystem")] + extern "C" { + #[link_name = "get-preopens"] + fn get_preopens_import(rval: *mut PreopenList); + } + let mut list = PreopenList { + base: std::ptr::null(), + len: 0, + }; + /* TODO alloc behavior set to arena for following call: */ + unsafe { get_preopens_import(&mut list as *mut _) }; + let preopens: &'static [Preopen] = unsafe { + /* allocation comes from long lived arena, so it is safe to + * cast this to a &'static slice: */ + std::slice::from_raw_parts(list.base, list.len) + }; + for preopen in preopens { + self.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + append: false, + }), + })) + .trapping_unwrap(); + } + self.preopens.set(Some(preopens)); + } + self.preopens.get().trapping_unwrap() + } + + fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { + self.get_preopens().get(fd.checked_sub(3)? as usize) + } } From cf2446efcc7217a04c3e4f64d27773e4214b1b5d Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 16 Feb 2023 15:08:05 -0800 Subject: [PATCH 084/153] add arena allocator for get_environment and get_preopens and fix other bugs in those implementations --- src/lib.rs | 74 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 70cbbd69b3f3..84729a9b6cb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,7 @@ fn align_to(ptr: usize, align: usize) -> usize { struct ImportAlloc { buffer: Cell<*mut u8>, len: Cell, + arena: Cell>, } impl ImportAlloc { @@ -165,13 +166,20 @@ impl ImportAlloc { ImportAlloc { buffer: Cell::new(std::ptr::null_mut()), len: Cell::new(0), + arena: Cell::new(None), } } + /// Expect at most one import allocation during execution of the provided closure. + /// Use the provided buffer to satisfy that import allocation. The user is responsible + /// for making sure allocated imports are not used beyond the lifetime of the buffer. fn with_buffer(&self, buffer: *mut u8, len: usize, f: impl FnOnce() -> T) -> T { + if self.arena.get().is_some() { + unreachable!("arena mode") + } let prev = self.buffer.replace(buffer); if !prev.is_null() { - unreachable!() + unreachable!("overwrote another buffer") } self.len.set(len); let r = f(); @@ -179,20 +187,44 @@ impl ImportAlloc { r } - fn alloc(&self, align: usize, size: usize) -> *mut u8 { - let buffer = self.buffer.get(); - if buffer.is_null() { - unreachable!("buffer not provided, or already used") + /// Permit many import allocations during execution of the provided closure. + /// Use the provided BumpArena to satisfry those allocations. The user is responsible + /// for making sure allocated imports are not used beyond the lifetime of the arena. + fn with_arena(&self, arena: &BumpArena, f: impl FnOnce() -> T) -> T { + if !self.buffer.get().is_null() { + unreachable!("buffer mode") } - let buffer = buffer as usize; - let alloc = align_to(buffer, align); - if alloc.checked_add(size).trapping_unwrap() - > buffer.checked_add(self.len.get()).trapping_unwrap() - { - unreachable!("out of memory") + let prev = self.arena.replace(Some(unsafe { + // Safety: Need to erase the lifetime to store in the arena cell. + std::mem::transmute::<&'_ BumpArena, &'static BumpArena>(arena) + })); + if prev.is_some() { + unreachable!("overwrote another arena") + } + let r = f(); + self.arena.set(None); + r + } + + /// To be used by cabi_import_realloc only! + fn alloc(&self, align: usize, size: usize) -> *mut u8 { + if let Some(arena) = self.arena.get() { + arena.alloc(align, size) + } else { + let buffer = self.buffer.get(); + if buffer.is_null() { + unreachable!("buffer not provided, or already used") + } + let buffer = buffer as usize; + let alloc = align_to(buffer, align); + if alloc.checked_add(size).trapping_unwrap() + > buffer.checked_add(self.len.get()).trapping_unwrap() + { + unreachable!("out of memory") + } + self.buffer.set(std::ptr::null_mut()); + alloc as *mut u8 } - self.buffer.set(std::ptr::null_mut()); - alloc as *mut u8 } } @@ -2271,7 +2303,7 @@ const fn bump_arena_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 22 * size_of::(); + start -= 24 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2566,8 +2598,10 @@ impl State { base: std::ptr::null(), len: 0, }; - /* TODO alloc behavior set to arena for following call: */ - unsafe { get_environment_import(&mut list as *mut _) }; + self.import_alloc + .with_arena(&self.long_lived_arena, || unsafe { + get_environment_import(&mut list as *mut _) + }); self.env_vars.set(Some(unsafe { /* allocation comes from long lived arena, so it is safe to * cast this to a &'static slice: */ @@ -2578,7 +2612,7 @@ impl State { } fn get_preopens(&self) -> &[Preopen] { - if self.env_vars.get().is_none() { + if self.preopens.get().is_none() { #[link(wasm_import_module = "wasi-filesystem")] extern "C" { #[link_name = "get-preopens"] @@ -2588,8 +2622,10 @@ impl State { base: std::ptr::null(), len: 0, }; - /* TODO alloc behavior set to arena for following call: */ - unsafe { get_preopens_import(&mut list as *mut _) }; + self.import_alloc + .with_arena(&self.long_lived_arena, || unsafe { + get_preopens_import(&mut list as *mut _) + }); let preopens: &'static [Preopen] = unsafe { /* allocation comes from long lived arena, so it is safe to * cast this to a &'static slice: */ From 0e8ee1d2ad9e472baa053cfc20f5c725de79121f Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 16 Feb 2023 15:38:42 -0800 Subject: [PATCH 085/153] comments --- src/lib.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 84729a9b6cb9..cb72c86a4462 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,9 +155,17 @@ fn align_to(ptr: usize, align: usize) -> usize { (ptr + (align - 1)) & !(align - 1) } +// Invariant: buffer not-null and arena is-some are never true at the same +// time. We did not use an enum to make this invalid behavior unrepresentable +// because we can't use RefCell to borrow() the variants of the enum - only +// Cell provides mutability without pulling in panic machinery - so it would +// make the accessors a lot more awkward to write. struct ImportAlloc { + // When not-null, allocator should use this buffer/len pair at most once + // to satisfy allocations. buffer: Cell<*mut u8>, len: Cell, + // When not-empty, allocator should use this arena to satisfy allocations. arena: Cell>, } @@ -2218,16 +2226,19 @@ struct State { /// /// This is used for the cabi_export_realloc to allocate data passed to the /// `command` entrypoint. Allocations in this arena are safe to use for - /// the lifetime of the State struct. + /// the lifetime of the State struct. It may also be used for import allocations + /// which need to be long-lived, by using `import_alloc.with_arena`. long_lived_arena: BumpArena, /// Arguments passed to the `command` entrypoint args: Option<&'static [WasmStr]>, - /// Environment variables + /// Environment variables. Initialized lazily. Access with `State::get_environment` + /// to take care of initialization. env_vars: Cell>, - /// Preopened directories + /// Preopened directories. Initialized lazily. Access with `State::get_preopens` + /// to take care of initialization. preopens: Cell>, /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path @@ -2627,11 +2638,13 @@ impl State { get_preopens_import(&mut list as *mut _) }); let preopens: &'static [Preopen] = unsafe { - /* allocation comes from long lived arena, so it is safe to - * cast this to a &'static slice: */ + // allocation comes from long lived arena, so it is safe to + // cast this to a &'static slice: std::slice::from_raw_parts(list.base, list.len) }; for preopen in preopens { + // Expectation is that the descriptor index is initialized with + // stdio (0,1,2) and no others, so that preopens are 3.. self.push_desc(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), From 8c51e155de458554a829f69cccf73a2019468112 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 21 Feb 2023 15:44:20 -0600 Subject: [PATCH 086/153] Make Preview2's directory iterator omit `.` and `..`. (#89) Preview1's `fd_readdir` includes `.` and `..`, but Preview2 is changing to avoid this. Update the Preview2 host implementation to omit these entries, and add code the polyfill to re-add them. --- src/lib.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cb72c86a4462..ed5f2ef15019 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -914,6 +914,13 @@ pub unsafe extern "C" fn fd_readdir( } else { None }; + + // Compute the inode of `.` so that the iterator can produce an entry + // for it. + let dir = state.get_dir(fd)?; + let stat = wasi_filesystem::stat(dir.fd)?; + let dot_inode = stat.ino; + let mut iter; match stream { // All our checks passed and a dirent cache was available with a @@ -926,6 +933,7 @@ pub unsafe extern "C" fn fd_readdir( state, cookie, use_cache: true, + dot_inode, } } @@ -935,12 +943,12 @@ pub unsafe extern "C" fn fd_readdir( // from scratch, and the `cookie` value indicates how many items // need skipping. None => { - let dir = state.get_dir(fd)?; iter = DirEntryIterator { state, cookie: wasi::DIRCOOKIE_START, use_cache: false, stream: DirEntryStream(wasi_filesystem::readdir(dir.fd)?), + dot_inode, }; // Skip to the entry that is requested by the `cookie` @@ -1019,6 +1027,7 @@ pub unsafe extern "C" fn fd_readdir( use_cache: bool, cookie: Dircookie, stream: DirEntryStream, + dot_inode: wasi::Inode, } impl<'a> Iterator for DirEntryIterator<'a> { @@ -1027,8 +1036,34 @@ pub unsafe extern "C" fn fd_readdir( type Item = Result<(wasi::Dirent, &'a [UnsafeCell]), Errno>; fn next(&mut self) -> Option { + let current_cookie = self.cookie; + self.cookie += 1; + // Preview1 programs expect to see `.` and `..` in the traversal, but + // Preview2 excludes them, so re-add them. + match current_cookie { + 0 => { + let dirent = wasi::Dirent { + d_next: self.cookie, + d_ino: self.dot_inode, + d_type: wasi::FILETYPE_DIRECTORY, + d_namlen: 1, + }; + return Some(Ok((dirent, &self.state.dotdot[..1]))); + } + 1 => { + let dirent = wasi::Dirent { + d_next: self.cookie, + d_ino: 0, + d_type: wasi::FILETYPE_DIRECTORY, + d_namlen: 2, + }; + return Some(Ok((dirent, &self.state.dotdot[..]))); + } + _ => {} + } + if self.use_cache { self.use_cache = false; return Some(unsafe { @@ -2251,6 +2286,9 @@ struct State { /// The clock handle for `CLOCKID_REALTIME`. default_wall_clock: Cell>, + /// The string `..` for use by the directory iterator. + dotdot: [UnsafeCell; 2], + /// Another canary constant located at the end of the structure to catch /// memory corruption coming from the bottom. magic2: u32, @@ -2438,6 +2476,7 @@ impl State { }, default_monotonic_clock: Cell::new(None), default_wall_clock: Cell::new(None), + dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')], })); &*ret }; From f807e25ed608c30d00636130849a9f3492f75b5b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 22 Feb 2023 09:37:16 -0600 Subject: [PATCH 087/153] Fix timestamp-to-nanosecond conversion in the adapter (#93) The order of operations for handling the seconds/nanoseconds part of the structure were a bit off which meant that a nonzero nanosecond field would have an unexpected effect on the result. Closes #92 --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ed5f2ef15019..aa16d6471e9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -378,9 +378,9 @@ pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errn } CLOCKID_REALTIME => { let res = wasi_wall_clock::resolution(state.default_wall_clock()); - *resolution = Timestamp::from(res.nanoseconds) - .checked_add(res.seconds) - .and_then(|secs| secs.checked_mul(1_000_000_000)) + *resolution = Timestamp::from(res.seconds) + .checked_mul(1_000_000_000) + .and_then(|ns| ns.checked_add(res.nanoseconds.into())) .ok_or(ERRNO_OVERFLOW)?; } _ => unreachable!(), @@ -408,9 +408,9 @@ pub unsafe extern "C" fn clock_time_get( } CLOCKID_REALTIME => { let res = wasi_wall_clock::now(state.default_wall_clock()); - *time = Timestamp::from(res.nanoseconds) - .checked_add(res.seconds) - .and_then(|secs| secs.checked_mul(1_000_000_000)) + *time = Timestamp::from(res.seconds) + .checked_mul(1_000_000_000) + .and_then(|ns| ns.checked_add(res.nanoseconds.into())) .ok_or(ERRNO_OVERFLOW)?; } _ => unreachable!(), From ed90c0e2f25eddadf7ca3e2e975ee6e6220e1cbd Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 22 Feb 2023 15:56:58 -0700 Subject: [PATCH 088/153] short-circuit `fd_write` in the adapter @guybedford was getting assertion errors in `State::new` due to the debugging print statements he had added to his `cabi_realloc` function, which called back into `fd_write`, leading to a circular dependency where `fd_write` needed to allocate, which caused `fd_write` to be called, etc. The fix is easy enough: short circuit this circularity the same way we're handling it in `environ_sizes_get`, `clock_time_get`, etc. If we wanted to be really paranoid, we could add this short circuit to all the functions, but I'd be really surprised if `cabi_realloc` needs to read from the filesystem, create directories, or other such things. If we see that in the wild, we can revisit. Signed-off-by: Joel Dice --- src/lib.rs | 80 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aa16d6471e9f..999eeb1636d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1200,46 +1200,54 @@ pub unsafe extern "C" fn fd_write( mut iovs_len: usize, nwritten: *mut Size, ) -> Errno { - // Advance to the first non-empty buffer. - while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { - iovs_ptr = iovs_ptr.add(1); - iovs_len -= 1; - } - if iovs_len == 0 { - *nwritten = 0; - return ERRNO_SUCCESS; - } + if matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nwritten = 0; + return ERRNO_SUCCESS; + } - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; - let bytes = slice::from_raw_parts(ptr, len); + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + let bytes = slice::from_raw_parts(ptr, len); - State::with(|state| match state.get(fd)? { - Descriptor::Streams(streams) => { - let wasi_stream = streams.get_write_stream()?; - let bytes = wasi_io::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; - - // If this is a file, keep the current-position pointer up to date. - if let StreamType::File(file) = &streams.type_ { - // But don't update if we're in append mode. Strictly speaking, - // we should set the position to the new end of the file, but - // we don't have an API to do that atomically. - if !file.append { - file.position - .set(file.position.get() + wasi_filesystem::Filesize::from(bytes)); + State::with(|state| match state.get(fd)? { + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_write_stream()?; + let bytes = wasi_io::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; + + // If this is a file, keep the current-position pointer up to date. + if let StreamType::File(file) = &streams.type_ { + // But don't update if we're in append mode. Strictly speaking, + // we should set the position to the new end of the file, but + // we don't have an API to do that atomically. + if !file.append { + file.position + .set(file.position.get() + wasi_filesystem::Filesize::from(bytes)); + } } - } - *nwritten = bytes as usize; - Ok(()) - } - Descriptor::Stderr => { - wasi_stderr::print(bytes); - *nwritten = len; - Ok(()) - } - Descriptor::Closed(_) => Err(ERRNO_BADF), - }) + *nwritten = bytes as usize; + Ok(()) + } + Descriptor::Stderr => { + wasi_stderr::print(bytes); + *nwritten = len; + Ok(()) + } + Descriptor::Closed(_) => Err(ERRNO_BADF), + }) + } else { + *nwritten = 0; + ERRNO_IO + } } /// Create a directory. From a8295ae98e92fe044cc2c859a8902401aaf38b61 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 24 Feb 2023 16:38:53 -0800 Subject: [PATCH 089/153] Rebase on the new wasi-sockets. (#91) * Rebase on the new wasi-sockets. This switches to using the wasi-sockets wit files from WebAssembly/wasi-sockets#16. Many things are still stubbed out with `todo!()` for now. * Fix compilation on Windows. --- src/lib.rs | 88 +++++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 999eeb1636d4..939e02844230 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_monotonic_clock, wasi_poll, - wasi_random, wasi_stderr, wasi_tcp, wasi_wall_clock, + wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_monotonic_clock, wasi_network, + wasi_poll, wasi_random, wasi_stderr, wasi_tcp, wasi_wall_clock, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -1576,11 +1576,12 @@ impl Drop for Pollables { } } -impl From for Errno { - fn from(error: wasi_tcp::Errno) -> Errno { - use wasi_tcp::Errno::*; - +impl From for Errno { + fn from(error: wasi_network::Error) -> Errno { match error { + wasi_network::Error::Unknown => unreachable!(), // TODO + wasi_network::Error::Again => ERRNO_AGAIN, + /* TODO // Use a black box to prevent the optimizer from generating a // lookup table, which would require a static initializer. ConnectionAborted => black_box(ERRNO_CONNABORTED), @@ -1591,6 +1592,7 @@ impl From for Errno { NetworkUnreachable => ERRNO_NETUNREACH, Timedout => ERRNO_TIMEDOUT, _ => unreachable!(), + */ } } } @@ -1802,22 +1804,25 @@ pub unsafe extern "C" fn poll_oneoff( } } StreamType::Socket(connection) => { - match wasi_tcp::bytes_readable(*connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - } + unreachable!() // TODO + /* + match wasi_tcp::bytes_readable(*connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } + */ } StreamType::EmptyStdin => { error = ERRNO_SUCCESS; @@ -1846,22 +1851,25 @@ pub unsafe extern "C" fn poll_oneoff( flags = 0; } StreamType::Socket(connection) => { - match wasi_tcp::bytes_writable(connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - } + unreachable!() // TODO + /* + match wasi_tcp::bytes_writable(connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } + */ } StreamType::EmptyStdin => { error = ERRNO_BADF; @@ -2193,7 +2201,7 @@ enum StreamType { File(File), /// Streaming data with a socket connection. - Socket(wasi_tcp::Connection), + Socket(wasi_tcp::TcpSocket), } impl Drop for Descriptor { @@ -2574,7 +2582,7 @@ impl State { } #[allow(dead_code)] // until Socket is implemented - fn get_socket(&self, fd: Fd) -> Result { + fn get_socket(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(Streams { type_: StreamType::Socket(socket), From 70a1528f5a9fabe830f978e7411b861218ffcc22 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 6 Mar 2023 10:24:26 -0800 Subject: [PATCH 090/153] Rebase on the wasi-cli world. (#100) * Rebase on the wasi-cli world. Rebase on the wasi-cli wit files. This incorporates some major renamings and reorganizations, and updates to use wasi-sockets from the repo rather than our old prototype files. At present this depends on several bugfixes in wasmtime and wit-bindgen so this is using git dependencies for now. This also temporarily preserves the dedicated stderr interface, which is useful for debugging the file descriptor table code, which needs to work before the regular stderr works. * Update the verify program for the new module names. * Disable debug-assertions in the adapter build. This is roughly the same as, the previous `unchecked` option in the bindings, with the changes in the latest wit-bindgen. * Update CI to use the new file names. --- src/lib.rs | 498 +++++++++++++++++++++++++------------------------- src/macros.rs | 13 +- 2 files changed, 257 insertions(+), 254 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 939e02844230..4164bb6db5d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - wasi_default_clocks, wasi_exit, wasi_filesystem, wasi_io, wasi_monotonic_clock, wasi_network, - wasi_poll, wasi_random, wasi_stderr, wasi_tcp, wasi_wall_clock, + exit, filesystem, instance_monotonic_clock, instance_wall_clock, monotonic_clock, network, + poll, random, streams, tcp, wall_clock, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; @@ -12,9 +12,9 @@ use core::hint::black_box; use core::mem::{self, align_of, forget, replace, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, null_mut}; use core::slice; +use poll::Pollable; +use streams::{InputStream, OutputStream}; use wasi::*; -use wasi_io::{InputStream, OutputStream}; -use wasi_poll::Pollable; #[macro_use] mod macros; @@ -22,22 +22,20 @@ mod macros; mod bindings { #[cfg(feature = "command")] wit_bindgen::generate!({ - world: "wasi-command", - no_std, + world: "cli", + std_feature, raw_strings, - unchecked, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["command", "get-preopens", "get-environment"], + skip: ["command", "preopens", "get-environment"], }); #[cfg(not(feature = "command"))] wit_bindgen::generate!({ - world: "wasi", - no_std, + world: "cli-reactor", + std_feature, raw_strings, - unchecked, - skip: ["get-preopens", "get-environment"], + skip: ["preopens", "get-environment"], }); } @@ -46,8 +44,10 @@ mod bindings { pub unsafe extern "C" fn command( stdin: InputStream, stdout: OutputStream, + stderr: OutputStream, args_ptr: *const WasmStr, args_len: usize, + preopens: PreopenList, ) -> u32 { State::with_mut(|state| { // Initialization of `State` automatically fills in some dummy @@ -68,6 +68,11 @@ pub unsafe extern "C" fn command( output: Cell::new(Some(stdout)), type_: StreamType::Unknown, }); + descriptors[2] = Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(Some(stderr)), + type_: StreamType::Unknown, + }); } state.args = Some(slice::from_raw_parts(args_ptr, args_len)); @@ -373,11 +378,11 @@ pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errn State::with(|state| { match id { CLOCKID_MONOTONIC => { - let res = wasi_monotonic_clock::resolution(state.default_monotonic_clock()); + let res = monotonic_clock::resolution(state.instance_monotonic_clock()); *resolution = res; } CLOCKID_REALTIME => { - let res = wasi_wall_clock::resolution(state.default_wall_clock()); + let res = wall_clock::resolution(state.instance_wall_clock()); *resolution = Timestamp::from(res.seconds) .checked_mul(1_000_000_000) .and_then(|ns| ns.checked_add(res.nanoseconds.into())) @@ -404,10 +409,10 @@ pub unsafe extern "C" fn clock_time_get( State::with(|state| { match id { CLOCKID_MONOTONIC => { - *time = wasi_monotonic_clock::now(state.default_monotonic_clock()); + *time = monotonic_clock::now(state.instance_monotonic_clock()); } CLOCKID_REALTIME => { - let res = wasi_wall_clock::now(state.default_wall_clock()); + let res = wall_clock::now(state.instance_wall_clock()); *time = Timestamp::from(res.seconds) .checked_mul(1_000_000_000) .and_then(|ns| ns.checked_add(res.nanoseconds.into())) @@ -433,17 +438,17 @@ pub unsafe extern "C" fn fd_advise( advice: Advice, ) -> Errno { let advice = match advice { - ADVICE_NORMAL => wasi_filesystem::Advice::Normal, - ADVICE_SEQUENTIAL => wasi_filesystem::Advice::Sequential, - ADVICE_RANDOM => wasi_filesystem::Advice::Random, - ADVICE_WILLNEED => wasi_filesystem::Advice::WillNeed, - ADVICE_DONTNEED => wasi_filesystem::Advice::DontNeed, - ADVICE_NOREUSE => wasi_filesystem::Advice::NoReuse, + ADVICE_NORMAL => filesystem::Advice::Normal, + ADVICE_SEQUENTIAL => filesystem::Advice::Sequential, + ADVICE_RANDOM => filesystem::Advice::Random, + ADVICE_WILLNEED => filesystem::Advice::WillNeed, + ADVICE_DONTNEED => filesystem::Advice::DontNeed, + ADVICE_NOREUSE => filesystem::Advice::NoReuse, _ => return ERRNO_INVAL, }; State::with(|state| { let file = state.get_seekable_file(fd)?; - wasi_filesystem::fadvise(file.fd, offset, len, advice)?; + filesystem::advise(file.fd, offset, len, advice)?; Ok(()) }) } @@ -481,7 +486,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { State::with(|state| { let file = state.get_file(fd)?; - wasi_filesystem::datasync(file.fd)?; + filesystem::sync_data(file.fd)?; Ok(()) }) } @@ -495,29 +500,29 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { type_: StreamType::File(file), .. }) => { - let flags = wasi_filesystem::flags(file.fd)?; - let type_ = wasi_filesystem::todo_type(file.fd)?; + let flags = filesystem::get_flags(file.fd)?; + let type_ = filesystem::get_type(file.fd)?; let fs_filetype = type_.into(); let mut fs_flags = 0; let mut fs_rights_base = !0; - if !flags.contains(wasi_filesystem::DescriptorFlags::READ) { + if !flags.contains(filesystem::DescriptorFlags::READ) { fs_rights_base &= !RIGHTS_FD_READ; } - if !flags.contains(wasi_filesystem::DescriptorFlags::WRITE) { + if !flags.contains(filesystem::DescriptorFlags::WRITE) { fs_rights_base &= !RIGHTS_FD_WRITE; } - if flags.contains(wasi_filesystem::DescriptorFlags::DSYNC) { + if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { fs_flags |= FDFLAGS_DSYNC; } - if flags.contains(wasi_filesystem::DescriptorFlags::NONBLOCK) { + if flags.contains(filesystem::DescriptorFlags::NON_BLOCKING) { fs_flags |= FDFLAGS_NONBLOCK; } - if flags.contains(wasi_filesystem::DescriptorFlags::RSYNC) { + if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { fs_flags |= FDFLAGS_RSYNC; } - if flags.contains(wasi_filesystem::DescriptorFlags::SYNC) { + if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { fs_flags |= FDFLAGS_SYNC; } if file.append { @@ -599,23 +604,23 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { - let mut new_flags = wasi_filesystem::DescriptorFlags::empty(); + let mut new_flags = filesystem::DescriptorFlags::empty(); if flags & FDFLAGS_DSYNC == FDFLAGS_DSYNC { - new_flags |= wasi_filesystem::DescriptorFlags::DSYNC; + new_flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC; } if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { - new_flags |= wasi_filesystem::DescriptorFlags::NONBLOCK; + new_flags |= filesystem::DescriptorFlags::NON_BLOCKING; } if flags & FDFLAGS_RSYNC == FDFLAGS_RSYNC { - new_flags |= wasi_filesystem::DescriptorFlags::RSYNC; + new_flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC; } if flags & FDFLAGS_SYNC == FDFLAGS_SYNC { - new_flags |= wasi_filesystem::DescriptorFlags::SYNC; + new_flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC; } State::with(|state| { let file = state.get_file(fd)?; - wasi_filesystem::set_flags(file.fd, new_flags)?; + filesystem::set_flags(file.fd, new_flags)?; Ok(()) }) } @@ -636,17 +641,17 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { let file = state.get_file(fd)?; - let stat = wasi_filesystem::stat(file.fd)?; + let stat = filesystem::stat(file.fd)?; let filetype = stat.type_.into(); *buf = Filestat { - dev: stat.dev, - ino: stat.ino, + dev: stat.device, + ino: stat.inode, filetype, - nlink: stat.nlink, + nlink: stat.link_count, size: stat.size, - atim: datetime_to_timestamp(stat.atim), - mtim: datetime_to_timestamp(stat.mtim), - ctim: datetime_to_timestamp(stat.ctim), + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), }; Ok(()) }) @@ -658,7 +663,7 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { State::with(|state| { let file = state.get_file(fd)?; - wasi_filesystem::set_size(file.fd, size)?; + filesystem::set_size(file.fd, size)?; Ok(()) }) } @@ -674,30 +679,30 @@ pub unsafe extern "C" fn fd_filestat_set_times( ) -> Errno { let atim = if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { - wasi_filesystem::NewTimestamp::Now + filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { - wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + filesystem::NewTimestamp::Timestamp(filesystem::Datetime { seconds: atim / 1_000_000_000, nanoseconds: (atim % 1_000_000_000) as _, }) } else { - wasi_filesystem::NewTimestamp::NoChange + filesystem::NewTimestamp::NoChange }; let mtim = if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { - wasi_filesystem::NewTimestamp::Now + filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { - wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + filesystem::NewTimestamp::Timestamp(filesystem::Datetime { seconds: mtim / 1_000_000_000, nanoseconds: (mtim % 1_000_000_000) as _, }) } else { - wasi_filesystem::NewTimestamp::NoChange + filesystem::NewTimestamp::NoChange }; State::with(|state| { let file = state.get_file(fd)?; - wasi_filesystem::set_times(file.fd, atim, mtim)?; + filesystem::set_times(file.fd, atim, mtim)?; Ok(()) }) } @@ -727,9 +732,9 @@ pub unsafe extern "C" fn fd_pread( let len = (*iovs_ptr).buf_len; let file = state.get_file(fd)?; - let (data, end) = state.import_alloc.with_buffer(ptr, len, || { - wasi_filesystem::pread(file.fd, len as u64, offset) - })?; + let (data, end) = state + .import_alloc + .with_buffer(ptr, len, || filesystem::read(file.fd, len as u64, offset))?; assert_eq!(data.as_ptr(), ptr); assert!(data.len() <= len); @@ -814,7 +819,7 @@ pub unsafe extern "C" fn fd_pwrite( State::with(|state| { let file = state.get_seekable_file(fd)?; - let bytes = wasi_filesystem::pwrite(file.fd, slice::from_raw_parts(ptr, len), offset)?; + let bytes = filesystem::write(file.fd, slice::from_raw_parts(ptr, len), offset)?; *nwritten = bytes as usize; Ok(()) }) @@ -851,7 +856,7 @@ pub unsafe extern "C" fn fd_read( let wasi_stream = streams.get_read_stream()?; let (data, end) = state .import_alloc - .with_buffer(ptr, len, || wasi_io::read(wasi_stream, read_len)) + .with_buffer(ptr, len, || streams::read(wasi_stream, read_len)) .map_err(|_| ERRNO_IO)?; assert_eq!(data.as_ptr(), ptr); @@ -860,7 +865,7 @@ pub unsafe extern "C" fn fd_read( // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { file.position - .set(file.position.get() + data.len() as wasi_filesystem::Filesize); + .set(file.position.get() + data.len() as filesystem::Filesize); } let len = data.len(); @@ -918,8 +923,8 @@ pub unsafe extern "C" fn fd_readdir( // Compute the inode of `.` so that the iterator can produce an entry // for it. let dir = state.get_dir(fd)?; - let stat = wasi_filesystem::stat(dir.fd)?; - let dot_inode = stat.ino; + let stat = filesystem::stat(dir.fd)?; + let dot_inode = stat.inode; let mut iter; match stream { @@ -928,7 +933,7 @@ pub unsafe extern "C" fn fd_readdir( // entry from cache and is additionally resuming at the `cookie` // specified. Some(stream) => { - iter = DirEntryIterator { + iter = DirectoryEntryIterator { stream, state, cookie, @@ -943,11 +948,11 @@ pub unsafe extern "C" fn fd_readdir( // from scratch, and the `cookie` value indicates how many items // need skipping. None => { - iter = DirEntryIterator { + iter = DirectoryEntryIterator { state, cookie: wasi::DIRCOOKIE_START, use_cache: false, - stream: DirEntryStream(wasi_filesystem::readdir(dir.fd)?), + stream: DirectoryEntryStream(filesystem::read_directory(dir.fd)?), dot_inode, }; @@ -1004,7 +1009,7 @@ pub unsafe extern "C" fn fd_readdir( // In that case there's not much we can do and let the next call to // `fd_readdir` start from scratch. if buf.len() == 0 && name.len() <= DIRENT_CACHE { - let DirEntryIterator { stream, cookie, .. } = iter; + let DirectoryEntryIterator { stream, cookie, .. } = iter; state.dirent_cache.stream.set(Some(stream)); state.dirent_cache.for_fd.set(fd); state.dirent_cache.cookie.set(cookie - 1); @@ -1022,15 +1027,15 @@ pub unsafe extern "C" fn fd_readdir( Ok(()) }); - struct DirEntryIterator<'a> { + struct DirectoryEntryIterator<'a> { state: &'a State, use_cache: bool, cookie: Dircookie, - stream: DirEntryStream, + stream: DirectoryEntryStream, dot_inode: wasi::Inode, } - impl<'a> Iterator for DirEntryIterator<'a> { + impl<'a> Iterator for DirectoryEntryIterator<'a> { // Note the usage of `UnsafeCell` here to indicate that the data can // alias the storage within `state`. type Item = Result<(wasi::Dirent, &'a [UnsafeCell]), Errno>; @@ -1078,7 +1083,7 @@ pub unsafe extern "C" fn fd_readdir( let entry = self.state.import_alloc.with_buffer( self.state.path_buf.get().cast(), PATH_MAX, - || wasi_filesystem::read_dir_entry(self.stream.0), + || filesystem::read_directory_entry(self.stream.0), ); let entry = match entry { Ok(Some(entry)) => entry, @@ -1086,11 +1091,11 @@ pub unsafe extern "C" fn fd_readdir( Err(e) => return Some(Err(e.into())), }; - let wasi_filesystem::DirEntry { ino, type_, name } = entry; + let filesystem::DirectoryEntry { inode, type_, name } = entry; let name = ManuallyDrop::new(name); let dirent = wasi::Dirent { d_next: self.cookie, - d_ino: ino.unwrap_or(0), + d_ino: inode.unwrap_or(0), d_namlen: u32::try_from(name.len()).trapping_unwrap(), d_type: type_.into(), }; @@ -1155,13 +1160,13 @@ pub unsafe extern "C" fn fd_seek( let from = match whence { WHENCE_SET => offset, WHENCE_CUR => (file.position.get() as i64).wrapping_add(offset), - WHENCE_END => (wasi_filesystem::stat(file.fd)?.size as i64) + offset, + WHENCE_END => (filesystem::stat(file.fd)?.size as i64) + offset, _ => return Err(ERRNO_INVAL), }; stream.input.set(None); stream.output.set(None); - file.position.set(from as wasi_filesystem::Filesize); - *newoffset = from as wasi_filesystem::Filesize; + file.position.set(from as filesystem::Filesize); + *newoffset = from as filesystem::Filesize; Ok(()) } else { Err(ERRNO_SPIPE) @@ -1175,7 +1180,7 @@ pub unsafe extern "C" fn fd_seek( pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { State::with(|state| { let file = state.get_file(fd)?; - wasi_filesystem::sync(file.fd)?; + filesystem::sync(file.fd)?; Ok(()) }) } @@ -1221,7 +1226,7 @@ pub unsafe extern "C" fn fd_write( State::with(|state| match state.get(fd)? { Descriptor::Streams(streams) => { let wasi_stream = streams.get_write_stream()?; - let bytes = wasi_io::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; + let bytes = streams::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { @@ -1230,7 +1235,7 @@ pub unsafe extern "C" fn fd_write( // we don't have an API to do that atomically. if !file.append { file.position - .set(file.position.get() + wasi_filesystem::Filesize::from(bytes)); + .set(file.position.get() + filesystem::Filesize::from(bytes)); } } @@ -1238,7 +1243,7 @@ pub unsafe extern "C" fn fd_write( Ok(()) } Descriptor::Stderr => { - wasi_stderr::print(bytes); + crate::macros::print(bytes); *nwritten = len; Ok(()) } @@ -1262,7 +1267,7 @@ pub unsafe extern "C" fn path_create_directory( State::with(|state| { let file = state.get_dir(fd)?; - wasi_filesystem::create_directory_at(file.fd, path)?; + filesystem::create_directory_at(file.fd, path)?; Ok(()) }) } @@ -1282,17 +1287,17 @@ pub unsafe extern "C" fn path_filestat_get( State::with(|state| { let file = state.get_dir(fd)?; - let stat = wasi_filesystem::stat_at(file.fd, at_flags, path)?; + let stat = filesystem::stat_at(file.fd, at_flags, path)?; let filetype = stat.type_.into(); *buf = Filestat { - dev: stat.dev, - ino: stat.ino, + dev: stat.device, + ino: stat.inode, filetype, - nlink: stat.nlink, + nlink: stat.link_count, size: stat.size, - atim: datetime_to_timestamp(stat.atim), - mtim: datetime_to_timestamp(stat.mtim), - ctim: datetime_to_timestamp(stat.ctim), + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), }; Ok(()) }) @@ -1312,25 +1317,25 @@ pub unsafe extern "C" fn path_filestat_set_times( ) -> Errno { let atim = if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { - wasi_filesystem::NewTimestamp::Now + filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { - wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + filesystem::NewTimestamp::Timestamp(filesystem::Datetime { seconds: atim / 1_000_000_000, nanoseconds: (atim % 1_000_000_000) as _, }) } else { - wasi_filesystem::NewTimestamp::NoChange + filesystem::NewTimestamp::NoChange }; let mtim = if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { - wasi_filesystem::NewTimestamp::Now + filesystem::NewTimestamp::Now } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { - wasi_filesystem::NewTimestamp::Timestamp(wasi_filesystem::Datetime { + filesystem::NewTimestamp::Timestamp(filesystem::Datetime { seconds: mtim / 1_000_000_000, nanoseconds: (mtim % 1_000_000_000) as _, }) } else { - wasi_filesystem::NewTimestamp::NoChange + filesystem::NewTimestamp::NoChange }; let path = slice::from_raw_parts(path_ptr, path_len); @@ -1338,7 +1343,7 @@ pub unsafe extern "C" fn path_filestat_set_times( State::with(|state| { let file = state.get_dir(fd)?; - wasi_filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; + filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; Ok(()) }) } @@ -1362,7 +1367,7 @@ pub unsafe extern "C" fn path_link( State::with(|state| { let old = state.get_dir(old_fd)?.fd; let new = state.get_dir(new_fd)?.fd; - wasi_filesystem::link_at(old, at_flags, old_path, new, new_path)?; + filesystem::link_at(old, at_flags, old_path, new, new_path)?; Ok(()) }) } @@ -1392,12 +1397,12 @@ pub unsafe extern "C" fn path_open( let at_flags = at_flags_from_lookupflags(dirflags); let o_flags = o_flags_from_oflags(oflags); let flags = descriptor_flags_from_flags(fs_rights_base, fdflags); - let mode = wasi_filesystem::Mode::READABLE | wasi_filesystem::Mode::WRITEABLE; + let mode = filesystem::Modes::READABLE | filesystem::Modes::WRITEABLE; let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; State::with_mut(|state| { let file = state.get_dir(fd)?; - let result = wasi_filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; + let result = filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; let desc = Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), @@ -1453,12 +1458,12 @@ pub unsafe extern "C" fn path_readlink( state .import_alloc .with_buffer(state.path_buf.get().cast(), PATH_MAX, || { - wasi_filesystem::readlink_at(file.fd, path) + filesystem::readlink_at(file.fd, path) })? } else { state .import_alloc - .with_buffer(buf, buf_len, || wasi_filesystem::readlink_at(file.fd, path))? + .with_buffer(buf, buf_len, || filesystem::readlink_at(file.fd, path))? }; assert_eq!(path.as_ptr(), buf); @@ -1493,7 +1498,7 @@ pub unsafe extern "C" fn path_remove_directory( State::with(|state| { let file = state.get_dir(fd)?; - wasi_filesystem::remove_directory_at(file.fd, path)?; + filesystem::remove_directory_at(file.fd, path)?; Ok(()) }) } @@ -1515,7 +1520,7 @@ pub unsafe extern "C" fn path_rename( State::with(|state| { let old = state.get_dir(old_fd)?.fd; let new = state.get_dir(new_fd)?.fd; - wasi_filesystem::rename_at(old, old_path, new, new_path)?; + filesystem::rename_at(old, old_path, new, new_path)?; Ok(()) }) } @@ -1535,7 +1540,7 @@ pub unsafe extern "C" fn path_symlink( State::with(|state| { let file = state.get_dir(fd)?; - wasi_filesystem::symlink_at(file.fd, old_path, new_path)?; + filesystem::symlink_at(file.fd, old_path, new_path)?; Ok(()) }) } @@ -1549,7 +1554,7 @@ pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: State::with(|state| { let file = state.get_dir(fd)?; - wasi_filesystem::unlink_file_at(file.fd, path)?; + filesystem::unlink_file_at(file.fd, path)?; Ok(()) }) } @@ -1571,16 +1576,16 @@ impl Pollables { impl Drop for Pollables { fn drop(&mut self) { for i in 0..self.index { - wasi_poll::drop_pollable(unsafe { *self.pointer.add(i) }) + poll::drop_pollable(unsafe { *self.pointer.add(i) }) } } } -impl From for Errno { - fn from(error: wasi_network::Error) -> Errno { +impl From for Errno { + fn from(error: network::Error) -> Errno { match error { - wasi_network::Error::Unknown => unreachable!(), // TODO - wasi_network::Error::Again => ERRNO_AGAIN, + network::Error::Unknown => unreachable!(), // TODO + network::Error::Again => ERRNO_AGAIN, /* TODO // Use a black box to prevent the optimizer from generating a // lookup table, which would require a static initializer. @@ -1667,13 +1672,13 @@ pub unsafe extern "C" fn poll_oneoff( CLOCKID_REALTIME => { let timeout = if absolute { // Convert `clock.timeout` to `Datetime`. - let mut datetime = wasi_wall_clock::Datetime { + let mut datetime = wall_clock::Datetime { seconds: clock.timeout / 1_000_000_000, nanoseconds: (clock.timeout % 1_000_000_000) as _, }; // Subtract `now`. - let now = wasi_wall_clock::now(state.default_wall_clock()); + let now = wall_clock::now(state.instance_wall_clock()); datetime.seconds -= now.seconds; if datetime.nanoseconds < now.nanoseconds { datetime.seconds -= 1; @@ -1693,15 +1698,15 @@ pub unsafe extern "C" fn poll_oneoff( clock.timeout }; - wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), + monotonic_clock::subscribe( + state.instance_monotonic_clock(), timeout, false, ) } - CLOCKID_MONOTONIC => wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), + CLOCKID_MONOTONIC => monotonic_clock::subscribe( + state.instance_monotonic_clock(), clock.timeout, absolute, ), @@ -1712,12 +1717,12 @@ pub unsafe extern "C" fn poll_oneoff( EVENTTYPE_FD_READ => { match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { - Ok(stream) => wasi_io::subscribe_read(stream), + Ok(stream) => streams::subscribe_to_input_stream(stream), // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll // immediately fail. - Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), + Err(ERRNO_BADF) => monotonic_clock::subscribe( + state.instance_monotonic_clock(), 0, false, ), @@ -1728,12 +1733,12 @@ pub unsafe extern "C" fn poll_oneoff( EVENTTYPE_FD_WRITE => { match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) { - Ok(stream) => wasi_io::subscribe(stream), + Ok(stream) => streams::subscribe_to_output_stream(stream), // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll // immediately fail. - Err(ERRNO_BADF) => wasi_monotonic_clock::subscribe( - state.default_monotonic_clock(), + Err(ERRNO_BADF) => monotonic_clock::subscribe( + state.instance_monotonic_clock(), 0, false, ), @@ -1745,10 +1750,8 @@ pub unsafe extern "C" fn poll_oneoff( }); } - let vec = wasi_poll::poll_oneoff(slice::from_raw_parts( - pollables.pointer, - pollables.length, - )); + let vec = + poll::poll_oneoff(slice::from_raw_parts(pollables.pointer, pollables.length)); assert_eq!(vec.len(), nsubscriptions); assert_eq!(vec.as_ptr(), results); @@ -1784,29 +1787,26 @@ pub unsafe extern "C" fn poll_oneoff( .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match &streams.type_ { - StreamType::File(file) => { - match wasi_filesystem::stat(file.fd) { - Ok(stat) => { - error = ERRNO_SUCCESS; - nbytes = - stat.size.saturating_sub(file.position.get()); - flags = if nbytes == 0 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 1; - flags = 0; - } + StreamType::File(file) => match filesystem::stat(file.fd) { + Ok(stat) => { + error = ERRNO_SUCCESS; + nbytes = stat.size.saturating_sub(file.position.get()); + flags = if nbytes == 0 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; } - } + Err(e) => { + error = e.into(); + nbytes = 1; + flags = 0; + } + }, StreamType::Socket(connection) => { unreachable!() // TODO /* - match wasi_tcp::bytes_readable(*connection) { + match tcp::bytes_readable(*connection) { Ok(result) => { error = ERRNO_SUCCESS; nbytes = result.0; @@ -1853,7 +1853,7 @@ pub unsafe extern "C" fn poll_oneoff( StreamType::Socket(connection) => { unreachable!() // TODO /* - match wasi_tcp::bytes_writable(connection) { + match tcp::bytes_writable(connection) { Ok(result) => { error = ERRNO_SUCCESS; nbytes = result.0; @@ -1908,7 +1908,7 @@ pub unsafe extern "C" fn poll_oneoff( #[no_mangle] pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! { let status = if rval == 0 { Ok(()) } else { Err(()) }; - wasi_exit::exit(status); // does not return + exit::exit(status); // does not return unreachable!("host exit implementation didn't exit!") // actually unreachable } @@ -1942,9 +1942,9 @@ pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { ) { State::with(|state| { assert_eq!(buf_len as u32 as Size, buf_len); - let result = state.import_alloc.with_buffer(buf, buf_len, || { - wasi_random::get_random_bytes(buf_len as u32) - }); + let result = state + .import_alloc + .with_buffer(buf, buf_len, || random::get_random_bytes(buf_len as u64)); assert_eq!(result.as_ptr(), buf); // The returned buffer's memory was allocated in `buf`, so don't separately @@ -2001,123 +2001,119 @@ pub unsafe extern "C" fn sock_shutdown(fd: Fd, how: Sdflags) -> Errno { unreachable!() } -fn datetime_to_timestamp(datetime: wasi_filesystem::Datetime) -> Timestamp { +fn datetime_to_timestamp(datetime: filesystem::Datetime) -> Timestamp { u64::from(datetime.nanoseconds).saturating_add(datetime.seconds.saturating_mul(1_000_000_000)) } -fn at_flags_from_lookupflags(flags: Lookupflags) -> wasi_filesystem::AtFlags { +fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags { if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW { - wasi_filesystem::AtFlags::SYMLINK_FOLLOW + filesystem::PathFlags::SYMLINK_FOLLOW } else { - wasi_filesystem::AtFlags::empty() + filesystem::PathFlags::empty() } } -fn o_flags_from_oflags(flags: Oflags) -> wasi_filesystem::OFlags { - let mut o_flags = wasi_filesystem::OFlags::empty(); +fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags { + let mut o_flags = filesystem::OpenFlags::empty(); if flags & OFLAGS_CREAT == OFLAGS_CREAT { - o_flags |= wasi_filesystem::OFlags::CREATE; + o_flags |= filesystem::OpenFlags::CREATE; } if flags & OFLAGS_DIRECTORY == OFLAGS_DIRECTORY { - o_flags |= wasi_filesystem::OFlags::DIRECTORY; + o_flags |= filesystem::OpenFlags::DIRECTORY; } if flags & OFLAGS_EXCL == OFLAGS_EXCL { - o_flags |= wasi_filesystem::OFlags::EXCL; + o_flags |= filesystem::OpenFlags::EXCLUSIVE; } if flags & OFLAGS_TRUNC == OFLAGS_TRUNC { - o_flags |= wasi_filesystem::OFlags::TRUNC; + o_flags |= filesystem::OpenFlags::TRUNCATE; } o_flags } -fn descriptor_flags_from_flags( - rights: Rights, - fdflags: Fdflags, -) -> wasi_filesystem::DescriptorFlags { - let mut flags = wasi_filesystem::DescriptorFlags::empty(); +fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags { + let mut flags = filesystem::DescriptorFlags::empty(); if rights & wasi::RIGHTS_FD_READ == wasi::RIGHTS_FD_READ { - flags |= wasi_filesystem::DescriptorFlags::READ; + flags |= filesystem::DescriptorFlags::READ; } if rights & wasi::RIGHTS_FD_WRITE == wasi::RIGHTS_FD_WRITE { - flags |= wasi_filesystem::DescriptorFlags::WRITE; + flags |= filesystem::DescriptorFlags::WRITE; } if fdflags & wasi::FDFLAGS_SYNC == wasi::FDFLAGS_SYNC { - flags |= wasi_filesystem::DescriptorFlags::SYNC; + flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC; } if fdflags & wasi::FDFLAGS_DSYNC == wasi::FDFLAGS_DSYNC { - flags |= wasi_filesystem::DescriptorFlags::DSYNC; + flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC; } if fdflags & wasi::FDFLAGS_RSYNC == wasi::FDFLAGS_RSYNC { - flags |= wasi_filesystem::DescriptorFlags::RSYNC; + flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC; } if fdflags & wasi::FDFLAGS_NONBLOCK == wasi::FDFLAGS_NONBLOCK { - flags |= wasi_filesystem::DescriptorFlags::NONBLOCK; + flags |= filesystem::DescriptorFlags::NON_BLOCKING; } flags } -impl From for Errno { +impl From for Errno { #[inline(never)] // Disable inlining as this is bulky and relatively cold. - fn from(err: wasi_filesystem::Errno) -> Errno { + fn from(err: filesystem::ErrorCode) -> Errno { match err { // Use a black box to prevent the optimizer from generating a // lookup table, which would require a static initializer. - wasi_filesystem::Errno::Access => black_box(ERRNO_ACCES), - wasi_filesystem::Errno::Again => ERRNO_AGAIN, - wasi_filesystem::Errno::Already => ERRNO_ALREADY, - wasi_filesystem::Errno::Badf => ERRNO_BADF, - wasi_filesystem::Errno::Busy => ERRNO_BUSY, - wasi_filesystem::Errno::Deadlk => ERRNO_DEADLK, - wasi_filesystem::Errno::Dquot => ERRNO_DQUOT, - wasi_filesystem::Errno::Exist => ERRNO_EXIST, - wasi_filesystem::Errno::Fbig => ERRNO_FBIG, - wasi_filesystem::Errno::Ilseq => ERRNO_ILSEQ, - wasi_filesystem::Errno::Inprogress => ERRNO_INPROGRESS, - wasi_filesystem::Errno::Intr => ERRNO_INTR, - wasi_filesystem::Errno::Inval => ERRNO_INVAL, - wasi_filesystem::Errno::Io => ERRNO_IO, - wasi_filesystem::Errno::Isdir => ERRNO_ISDIR, - wasi_filesystem::Errno::Loop => ERRNO_LOOP, - wasi_filesystem::Errno::Mlink => ERRNO_MLINK, - wasi_filesystem::Errno::Msgsize => ERRNO_MSGSIZE, - wasi_filesystem::Errno::Nametoolong => ERRNO_NAMETOOLONG, - wasi_filesystem::Errno::Nodev => ERRNO_NODEV, - wasi_filesystem::Errno::Noent => ERRNO_NOENT, - wasi_filesystem::Errno::Nolck => ERRNO_NOLCK, - wasi_filesystem::Errno::Nomem => ERRNO_NOMEM, - wasi_filesystem::Errno::Nospc => ERRNO_NOSPC, - wasi_filesystem::Errno::Nosys => ERRNO_NOSYS, - wasi_filesystem::Errno::Notdir => ERRNO_NOTDIR, - wasi_filesystem::Errno::Notempty => ERRNO_NOTEMPTY, - wasi_filesystem::Errno::Notrecoverable => ERRNO_NOTRECOVERABLE, - wasi_filesystem::Errno::Notsup => ERRNO_NOTSUP, - wasi_filesystem::Errno::Notty => ERRNO_NOTTY, - wasi_filesystem::Errno::Nxio => ERRNO_NXIO, - wasi_filesystem::Errno::Overflow => ERRNO_OVERFLOW, - wasi_filesystem::Errno::Perm => ERRNO_PERM, - wasi_filesystem::Errno::Pipe => ERRNO_PIPE, - wasi_filesystem::Errno::Rofs => ERRNO_ROFS, - wasi_filesystem::Errno::Spipe => ERRNO_SPIPE, - wasi_filesystem::Errno::Txtbsy => ERRNO_TXTBSY, - wasi_filesystem::Errno::Xdev => ERRNO_XDEV, + filesystem::ErrorCode::Access => black_box(ERRNO_ACCES), + filesystem::ErrorCode::WouldBlock => ERRNO_AGAIN, + filesystem::ErrorCode::Already => ERRNO_ALREADY, + filesystem::ErrorCode::BadDescriptor => ERRNO_BADF, + filesystem::ErrorCode::Busy => ERRNO_BUSY, + filesystem::ErrorCode::Deadlock => ERRNO_DEADLK, + filesystem::ErrorCode::Quota => ERRNO_DQUOT, + filesystem::ErrorCode::Exist => ERRNO_EXIST, + filesystem::ErrorCode::FileTooLarge => ERRNO_FBIG, + filesystem::ErrorCode::IllegalByteSequence => ERRNO_ILSEQ, + filesystem::ErrorCode::InProgress => ERRNO_INPROGRESS, + filesystem::ErrorCode::Interrupted => ERRNO_INTR, + filesystem::ErrorCode::Invalid => ERRNO_INVAL, + filesystem::ErrorCode::Io => ERRNO_IO, + filesystem::ErrorCode::IsDirectory => ERRNO_ISDIR, + filesystem::ErrorCode::Loop => ERRNO_LOOP, + filesystem::ErrorCode::TooManyLinks => ERRNO_MLINK, + filesystem::ErrorCode::MessageSize => ERRNO_MSGSIZE, + filesystem::ErrorCode::NameTooLong => ERRNO_NAMETOOLONG, + filesystem::ErrorCode::NoDevice => ERRNO_NODEV, + filesystem::ErrorCode::NoEntry => ERRNO_NOENT, + filesystem::ErrorCode::NoLock => ERRNO_NOLCK, + filesystem::ErrorCode::InsufficientMemory => ERRNO_NOMEM, + filesystem::ErrorCode::InsufficientSpace => ERRNO_NOSPC, + filesystem::ErrorCode::Unsupported => ERRNO_NOTSUP, + filesystem::ErrorCode::NotDirectory => ERRNO_NOTDIR, + filesystem::ErrorCode::NotEmpty => ERRNO_NOTEMPTY, + filesystem::ErrorCode::NotRecoverable => ERRNO_NOTRECOVERABLE, + filesystem::ErrorCode::NoTty => ERRNO_NOTTY, + filesystem::ErrorCode::NoSuchDevice => ERRNO_NXIO, + filesystem::ErrorCode::Overflow => ERRNO_OVERFLOW, + filesystem::ErrorCode::NotPermitted => ERRNO_PERM, + filesystem::ErrorCode::Pipe => ERRNO_PIPE, + filesystem::ErrorCode::ReadOnly => ERRNO_ROFS, + filesystem::ErrorCode::InvalidSeek => ERRNO_SPIPE, + filesystem::ErrorCode::TextFileBusy => ERRNO_TXTBSY, + filesystem::ErrorCode::CrossDevice => ERRNO_XDEV, } } } -impl From for wasi::Filetype { - fn from(ty: wasi_filesystem::DescriptorType) -> wasi::Filetype { +impl From for wasi::Filetype { + fn from(ty: filesystem::DescriptorType) -> wasi::Filetype { match ty { - wasi_filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, - wasi_filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, - wasi_filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, - wasi_filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, + filesystem::DescriptorType::RegularFile => FILETYPE_REGULAR_FILE, + filesystem::DescriptorType::Directory => FILETYPE_DIRECTORY, + filesystem::DescriptorType::BlockDevice => FILETYPE_BLOCK_DEVICE, + filesystem::DescriptorType::CharacterDevice => FILETYPE_CHARACTER_DEVICE, // preview1 never had a FIFO code. - wasi_filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, + filesystem::DescriptorType::Fifo => FILETYPE_UNKNOWN, // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and // FILETYPE_SOCKET_DGRAM. - wasi_filesystem::DescriptorType::Socket => unreachable!(), - wasi_filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, - wasi_filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, + filesystem::DescriptorType::Socket => unreachable!(), + filesystem::DescriptorType::SymbolicLink => FILETYPE_SYMBOLIC_LINK, + filesystem::DescriptorType::Unknown => FILETYPE_UNKNOWN, } } } @@ -2158,7 +2154,7 @@ impl Streams { // For files, we may have adjusted the position for seeking, so // create a new stream. StreamType::File(file) => { - let input = wasi_filesystem::read_via_stream(file.fd, file.position.get())?; + let input = filesystem::read_via_stream(file.fd, file.position.get())?; self.input.set(Some(input)); Ok(input) } @@ -2176,9 +2172,9 @@ impl Streams { // create a new stream. StreamType::File(file) => { let output = if file.append { - wasi_filesystem::append_via_stream(file.fd)? + filesystem::append_via_stream(file.fd)? } else { - wasi_filesystem::write_via_stream(file.fd, file.position.get())? + filesystem::write_via_stream(file.fd, file.position.get())? }; self.output.set(Some(output)); Ok(output) @@ -2201,7 +2197,7 @@ enum StreamType { File(File), /// Streaming data with a socket connection. - Socket(wasi_tcp::TcpSocket), + Socket(tcp::TcpSocket), } impl Drop for Descriptor { @@ -2209,13 +2205,13 @@ impl Drop for Descriptor { match self { Descriptor::Streams(stream) => { if let Some(input) = stream.input.get() { - wasi_io::drop_input_stream(input); + streams::drop_input_stream(input); } if let Some(output) = stream.output.get() { - wasi_io::drop_output_stream(output); + streams::drop_output_stream(output); } match &stream.type_ { - StreamType::File(file) => wasi_filesystem::drop_descriptor(file.fd), + StreamType::File(file) => filesystem::drop_descriptor(file.fd), StreamType::Socket(_) => unreachable!(), StreamType::EmptyStdin | StreamType::Unknown => {} } @@ -2229,10 +2225,10 @@ impl Drop for Descriptor { #[repr(C)] struct File { /// The handle to the preview2 descriptor that this file is referencing. - fd: wasi_filesystem::Descriptor, + fd: filesystem::Descriptor, /// The current-position pointer. - position: Cell, + position: Cell, /// In append mode, all writes append to the file. append: bool, @@ -2297,10 +2293,10 @@ struct State { dirent_cache: DirentCache, /// The clock handle for `CLOCKID_MONOTONIC`. - default_monotonic_clock: Cell>, + instance_monotonic_clock: Cell>, /// The clock handle for `CLOCKID_REALTIME`. - default_wall_clock: Cell>, + instance_wall_clock: Cell>, /// The string `..` for use by the directory iterator. dotdot: [UnsafeCell; 2], @@ -2311,18 +2307,18 @@ struct State { } struct DirentCache { - stream: Cell>, + stream: Cell>, for_fd: Cell, cookie: Cell, cached_dirent: Cell, path_data: UnsafeCell>, } -struct DirEntryStream(wasi_filesystem::DirEntryStream); +struct DirectoryEntryStream(filesystem::DirectoryEntryStream); -impl Drop for DirEntryStream { +impl Drop for DirectoryEntryStream { fn drop(&mut self) { - wasi_filesystem::drop_dir_entry_stream(self.0); + filesystem::drop_directory_entry_stream(self.0); } } @@ -2490,8 +2486,8 @@ impl State { }), path_data: UnsafeCell::new(MaybeUninit::uninit()), }, - default_monotonic_clock: Cell::new(None), - default_wall_clock: Cell::new(None), + instance_monotonic_clock: Cell::new(None), + instance_wall_clock: Cell::new(None), dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')], })); &*ret @@ -2582,7 +2578,7 @@ impl State { } #[allow(dead_code)] // until Socket is implemented - fn get_socket(&self, fd: Fd) -> Result { + fn get_socket(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(Streams { type_: StreamType::Socket(socket), @@ -2625,37 +2621,37 @@ impl State { /// Return a handle to the default wall clock, creating one if we /// don't already have one. - fn default_wall_clock(&self) -> Fd { - match self.default_wall_clock.get() { + fn instance_wall_clock(&self) -> Fd { + match self.instance_wall_clock.get() { Some(fd) => fd, - None => self.init_default_wall_clock(), + None => self.init_instance_wall_clock(), } } - fn init_default_wall_clock(&self) -> Fd { - let clock = wasi_default_clocks::default_wall_clock(); - self.default_wall_clock.set(Some(clock)); + fn init_instance_wall_clock(&self) -> Fd { + let clock = instance_wall_clock::instance_wall_clock(); + self.instance_wall_clock.set(Some(clock)); clock } /// Return a handle to the default monotonic clock, creating one if we /// don't already have one. - fn default_monotonic_clock(&self) -> Fd { - match self.default_monotonic_clock.get() { + fn instance_monotonic_clock(&self) -> Fd { + match self.instance_monotonic_clock.get() { Some(fd) => fd, - None => self.init_default_monotonic_clock(), + None => self.init_instance_monotonic_clock(), } } - fn init_default_monotonic_clock(&self) -> Fd { - let clock = wasi_default_clocks::default_monotonic_clock(); - self.default_monotonic_clock.set(Some(clock)); + fn init_instance_monotonic_clock(&self) -> Fd { + let clock = instance_monotonic_clock::instance_monotonic_clock(); + self.instance_monotonic_clock.set(Some(clock)); clock } fn get_environment(&self) -> &[StrTuple] { if self.env_vars.get().is_none() { - #[link(wasm_import_module = "wasi-environment")] + #[link(wasm_import_module = "environment")] extern "C" { #[link_name = "get-environment"] fn get_environment_import(rval: *mut StrTupleList); @@ -2679,9 +2675,9 @@ impl State { fn get_preopens(&self) -> &[Preopen] { if self.preopens.get().is_none() { - #[link(wasm_import_module = "wasi-filesystem")] + #[link(wasm_import_module = "environment-preopens")] extern "C" { - #[link_name = "get-preopens"] + #[link_name = "preopens"] fn get_preopens_import(rval: *mut PreopenList); } let mut list = PreopenList { diff --git a/src/macros.rs b/src/macros.rs index 608b0d65e5a0..b556470f5085 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -3,6 +3,13 @@ //! We're avoiding static initializers, so we can't have things like string //! literals. Replace the standard assert macros with simpler implementations. +// TODO: Wire this up to stderr. +#[allow(dead_code)] +#[doc(hidden)] +pub fn print(message: &[u8]) { + crate::bindings::stderr::print(message) +} + /// A minimal `eprint` for debugging. #[allow(unused_macros)] macro_rules! eprint { @@ -10,7 +17,7 @@ macro_rules! eprint { // We have to expand string literals into byte arrays to prevent them // from getting statically initialized. let message = byte_array::str!($arg); - crate::bindings::wasi_stderr::print(&message); + $crate::macros::print(&message); }}; } @@ -21,7 +28,7 @@ macro_rules! eprintln { // We have to expand string literals into byte arrays to prevent them // from getting statically initialized. let message = byte_array::str_nl!($arg); - crate::bindings::wasi_stderr::print(&message); + $crate::macros::print(&message); }}; } @@ -37,7 +44,7 @@ pub(crate) fn eprint_u32(x: u32) { eprint_u32_impl(x / 10); let digit = [b'0' + ((x % 10) as u8)]; - crate::bindings::wasi_stderr::print(&digit); + crate::macros::print(&digit); } } } From 34aaaa7f15237671d0235f16ce64af8c554050fa Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 7 Mar 2023 16:47:44 -0800 Subject: [PATCH 091/153] code motion: cli related feature names (#104) * rename host runtime tests to command tests * add a test for using wasi in a reactor * commit cargo.lock * reactor tests: fix wit deps * test-programs: reactor-tests can build on stable note that this fails because it exposes wasi-libc ctors calling import functions from inside cabi_realloc * test-programs: show that ctors fix in wit-bindgen fixes bug * ci: install wasm32 targets for stable as well as nightly * wit-bindgen: use 0.4.0 * ci: use wit-bindgen 0.4.0 * Co-habitate with wasi-common from wasmtime * adapter: code motion in cargo feature & artifact names to cli-command, cli-reactor there will shortly be a third type of reactor (non-cli, idk what to call it) --------- Co-authored-by: Trevor Elliott --- src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4164bb6db5d6..5072fe777379 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,11 +16,16 @@ use poll::Pollable; use streams::{InputStream, OutputStream}; use wasi::*; +#[cfg(all(feature = "cli-command", feature = "cli-reactor"))] +compile_error!( + "only one of the `cli-command` and `cli-reactor` features may be selected at a time" +); + #[macro_use] mod macros; mod bindings { - #[cfg(feature = "command")] + #[cfg(feature = "cli-command")] wit_bindgen::generate!({ world: "cli", std_feature, @@ -30,7 +35,7 @@ mod bindings { skip: ["command", "preopens", "get-environment"], }); - #[cfg(not(feature = "command"))] + #[cfg(feature = "cli-reactor")] wit_bindgen::generate!({ world: "cli-reactor", std_feature, @@ -40,7 +45,7 @@ mod bindings { } #[no_mangle] -#[cfg(feature = "command")] +#[cfg(feature = "cli-command")] pub unsafe extern "C" fn command( stdin: InputStream, stdout: OutputStream, From 6019641ed9b86d655682da6ecea02cbfe8b906f9 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 27 Feb 2023 15:47:56 -0800 Subject: [PATCH 092/153] Update to the latest wit files. This updates to the latest wasi-filesystem, wasi-sockets, wasi-io, and wasi-cli wit changes, except for two things: - `filesystem.types` is temporarily still named `filesystem.filesystem`, to work around bytecodealliance/wasmtime#5961 - wasi-stderr is temporarily still present, for debugging --- src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5072fe777379..76b7784b2ccb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,17 +27,17 @@ mod macros; mod bindings { #[cfg(feature = "cli-command")] wit_bindgen::generate!({ - world: "cli", + world: "command", std_feature, raw_strings, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["command", "preopens", "get-environment"], + skip: ["main", "preopens", "get-environment"], }); #[cfg(feature = "cli-reactor")] wit_bindgen::generate!({ - world: "cli-reactor", + world: "reactor", std_feature, raw_strings, skip: ["preopens", "get-environment"], @@ -46,7 +46,7 @@ mod bindings { #[no_mangle] #[cfg(feature = "cli-command")] -pub unsafe extern "C" fn command( +pub unsafe extern "C" fn main( stdin: InputStream, stdout: OutputStream, stderr: OutputStream, @@ -246,7 +246,7 @@ impl ImportAlloc { } } -/// This allocator is only used for the `command` entrypoint. +/// This allocator is only used for the `main` entrypoint. /// /// The implementation here is a bump allocator into `State::command_data` which /// traps when it runs out of data. This means that the total size of @@ -2159,7 +2159,7 @@ impl Streams { // For files, we may have adjusted the position for seeking, so // create a new stream. StreamType::File(file) => { - let input = filesystem::read_via_stream(file.fd, file.position.get())?; + let input = filesystem::read_via_stream(file.fd, file.position.get()); self.input.set(Some(input)); Ok(input) } @@ -2177,9 +2177,9 @@ impl Streams { // create a new stream. StreamType::File(file) => { let output = if file.append { - filesystem::append_via_stream(file.fd)? + filesystem::append_via_stream(file.fd) } else { - filesystem::write_via_stream(file.fd, file.position.get())? + filesystem::write_via_stream(file.fd, file.position.get()) }; self.output.set(Some(output)); Ok(output) @@ -2277,12 +2277,12 @@ struct State { /// Long-lived bump allocated memory arena. /// /// This is used for the cabi_export_realloc to allocate data passed to the - /// `command` entrypoint. Allocations in this arena are safe to use for + /// `main` entrypoint. Allocations in this arena are safe to use for /// the lifetime of the State struct. It may also be used for import allocations /// which need to be long-lived, by using `import_alloc.with_arena`. long_lived_arena: BumpArena, - /// Arguments passed to the `command` entrypoint + /// Arguments passed to the `main` entrypoint args: Option<&'static [WasmStr]>, /// Environment variables. Initialized lazily. Access with `State::get_environment` @@ -2504,7 +2504,7 @@ impl State { } fn init(&mut self) { - // Set up a default stdin. This will be overridden when `command` + // Set up a default stdin. This will be overridden when `main` // is called. self.push_desc(Descriptor::Streams(Streams { input: Cell::new(None), @@ -2513,7 +2513,7 @@ impl State { })) .trapping_unwrap(); // Set up a default stdout, writing to the stderr device. This will - // be overridden when `command` is called. + // be overridden when `main` is called. self.push_desc(Descriptor::Stderr).trapping_unwrap(); // Set up a default stderr. self.push_desc(Descriptor::Stderr).trapping_unwrap(); From bb86a3ea6b66b057031abcf93b27908c8f4221d6 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 7 Mar 2023 18:42:28 -0800 Subject: [PATCH 093/153] Support command-line argument preopens. --- src/lib.rs | 77 ++++++++++++++++++++++++++++++++++++--------------- src/macros.rs | 2 ++ 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 76b7784b2ccb..e921959798d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,12 @@ pub unsafe extern "C" fn main( } state.args = Some(slice::from_raw_parts(args_ptr, args_len)); + // Initialize `arg_preopens`. + let preopens: &'static [Preopen] = + unsafe { std::slice::from_raw_parts(preopens.base, preopens.len) }; + state.process_preopens(&preopens); + state.arg_preopens.set(Some(preopens)); + Ok(()) }); @@ -2289,9 +2295,13 @@ struct State { /// to take care of initialization. env_vars: Cell>, + /// Preopened directories passed along with `main` args. Access with + /// `State::get_preopens` to take care of initialization. + arg_preopens: Cell>, + /// Preopened directories. Initialized lazily. Access with `State::get_preopens` /// to take care of initialization. - preopens: Cell>, + env_preopens: Cell>, /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. @@ -2369,7 +2379,7 @@ const fn bump_arena_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 24 * size_of::(); + start -= 25 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2478,7 +2488,8 @@ impl State { long_lived_arena: BumpArena::new(), args: None, env_vars: Cell::new(None), - preopens: Cell::new(None), + arg_preopens: Cell::new(None), + env_preopens: Cell::new(None), dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), @@ -2678,8 +2689,9 @@ impl State { self.env_vars.get().trapping_unwrap() } - fn get_preopens(&self) -> &[Preopen] { - if self.preopens.get().is_none() { + fn get_preopens(&self) -> (Option<&[Preopen]>, &[Preopen]) { + // Lazily initialize `env_preopens`. + if self.env_preopens.get().is_none() { #[link(wasm_import_module = "environment-preopens")] extern "C" { #[link_name = "preopens"] @@ -2698,26 +2710,47 @@ impl State { // cast this to a &'static slice: std::slice::from_raw_parts(list.base, list.len) }; - for preopen in preopens { - // Expectation is that the descriptor index is initialized with - // stdio (0,1,2) and no others, so that preopens are 3.. - self.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::File(File { - fd: preopen.descriptor, - position: Cell::new(0), - append: false, - }), - })) - .trapping_unwrap(); - } - self.preopens.set(Some(preopens)); + self.process_preopens(preopens); + self.env_preopens.set(Some(preopens)); } - self.preopens.get().trapping_unwrap() + + let arg_preopens = self.arg_preopens.get(); + let env_preopens = self.env_preopens.get().trapping_unwrap(); + (arg_preopens, env_preopens) } fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { - self.get_preopens().get(fd.checked_sub(3)? as usize) + // Lazily initialize the preopens and obtain the two slices. + let (arg_preopens, env_preopens) = self.get_preopens(); + + // Subtract 3 or the stdio indices to compute the preopen index. + let index = fd.checked_sub(3)? as usize; + + // Index into the conceptually concatenated preopen slices. + if let Some(arg_preopens) = arg_preopens { + if let Some(preopen) = arg_preopens.get(index) { + return Some(preopen); + } + env_preopens.get(index - arg_preopens.len()) + } else { + env_preopens.get(index) + } + } + + fn process_preopens(&self, preopens: &[Preopen]) { + for preopen in preopens { + // Expectation is that the descriptor index is initialized with + // stdio (0,1,2) and no others, so that preopens are 3.. + self.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + append: false, + }), + })) + .trapping_unwrap(); + } } } diff --git a/src/macros.rs b/src/macros.rs index b556470f5085..35be0505cfee 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -54,6 +54,7 @@ macro_rules! unreachable { () => {{ eprint!("unreachable executed at line "); crate::macros::eprint_u32(line!()); + eprint!("\n"); wasm32::unreachable() }}; @@ -62,6 +63,7 @@ macro_rules! unreachable { crate::macros::eprint_u32(line!()); eprint!(": "); eprintln!($arg); + eprint!("\n"); wasm32::unreachable() }}; } From 684bebbfddc8d89290485931ac74c1b1f205983e Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 8 Mar 2022 08:20:13 -0800 Subject: [PATCH 094/153] Minor code simplification. --- src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e921959798d4..42cc3d313579 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2724,17 +2724,16 @@ impl State { let (arg_preopens, env_preopens) = self.get_preopens(); // Subtract 3 or the stdio indices to compute the preopen index. - let index = fd.checked_sub(3)? as usize; + let mut index = fd.checked_sub(3)? as usize; // Index into the conceptually concatenated preopen slices. if let Some(arg_preopens) = arg_preopens { if let Some(preopen) = arg_preopens.get(index) { return Some(preopen); } - env_preopens.get(index - arg_preopens.len()) - } else { - env_preopens.get(index) + index -= arg_preopens.len(); } + env_preopens.get(index) } fn process_preopens(&self, preopens: &[Preopen]) { From d4df7eb909a0743c9497fe0e44ce03aedc2faf87 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 8 Mar 2023 13:42:20 -0800 Subject: [PATCH 095/153] drop cli- prefix from reactor, command features & artifact names --- src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 42cc3d313579..a8919b73c3d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,16 +16,14 @@ use poll::Pollable; use streams::{InputStream, OutputStream}; use wasi::*; -#[cfg(all(feature = "cli-command", feature = "cli-reactor"))] -compile_error!( - "only one of the `cli-command` and `cli-reactor` features may be selected at a time" -); +#[cfg(all(feature = "command", feature = "reactor"))] +compile_error!("only one of the `command` and `reactor` features may be selected at a time"); #[macro_use] mod macros; mod bindings { - #[cfg(feature = "cli-command")] + #[cfg(feature = "command")] wit_bindgen::generate!({ world: "command", std_feature, @@ -35,7 +33,7 @@ mod bindings { skip: ["main", "preopens", "get-environment"], }); - #[cfg(feature = "cli-reactor")] + #[cfg(feature = "reactor")] wit_bindgen::generate!({ world: "reactor", std_feature, @@ -45,7 +43,7 @@ mod bindings { } #[no_mangle] -#[cfg(feature = "cli-command")] +#[cfg(feature = "command")] pub unsafe extern "C" fn main( stdin: InputStream, stdout: OutputStream, @@ -254,7 +252,7 @@ impl ImportAlloc { /// This allocator is only used for the `main` entrypoint. /// -/// The implementation here is a bump allocator into `State::command_data` which +/// The implementation here is a bump allocator into `State::long_lived_arena` which /// traps when it runs out of data. This means that the total size of /// arguments/env/etc coming into a component is bounded by the current 64k /// (ish) limit. That's just an implementation limit though which can be lifted From 788e3d251bfb055eb468c37a88d92d4ebaedbe02 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 8 Mar 2023 14:47:07 -0800 Subject: [PATCH 096/153] intrinsics: just abstract the archive syms a little bit --- build.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/build.rs b/build.rs index 6b0dc9652b25..d6766ac7e49b 100644 --- a/build.rs +++ b/build.rs @@ -253,16 +253,23 @@ fn build_archive(wasm: &[u8]) -> Vec { // Here we're building an archive with just a few symbols so it's a bit // easier. Note though we don't know the offset of our `intrinsics.o` up // front so it's left as 0 for now and filled in later. + + let syms = [ + "get_state_ptr", + "set_state_ptr", + "get_allocation_state", + "set_allocation_state", + "allocation_state", + ]; + let mut symbol_table = Vec::new(); - symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 5))); - for _ in 0..5 { + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, syms.len() as u32))); + for _ in syms.iter() { symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); } - symbol_table.extend_from_slice(b"get_state_ptr\0"); - symbol_table.extend_from_slice(b"set_state_ptr\0"); - symbol_table.extend_from_slice(b"get_allocation_state\0"); - symbol_table.extend_from_slice(b"set_allocation_state\0"); - symbol_table.extend_from_slice(b"allocation_state\0"); + for s in syms.iter() { + symbol_table.extend_from_slice(&std::ffi::CString::new(*s).unwrap().into_bytes_with_nul()); + } archive.extend_from_slice(bytes_of(&object::archive::Header { name: *b"/ ", @@ -287,7 +294,8 @@ fn build_archive(wasm: &[u8]) -> Vec { // Now that we have the starting offset of the `intrinsics.o` file go back // and fill in the offset within the symbol table generated earlier. let member_offset = archive.len(); - for index in 1..6 { + for (index, _) in syms.iter().enumerate() { + let index = index + 1; archive[symtab_offset + (index * 4)..][..4].copy_from_slice(bytes_of(&U32Bytes::new( BigEndian, member_offset.try_into().unwrap(), From b5f6a9c5b703541e349560327dad427d1cd32acb Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 8 Mar 2023 15:00:49 -0800 Subject: [PATCH 097/153] intrinsics: add stderr stream to globals, plus getter/setter funcs --- build.rs | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/build.rs b/build.rs index d6766ac7e49b..26ed1b1eb87d 100644 --- a/build.rs +++ b/build.rs @@ -84,6 +84,8 @@ fn build_raw_intrinsics() -> Vec { funcs.function(1); funcs.function(0); funcs.function(1); + funcs.function(0); + funcs.function(1); module.section(&funcs); // Declare the globals. @@ -104,6 +106,14 @@ fn build_raw_intrinsics() -> Vec { }, &ConstExpr::i32_const(0), ); + // stderr_stream + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + }, + &ConstExpr::i32_const(123), + ); module.section(&globals); // Here the `code` section is defined. This is tricky because an offset is @@ -115,7 +125,7 @@ fn build_raw_intrinsics() -> Vec { // code section. let mut code = Vec::new(); - 4u32.encode(&mut code); // number of functions + 6u32.encode(&mut code); // number of functions let global_get = 0x23; let global_set = 0x24; @@ -142,6 +152,8 @@ fn build_raw_intrinsics() -> Vec { let internal_state_ptr_ref2 = encode(&mut code, 0, global_set); // set_state_ptr let allocation_state_ref1 = encode(&mut code, 1, global_get); // get_allocation_state let allocation_state_ref2 = encode(&mut code, 1, global_set); // set_allocation_state + let stderr_stream_ref1 = encode(&mut code, 2, global_get); // get_stderr_stream + let stderr_stream_ref2 = encode(&mut code, 2, global_set); // set_stderr_stream module.section(&RawSection { id: SectionId::Code as u8, @@ -159,7 +171,7 @@ fn build_raw_intrinsics() -> Vec { linking.push(0x08); // `WASM_SYMBOL_TABLE` let mut subsection = Vec::new(); - 6u32.encode(&mut subsection); // 6 symbols (4 functions + 2 globals) + 9u32.encode(&mut subsection); // 9 symbols (6 functions + 3 globals) subsection.push(0x00); // SYMTAB_FUNCTION 0x00.encode(&mut subsection); // flags @@ -181,6 +193,16 @@ fn build_raw_intrinsics() -> Vec { 3u32.encode(&mut subsection); // function index "set_allocation_state".encode(&mut subsection); // symbol name + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 4u32.encode(&mut subsection); // function index + "get_stderr_stream".encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 5u32.encode(&mut subsection); // function index + "set_stderr_stream".encode(&mut subsection); // symbol name + subsection.push(0x02); // SYMTAB_GLOBAL 0x02.encode(&mut subsection); // flags (WASM_SYM_BINDING_LOCAL) 0u32.encode(&mut subsection); // global index @@ -191,6 +213,11 @@ fn build_raw_intrinsics() -> Vec { 1u32.encode(&mut subsection); // global index "allocation_state".encode(&mut subsection); // symbol name + subsection.push(0x02); // SYMTAB_GLOBAL + 0x00.encode(&mut subsection); // flags + 2u32.encode(&mut subsection); // global index + "stderr_stream".encode(&mut subsection); // symbol name + subsection.encode(&mut linking); module.section(&CustomSection { name: "linking", @@ -203,23 +230,31 @@ fn build_raw_intrinsics() -> Vec { { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 4u32.encode(&mut reloc); // 4 relocations + 6u32.encode(&mut reloc); // 4 relocations reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB internal_state_ptr_ref1.encode(&mut reloc); // offset - 4u32.encode(&mut reloc); // symbol index + 6u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB internal_state_ptr_ref2.encode(&mut reloc); // offset - 4u32.encode(&mut reloc); // symbol index + 6u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB allocation_state_ref1.encode(&mut reloc); // offset - 5u32.encode(&mut reloc); // symbol index + 7u32.encode(&mut reloc); // symbol index reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB allocation_state_ref2.encode(&mut reloc); // offset - 5u32.encode(&mut reloc); // symbol index + 7u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + stderr_stream_ref1.encode(&mut reloc); // offset + 8u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + stderr_stream_ref2.encode(&mut reloc); // offset + 8u32.encode(&mut reloc); // symbol index module.section(&CustomSection { name: "reloc.CODE", From 777dd77f651cd573f03219da15733de885773bcf Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 8 Mar 2023 15:04:27 -0800 Subject: [PATCH 098/153] wire up adapter macros to stderr stream print --- src/lib.rs | 3 +++ src/macros.rs | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a8919b73c3d4..7ef24453d631 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ pub unsafe extern "C" fn main( args_len: usize, preopens: PreopenList, ) -> u32 { + set_stderr_stream(stderr); State::with_mut(|state| { // Initialization of `State` automatically fills in some dummy // structures for fds 0, 1, and 2. Overwrite the stdin/stdout slots of 0 @@ -2406,6 +2407,8 @@ extern "C" { fn set_state_ptr(state: *const RefCell); fn get_allocation_state() -> AllocationState; fn set_allocation_state(state: AllocationState); + fn get_stderr_stream() -> Fd; + fn set_stderr_stream(fd: Fd); } impl State { diff --git a/src/macros.rs b/src/macros.rs index 35be0505cfee..0a4c6114fa22 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -2,12 +2,10 @@ //! //! We're avoiding static initializers, so we can't have things like string //! literals. Replace the standard assert macros with simpler implementations. - -// TODO: Wire this up to stderr. #[allow(dead_code)] #[doc(hidden)] pub fn print(message: &[u8]) { - crate::bindings::stderr::print(message) + let _ = unsafe { crate::bindings::streams::write(crate::get_stderr_stream(), message) }; } /// A minimal `eprint` for debugging. From f8068634ba455c3934adc6723db0f27864b625cc Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 8 Mar 2023 17:54:08 -0800 Subject: [PATCH 099/153] adapter: make it a little clearer where unreachable is coming from --- src/macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 0a4c6114fa22..ad99834cd9ef 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -50,14 +50,14 @@ pub(crate) fn eprint_u32(x: u32) { /// A minimal `unreachable`. macro_rules! unreachable { () => {{ - eprint!("unreachable executed at line "); + eprint!("unreachable executed at adapter line "); crate::macros::eprint_u32(line!()); eprint!("\n"); wasm32::unreachable() }}; ($arg:tt) => {{ - eprint!("unreachable executed at line "); + eprint!("unreachable executed at adapter line "); crate::macros::eprint_u32(line!()); eprint!(": "); eprintln!($arg); From 9d23a6c0c1695c9989fd9cbf7a55ce2a4b29f2f9 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 21 Mar 2023 16:48:16 -0700 Subject: [PATCH 100/153] host and adapter: provide stdio via preopens, dont pass stdio or arg preopens to main stdio never gets initialized for the reactor, tha --- src/lib.rs | 153 +++++++++++++++-------------------------------------- 1 file changed, 44 insertions(+), 109 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7ef24453d631..34a1d0e8ce02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,48 +44,32 @@ mod bindings { #[no_mangle] #[cfg(feature = "command")] -pub unsafe extern "C" fn main( - stdin: InputStream, - stdout: OutputStream, - stderr: OutputStream, - args_ptr: *const WasmStr, - args_len: usize, - preopens: PreopenList, -) -> u32 { - set_stderr_stream(stderr); +pub unsafe extern "C" fn main(args_ptr: *const WasmStr, args_len: usize) -> u32 { State::with_mut(|state| { - // Initialization of `State` automatically fills in some dummy - // structures for fds 0, 1, and 2. Overwrite the stdin/stdout slots of 0 - // and 1 with actual files. - { - let descriptors = state.descriptors_mut(); - if descriptors.len() < 3 { - unreachable!("insufficient memory for stdio descriptors"); - } - descriptors[0] = Descriptor::Streams(Streams { - input: Cell::new(Some(stdin)), - output: Cell::new(None), - type_: StreamType::Unknown, - }); - descriptors[1] = Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(Some(stdout)), - type_: StreamType::Unknown, - }); - descriptors[2] = Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(Some(stderr)), - type_: StreamType::Unknown, - }); + // Initialize the stdio descriptors + let stdio = crate::bindings::preopens::get_stdio(); + unsafe { set_stderr_stream(stdio.stderr) }; + let descriptors = state.descriptors_mut(); + if descriptors.len() < 3 { + unreachable!("unsufficient memory for stdio descriptors"); } - state.args = Some(slice::from_raw_parts(args_ptr, args_len)); - - // Initialize `arg_preopens`. - let preopens: &'static [Preopen] = - unsafe { std::slice::from_raw_parts(preopens.base, preopens.len) }; - state.process_preopens(&preopens); - state.arg_preopens.set(Some(preopens)); + descriptors[0] = Descriptor::Streams(Streams { + input: Cell::new(Some(stdio.stdin)), + output: Cell::new(None), + type_: StreamType::Unknown, + }); + descriptors[1] = Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(Some(stdio.stdout)), + type_: StreamType::Unknown, + }); + descriptors[2] = Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(Some(stdio.stderr)), + type_: StreamType::Unknown, + }); + state.args = Some(slice::from_raw_parts(args_ptr, args_len)); Ok(()) }); @@ -548,19 +532,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } - Descriptor::Stderr => { - let fs_filetype = FILETYPE_UNKNOWN; - let fs_flags = 0; - let fs_rights_base = !RIGHTS_FD_READ; - let fs_rights_inheriting = fs_rights_base; - stat.write(Fdstat { - fs_filetype, - fs_flags, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) - } Descriptor::Streams(Streams { input, output, @@ -589,23 +560,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { }); Ok(()) } - Descriptor::Streams(Streams { - input, - output, - type_: StreamType::EmptyStdin, - }) => { - let fs_filetype = FILETYPE_UNKNOWN; - let fs_flags = 0; - let fs_rights_base = RIGHTS_FD_READ; - let fs_rights_inheriting = fs_rights_base; - stat.write(Fdstat { - fs_filetype, - fs_flags, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) - } Descriptor::Closed(_) => Err(ERRNO_BADF), }) } @@ -887,7 +841,7 @@ pub unsafe extern "C" fn fd_read( Ok(()) } } - Descriptor::Stderr | Descriptor::Closed(_) => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), } }) } @@ -1252,11 +1206,6 @@ pub unsafe extern "C" fn fd_write( *nwritten = bytes as usize; Ok(()) } - Descriptor::Stderr => { - crate::macros::print(bytes); - *nwritten = len; - Ok(()) - } Descriptor::Closed(_) => Err(ERRNO_BADF), }) } else { @@ -1834,11 +1783,6 @@ pub unsafe extern "C" fn poll_oneoff( } */ } - StreamType::EmptyStdin => { - error = ERRNO_SUCCESS; - nbytes = 0; - flags = EVENTRWFLAGS_FD_READWRITE_HANGUP; - } StreamType::Unknown => { error = ERRNO_SUCCESS; nbytes = 1; @@ -1881,11 +1825,6 @@ pub unsafe extern "C" fn poll_oneoff( } */ } - StreamType::EmptyStdin => { - error = ERRNO_BADF; - nbytes = 0; - flags = 0; - } }, _ => unreachable!(), } @@ -2136,9 +2075,6 @@ enum Descriptor { /// Input and/or output wasi-streams, along with stream metadata. Streams(Streams), - - /// Writes to `fd_write` will go to the `wasi-stderr` API. - Stderr, } /// Input and/or output wasi-streams, along with a stream type that @@ -2200,9 +2136,6 @@ enum StreamType { /// It's a valid stream but we don't know where it comes from. Unknown, - /// A stdin source containing no bytes. - EmptyStdin, - /// Streaming data with a file. File(File), @@ -2223,10 +2156,9 @@ impl Drop for Descriptor { match &stream.type_ { StreamType::File(file) => filesystem::drop_descriptor(file.fd), StreamType::Socket(_) => unreachable!(), - StreamType::EmptyStdin | StreamType::Unknown => {} + StreamType::Unknown => {} } } - Descriptor::Stderr => {} Descriptor::Closed(_) => {} } } @@ -2509,26 +2441,30 @@ impl State { })); &*ret }; - ret.try_borrow_mut() - .unwrap_or_else(|_| unreachable!()) - .init(); + ret.borrow().init_empty_stdio(); ret } - fn init(&mut self) { - // Set up a default stdin. This will be overridden when `main` - // is called. + fn init_empty_stdio(&self) { + // Initialize the stdio descriptors as empty + self.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::Unknown, + })) + .trapping_unwrap(); + self.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::Unknown, + })) + .trapping_unwrap(); self.push_desc(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), type_: StreamType::Unknown, })) .trapping_unwrap(); - // Set up a default stdout, writing to the stderr device. This will - // be overridden when `main` is called. - self.push_desc(Descriptor::Stderr).trapping_unwrap(); - // Set up a default stderr. - self.push_desc(Descriptor::Stderr).trapping_unwrap(); } fn push_desc(&self, desc: Descriptor) -> Result { @@ -2579,7 +2515,6 @@ impl State { match self.get(fd)? { Descriptor::Streams(streams) => Ok(streams), Descriptor::Closed(_) => Err(ERRNO_BADF), - _ => Err(error), } } @@ -2625,14 +2560,14 @@ impl State { fn get_read_stream(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_read_stream(), - Descriptor::Closed(_) | Descriptor::Stderr => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), } } fn get_write_stream(&self, fd: Fd) -> Result { match self.get(fd)? { Descriptor::Streams(streams) => streams.get_write_stream(), - Descriptor::Closed(_) | Descriptor::Stderr => Err(ERRNO_BADF), + Descriptor::Closed(_) => Err(ERRNO_BADF), } } @@ -2693,9 +2628,9 @@ impl State { fn get_preopens(&self) -> (Option<&[Preopen]>, &[Preopen]) { // Lazily initialize `env_preopens`. if self.env_preopens.get().is_none() { - #[link(wasm_import_module = "environment-preopens")] + #[link(wasm_import_module = "preopens")] extern "C" { - #[link_name = "preopens"] + #[link_name = "get-directories"] fn get_preopens_import(rval: *mut PreopenList); } let mut list = PreopenList { From ffa75f0f0dded737572a7d29f292922a46960d70 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 22 Mar 2023 09:41:18 -0700 Subject: [PATCH 101/153] get rid of arg_preopens from state, env_preopens are just preopens --- src/lib.rs | 75 ++++++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 34a1d0e8ce02..05d2c557e7f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2226,13 +2226,9 @@ struct State { /// to take care of initialization. env_vars: Cell>, - /// Preopened directories passed along with `main` args. Access with - /// `State::get_preopens` to take care of initialization. - arg_preopens: Cell>, - /// Preopened directories. Initialized lazily. Access with `State::get_preopens` /// to take care of initialization. - env_preopens: Cell>, + preopens: Cell>, /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. @@ -2310,7 +2306,7 @@ const fn bump_arena_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 25 * size_of::(); + start -= 24 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2421,8 +2417,7 @@ impl State { long_lived_arena: BumpArena::new(), args: None, env_vars: Cell::new(None), - arg_preopens: Cell::new(None), - env_preopens: Cell::new(None), + preopens: Cell::new(None), dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), @@ -2625,9 +2620,9 @@ impl State { self.env_vars.get().trapping_unwrap() } - fn get_preopens(&self) -> (Option<&[Preopen]>, &[Preopen]) { - // Lazily initialize `env_preopens`. - if self.env_preopens.get().is_none() { + fn get_preopens(&self) -> &[Preopen] { + // Lazily initialize `preopens`. + if self.preopens.get().is_none() { #[link(wasm_import_module = "preopens")] extern "C" { #[link_name = "get-directories"] @@ -2646,46 +2641,32 @@ impl State { // cast this to a &'static slice: std::slice::from_raw_parts(list.base, list.len) }; - self.process_preopens(preopens); - self.env_preopens.set(Some(preopens)); + for preopen in preopens { + // Expectation is that the descriptor index is initialized with + // stdio (0,1,2) and no others, so that preopens are 3.. + self.push_desc(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + append: false, + }), + })) + .trapping_unwrap(); + } + + self.preopens.set(Some(preopens)); } - let arg_preopens = self.arg_preopens.get(); - let env_preopens = self.env_preopens.get().trapping_unwrap(); - (arg_preopens, env_preopens) + self.preopens.get().trapping_unwrap() } fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { - // Lazily initialize the preopens and obtain the two slices. - let (arg_preopens, env_preopens) = self.get_preopens(); - - // Subtract 3 or the stdio indices to compute the preopen index. - let mut index = fd.checked_sub(3)? as usize; - - // Index into the conceptually concatenated preopen slices. - if let Some(arg_preopens) = arg_preopens { - if let Some(preopen) = arg_preopens.get(index) { - return Some(preopen); - } - index -= arg_preopens.len(); - } - env_preopens.get(index) - } - - fn process_preopens(&self, preopens: &[Preopen]) { - for preopen in preopens { - // Expectation is that the descriptor index is initialized with - // stdio (0,1,2) and no others, so that preopens are 3.. - self.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::File(File { - fd: preopen.descriptor, - position: Cell::new(0), - append: false, - }), - })) - .trapping_unwrap(); - } + // Lazily initialize the preopens and obtain the slice + let preopens = self.get_preopens(); + // Subtract 3 for the stdio indices to compute the preopen index. + let index = fd.checked_sub(3)? as usize; + preopens.get(index) } } From 1af3c418532a89b224bf858f0f66d75622dbc3ac Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 22 Mar 2023 16:43:28 -0700 Subject: [PATCH 102/153] factor descriptors out into a separate module i didnt run verify step-by-step during this so i accidentally got panick machinery in here. thats what i get --- src/descriptors.rs | 390 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 466 +++++++-------------------------------------- 2 files changed, 459 insertions(+), 397 deletions(-) create mode 100644 src/descriptors.rs diff --git a/src/descriptors.rs b/src/descriptors.rs new file mode 100644 index 000000000000..cebb5994ef2d --- /dev/null +++ b/src/descriptors.rs @@ -0,0 +1,390 @@ +use crate::bindings::streams::{self, InputStream, OutputStream}; +use crate::bindings::{filesystem, tcp}; +use crate::{set_stderr_stream, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr}; +use core::cell::{Cell, UnsafeCell}; +use core::mem::MaybeUninit; +use wasi::{Errno, Fd}; + +pub const MAX_DESCRIPTORS: usize = 128; + +#[repr(C)] +pub enum Descriptor { + /// A closed descriptor, holding a reference to the previous closed + /// descriptor to support reusing them. + Closed(Option), + + /// Input and/or output wasi-streams, along with stream metadata. + Streams(Streams), +} + +impl Drop for Descriptor { + fn drop(&mut self) { + match self { + Descriptor::Streams(stream) => { + if let Some(input) = stream.input.get() { + streams::drop_input_stream(input); + } + if let Some(output) = stream.output.get() { + streams::drop_output_stream(output); + } + match &stream.type_ { + StreamType::File(file) => filesystem::drop_descriptor(file.fd), + StreamType::Socket(_) => unreachable!(), + StreamType::Unknown => {} + } + } + Descriptor::Closed(_) => {} + } + } +} + +/// Input and/or output wasi-streams, along with a stream type that +/// identifies what kind of stream they are and possibly supporting +/// type-specific operations like seeking. +pub struct Streams { + /// The output stream, if present. + pub input: Cell>, + + /// The input stream, if present. + pub output: Cell>, + + /// Information about the source of the stream. + pub type_: StreamType, +} + +impl Streams { + /// Return the input stream, initializing it on the fly if needed. + pub fn get_read_stream(&self) -> Result { + match &self.input.get() { + Some(wasi_stream) => Ok(*wasi_stream), + None => match &self.type_ { + // For files, we may have adjusted the position for seeking, so + // create a new stream. + StreamType::File(file) => { + let input = filesystem::read_via_stream(file.fd, file.position.get()); + self.input.set(Some(input)); + Ok(input) + } + _ => Err(wasi::ERRNO_BADF), + }, + } + } + + /// Return the output stream, initializing it on the fly if needed. + pub fn get_write_stream(&self) -> Result { + match &self.output.get() { + Some(wasi_stream) => Ok(*wasi_stream), + None => match &self.type_ { + // For files, we may have adjusted the position for seeking, so + // create a new stream. + StreamType::File(file) => { + let output = if file.append { + filesystem::append_via_stream(file.fd) + } else { + filesystem::write_via_stream(file.fd, file.position.get()) + }; + self.output.set(Some(output)); + Ok(output) + } + _ => Err(wasi::ERRNO_BADF), + }, + } + } +} + +#[allow(dead_code)] // until Socket is implemented +pub enum StreamType { + /// It's a valid stream but we don't know where it comes from. + Unknown, + + /// Streaming data with a file. + File(File), + + /// Streaming data with a socket connection. + Socket(tcp::TcpSocket), +} + +#[repr(C)] +pub struct Descriptors { + /// Storage of mapping from preview1 file descriptors to preview2 file + /// descriptors. + table: UnsafeCell>, + table_len: Cell, + + /// Points to the head of a free-list of closed file descriptors. + closed: Option, + + /// Preopened directories. Initialized lazily. Access with `State::get_preopens` + /// to take care of initialization. + preopens: Cell>, +} + +impl Descriptors { + pub fn new() -> Self { + Descriptors { + table: UnsafeCell::new(MaybeUninit::uninit()), + table_len: Cell::new(0), + closed: None, + preopens: Cell::new(None), + } + } + pub fn is_initialized(&self) -> bool { + self.table_len.get() != 0 + } + pub fn init(&self, import_alloc: &ImportAlloc, arena: &BumpArena) { + if self.is_initialized() { + unreachable!("cannot initialize descriptors more than once") + } + + let stdio = crate::bindings::preopens::get_stdio(); + unsafe { set_stderr_stream(stdio.stderr) }; + + self.push(Descriptor::Streams(Streams { + input: Cell::new(Some(stdio.stdin)), + output: Cell::new(None), + type_: StreamType::Unknown, + })) + .trapping_unwrap(); + self.push(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(Some(stdio.stdout)), + type_: StreamType::Unknown, + })) + .trapping_unwrap(); + self.push(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(Some(stdio.stderr)), + type_: StreamType::Unknown, + })) + .trapping_unwrap(); + + #[link(wasm_import_module = "preopens")] + extern "C" { + #[link_name = "get-directories"] + fn get_preopens_import(rval: *mut PreopenList); + } + let mut list = PreopenList { + base: std::ptr::null(), + len: 0, + }; + import_alloc.with_arena(arena, || unsafe { + get_preopens_import(&mut list as *mut _) + }); + let preopens: &'static [Preopen] = unsafe { + // allocation comes from long lived arena, so it is safe to + // cast this to a &'static slice: + std::slice::from_raw_parts(list.base, list.len) + }; + for preopen in preopens { + // Expectation is that the descriptor index is initialized with + // stdio (0,1,2) and no others, so that preopens are 3.. + self.push(Descriptor::Streams(Streams { + input: Cell::new(None), + output: Cell::new(None), + type_: StreamType::File(File { + fd: preopen.descriptor, + position: Cell::new(0), + append: false, + }), + })) + .trapping_unwrap(); + } + + self.preopens.set(Some(preopens)); + } + + fn push(&self, desc: Descriptor) -> Result { + unsafe { + let table = (*self.table.get()).as_mut_ptr(); + let len = usize::try_from(self.table_len.get()).trapping_unwrap(); + if len >= (*table).len() { + return Err(wasi::ERRNO_NOMEM); + } + core::ptr::addr_of_mut!((*table)[len]).write(desc); + self.table_len.set(u16::try_from(len + 1).trapping_unwrap()); + Ok(Fd::from(u32::try_from(len).trapping_unwrap())) + } + } + + fn table(&self) -> &[Descriptor] { + unsafe { + std::slice::from_raw_parts( + (*self.table.get()).as_ptr().cast(), + usize::try_from(self.table_len.get()).trapping_unwrap(), + ) + } + } + + fn table_mut(&mut self) -> &mut [Descriptor] { + unsafe { + std::slice::from_raw_parts_mut( + (*self.table.get()).as_mut_ptr().cast(), + usize::try_from(self.table_len.get()).trapping_unwrap(), + ) + } + } + + pub fn open(&mut self, d: Descriptor) -> Result { + match self.closed { + // No closed descriptors: expand table + None => self.push(d), + Some(freelist_head) => { + // Pop an item off the freelist + let freelist_desc = self.get_mut(freelist_head).trapping_unwrap(); + let next_closed = match freelist_desc { + Descriptor::Closed(next) => *next, + _ => unreachable!("impossible: freelist points to a closed descriptor"), + }; + // Write descriptor to the entry at the nead of the list + *freelist_desc = d; + // Point closed to the following item + self.closed = next_closed; + Ok(freelist_head) + } + } + } + + pub fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { + if !self.is_initialized() { + unreachable!("bug: descriptors should be initialized") + } + self.table() + .get(usize::try_from(fd).trapping_unwrap()) + .ok_or(wasi::ERRNO_BADF) + } + + pub fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { + if !self.is_initialized() { + unreachable!("bug: descriptors should be initialized") + } + self.table_mut() + .get_mut(usize::try_from(fd).trapping_unwrap()) + .ok_or(wasi::ERRNO_BADF) + } + + pub fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { + if !self.is_initialized() { + unreachable!("bug: descriptors should be initialized") + } + let preopens = self.preopens.get().trapping_unwrap(); + // Subtract 3 for the stdio indices to compute the preopen index. + let index = fd.checked_sub(3)? as usize; + preopens.get(index) + } + + // Internal: close a fd, returning the descriptor. + fn close_(&mut self, fd: Fd) -> Result { + // Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list: + let last_closed = self.closed; + let prev = std::mem::replace(self.get_mut(fd)?, Descriptor::Closed(last_closed)); + self.closed = Some(fd); + Ok(prev) + } + + // Close an fd. + pub fn close(&mut self, fd: Fd) -> Result<(), Errno> { + drop(self.close_(fd)?); + Ok(()) + } + + // Expand the table by pushing a closed descriptor to the end. Used for renumbering. + fn push_closed(&mut self) -> Result<(), Errno> { + let old_closed = self.closed; + let new_closed = self.push(Descriptor::Closed(old_closed))?; + self.closed = Some(new_closed); + Ok(()) + } + + // + pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> { + // First, ensure from_fd is in bounds: + drop(self.get(from_fd)?); + // Then, make sure to_fd is not already open: + if self.get(to_fd).is_ok() { + return Err(wasi::ERRNO_INVAL); + } + // Expand table until to_fd is in bounds as well: + while self.table_len.get() as u32 <= to_fd as u32 { + self.push_closed()?; + } + // Then, close from_fd and put its contents into to_fd: + let desc = self.close_(from_fd)?; + *self.get_mut(to_fd)? = desc; + + Ok(()) + } + + // A bunch of helper functions implemented in terms of the above pub functions: + + pub fn get_stream_with_error(&self, fd: Fd, error: Errno) -> Result<&Streams, Errno> { + match self.get(fd)? { + Descriptor::Streams(streams) => Ok(streams), + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), + } + } + + pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { + match self.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => Ok(file), + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), + _ => Err(error), + } + } + + #[allow(dead_code)] // until Socket is implemented + pub fn get_socket(&self, fd: Fd) -> Result { + match self.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::Socket(socket), + .. + }) => Ok(*socket), + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), + _ => Err(wasi::ERRNO_INVAL), + } + } + + pub fn get_file(&self, fd: Fd) -> Result<&File, Errno> { + self.get_file_with_error(fd, wasi::ERRNO_INVAL) + } + + pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { + self.get_file_with_error(fd, wasi::ERRNO_NOTDIR) + } + + pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { + self.get_file_with_error(fd, wasi::ERRNO_SPIPE) + } + + pub fn get_seekable_stream(&self, fd: Fd) -> Result<&Streams, Errno> { + self.get_stream_with_error(fd, wasi::ERRNO_SPIPE) + } + + pub fn get_read_stream(&self, fd: Fd) -> Result { + match self.get(fd)? { + Descriptor::Streams(streams) => streams.get_read_stream(), + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), + } + } + + pub fn get_write_stream(&self, fd: Fd) -> Result { + match self.get(fd)? { + Descriptor::Streams(streams) => streams.get_write_stream(), + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), + } + } +} + +#[repr(C)] +pub struct Preopen { + pub descriptor: u32, + pub path: WasmStr, +} + +#[repr(C)] +pub struct PreopenList { + pub base: *const Preopen, + pub len: usize, +} diff --git a/src/lib.rs b/src/lib.rs index 05d2c557e7f2..881235ca0851 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,27 +2,28 @@ use crate::bindings::{ exit, filesystem, instance_monotonic_clock, instance_wall_clock, monotonic_clock, network, - poll, random, streams, tcp, wall_clock, + poll, random, streams, wall_clock, }; use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; use core::cmp::min; use core::ffi::c_void; use core::hint::black_box; -use core::mem::{self, align_of, forget, replace, size_of, ManuallyDrop, MaybeUninit}; +use core::mem::{self, align_of, forget, size_of, ManuallyDrop, MaybeUninit}; use core::ptr::{self, null_mut}; use core::slice; use poll::Pollable; -use streams::{InputStream, OutputStream}; use wasi::*; #[cfg(all(feature = "command", feature = "reactor"))] compile_error!("only one of the `command` and `reactor` features may be selected at a time"); +mod descriptors; +use crate::descriptors::{Descriptor, Descriptors, StreamType, Streams}; #[macro_use] mod macros; -mod bindings { +pub mod bindings { #[cfg(feature = "command")] wit_bindgen::generate!({ world: "command", @@ -30,7 +31,7 @@ mod bindings { raw_strings, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["main", "preopens", "get-environment"], + skip: ["main", "get-directories", "get-environment"], }); #[cfg(feature = "reactor")] @@ -38,7 +39,7 @@ mod bindings { world: "reactor", std_feature, raw_strings, - skip: ["preopens", "get-environment"], + skip: ["get-directories", "get-environment"], }); } @@ -46,29 +47,6 @@ mod bindings { #[cfg(feature = "command")] pub unsafe extern "C" fn main(args_ptr: *const WasmStr, args_len: usize) -> u32 { State::with_mut(|state| { - // Initialize the stdio descriptors - let stdio = crate::bindings::preopens::get_stdio(); - unsafe { set_stderr_stream(stdio.stderr) }; - let descriptors = state.descriptors_mut(); - if descriptors.len() < 3 { - unreachable!("unsufficient memory for stdio descriptors"); - } - descriptors[0] = Descriptor::Streams(Streams { - input: Cell::new(Some(stdio.stdin)), - output: Cell::new(None), - type_: StreamType::Unknown, - }); - descriptors[1] = Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(Some(stdio.stdout)), - type_: StreamType::Unknown, - }); - descriptors[2] = Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(Some(stdio.stderr)), - type_: StreamType::Unknown, - }); - state.args = Some(slice::from_raw_parts(args_ptr, args_len)); Ok(()) }); @@ -126,7 +104,7 @@ pub unsafe extern "C" fn cabi_import_realloc( /// Bump-allocated memory arena. This is a singleton - the /// memory will be sized according to `bump_arena_size()`. -struct BumpArena { +pub struct BumpArena { data: MaybeUninit<[u8; bump_arena_size()]>, position: Cell, } @@ -159,7 +137,7 @@ fn align_to(ptr: usize, align: usize) -> usize { // because we can't use RefCell to borrow() the variants of the enum - only // Cell provides mutability without pulling in panic machinery - so it would // make the accessors a lot more awkward to write. -struct ImportAlloc { +pub struct ImportAlloc { // When not-null, allocator should use this buffer/len pair at most once // to satisfy allocations. buffer: Cell<*mut u8>, @@ -441,7 +419,7 @@ pub unsafe extern "C" fn fd_advise( _ => return ERRNO_INVAL, }; State::with(|state| { - let file = state.get_seekable_file(fd)?; + let file = state.descriptors().get_seekable_file(fd)?; filesystem::advise(file.fd, offset, len, advice)?; Ok(()) }) @@ -466,10 +444,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { drop(state.dirent_cache.stream.replace(None)); } - let closed = state.closed; - let desc = state.get_mut(fd)?; - *desc = Descriptor::Closed(closed); - state.closed = Some(fd); + let desc = state.descriptors_mut().close(fd)?; Ok(()) }) } @@ -479,7 +454,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { State::with(|state| { - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; filesystem::sync_data(file.fd)?; Ok(()) }) @@ -489,7 +464,7 @@ pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { - State::with(|state| match state.get(fd)? { + State::with(|state| match state.descriptors().get(fd)? { Descriptor::Streams(Streams { type_: StreamType::File(file), .. @@ -583,7 +558,7 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { } State::with(|state| { - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; filesystem::set_flags(file.fd, new_flags)?; Ok(()) }) @@ -604,7 +579,7 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( #[no_mangle] pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; let stat = filesystem::stat(file.fd)?; let filetype = stat.type_.into(); *buf = Filestat { @@ -626,7 +601,7 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { State::with(|state| { - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; filesystem::set_size(file.fd, size)?; Ok(()) }) @@ -665,7 +640,7 @@ pub unsafe extern "C" fn fd_filestat_set_times( }; State::with(|state| { - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; filesystem::set_times(file.fd, atim, mtim)?; Ok(()) }) @@ -695,7 +670,7 @@ pub unsafe extern "C" fn fd_pread( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; let (data, end) = state .import_alloc .with_buffer(ptr, len, || filesystem::read(file.fd, len as u64, offset))?; @@ -721,7 +696,7 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { AllocationState::StackAllocated | AllocationState::StateAllocated ) { State::with(|state| { - if let Some(preopen) = state.get_preopen(fd) { + if let Some(preopen) = state.descriptors().get_preopen(fd) { buf.write(Prestat { tag: 0, u: PrestatU { @@ -745,7 +720,7 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_len: Size) -> Errno { State::with(|state| { - if let Some(preopen) = state.get_preopen(fd) { + if let Some(preopen) = state.descriptors().get_preopen(fd) { if preopen.path.len < path_len as usize { Err(ERRNO_NAMETOOLONG) } else { @@ -782,7 +757,7 @@ pub unsafe extern "C" fn fd_pwrite( let len = (*iovs_ptr).buf_len; State::with(|state| { - let file = state.get_seekable_file(fd)?; + let file = state.descriptors().get_seekable_file(fd)?; let bytes = filesystem::write(file.fd, slice::from_raw_parts(ptr, len), offset)?; *nwritten = bytes as usize; Ok(()) @@ -812,7 +787,7 @@ pub unsafe extern "C" fn fd_read( let len = (*iovs_ptr).buf_len; State::with(|state| { - match state.get(fd)? { + match state.descriptors().get(fd)? { Descriptor::Streams(streams) => { let wasi_stream = streams.get_read_stream()?; @@ -886,7 +861,7 @@ pub unsafe extern "C" fn fd_readdir( // Compute the inode of `.` so that the iterator can produce an entry // for it. - let dir = state.get_dir(fd)?; + let dir = state.descriptors().get_dir(fd)?; let stat = filesystem::stat(dir.fd)?; let dot_inode = stat.inode; @@ -1084,25 +1059,7 @@ pub unsafe extern "C" fn fd_readdir( /// would disappear if `dup2()` were to be removed entirely. #[no_mangle] pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { - State::with_mut(|state| { - let closed = state.closed; - - // Ensure the table is big enough to contain `to`. Do this before - // looking up `fd` as it can fail due to `NOMEM`. - while Fd::from(state.ndescriptors.get()) <= to { - let old_closed = state.closed; - let new_closed = state.push_desc(Descriptor::Closed(old_closed))?; - state.closed = Some(new_closed); - } - - let fd_desc = state.get_mut(fd)?; - let desc = replace(fd_desc, Descriptor::Closed(closed)); - - let to_desc = state.get_mut(to).trapping_unwrap(); - *to_desc = desc; - state.closed = Some(fd); - Ok(()) - }) + State::with_mut(|state| state.descriptors_mut().renumber(fd, to)) } /// Move the offset of a file descriptor. @@ -1115,7 +1072,7 @@ pub unsafe extern "C" fn fd_seek( newoffset: *mut Filesize, ) -> Errno { State::with(|state| { - let stream = state.get_seekable_stream(fd)?; + let stream = state.descriptors().get_seekable_stream(fd)?; // Seeking only works on files. if let StreamType::File(file) = &stream.type_ { @@ -1143,7 +1100,7 @@ pub unsafe extern "C" fn fd_seek( #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { State::with(|state| { - let file = state.get_file(fd)?; + let file = state.descriptors().get_file(fd)?; filesystem::sync(file.fd)?; Ok(()) }) @@ -1154,7 +1111,7 @@ pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { State::with(|state| { - let file = state.get_seekable_file(fd)?; + let file = state.descriptors().get_seekable_file(fd)?; *offset = file.position.get() as Filesize; Ok(()) }) @@ -1187,7 +1144,7 @@ pub unsafe extern "C" fn fd_write( let len = (*iovs_ptr).buf_len; let bytes = slice::from_raw_parts(ptr, len); - State::with(|state| match state.get(fd)? { + State::with(|state| match state.descriptors().get(fd)? { Descriptor::Streams(streams) => { let wasi_stream = streams.get_write_stream()?; let bytes = streams::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; @@ -1225,7 +1182,7 @@ pub unsafe extern "C" fn path_create_directory( let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; filesystem::create_directory_at(file.fd, path)?; Ok(()) }) @@ -1245,7 +1202,7 @@ pub unsafe extern "C" fn path_filestat_get( let at_flags = at_flags_from_lookupflags(flags); State::with(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; let stat = filesystem::stat_at(file.fd, at_flags, path)?; let filetype = stat.type_.into(); *buf = Filestat { @@ -1301,7 +1258,7 @@ pub unsafe extern "C" fn path_filestat_set_times( let at_flags = at_flags_from_lookupflags(flags); State::with(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; Ok(()) }) @@ -1324,8 +1281,8 @@ pub unsafe extern "C" fn path_link( let at_flags = at_flags_from_lookupflags(old_flags); State::with(|state| { - let old = state.get_dir(old_fd)?.fd; - let new = state.get_dir(new_fd)?.fd; + let old = state.descriptors().get_dir(old_fd)?.fd; + let new = state.descriptors().get_dir(new_fd)?.fd; filesystem::link_at(old, at_flags, old_path, new, new_path)?; Ok(()) }) @@ -1360,7 +1317,7 @@ pub unsafe extern "C" fn path_open( let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; State::with_mut(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; let result = filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; let desc = Descriptor::Streams(Streams { input: Cell::new(None), @@ -1372,22 +1329,7 @@ pub unsafe extern "C" fn path_open( }), }); - let fd = match state.closed { - // No free fds; create a new one. - None => state.push_desc(desc)?, - // `recycle_fd` is a free fd. - Some(recycle_fd) => { - let recycle_desc = state.get_mut(recycle_fd).trapping_unwrap(); - let next_closed = match recycle_desc { - Descriptor::Closed(next) => *next, - _ => unreachable!(), - }; - *recycle_desc = desc; - state.closed = next_closed; - recycle_fd - } - }; - + let fd = state.descriptors_mut().open(desc)?; *opened_fd = fd; Ok(()) }) @@ -1412,7 +1354,7 @@ pub unsafe extern "C" fn path_readlink( // so instead we handle this case specially. let use_state_buf = buf_len < PATH_MAX; - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; let path = if use_state_buf { state .import_alloc @@ -1456,7 +1398,7 @@ pub unsafe extern "C" fn path_remove_directory( let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; filesystem::remove_directory_at(file.fd, path)?; Ok(()) }) @@ -1477,8 +1419,8 @@ pub unsafe extern "C" fn path_rename( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); State::with(|state| { - let old = state.get_dir(old_fd)?.fd; - let new = state.get_dir(new_fd)?.fd; + let old = state.descriptors().get_dir(old_fd)?.fd; + let new = state.descriptors().get_dir(new_fd)?.fd; filesystem::rename_at(old, old_path, new, new_path)?; Ok(()) }) @@ -1498,7 +1440,7 @@ pub unsafe extern "C" fn path_symlink( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); State::with(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; filesystem::symlink_at(file.fd, old_path, new_path)?; Ok(()) }) @@ -1512,7 +1454,7 @@ pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let file = state.get_dir(fd)?; + let file = state.descriptors().get_dir(fd)?; filesystem::unlink_file_at(file.fd, path)?; Ok(()) }) @@ -1675,7 +1617,10 @@ pub unsafe extern "C" fn poll_oneoff( } EVENTTYPE_FD_READ => { - match state.get_read_stream(subscription.u.u.fd_read.file_descriptor) { + match state + .descriptors() + .get_read_stream(subscription.u.u.fd_read.file_descriptor) + { Ok(stream) => streams::subscribe_to_input_stream(stream), // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll @@ -1690,7 +1635,9 @@ pub unsafe extern "C" fn poll_oneoff( } EVENTTYPE_FD_WRITE => { - match state.get_write_stream(subscription.u.u.fd_write.file_descriptor) + match state + .descriptors() + .get_write_stream(subscription.u.u.fd_write.file_descriptor) { Ok(stream) => streams::subscribe_to_output_stream(stream), // If the file descriptor isn't a stream, request a @@ -1742,6 +1689,7 @@ pub unsafe extern "C" fn poll_oneoff( 1 => { type_ = EVENTTYPE_FD_READ; let desc = state + .descriptors() .get(subscription.u.u.fd_read.file_descriptor) .trapping_unwrap(); match desc { @@ -1795,6 +1743,7 @@ pub unsafe extern "C" fn poll_oneoff( 2 => { type_ = EVENTTYPE_FD_WRITE; let desc = state + .descriptors() .get(subscription.u.u.fd_read.file_descriptor) .trapping_unwrap(); match desc { @@ -2068,104 +2017,7 @@ impl From for wasi::Filetype { } #[repr(C)] -enum Descriptor { - /// A closed descriptor, holding a reference to the previous closed - /// descriptor to support reusing them. - Closed(Option), - - /// Input and/or output wasi-streams, along with stream metadata. - Streams(Streams), -} - -/// Input and/or output wasi-streams, along with a stream type that -/// identifies what kind of stream they are and possibly supporting -/// type-specific operations like seeking. -struct Streams { - /// The output stream, if present. - input: Cell>, - - /// The input stream, if present. - output: Cell>, - - /// Information about the source of the stream. - type_: StreamType, -} - -impl Streams { - /// Return the input stream, initializing it on the fly if needed. - fn get_read_stream(&self) -> Result { - match &self.input.get() { - Some(wasi_stream) => Ok(*wasi_stream), - None => match &self.type_ { - // For files, we may have adjusted the position for seeking, so - // create a new stream. - StreamType::File(file) => { - let input = filesystem::read_via_stream(file.fd, file.position.get()); - self.input.set(Some(input)); - Ok(input) - } - _ => Err(ERRNO_BADF), - }, - } - } - - /// Return the output stream, initializing it on the fly if needed. - fn get_write_stream(&self) -> Result { - match &self.output.get() { - Some(wasi_stream) => Ok(*wasi_stream), - None => match &self.type_ { - // For files, we may have adjusted the position for seeking, so - // create a new stream. - StreamType::File(file) => { - let output = if file.append { - filesystem::append_via_stream(file.fd) - } else { - filesystem::write_via_stream(file.fd, file.position.get()) - }; - self.output.set(Some(output)); - Ok(output) - } - _ => Err(ERRNO_BADF), - }, - } - } -} - -#[allow(dead_code)] // until Socket is implemented -enum StreamType { - /// It's a valid stream but we don't know where it comes from. - Unknown, - - /// Streaming data with a file. - File(File), - - /// Streaming data with a socket connection. - Socket(tcp::TcpSocket), -} - -impl Drop for Descriptor { - fn drop(&mut self) { - match self { - Descriptor::Streams(stream) => { - if let Some(input) = stream.input.get() { - streams::drop_input_stream(input); - } - if let Some(output) = stream.output.get() { - streams::drop_output_stream(output); - } - match &stream.type_ { - StreamType::File(file) => filesystem::drop_descriptor(file.fd), - StreamType::Socket(_) => unreachable!(), - StreamType::Unknown => {} - } - } - Descriptor::Closed(_) => {} - } - } -} - -#[repr(C)] -struct File { +pub struct File { /// The handle to the preview2 descriptor that this file is referencing. fd: filesystem::Descriptor, @@ -2202,11 +2054,10 @@ struct State { /// Storage of mapping from preview1 file descriptors to preview2 file /// descriptors. - ndescriptors: Cell, - descriptors: UnsafeCell>, - - /// Points to the head of a free-list of closed file descriptors. - closed: Option, + /// + /// Do not use this member directly - use State::descriptors() to ensure + /// lazy initialization happens. + descriptors: Descriptors, /// Auxiliary storage to handle the `path_readlink` function. path_buf: UnsafeCell>, @@ -2226,10 +2077,6 @@ struct State { /// to take care of initialization. env_vars: Cell>, - /// Preopened directories. Initialized lazily. Access with `State::get_preopens` - /// to take care of initialization. - preopens: Cell>, - /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. dirent_cache: DirentCache, @@ -2283,18 +2130,6 @@ pub struct StrTupleList { len: usize, } -#[repr(C)] -pub struct Preopen { - descriptor: u32, - path: WasmStr, -} - -#[repr(C)] -pub struct PreopenList { - base: *const Preopen, - len: usize, -} - const fn bump_arena_size() -> usize { // The total size of the struct should be a page, so start there let mut start = PAGE_SIZE; @@ -2405,19 +2240,16 @@ impl State { unsafe { set_allocation_state(AllocationState::StateAllocated) }; - let ret = unsafe { + unsafe { ret.write(RefCell::new(State { magic1: MAGIC, magic2: MAGIC, import_alloc: ImportAlloc::new(), - closed: None, - ndescriptors: Cell::new(0), - descriptors: UnsafeCell::new(MaybeUninit::uninit()), + descriptors: Descriptors::new(), path_buf: UnsafeCell::new(MaybeUninit::uninit()), long_lived_arena: BumpArena::new(), args: None, env_vars: Cell::new(None), - preopens: Cell::new(None), dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), @@ -2435,135 +2267,25 @@ impl State { dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')], })); &*ret - }; - ret.borrow().init_empty_stdio(); - ret - } - - fn init_empty_stdio(&self) { - // Initialize the stdio descriptors as empty - self.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::Unknown, - })) - .trapping_unwrap(); - self.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::Unknown, - })) - .trapping_unwrap(); - self.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::Unknown, - })) - .trapping_unwrap(); - } - - fn push_desc(&self, desc: Descriptor) -> Result { - unsafe { - let descriptors = (*self.descriptors.get()).as_mut_ptr(); - let ndescriptors = usize::try_from(self.ndescriptors.get()).trapping_unwrap(); - if ndescriptors >= (*descriptors).len() { - return Err(ERRNO_NOMEM); - } - ptr::addr_of_mut!((*descriptors)[ndescriptors]).write(desc); - self.ndescriptors - .set(u16::try_from(ndescriptors + 1).trapping_unwrap()); - Ok(Fd::from(u32::try_from(ndescriptors).trapping_unwrap())) } } - fn descriptors(&self) -> &[Descriptor] { - unsafe { - slice::from_raw_parts( - (*self.descriptors.get()).as_ptr().cast(), - usize::try_from(self.ndescriptors.get()).trapping_unwrap(), - ) + /// Accessor for the descriptors member that ensures it is properly initialized + fn descriptors(&self) -> &Descriptors { + if !self.descriptors.is_initialized() { + self.descriptors + .init(&self.import_alloc, &self.long_lived_arena); } + &self.descriptors } - fn descriptors_mut(&mut self) -> &mut [Descriptor] { - unsafe { - slice::from_raw_parts_mut( - (*self.descriptors.get()).as_mut_ptr().cast(), - usize::try_from(self.ndescriptors.get()).trapping_unwrap(), - ) - } - } - - fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { - self.descriptors() - .get(usize::try_from(fd).trapping_unwrap()) - .ok_or(ERRNO_BADF) - } - - fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { - self.descriptors_mut() - .get_mut(usize::try_from(fd).trapping_unwrap()) - .ok_or(ERRNO_BADF) - } - - fn get_stream_with_error(&self, fd: Fd, error: Errno) -> Result<&Streams, Errno> { - match self.get(fd)? { - Descriptor::Streams(streams) => Ok(streams), - Descriptor::Closed(_) => Err(ERRNO_BADF), - } - } - - fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { - match self.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => Ok(file), - Descriptor::Closed(_) => Err(ERRNO_BADF), - _ => Err(error), - } - } - - #[allow(dead_code)] // until Socket is implemented - fn get_socket(&self, fd: Fd) -> Result { - match self.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::Socket(socket), - .. - }) => Ok(*socket), - Descriptor::Closed(_) => Err(ERRNO_BADF), - _ => Err(ERRNO_INVAL), - } - } - - fn get_file(&self, fd: Fd) -> Result<&File, Errno> { - self.get_file_with_error(fd, ERRNO_INVAL) - } - - fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { - self.get_file_with_error(fd, ERRNO_NOTDIR) - } - - fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { - self.get_file_with_error(fd, ERRNO_SPIPE) - } - - fn get_seekable_stream(&self, fd: Fd) -> Result<&Streams, Errno> { - self.get_stream_with_error(fd, ERRNO_SPIPE) - } - - fn get_read_stream(&self, fd: Fd) -> Result { - match self.get(fd)? { - Descriptor::Streams(streams) => streams.get_read_stream(), - Descriptor::Closed(_) => Err(ERRNO_BADF), - } - } - - fn get_write_stream(&self, fd: Fd) -> Result { - match self.get(fd)? { - Descriptor::Streams(streams) => streams.get_write_stream(), - Descriptor::Closed(_) => Err(ERRNO_BADF), + /// Mut accessor for the descriptors member that ensures it is properly initialized + fn descriptors_mut(&mut self) -> &mut Descriptors { + if !self.descriptors.is_initialized() { + self.descriptors + .init(&self.import_alloc, &self.long_lived_arena); } + &mut self.descriptors } /// Return a handle to the default wall clock, creating one if we @@ -2619,54 +2341,4 @@ impl State { } self.env_vars.get().trapping_unwrap() } - - fn get_preopens(&self) -> &[Preopen] { - // Lazily initialize `preopens`. - if self.preopens.get().is_none() { - #[link(wasm_import_module = "preopens")] - extern "C" { - #[link_name = "get-directories"] - fn get_preopens_import(rval: *mut PreopenList); - } - let mut list = PreopenList { - base: std::ptr::null(), - len: 0, - }; - self.import_alloc - .with_arena(&self.long_lived_arena, || unsafe { - get_preopens_import(&mut list as *mut _) - }); - let preopens: &'static [Preopen] = unsafe { - // allocation comes from long lived arena, so it is safe to - // cast this to a &'static slice: - std::slice::from_raw_parts(list.base, list.len) - }; - for preopen in preopens { - // Expectation is that the descriptor index is initialized with - // stdio (0,1,2) and no others, so that preopens are 3.. - self.push_desc(Descriptor::Streams(Streams { - input: Cell::new(None), - output: Cell::new(None), - type_: StreamType::File(File { - fd: preopen.descriptor, - position: Cell::new(0), - append: false, - }), - })) - .trapping_unwrap(); - } - - self.preopens.set(Some(preopens)); - } - - self.preopens.get().trapping_unwrap() - } - - fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { - // Lazily initialize the preopens and obtain the slice - let preopens = self.get_preopens(); - // Subtract 3 for the stdio indices to compute the preopen index. - let index = fd.checked_sub(3)? as usize; - preopens.get(index) - } } From e199109a0bfa90845f99b44659b9b59a37903dbc Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 22 Mar 2023 17:05:42 -0700 Subject: [PATCH 103/153] fix macros --- src/lib.rs | 6 +++--- src/macros.rs | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 881235ca0851..4fd7eced6f18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ use crate::bindings::{ exit, filesystem, instance_monotonic_clock, instance_wall_clock, monotonic_clock, network, poll, random, streams, wall_clock, }; -use core::arch::wasm32; use core::cell::{Cell, RefCell, UnsafeCell}; use core::cmp::min; use core::ffi::c_void; @@ -18,11 +17,12 @@ use wasi::*; #[cfg(all(feature = "command", feature = "reactor"))] compile_error!("only one of the `command` and `reactor` features may be selected at a time"); -mod descriptors; -use crate::descriptors::{Descriptor, Descriptors, StreamType, Streams}; #[macro_use] mod macros; +mod descriptors; +use crate::descriptors::{Descriptor, Descriptors, StreamType, Streams}; + pub mod bindings { #[cfg(feature = "command")] wit_bindgen::generate!({ diff --git a/src/macros.rs b/src/macros.rs index ad99834cd9ef..770968ee411d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -53,7 +53,11 @@ macro_rules! unreachable { eprint!("unreachable executed at adapter line "); crate::macros::eprint_u32(line!()); eprint!("\n"); - wasm32::unreachable() + #[cfg(target_arch = "wasm32")] + core::arch::wasm32::unreachable(); + // This is here to keep rust-analyzer happy when building for native: + #[cfg(not(target_arch = "wasm32"))] + std::process::abort(); }}; ($arg:tt) => {{ @@ -62,7 +66,11 @@ macro_rules! unreachable { eprint!(": "); eprintln!($arg); eprint!("\n"); - wasm32::unreachable() + #[cfg(target_arch = "wasm32")] + core::arch::wasm32::unreachable(); + // This is here to keep rust-analyzer happy when building for native: + #[cfg(not(target_arch = "wasm32"))] + std::process::abort(); }}; } From 0be419f46f7cbb4b073ba6b8d41492a1b6b0ec7f Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 22 Mar 2023 19:19:11 -0700 Subject: [PATCH 104/153] fix --- src/descriptors.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index cebb5994ef2d..715308f39ef4 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -283,6 +283,9 @@ impl Descriptors { // Close an fd. pub fn close(&mut self, fd: Fd) -> Result<(), Errno> { + if !self.is_initialized() { + unreachable!("bug: descriptors should be initialized") + } drop(self.close_(fd)?); Ok(()) } @@ -295,7 +298,7 @@ impl Descriptors { Ok(()) } - // + // Implementation of fd_renumber pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> { // First, ensure from_fd is in bounds: drop(self.get(from_fd)?); From a9ddff85f0fbf297d3dbcb0d10e01a8ea02506a8 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 22 Mar 2023 19:36:05 -0700 Subject: [PATCH 105/153] renumber: allow overwriting. note it may invalidate preopen table --- src/descriptors.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index 715308f39ef4..2937d5acd754 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -302,16 +302,13 @@ impl Descriptors { pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> { // First, ensure from_fd is in bounds: drop(self.get(from_fd)?); - // Then, make sure to_fd is not already open: - if self.get(to_fd).is_ok() { - return Err(wasi::ERRNO_INVAL); - } // Expand table until to_fd is in bounds as well: while self.table_len.get() as u32 <= to_fd as u32 { self.push_closed()?; } // Then, close from_fd and put its contents into to_fd: let desc = self.close_(from_fd)?; + // TODO FIXME if this overwrites a preopen, do we need to clear it from the preopen table? *self.get_mut(to_fd)? = desc; Ok(()) From c5ca3f1eb30051316c06343de28742ad5f26416b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 22 Mar 2023 19:36:29 -0700 Subject: [PATCH 106/153] poll_oneoff: use import allocator solely on the import func call this is required because calling `state.descriptors()` could end up initializing the descriptor table, which also uses the import allocator. this is a huge whitespace change, but effectively its just a much smaller body in the closure. --- src/lib.rs | 440 ++++++++++++++++++++++++++--------------------------- 1 file changed, 216 insertions(+), 224 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4fd7eced6f18..43579cb39f44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1548,255 +1548,247 @@ pub unsafe extern "C" fn poll_oneoff( } State::with(|state| { - state.import_alloc.with_buffer( - results, - nsubscriptions - .checked_mul(size_of::()) - .trapping_unwrap(), - || { - let mut pollables = Pollables { - pointer: pollables, - index: 0, - length: nsubscriptions, - }; - - for subscription in subscriptions { - const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); - const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); - const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); - pollables.push(match subscription.u.tag { - EVENTTYPE_CLOCK => { - let clock = &subscription.u.u.clock; - let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) - == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME; - match clock.id { - CLOCKID_REALTIME => { - let timeout = if absolute { - // Convert `clock.timeout` to `Datetime`. - let mut datetime = wall_clock::Datetime { - seconds: clock.timeout / 1_000_000_000, - nanoseconds: (clock.timeout % 1_000_000_000) as _, - }; - - // Subtract `now`. - let now = wall_clock::now(state.instance_wall_clock()); - datetime.seconds -= now.seconds; - if datetime.nanoseconds < now.nanoseconds { - datetime.seconds -= 1; - datetime.nanoseconds += 1_000_000_000; - } - datetime.nanoseconds -= now.nanoseconds; - - // Convert to nanoseconds. - let nanos = datetime - .seconds - .checked_mul(1_000_000_000) - .ok_or(ERRNO_OVERFLOW)?; - nanos - .checked_add(datetime.nanoseconds.into()) - .ok_or(ERRNO_OVERFLOW)? - } else { - clock.timeout - }; + let mut pollables = Pollables { + pointer: pollables, + index: 0, + length: nsubscriptions, + }; - monotonic_clock::subscribe( - state.instance_monotonic_clock(), - timeout, - false, - ) + for subscription in subscriptions { + const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); + const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); + const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); + pollables.push(match subscription.u.tag { + EVENTTYPE_CLOCK => { + let clock = &subscription.u.u.clock; + let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) + == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME; + match clock.id { + CLOCKID_REALTIME => { + let timeout = if absolute { + // Convert `clock.timeout` to `Datetime`. + let mut datetime = wall_clock::Datetime { + seconds: clock.timeout / 1_000_000_000, + nanoseconds: (clock.timeout % 1_000_000_000) as _, + }; + + // Subtract `now`. + let now = wall_clock::now(state.instance_wall_clock()); + datetime.seconds -= now.seconds; + if datetime.nanoseconds < now.nanoseconds { + datetime.seconds -= 1; + datetime.nanoseconds += 1_000_000_000; } + datetime.nanoseconds -= now.nanoseconds; + + // Convert to nanoseconds. + let nanos = datetime + .seconds + .checked_mul(1_000_000_000) + .ok_or(ERRNO_OVERFLOW)?; + nanos + .checked_add(datetime.nanoseconds.into()) + .ok_or(ERRNO_OVERFLOW)? + } else { + clock.timeout + }; + + monotonic_clock::subscribe( + state.instance_monotonic_clock(), + timeout, + false, + ) + } - CLOCKID_MONOTONIC => monotonic_clock::subscribe( - state.instance_monotonic_clock(), - clock.timeout, - absolute, - ), + CLOCKID_MONOTONIC => monotonic_clock::subscribe( + state.instance_monotonic_clock(), + clock.timeout, + absolute, + ), - _ => return Err(ERRNO_INVAL), - } - } + _ => return Err(ERRNO_INVAL), + } + } - EVENTTYPE_FD_READ => { - match state - .descriptors() - .get_read_stream(subscription.u.u.fd_read.file_descriptor) - { - Ok(stream) => streams::subscribe_to_input_stream(stream), - // If the file descriptor isn't a stream, request a - // pollable which completes immediately so that it'll - // immediately fail. - Err(ERRNO_BADF) => monotonic_clock::subscribe( - state.instance_monotonic_clock(), - 0, - false, - ), - Err(e) => return Err(e), - } + EVENTTYPE_FD_READ => { + match state + .descriptors() + .get_read_stream(subscription.u.u.fd_read.file_descriptor) + { + Ok(stream) => streams::subscribe_to_input_stream(stream), + // If the file descriptor isn't a stream, request a + // pollable which completes immediately so that it'll + // immediately fail. + Err(ERRNO_BADF) => { + monotonic_clock::subscribe(state.instance_monotonic_clock(), 0, false) } + Err(e) => return Err(e), + } + } - EVENTTYPE_FD_WRITE => { - match state - .descriptors() - .get_write_stream(subscription.u.u.fd_write.file_descriptor) - { - Ok(stream) => streams::subscribe_to_output_stream(stream), - // If the file descriptor isn't a stream, request a - // pollable which completes immediately so that it'll - // immediately fail. - Err(ERRNO_BADF) => monotonic_clock::subscribe( - state.instance_monotonic_clock(), - 0, - false, - ), - Err(e) => return Err(e), - } + EVENTTYPE_FD_WRITE => { + match state + .descriptors() + .get_write_stream(subscription.u.u.fd_write.file_descriptor) + { + Ok(stream) => streams::subscribe_to_output_stream(stream), + // If the file descriptor isn't a stream, request a + // pollable which completes immediately so that it'll + // immediately fail. + Err(ERRNO_BADF) => { + monotonic_clock::subscribe(state.instance_monotonic_clock(), 0, false) } - - _ => return Err(ERRNO_INVAL), - }); + Err(e) => return Err(e), + } } - let vec = - poll::poll_oneoff(slice::from_raw_parts(pollables.pointer, pollables.length)); + _ => return Err(ERRNO_INVAL), + }); + } + let vec = state.import_alloc.with_buffer( + results, + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap(), + || poll::poll_oneoff(slice::from_raw_parts(pollables.pointer, pollables.length)), + ); - assert_eq!(vec.len(), nsubscriptions); - assert_eq!(vec.as_ptr(), results); - forget(vec); + assert_eq!(vec.len(), nsubscriptions); + assert_eq!(vec.as_ptr(), results); + forget(vec); - drop(pollables); + drop(pollables); - let ready = subscriptions - .iter() - .enumerate() - .filter_map(|(i, s)| (*results.add(i) != 0).then_some(s)); + let ready = subscriptions + .iter() + .enumerate() + .filter_map(|(i, s)| (*results.add(i) != 0).then_some(s)); - let mut count = 0; + let mut count = 0; - for subscription in ready { - let error; - let type_; - let nbytes; - let flags; + for subscription in ready { + let error; + let type_; + let nbytes; + let flags; - match subscription.u.tag { - 0 => { - error = ERRNO_SUCCESS; - type_ = EVENTTYPE_CLOCK; - nbytes = 0; - flags = 0; - } + match subscription.u.tag { + 0 => { + error = ERRNO_SUCCESS; + type_ = EVENTTYPE_CLOCK; + nbytes = 0; + flags = 0; + } - 1 => { - type_ = EVENTTYPE_FD_READ; - let desc = state - .descriptors() - .get(subscription.u.u.fd_read.file_descriptor) - .trapping_unwrap(); - match desc { - Descriptor::Streams(streams) => match &streams.type_ { - StreamType::File(file) => match filesystem::stat(file.fd) { - Ok(stat) => { - error = ERRNO_SUCCESS; - nbytes = stat.size.saturating_sub(file.position.get()); - flags = if nbytes == 0 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 1; - flags = 0; - } - }, - StreamType::Socket(connection) => { - unreachable!() // TODO - /* - match tcp::bytes_readable(*connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - } - */ - } - StreamType::Unknown => { - error = ERRNO_SUCCESS; - nbytes = 1; - flags = 0; - } - }, - _ => unreachable!(), + 1 => { + type_ = EVENTTYPE_FD_READ; + let desc = state + .descriptors() + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::File(file) => match filesystem::stat(file.fd) { + Ok(stat) => { + error = ERRNO_SUCCESS; + nbytes = stat.size.saturating_sub(file.position.get()); + flags = if nbytes == 0 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 1; + flags = 0; + } + }, + StreamType::Socket(connection) => { + unreachable!() // TODO + /* + match tcp::bytes_readable(*connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } + */ } - } - 2 => { - type_ = EVENTTYPE_FD_WRITE; - let desc = state - .descriptors() - .get(subscription.u.u.fd_read.file_descriptor) - .trapping_unwrap(); - match desc { - Descriptor::Streams(streams) => match streams.type_ { - StreamType::File(_) | StreamType::Unknown => { - error = ERRNO_SUCCESS; - nbytes = 1; - flags = 0; - } - StreamType::Socket(connection) => { - unreachable!() // TODO - /* - match tcp::bytes_writable(connection) { - Ok(result) => { - error = ERRNO_SUCCESS; - nbytes = result.0; - flags = if result.1 { - EVENTRWFLAGS_FD_READWRITE_HANGUP - } else { - 0 - }; - } - Err(e) => { - error = e.into(); - nbytes = 0; - flags = 0; - } - } - */ - } - }, - _ => unreachable!(), + StreamType::Unknown => { + error = ERRNO_SUCCESS; + nbytes = 1; + flags = 0; } - } - + }, + _ => unreachable!(), + } + } + 2 => { + type_ = EVENTTYPE_FD_WRITE; + let desc = state + .descriptors() + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match streams.type_ { + StreamType::File(_) | StreamType::Unknown => { + error = ERRNO_SUCCESS; + nbytes = 1; + flags = 0; + } + StreamType::Socket(connection) => { + unreachable!() // TODO + /* + match tcp::bytes_writable(connection) { + Ok(result) => { + error = ERRNO_SUCCESS; + nbytes = result.0; + flags = if result.1 { + EVENTRWFLAGS_FD_READWRITE_HANGUP + } else { + 0 + }; + } + Err(e) => { + error = e.into(); + nbytes = 0; + flags = 0; + } + } + */ + } + }, _ => unreachable!(), } + } - *out.add(count) = Event { - userdata: subscription.userdata, - error, - type_, - fd_readwrite: EventFdReadwrite { nbytes, flags }, - }; + _ => unreachable!(), + } - count += 1; - } + *out.add(count) = Event { + userdata: subscription.userdata, + error, + type_, + fd_readwrite: EventFdReadwrite { nbytes, flags }, + }; - *nevents = count; + count += 1; + } - Ok(()) - }, - ) + *nevents = count; + + Ok(()) }) } From 378537c5a601c29eac132294fa53cff84dfa7d33 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 23 Mar 2023 14:48:04 -0700 Subject: [PATCH 107/153] Update build.rs Co-authored-by: Joel Dice --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 26ed1b1eb87d..fcd7b7c6dac9 100644 --- a/build.rs +++ b/build.rs @@ -230,7 +230,7 @@ fn build_raw_intrinsics() -> Vec { { let mut reloc = Vec::new(); 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) - 6u32.encode(&mut reloc); // 4 relocations + 6u32.encode(&mut reloc); // 6 relocations reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB internal_state_ptr_ref1.encode(&mut reloc); // offset From fdd91a99dafdb4ee3d42ee9b92a647734853041a Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 23 Mar 2023 15:17:00 -0700 Subject: [PATCH 108/153] Descriptors is initialized on creation, use refcell> to represent uninit --- src/descriptors.rs | 37 +++--------- src/lib.rs | 147 +++++++++++++++++++++++++++------------------ 2 files changed, 98 insertions(+), 86 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index 2937d5acd754..288a2099219d 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -120,38 +120,30 @@ pub struct Descriptors { } impl Descriptors { - pub fn new() -> Self { - Descriptors { + pub fn new(import_alloc: &ImportAlloc, arena: &BumpArena) -> Self { + let d = Descriptors { table: UnsafeCell::new(MaybeUninit::uninit()), table_len: Cell::new(0), closed: None, preopens: Cell::new(None), - } - } - pub fn is_initialized(&self) -> bool { - self.table_len.get() != 0 - } - pub fn init(&self, import_alloc: &ImportAlloc, arena: &BumpArena) { - if self.is_initialized() { - unreachable!("cannot initialize descriptors more than once") - } + }; let stdio = crate::bindings::preopens::get_stdio(); unsafe { set_stderr_stream(stdio.stderr) }; - self.push(Descriptor::Streams(Streams { + d.push(Descriptor::Streams(Streams { input: Cell::new(Some(stdio.stdin)), output: Cell::new(None), type_: StreamType::Unknown, })) .trapping_unwrap(); - self.push(Descriptor::Streams(Streams { + d.push(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(Some(stdio.stdout)), type_: StreamType::Unknown, })) .trapping_unwrap(); - self.push(Descriptor::Streams(Streams { + d.push(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(Some(stdio.stderr)), type_: StreamType::Unknown, @@ -178,7 +170,7 @@ impl Descriptors { for preopen in preopens { // Expectation is that the descriptor index is initialized with // stdio (0,1,2) and no others, so that preopens are 3.. - self.push(Descriptor::Streams(Streams { + d.push(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), type_: StreamType::File(File { @@ -190,7 +182,8 @@ impl Descriptors { .trapping_unwrap(); } - self.preopens.set(Some(preopens)); + d.preopens.set(Some(preopens)); + d } fn push(&self, desc: Descriptor) -> Result { @@ -245,27 +238,18 @@ impl Descriptors { } pub fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { - if !self.is_initialized() { - unreachable!("bug: descriptors should be initialized") - } self.table() .get(usize::try_from(fd).trapping_unwrap()) .ok_or(wasi::ERRNO_BADF) } pub fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { - if !self.is_initialized() { - unreachable!("bug: descriptors should be initialized") - } self.table_mut() .get_mut(usize::try_from(fd).trapping_unwrap()) .ok_or(wasi::ERRNO_BADF) } pub fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { - if !self.is_initialized() { - unreachable!("bug: descriptors should be initialized") - } let preopens = self.preopens.get().trapping_unwrap(); // Subtract 3 for the stdio indices to compute the preopen index. let index = fd.checked_sub(3)? as usize; @@ -283,9 +267,6 @@ impl Descriptors { // Close an fd. pub fn close(&mut self, fd: Fd) -> Result<(), Errno> { - if !self.is_initialized() { - unreachable!("bug: descriptors should be initialized") - } drop(self.close_(fd)?); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 43579cb39f44..e36f27e909b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,12 @@ use crate::bindings::{ exit, filesystem, instance_monotonic_clock, instance_wall_clock, monotonic_clock, network, poll, random, streams, wall_clock, }; -use core::cell::{Cell, RefCell, UnsafeCell}; +use core::cell::{Cell, RefCell, RefMut, UnsafeCell}; use core::cmp::min; use core::ffi::c_void; use core::hint::black_box; use core::mem::{self, align_of, forget, size_of, ManuallyDrop, MaybeUninit}; +use core::ops::{Deref, DerefMut}; use core::ptr::{self, null_mut}; use core::slice; use poll::Pollable; @@ -419,7 +420,8 @@ pub unsafe extern "C" fn fd_advise( _ => return ERRNO_INVAL, }; State::with(|state| { - let file = state.descriptors().get_seekable_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_seekable_file(fd)?; filesystem::advise(file.fd, offset, len, advice)?; Ok(()) }) @@ -454,7 +456,8 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { State::with(|state| { - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; filesystem::sync_data(file.fd)?; Ok(()) }) @@ -558,7 +561,8 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { } State::with(|state| { - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; filesystem::set_flags(file.fd, new_flags)?; Ok(()) }) @@ -579,7 +583,8 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( #[no_mangle] pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; let stat = filesystem::stat(file.fd)?; let filetype = stat.type_.into(); *buf = Filestat { @@ -601,7 +606,8 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { State::with(|state| { - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; filesystem::set_size(file.fd, size)?; Ok(()) }) @@ -640,7 +646,8 @@ pub unsafe extern "C" fn fd_filestat_set_times( }; State::with(|state| { - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; filesystem::set_times(file.fd, atim, mtim)?; Ok(()) }) @@ -670,7 +677,8 @@ pub unsafe extern "C" fn fd_pread( let ptr = (*iovs_ptr).buf; let len = (*iovs_ptr).buf_len; - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; let (data, end) = state .import_alloc .with_buffer(ptr, len, || filesystem::read(file.fd, len as u64, offset))?; @@ -757,7 +765,8 @@ pub unsafe extern "C" fn fd_pwrite( let len = (*iovs_ptr).buf_len; State::with(|state| { - let file = state.descriptors().get_seekable_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_seekable_file(fd)?; let bytes = filesystem::write(file.fd, slice::from_raw_parts(ptr, len), offset)?; *nwritten = bytes as usize; Ok(()) @@ -861,7 +870,8 @@ pub unsafe extern "C" fn fd_readdir( // Compute the inode of `.` so that the iterator can produce an entry // for it. - let dir = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let dir = ds.get_dir(fd)?; let stat = filesystem::stat(dir.fd)?; let dot_inode = stat.inode; @@ -1072,7 +1082,8 @@ pub unsafe extern "C" fn fd_seek( newoffset: *mut Filesize, ) -> Errno { State::with(|state| { - let stream = state.descriptors().get_seekable_stream(fd)?; + let ds = state.descriptors(); + let stream = ds.get_seekable_stream(fd)?; // Seeking only works on files. if let StreamType::File(file) = &stream.type_ { @@ -1100,7 +1111,8 @@ pub unsafe extern "C" fn fd_seek( #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { State::with(|state| { - let file = state.descriptors().get_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; filesystem::sync(file.fd)?; Ok(()) }) @@ -1111,7 +1123,8 @@ pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { State::with(|state| { - let file = state.descriptors().get_seekable_file(fd)?; + let ds = state.descriptors(); + let file = ds.get_seekable_file(fd)?; *offset = file.position.get() as Filesize; Ok(()) }) @@ -1144,26 +1157,29 @@ pub unsafe extern "C" fn fd_write( let len = (*iovs_ptr).buf_len; let bytes = slice::from_raw_parts(ptr, len); - State::with(|state| match state.descriptors().get(fd)? { - Descriptor::Streams(streams) => { - let wasi_stream = streams.get_write_stream()?; - let bytes = streams::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; - - // If this is a file, keep the current-position pointer up to date. - if let StreamType::File(file) = &streams.type_ { - // But don't update if we're in append mode. Strictly speaking, - // we should set the position to the new end of the file, but - // we don't have an API to do that atomically. - if !file.append { - file.position - .set(file.position.get() + filesystem::Filesize::from(bytes)); + State::with(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_write_stream()?; + let bytes = streams::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; + + // If this is a file, keep the current-position pointer up to date. + if let StreamType::File(file) = &streams.type_ { + // But don't update if we're in append mode. Strictly speaking, + // we should set the position to the new end of the file, but + // we don't have an API to do that atomically. + if !file.append { + file.position + .set(file.position.get() + filesystem::Filesize::from(bytes)); + } } - } - *nwritten = bytes as usize; - Ok(()) + *nwritten = bytes as usize; + Ok(()) + } + Descriptor::Closed(_) => Err(ERRNO_BADF), } - Descriptor::Closed(_) => Err(ERRNO_BADF), }) } else { *nwritten = 0; @@ -1182,7 +1198,8 @@ pub unsafe extern "C" fn path_create_directory( let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; filesystem::create_directory_at(file.fd, path)?; Ok(()) }) @@ -1202,7 +1219,8 @@ pub unsafe extern "C" fn path_filestat_get( let at_flags = at_flags_from_lookupflags(flags); State::with(|state| { - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; let stat = filesystem::stat_at(file.fd, at_flags, path)?; let filetype = stat.type_.into(); *buf = Filestat { @@ -1258,7 +1276,8 @@ pub unsafe extern "C" fn path_filestat_set_times( let at_flags = at_flags_from_lookupflags(flags); State::with(|state| { - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; Ok(()) }) @@ -1317,7 +1336,8 @@ pub unsafe extern "C" fn path_open( let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; State::with_mut(|state| { - let file = state.descriptors().get_dir(fd)?; + let mut ds = state.descriptors_mut(); + let file = ds.get_dir(fd)?; let result = filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; let desc = Descriptor::Streams(Streams { input: Cell::new(None), @@ -1329,7 +1349,7 @@ pub unsafe extern "C" fn path_open( }), }); - let fd = state.descriptors_mut().open(desc)?; + let fd = ds.open(desc)?; *opened_fd = fd; Ok(()) }) @@ -1354,7 +1374,8 @@ pub unsafe extern "C" fn path_readlink( // so instead we handle this case specially. let use_state_buf = buf_len < PATH_MAX; - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; let path = if use_state_buf { state .import_alloc @@ -1398,7 +1419,8 @@ pub unsafe extern "C" fn path_remove_directory( let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; filesystem::remove_directory_at(file.fd, path)?; Ok(()) }) @@ -1419,8 +1441,9 @@ pub unsafe extern "C" fn path_rename( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); State::with(|state| { - let old = state.descriptors().get_dir(old_fd)?.fd; - let new = state.descriptors().get_dir(new_fd)?.fd; + let ds = state.descriptors(); + let old = ds.get_dir(old_fd)?.fd; + let new = ds.get_dir(new_fd)?.fd; filesystem::rename_at(old, old_path, new, new_path)?; Ok(()) }) @@ -1440,7 +1463,8 @@ pub unsafe extern "C" fn path_symlink( let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); State::with(|state| { - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; filesystem::symlink_at(file.fd, old_path, new_path)?; Ok(()) }) @@ -1454,7 +1478,8 @@ pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: let path = slice::from_raw_parts(path_ptr, path_len); State::with(|state| { - let file = state.descriptors().get_dir(fd)?; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; filesystem::unlink_file_at(file.fd, path)?; Ok(()) }) @@ -1682,8 +1707,8 @@ pub unsafe extern "C" fn poll_oneoff( 1 => { type_ = EVENTTYPE_FD_READ; - let desc = state - .descriptors() + let ds = state.descriptors(); + let desc = ds .get(subscription.u.u.fd_read.file_descriptor) .trapping_unwrap(); match desc { @@ -1736,8 +1761,8 @@ pub unsafe extern "C" fn poll_oneoff( } 2 => { type_ = EVENTTYPE_FD_WRITE; - let desc = state - .descriptors() + let ds = state.descriptors(); + let desc = ds .get(subscription.u.u.fd_read.file_descriptor) .trapping_unwrap(); match desc { @@ -2049,7 +2074,7 @@ struct State { /// /// Do not use this member directly - use State::descriptors() to ensure /// lazy initialization happens. - descriptors: Descriptors, + descriptors: RefCell>, /// Auxiliary storage to handle the `path_readlink` function. path_buf: UnsafeCell>, @@ -2133,7 +2158,7 @@ const fn bump_arena_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 24 * size_of::(); + start -= 25 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2237,7 +2262,7 @@ impl State { magic1: MAGIC, magic2: MAGIC, import_alloc: ImportAlloc::new(), - descriptors: Descriptors::new(), + descriptors: RefCell::new(None), path_buf: UnsafeCell::new(MaybeUninit::uninit()), long_lived_arena: BumpArena::new(), args: None, @@ -2263,21 +2288,27 @@ impl State { } /// Accessor for the descriptors member that ensures it is properly initialized - fn descriptors(&self) -> &Descriptors { - if !self.descriptors.is_initialized() { - self.descriptors - .init(&self.import_alloc, &self.long_lived_arena); + fn descriptors<'a>(&'a self) -> impl Deref + 'a { + let mut d = self + .descriptors + .try_borrow_mut() + .unwrap_or_else(|_| unreachable!()); + if d.is_none() { + *d = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); } - &self.descriptors + RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) } /// Mut accessor for the descriptors member that ensures it is properly initialized - fn descriptors_mut(&mut self) -> &mut Descriptors { - if !self.descriptors.is_initialized() { - self.descriptors - .init(&self.import_alloc, &self.long_lived_arena); + fn descriptors_mut<'a>(&'a mut self) -> impl DerefMut + Deref + 'a { + let mut d = self + .descriptors + .try_borrow_mut() + .unwrap_or_else(|_| unreachable!()); + if d.is_none() { + *d = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); } - &mut self.descriptors + RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) } /// Return a handle to the default wall clock, creating one if we From f336265782d144485239504a0abb5309db7208c3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 24 Mar 2023 11:39:30 -0700 Subject: [PATCH 109/153] adapter: arguments are lazily initialized in State instead of passed to main --- src/lib.rs | 89 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e36f27e909b9..4ca3a2d69dbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,12 +46,7 @@ pub mod bindings { #[no_mangle] #[cfg(feature = "command")] -pub unsafe extern "C" fn main(args_ptr: *const WasmStr, args_len: usize) -> u32 { - State::with_mut(|state| { - state.args = Some(slice::from_raw_parts(args_ptr, args_len)); - Ok(()) - }); - +pub unsafe extern "C" fn main() -> u32 { #[link(wasm_import_module = "__main_module__")] extern "C" { fn _start(); @@ -244,21 +239,19 @@ pub unsafe extern "C" fn cabi_export_realloc( #[no_mangle] pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) -> Errno { State::with(|state| { - if let Some(args) = state.args { - for arg in args { - // Copy the argument into `argv_buf` which must be sized - // appropriately by the caller. - ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len); - *argv_buf.add(arg.len) = 0; - - // Copy the argument pointer into the `argv` buf - *argv = argv_buf; - - // Update our pointers past what's written to prepare for the - // next argument. - argv = argv.add(1); - argv_buf = argv_buf.add(arg.len + 1); - } + for arg in state.get_args() { + // Copy the argument into `argv_buf` which must be sized + // appropriately by the caller. + ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len); + *argv_buf.add(arg.len) = 0; + + // Copy the argument pointer into the `argv` buf + *argv = argv_buf; + + // Update our pointers past what's written to prepare for the + // next argument. + argv = argv.add(1); + argv_buf = argv_buf.add(arg.len + 1); } Ok(()) }) @@ -268,18 +261,11 @@ pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) #[no_mangle] pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { State::with(|state| { - match state.args { - Some(args) => { - *argc = args.len(); - // Add one to each length for the terminating nul byte added by - // the `args_get` function. - *argv_buf_size = args.iter().map(|s| s.len + 1).sum(); - } - None => { - *argc = 0; - *argv_buf_size = 0; - } - } + let args = state.get_args(); + *argc = args.len(); + // Add one to each length for the terminating nul byte added by + // the `args_get` function. + *argv_buf_size = args.iter().map(|s| s.len + 1).sum(); Ok(()) }) } @@ -2087,8 +2073,9 @@ struct State { /// which need to be long-lived, by using `import_alloc.with_arena`. long_lived_arena: BumpArena, - /// Arguments passed to the `main` entrypoint - args: Option<&'static [WasmStr]>, + /// Arguments. Initialized lazily. Access with `State::get_args` to take care of + /// initialization. + args: Cell>, /// Environment variables. Initialized lazily. Access with `State::get_environment` /// to take care of initialization. @@ -2134,6 +2121,12 @@ pub struct WasmStr { len: usize, } +#[repr(C)] +pub struct WasmStrList { + base: *const WasmStr, + len: usize, +} + #[repr(C)] pub struct StrTuple { key: WasmStr, @@ -2265,7 +2258,7 @@ impl State { descriptors: RefCell::new(None), path_buf: UnsafeCell::new(MaybeUninit::uninit()), long_lived_arena: BumpArena::new(), - args: None, + args: Cell::new(None), env_vars: Cell::new(None), dirent_cache: DirentCache { stream: Cell::new(None), @@ -2364,4 +2357,28 @@ impl State { } self.env_vars.get().trapping_unwrap() } + + fn get_args(&self) -> &[WasmStr] { + if self.args.get().is_none() { + #[link(wasm_import_module = "environment")] + extern "C" { + #[link_name = "get-arguments"] + fn get_args_import(rval: *mut WasmStrList); + } + let mut list = WasmStrList { + base: std::ptr::null(), + len: 0, + }; + self.import_alloc + .with_arena(&self.long_lived_arena, || unsafe { + get_args_import(&mut list as *mut _) + }); + self.args.set(Some(unsafe { + /* allocation comes from long lived arena, so it is safe to + * cast this to a &'static slice: */ + std::slice::from_raw_parts(list.base, list.len) + })); + } + self.args.get().trapping_unwrap() + } } From 566da91239cf2df19626bce28e4a5d65e7b654c4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 24 Mar 2023 12:28:54 -0700 Subject: [PATCH 110/153] rename command's `main` to `run` (unfortunately) this is an unfortunate problem with building the adapter: for some reason, wasm-ld will modify an export named `main` with no parameters by renaming it __original_main, and exporting a `main` with two i32 parameters. we should see if this behavior is fixable, this is hopefully a temporary change because I really liked that commands could just be invoked by calling `main`... --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4ca3a2d69dbe..afd7bddc2fa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ pub mod bindings { raw_strings, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["main", "get-directories", "get-environment"], + skip: ["run", "get-directories", "get-environment"], }); #[cfg(feature = "reactor")] @@ -46,7 +46,7 @@ pub mod bindings { #[no_mangle] #[cfg(feature = "command")] -pub unsafe extern "C" fn main() -> u32 { +pub unsafe extern "C" fn run() -> u32 { #[link(wasm_import_module = "__main_module__")] extern "C" { fn _start(); From e5b6452f81b400827330f15d12b0f65c8cb0d324 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 4 Apr 2023 15:40:39 -0700 Subject: [PATCH 111/153] Remove resource abstraction from clocks (#129) * wit: remove resource index from monotonic-clocks and wall-clocks, delete instance-*-clock interfaces At this time, we don't have use cases for providing multiple wall clocks or multiple monotonic clocks to the same component. So, according to the principle of following preview 1's design as close as possible, we are changing these interfaces to being provided by ambient functions, rather than methods on a resource. * adapter: unresourcify clocks * host: de-resourcify clocks --- src/lib.rs | 73 ++++++++---------------------------------------------- 1 file changed, 11 insertions(+), 62 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index afd7bddc2fa9..7212f5b88cb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ #![allow(unused_variables)] // TODO: remove this when more things are implemented use crate::bindings::{ - exit, filesystem, instance_monotonic_clock, instance_wall_clock, monotonic_clock, network, - poll, random, streams, wall_clock, + exit, filesystem, monotonic_clock, network, poll, random, streams, wall_clock, }; use core::cell::{Cell, RefCell, RefMut, UnsafeCell}; use core::cmp::min; @@ -337,11 +336,11 @@ pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errn State::with(|state| { match id { CLOCKID_MONOTONIC => { - let res = monotonic_clock::resolution(state.instance_monotonic_clock()); + let res = monotonic_clock::resolution(); *resolution = res; } CLOCKID_REALTIME => { - let res = wall_clock::resolution(state.instance_wall_clock()); + let res = wall_clock::resolution(); *resolution = Timestamp::from(res.seconds) .checked_mul(1_000_000_000) .and_then(|ns| ns.checked_add(res.nanoseconds.into())) @@ -368,10 +367,10 @@ pub unsafe extern "C" fn clock_time_get( State::with(|state| { match id { CLOCKID_MONOTONIC => { - *time = monotonic_clock::now(state.instance_monotonic_clock()); + *time = monotonic_clock::now(); } CLOCKID_REALTIME => { - let res = wall_clock::now(state.instance_wall_clock()); + let res = wall_clock::now(); *time = Timestamp::from(res.seconds) .checked_mul(1_000_000_000) .and_then(|ns| ns.checked_add(res.nanoseconds.into())) @@ -1584,7 +1583,7 @@ pub unsafe extern "C" fn poll_oneoff( }; // Subtract `now`. - let now = wall_clock::now(state.instance_wall_clock()); + let now = wall_clock::now(); datetime.seconds -= now.seconds; if datetime.nanoseconds < now.nanoseconds { datetime.seconds -= 1; @@ -1604,18 +1603,10 @@ pub unsafe extern "C" fn poll_oneoff( clock.timeout }; - monotonic_clock::subscribe( - state.instance_monotonic_clock(), - timeout, - false, - ) + monotonic_clock::subscribe(timeout, false) } - CLOCKID_MONOTONIC => monotonic_clock::subscribe( - state.instance_monotonic_clock(), - clock.timeout, - absolute, - ), + CLOCKID_MONOTONIC => monotonic_clock::subscribe(clock.timeout, absolute), _ => return Err(ERRNO_INVAL), } @@ -1630,9 +1621,7 @@ pub unsafe extern "C" fn poll_oneoff( // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll // immediately fail. - Err(ERRNO_BADF) => { - monotonic_clock::subscribe(state.instance_monotonic_clock(), 0, false) - } + Err(ERRNO_BADF) => monotonic_clock::subscribe(0, false), Err(e) => return Err(e), } } @@ -1646,9 +1635,7 @@ pub unsafe extern "C" fn poll_oneoff( // If the file descriptor isn't a stream, request a // pollable which completes immediately so that it'll // immediately fail. - Err(ERRNO_BADF) => { - monotonic_clock::subscribe(state.instance_monotonic_clock(), 0, false) - } + Err(ERRNO_BADF) => monotonic_clock::subscribe(0, false), Err(e) => return Err(e), } } @@ -2085,12 +2072,6 @@ struct State { /// name that didn't fit into the caller's buffer. dirent_cache: DirentCache, - /// The clock handle for `CLOCKID_MONOTONIC`. - instance_monotonic_clock: Cell>, - - /// The clock handle for `CLOCKID_REALTIME`. - instance_wall_clock: Cell>, - /// The string `..` for use by the directory iterator. dotdot: [UnsafeCell; 2], @@ -2151,7 +2132,7 @@ const fn bump_arena_size() -> usize { start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 25 * size_of::(); + start -= 22 * size_of::(); // Everything else is the `command_data` allocation. start @@ -2272,8 +2253,6 @@ impl State { }), path_data: UnsafeCell::new(MaybeUninit::uninit()), }, - instance_monotonic_clock: Cell::new(None), - instance_wall_clock: Cell::new(None), dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')], })); &*ret @@ -2304,36 +2283,6 @@ impl State { RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) } - /// Return a handle to the default wall clock, creating one if we - /// don't already have one. - fn instance_wall_clock(&self) -> Fd { - match self.instance_wall_clock.get() { - Some(fd) => fd, - None => self.init_instance_wall_clock(), - } - } - - fn init_instance_wall_clock(&self) -> Fd { - let clock = instance_wall_clock::instance_wall_clock(); - self.instance_wall_clock.set(Some(clock)); - clock - } - - /// Return a handle to the default monotonic clock, creating one if we - /// don't already have one. - fn instance_monotonic_clock(&self) -> Fd { - match self.instance_monotonic_clock.get() { - Some(fd) => fd, - None => self.init_instance_monotonic_clock(), - } - } - - fn init_instance_monotonic_clock(&self) -> Fd { - let clock = instance_monotonic_clock::instance_monotonic_clock(); - self.instance_monotonic_clock.set(Some(clock)); - clock - } - fn get_environment(&self) -> &[StrTuple] { if self.env_vars.get().is_none() { #[link(wasm_import_module = "environment")] From de9711ae8549c37afec4937b346d9958337d19b5 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 Apr 2023 13:22:11 -0700 Subject: [PATCH 112/153] Rename the command entrypoint from `run` back to `main`. (#131) LLVM has special handling for functions named `main`, which we need to avoid because we're creating the component-level `main` rather than the C-level `main`. To do this, write the `main` function in assembly, which is fortunately very simple now. --- build.rs | 6 ++++++ src/lib.rs | 13 +------------ src/main.o | Bin 0 -> 181 bytes src/main.s | 27 +++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 src/main.o create mode 100644 src/main.s diff --git a/build.rs b/build.rs index fcd7b7c6dac9..f3514d64c43f 100644 --- a/build.rs +++ b/build.rs @@ -14,6 +14,12 @@ fn main() { out_dir.to_str().unwrap() ); + // LLVM has special handling for `main` so use a .s file to define the main + // function for the adapter. See the comments in src/main.s for details. + if env::var("CARGO_FEATURE_COMMAND").is_ok() { + println!("cargo:rustc-link-arg=src/main.o"); + } + // Some specific flags to `wasm-ld` to inform the shape of this adapter. // Notably we're importing memory from the main module and additionally our // own module has no stack at all since it's specifically allocated at diff --git a/src/lib.rs b/src/lib.rs index 7212f5b88cb6..630227bb76cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub mod bindings { raw_strings, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["run", "get-directories", "get-environment"], + skip: ["main", "get-directories", "get-environment"], }); #[cfg(feature = "reactor")] @@ -43,17 +43,6 @@ pub mod bindings { }); } -#[no_mangle] -#[cfg(feature = "command")] -pub unsafe extern "C" fn run() -> u32 { - #[link(wasm_import_module = "__main_module__")] - extern "C" { - fn _start(); - } - _start(); - 0 -} - // The unwrap/expect methods in std pull panic when they fail, which pulls // in unwinding machinery that we can't use in the adapter. Instead, use this // extension trait to get postfixed upwrap on Option and Result. diff --git a/src/main.o b/src/main.o new file mode 100644 index 0000000000000000000000000000000000000000..8f0af8b73feb57955cd6d54dbc4570728c356ddb GIT binary patch literal 181 zcmXwxK?=e!6h!Bz!AiTRC-DwqUF!{gg9a%!38YnUWpL$2aN!N~0^ZQ1=xYAVpLtNd zApmp>AwV>sO9P0{=rV0j6?4~GtvnlToIeX7Mn<)kjoTeN%{<|LP<{Yq*EA#KBaCEf wfr|T>mY78iSfDELN>?7iht}@J^qH){n|_-O{>(@E6kd03J72Erl_W^|0&03QG5`Po literal 0 HcmV?d00001 diff --git a/src/main.s b/src/main.s new file mode 100644 index 000000000000..4a252e3f8901 --- /dev/null +++ b/src/main.s @@ -0,0 +1,27 @@ +# The component model CLI world exports its entrypoint under the name +# "main". However, LLVM has special handling for functions named `main` +# in order to handle the `main(void)` vs `main(int argc, char **argv)` +# difference on Wasm where the caller needs to know the exact signature. +# To avoid this, define a function with a different name and export it +# as `main`. +# +# To generate the `main.o` file from this `main.s` file, compile with +# `clang --target=wasm32-wasi -c main.s` + + .text + .functype main () -> (i32) + .export_name main, main + .functype _start () -> () + .import_name _start, _start + .import_module _start, __main_module__ + .section .text.main,"",@ + .hidden main + .globl main + .type main,@function +main: + .functype main () -> (i32) + call _start + i32.const 0 + return + end_function + .no_dead_strip main From 77845fcf38bfb37a4e48fcb17098c6cbb55789bf Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 Apr 2023 15:10:36 -0700 Subject: [PATCH 113/153] fix wasi renumber test: return an error in adapter if closing a closed fd --- src/descriptors.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/descriptors.rs b/src/descriptors.rs index 288a2099219d..f05ba82e5986 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -258,6 +258,11 @@ impl Descriptors { // Internal: close a fd, returning the descriptor. fn close_(&mut self, fd: Fd) -> Result { + // Throw an error if closing an fd which is already closed + match self.get_mut(fd)? { + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?, + _ => {} + } // Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list: let last_closed = self.closed; let prev = std::mem::replace(self.get_mut(fd)?, Descriptor::Closed(last_closed)); From 0042b6f4e31754d434ab61a97156d32343509f94 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 Apr 2023 17:04:11 -0700 Subject: [PATCH 114/153] NFC: pull eventtype const definitions out to be reused further down --- src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 630227bb76cc..c3eae765defc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1547,6 +1547,10 @@ pub unsafe extern "C" fn poll_oneoff( } State::with(|state| { + const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); + const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); + const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); + let mut pollables = Pollables { pointer: pollables, index: 0, @@ -1554,9 +1558,6 @@ pub unsafe extern "C" fn poll_oneoff( }; for subscription in subscriptions { - const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); - const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); - const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); pollables.push(match subscription.u.tag { EVENTTYPE_CLOCK => { let clock = &subscription.u.u.clock; @@ -1660,15 +1661,15 @@ pub unsafe extern "C" fn poll_oneoff( let flags; match subscription.u.tag { - 0 => { + EVENTTYPE_CLOCK => { error = ERRNO_SUCCESS; - type_ = EVENTTYPE_CLOCK; + type_ = wasi::EVENTTYPE_CLOCK; nbytes = 0; flags = 0; } - 1 => { - type_ = EVENTTYPE_FD_READ; + EVENTTYPE_FD_READ => { + type_ = wasi::EVENTTYPE_FD_READ; let ds = state.descriptors(); let desc = ds .get(subscription.u.u.fd_read.file_descriptor) @@ -1721,8 +1722,8 @@ pub unsafe extern "C" fn poll_oneoff( _ => unreachable!(), } } - 2 => { - type_ = EVENTTYPE_FD_WRITE; + EVENTTYPE_FD_WRITE => { + type_ = wasi::EVENTTYPE_FD_WRITE; let ds = state.descriptors(); let desc = ds .get(subscription.u.u.fd_read.file_descriptor) From 8f80add40f9db274a437db057f37cd627c6f07b7 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 Apr 2023 17:05:23 -0700 Subject: [PATCH 115/153] size assertion: refactor to de-duplicate MAX_DESCRIPTORS definition --- src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3eae765defc..de39e3e140d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2015,8 +2015,6 @@ const PAGE_SIZE: usize = 65536; /// polyfill. const PATH_MAX: usize = 4096; -const MAX_DESCRIPTORS: usize = 128; - /// Maximum number of bytes to cache for a `wasi::Dirent` plus its path name. const DIRENT_CACHE: usize = 256; @@ -2118,11 +2116,11 @@ const fn bump_arena_size() -> usize { // Remove the big chunks of the struct, the `path_buf` and `descriptors` // fields. start -= PATH_MAX; - start -= size_of::() * MAX_DESCRIPTORS; + start -= size_of::(); start -= size_of::(); // Remove miscellaneous metadata also stored in state. - start -= 22 * size_of::(); + start -= 16 * size_of::(); // Everything else is the `command_data` allocation. start From 8a32f7dfe84f998eb126e28df7eac86cefe7dcf3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 Apr 2023 17:04:49 -0700 Subject: [PATCH 116/153] File: we need to have the DescriptorType around to handle certain corner cases --- src/descriptors.rs | 4 ++++ src/lib.rs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/descriptors.rs b/src/descriptors.rs index 288a2099219d..036fd88ab735 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -175,6 +175,8 @@ impl Descriptors { output: Cell::new(None), type_: StreamType::File(File { fd: preopen.descriptor, + descriptor_type: crate::bindings::filesystem::get_type(preopen.descriptor) + .trapping_unwrap(), position: Cell::new(0), append: false, }), @@ -332,6 +334,8 @@ impl Descriptors { } pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { + // FIXME: do we look at the StreamType::File(File { descriptor_type }) and make sure it + // really is a DescriptorType::Directory here? self.get_file_with_error(fd, wasi::ERRNO_NOTDIR) } diff --git a/src/lib.rs b/src/lib.rs index 630227bb76cc..be2448e9cb21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1313,11 +1313,13 @@ pub unsafe extern "C" fn path_open( let mut ds = state.descriptors_mut(); let file = ds.get_dir(fd)?; let result = filesystem::open_at(file.fd, at_flags, path, o_flags, flags, mode)?; + let descriptor_type = filesystem::get_type(result)?; let desc = Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(None), type_: StreamType::File(File { fd: result, + descriptor_type, position: Cell::new(0), append, }), @@ -2000,6 +2002,9 @@ pub struct File { /// The handle to the preview2 descriptor that this file is referencing. fd: filesystem::Descriptor, + /// The descriptor type, as supplied by filesystem::get_type at opening + descriptor_type: filesystem::DescriptorType, + /// The current-position pointer. position: Cell, From 2078d1314b3e6b07d8e59c4ec85d765bb43fce6d Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 Apr 2023 17:08:49 -0700 Subject: [PATCH 117/153] fd_seek: return the expected errno when trying to seek on a directory. --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index be2448e9cb21..480ed2461681 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1061,6 +1061,12 @@ pub unsafe extern "C" fn fd_seek( // Seeking only works on files. if let StreamType::File(file) = &stream.type_ { + match file.descriptor_type { + // This isn't really the "right" errno, but it is consistient with wasmtime's + // preview 1 tests. + filesystem::DescriptorType::Directory => return Err(ERRNO_BADF), + _ => {} + } // It's ok to cast these indices; the WASI API will fail if // the resulting values are out of range. let from = match whence { From 47554647f35915edb178b07096b7527c6a2e9072 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 10 Apr 2023 19:19:11 -0700 Subject: [PATCH 118/153] fix fd_filestat_get: adapter special-case StreamType::Unknown (#139) giving an empty Filestat, instead of returning an error. This implements the behavior expected by the fd_filestat_get test. We might end up being able to rename StreamType's Unknown variant to Stdio, since it looks like that is the only place we construct it. --- src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index de39e3e140d9..9d9a67e8afd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -558,20 +558,46 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { let ds = state.descriptors(); - let file = ds.get_file(fd)?; - let stat = filesystem::stat(file.fd)?; - let filetype = stat.type_.into(); - *buf = Filestat { - dev: stat.device, - ino: stat.inode, - filetype, - nlink: stat.link_count, - size: stat.size, - atim: datetime_to_timestamp(stat.data_access_timestamp), - mtim: datetime_to_timestamp(stat.data_modification_timestamp), - ctim: datetime_to_timestamp(stat.status_change_timestamp), - }; - Ok(()) + + match ds.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => { + let stat = filesystem::stat(file.fd)?; + let filetype = stat.type_.into(); + *buf = Filestat { + dev: stat.device, + ino: stat.inode, + filetype, + nlink: stat.link_count, + size: stat.size, + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), + }; + Ok(()) + } + // For unknown (effectively, stdio) streams, instead of returning an error, return a + // Filestat with all zero fields and Filetype::Unknown. + Descriptor::Streams(Streams { + type_: StreamType::Unknown, + .. + }) => { + *buf = Filestat { + dev: 0, + ino: 0, + filetype: FILETYPE_UNKNOWN, + nlink: 0, + size: 0, + atim: 0, + mtim: 0, + ctim: 0, + }; + Ok(()) + } + _ => Err(wasi::ERRNO_BADF), + } }) } From b6d5f24abc3c3fbd3a4dea00430e2fb9599e02eb Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 13 Apr 2023 16:08:33 -0700 Subject: [PATCH 119/153] return ERRNO_BADF on directory descriptors in appropriate fd operations --- src/descriptors.rs | 47 +++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 8 ++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index 036fd88ab735..1dd60084ac45 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -58,6 +58,12 @@ impl Streams { match &self.input.get() { Some(wasi_stream) => Ok(*wasi_stream), None => match &self.type_ { + // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read + // or write. + StreamType::File(File { + descriptor_type: filesystem::DescriptorType::Directory, + .. + }) => Err(wasi::ERRNO_BADF), // For files, we may have adjusted the position for seeking, so // create a new stream. StreamType::File(file) => { @@ -75,6 +81,12 @@ impl Streams { match &self.output.get() { Some(wasi_stream) => Ok(*wasi_stream), None => match &self.type_ { + // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read + // or write. + StreamType::File(File { + descriptor_type: filesystem::DescriptorType::Directory, + .. + }) => Err(wasi::ERRNO_BADF), // For files, we may have adjusted the position for seeking, so // create a new stream. StreamType::File(file) => { @@ -308,6 +320,14 @@ impl Descriptors { pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { + Descriptor::Streams(Streams { + type_: + StreamType::File(File { + descriptor_type: filesystem::DescriptorType::Directory, + .. + }), + .. + }) => Err(wasi::ERRNO_BADF), Descriptor::Streams(Streams { type_: StreamType::File(file), .. @@ -334,9 +354,30 @@ impl Descriptors { } pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { - // FIXME: do we look at the StreamType::File(File { descriptor_type }) and make sure it - // really is a DescriptorType::Directory here? - self.get_file_with_error(fd, wasi::ERRNO_NOTDIR) + match self.get(fd)? { + Descriptor::Streams(Streams { + type_: + StreamType::File( + file @ File { + descriptor_type: filesystem::DescriptorType::Directory, + .. + }, + ), + .. + }) => Ok(file), + _ => Err(wasi::ERRNO_BADF), + } + } + + pub fn get_file_or_dir(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { + match self.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => Ok(file), + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), + _ => Err(error), + } } pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { diff --git a/src/lib.rs b/src/lib.rs index 480ed2461681..802275607a0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -405,7 +405,11 @@ pub unsafe extern "C" fn fd_advise( /// Note: This is similar to `posix_fallocate` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { - unreachable!("fd_allocate") + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_file(fd)?; + unreachable!("fd_allocate is unimplemented") + }) } /// Close a file descriptor. @@ -558,7 +562,7 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { State::with(|state| { let ds = state.descriptors(); - let file = ds.get_file(fd)?; + let file = ds.get_file_or_dir(fd, wasi::ERRNO_BADF)?; let stat = filesystem::stat(file.fd)?; let filetype = stat.type_.into(); *buf = Filestat { From ea15c6f3c508f5ee151fb372e380d13244591ad3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 14 Apr 2023 08:28:23 -0700 Subject: [PATCH 120/153] dead code --- src/descriptors.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index fb534b2c123e..59e8661b1fa5 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -374,17 +374,6 @@ impl Descriptors { } } - pub fn get_file_or_dir(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { - match self.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => Ok(file), - Descriptor::Closed(_) => Err(wasi::ERRNO_BADF), - _ => Err(error), - } - } - pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { self.get_file_with_error(fd, wasi::ERRNO_SPIPE) } From 81b4ffe5e0a641e31a20a8b028c3dfb33b5de19a Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 17 Apr 2023 14:29:40 -0700 Subject: [PATCH 121/153] adapter: fd_allocate gives notsup on all files --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0f206eb569b3..5304d54db444 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -407,8 +407,10 @@ pub unsafe extern "C" fn fd_advise( pub unsafe extern "C" fn fd_allocate(fd: Fd, offset: Filesize, len: Filesize) -> Errno { State::with(|state| { let ds = state.descriptors(); + // For not-files, fail with BADF let file = ds.get_file(fd)?; - unreachable!("fd_allocate is unimplemented") + // For all files, fail with NOTSUP, because this call does not exist in preview 2. + Err(wasi::ERRNO_NOTSUP) }) } From 3d6e55e0fcb1335fd200706ca3fe02a7bf5877d8 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 17 Apr 2023 14:32:41 -0700 Subject: [PATCH 122/153] adapter: remove incorrect assertions from path_readlink --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0f206eb569b3..a2a685f0dc30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1399,9 +1399,6 @@ pub unsafe extern "C" fn path_readlink( .with_buffer(buf, buf_len, || filesystem::readlink_at(file.fd, path))? }; - assert_eq!(path.as_ptr(), buf); - assert!(path.len() <= buf_len); - *bufused = path.len(); if use_state_buf { // Preview1 follows POSIX in truncating the returned path if it From 4bba05d7168c67c3875c2b46c2b53e7f7dcf5fe9 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 17 Apr 2023 15:13:39 -0700 Subject: [PATCH 123/153] readlink: fix adapter to return right len, and test to expect truncation truncation behavior is backported to upstream in https://github.com/bytecodealliance/wasmtime/pull/6225 --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a2a685f0dc30..ea4315b1e87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1399,12 +1399,14 @@ pub unsafe extern "C" fn path_readlink( .with_buffer(buf, buf_len, || filesystem::readlink_at(file.fd, path))? }; - *bufused = path.len(); if use_state_buf { // Preview1 follows POSIX in truncating the returned path if it // doesn't fit. let len = min(path.len(), buf_len); ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len); + *bufused = len; + } else { + *bufused = path.len(); } // The returned string's memory was allocated in `buf`, so don't separately From b8018edad965456b8826ac5277d5e9acb744adab Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 19 Apr 2023 17:21:05 -0700 Subject: [PATCH 124/153] adapter: track blocking state in File, remove uses of set-flags --- src/descriptors.rs | 1 + src/lib.rs | 79 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index 59e8661b1fa5..6340da968c64 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -191,6 +191,7 @@ impl Descriptors { .trapping_unwrap(), position: Cell::new(0), append: false, + blocking: false, }), })) .trapping_unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 9c30ceb8ba6a..0a88f7a98555 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -468,9 +468,6 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { fs_flags |= FDFLAGS_DSYNC; } - if flags.contains(filesystem::DescriptorFlags::NON_BLOCKING) { - fs_flags |= FDFLAGS_NONBLOCK; - } if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { fs_flags |= FDFLAGS_RSYNC; } @@ -480,6 +477,9 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { if file.append { fs_flags |= FDFLAGS_APPEND; } + if !file.blocking { + fs_flags |= FDFLAGS_NONBLOCK; + } let fs_rights_inheriting = fs_rights_base; stat.write(Fdstat { @@ -526,24 +526,21 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { - let mut new_flags = filesystem::DescriptorFlags::empty(); - if flags & FDFLAGS_DSYNC == FDFLAGS_DSYNC { - new_flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC; - } - if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { - new_flags |= filesystem::DescriptorFlags::NON_BLOCKING; - } - if flags & FDFLAGS_RSYNC == FDFLAGS_RSYNC { - new_flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC; - } - if flags & FDFLAGS_SYNC == FDFLAGS_SYNC { - new_flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC; + // Only support changing the NONBLOCK flag. + if flags & !FDFLAGS_NONBLOCK != 0 { + return wasi::ERRNO_INVAL; } - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - filesystem::set_flags(file.fd, new_flags)?; + State::with_mut(|state| { + let mut ds = state.descriptors_mut(); + let file = match ds.get_mut(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) if !file.is_dir() => file, + _ => Err(wasi::ERRNO_BADF)?, + }; + file.blocking = !(flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK); Ok(()) }) } @@ -805,11 +802,23 @@ pub unsafe extern "C" fn fd_read( Descriptor::Streams(streams) => { let wasi_stream = streams.get_read_stream()?; + let blocking = if let StreamType::File(file) = &streams.type_ { + file.blocking + } else { + false + }; + let read_len = u64::try_from(len).trapping_unwrap(); let wasi_stream = streams.get_read_stream()?; let (data, end) = state .import_alloc - .with_buffer(ptr, len, || streams::read(wasi_stream, read_len)) + .with_buffer(ptr, len, || { + if blocking { + streams::blocking_read(wasi_stream, read_len) + } else { + streams::read(wasi_stream, read_len) + } + }) .map_err(|_| ERRNO_IO)?; assert_eq!(data.as_ptr(), ptr); @@ -1173,7 +1182,17 @@ pub unsafe extern "C" fn fd_write( match ds.get(fd)? { Descriptor::Streams(streams) => { let wasi_stream = streams.get_write_stream()?; - let bytes = streams::write(wasi_stream, bytes).map_err(|_| ERRNO_IO)?; + + let bytes = if let StreamType::File(file) = &streams.type_ { + if file.blocking { + streams::blocking_write(wasi_stream, bytes) + } else { + streams::write(wasi_stream, bytes) + } + } else { + streams::write(wasi_stream, bytes) + } + .map_err(|_| ERRNO_IO)?; // If this is a file, keep the current-position pointer up to date. if let StreamType::File(file) = &streams.type_ { @@ -1359,6 +1378,7 @@ pub unsafe extern "C" fn path_open( descriptor_type, position: Cell::new(0), append, + blocking: (fdflags & wasi::FDFLAGS_NONBLOCK) == 0, }), }); @@ -1963,9 +1983,6 @@ fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem:: if fdflags & wasi::FDFLAGS_RSYNC == wasi::FDFLAGS_RSYNC { flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC; } - if fdflags & wasi::FDFLAGS_NONBLOCK == wasi::FDFLAGS_NONBLOCK { - flags |= filesystem::DescriptorFlags::NON_BLOCKING; - } flags } @@ -2047,6 +2064,20 @@ pub struct File { /// In append mode, all writes append to the file. append: bool, + + /// In blocking mode, read and write calls dispatch to blocking_read and + /// blocking_write on the underlying streams. When false, read and write + /// dispatch to stream's plain read and write. + blocking: bool, +} + +impl File { + fn is_dir(&self) -> bool { + match self.descriptor_type { + filesystem::DescriptorType::Directory => true, + _ => false, + } + } } const PAGE_SIZE: usize = 65536; From b0fed76389af629463ce8e32f48b02b4d59db70f Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 20 Apr 2023 12:08:47 -0700 Subject: [PATCH 125/153] adapter: change logic around interpeting ATIM and ATIM_NOW (and MTIM equiv) to be consistient with upstream in wasi-common preview1, ATIM and ATIM_NOW (and MTIM and MTIM now) were mutually exclusive and would result in an INVAL error, whereas in the adapter previously, ATIM_NOW implied ATIM, but would silently do nothing if ATIM was not set. I decided to be consistient with the upstream behavior here because it is pretty arbitrary and I don't think there's a good reason to break compatibility. This fixes the `path_filestat` test. --- src/lib.rs | 82 ++++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9c30ceb8ba6a..ad9b4dfdcb5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -618,6 +618,21 @@ pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { }) } +fn systimespec(set: bool, ts: Timestamp, now: bool) -> Result { + if set && now { + Err(wasi::ERRNO_INVAL) + } else if set { + Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime { + seconds: ts / 1_000_000_000, + nanoseconds: (ts % 1_000_000_000) as _, + })) + } else if now { + Ok(filesystem::NewTimestamp::Now) + } else { + Ok(filesystem::NewTimestamp::NoChange) + } +} + /// Adjust the timestamps of an open file or directory. /// Note: This is similar to `futimens` in POSIX. #[no_mangle] @@ -627,30 +642,17 @@ pub unsafe extern "C" fn fd_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - let atim = - if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { - filesystem::NewTimestamp::Now - } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { - filesystem::NewTimestamp::Timestamp(filesystem::Datetime { - seconds: atim / 1_000_000_000, - nanoseconds: (atim % 1_000_000_000) as _, - }) - } else { - filesystem::NewTimestamp::NoChange - }; - let mtim = - if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { - filesystem::NewTimestamp::Now - } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { - filesystem::NewTimestamp::Timestamp(filesystem::Datetime { - seconds: mtim / 1_000_000_000, - nanoseconds: (mtim % 1_000_000_000) as _, - }) - } else { - filesystem::NewTimestamp::NoChange - }; - State::with(|state| { + let atim = systimespec( + fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM, + atim, + fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW, + )?; + let mtim = systimespec( + fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM, + mtim, + fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, + )?; let ds = state.descriptors(); let file = ds.get_file(fd)?; filesystem::set_times(file.fd, atim, mtim)?; @@ -1260,33 +1262,21 @@ pub unsafe extern "C" fn path_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - let atim = - if fst_flags & (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) == (FSTFLAGS_ATIM | FSTFLAGS_ATIM_NOW) { - filesystem::NewTimestamp::Now - } else if fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM { - filesystem::NewTimestamp::Timestamp(filesystem::Datetime { - seconds: atim / 1_000_000_000, - nanoseconds: (atim % 1_000_000_000) as _, - }) - } else { - filesystem::NewTimestamp::NoChange - }; - let mtim = - if fst_flags & (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) == (FSTFLAGS_MTIM | FSTFLAGS_MTIM_NOW) { - filesystem::NewTimestamp::Now - } else if fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM { - filesystem::NewTimestamp::Timestamp(filesystem::Datetime { - seconds: mtim / 1_000_000_000, - nanoseconds: (mtim % 1_000_000_000) as _, - }) - } else { - filesystem::NewTimestamp::NoChange - }; - let path = slice::from_raw_parts(path_ptr, path_len); let at_flags = at_flags_from_lookupflags(flags); State::with(|state| { + let atim = systimespec( + fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM, + atim, + fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW, + )?; + let mtim = systimespec( + fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM, + mtim, + fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, + )?; + let ds = state.descriptors(); let file = ds.get_dir(fd)?; filesystem::set_times_at(file.fd, at_flags, path, atim, mtim)?; From fddf6ca5aadc885b4d06f8fbbc3defaa8b3c8b61 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 20 Apr 2023 15:47:42 -0700 Subject: [PATCH 126/153] fd_seek: error with invalid offsets in the adapter --- src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e027e3ebf7f..fe98dd75f031 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1109,12 +1109,16 @@ pub unsafe extern "C" fn fd_seek( filesystem::DescriptorType::Directory => return Err(ERRNO_BADF), _ => {} } - // It's ok to cast these indices; the WASI API will fail if - // the resulting values are out of range. let from = match whence { - WHENCE_SET => offset, - WHENCE_CUR => (file.position.get() as i64).wrapping_add(offset), - WHENCE_END => (filesystem::stat(file.fd)?.size as i64) + offset, + WHENCE_SET if offset >= 0 => offset, + WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) { + Some(pos) if pos >= 0 => pos, + _ => return Err(ERRNO_INVAL), + }, + WHENCE_END => match (filesystem::stat(file.fd)?.size as i64).checked_add(offset) { + Some(pos) if pos >= 0 => pos, + _ => return Err(ERRNO_INVAL), + }, _ => return Err(ERRNO_INVAL), }; stream.input.set(None); From 93e4bd16301b8bb49a0d9ea39e143bb6ed595a90 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 20 Apr 2023 15:49:34 -0700 Subject: [PATCH 127/153] NFC, looks more consistient to my eye though --- src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fe98dd75f031..37f800e0286b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1103,11 +1103,10 @@ pub unsafe extern "C" fn fd_seek( // Seeking only works on files. if let StreamType::File(file) = &stream.type_ { - match file.descriptor_type { + if let filesystem::DescriptorType::Directory = file.descriptor_type { // This isn't really the "right" errno, but it is consistient with wasmtime's // preview 1 tests. - filesystem::DescriptorType::Directory => return Err(ERRNO_BADF), - _ => {} + return Err(ERRNO_BADF); } let from = match whence { WHENCE_SET if offset >= 0 => offset, From f0ed67a15c4a85f10260b0c0f44464b8b5c6767b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 20 Apr 2023 17:19:48 -0700 Subject: [PATCH 128/153] fix bug in adapter fd_fdstat_set_flags to frob the append flag. this fixes the fd_flags_set test. --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 37f800e0286b..c2a9f714869f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,8 +526,8 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { /// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { - // Only support changing the NONBLOCK flag. - if flags & !FDFLAGS_NONBLOCK != 0 { + // Only support changing the NONBLOCK or APPEND flags. + if flags & !(FDFLAGS_NONBLOCK | FDFLAGS_APPEND) != 0 { return wasi::ERRNO_INVAL; } @@ -540,6 +540,7 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { }) if !file.is_dir() => file, _ => Err(wasi::ERRNO_BADF)?, }; + file.append = (flags & FDFLAGS_APPEND == FDFLAGS_APPEND); file.blocking = !(flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK); Ok(()) }) From 806d23d37cdf568fb8450b1d1c55e530ec91938e Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 20 Apr 2023 17:20:39 -0700 Subject: [PATCH 129/153] delete spurrious extra line in adapter (this same var is created below) --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c2a9f714869f..ab7c5559251c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -803,8 +803,6 @@ pub unsafe extern "C" fn fd_read( State::with(|state| { match state.descriptors().get(fd)? { Descriptor::Streams(streams) => { - let wasi_stream = streams.get_read_stream()?; - let blocking = if let StreamType::File(file) = &streams.type_ { file.blocking } else { From 022926019bed1686deebf2d3ae9921ab49d874c4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 24 Apr 2023 15:08:42 -0700 Subject: [PATCH 130/153] adapter poll_oneoff: when descriptors.get_*_stream(fd) fails, die with that error (#154) * adapter poll_oneoff: when descriptors.get_*_stream(fd) fails, die with that error There was a special case in poll_oneoff that put in a fake clock stream when a read/write stream for a descriptor wasn't available. The poll_oneoff_files test (in `test_fd_readwrite_invalid_fd()`) checks that poll_oneoff returns a BADF when an invalid fd is subscribed to. I'm not sure what the special case was patching over, but this passes all of the other tests right now. * poll_oneoff_files fails on windows with god knows what error diff --git a/host/tests/command.rs b/host/tests/command.rs index 7af7bd0..67c8c0b 100644 --- a/host/tests/command.rs +++ b/host/tests/command.rs @@ -466,10 +466,11 @@ async fn run_path_symlink_trailing_slashes(store: Store, wasi: Command) } async fn run_poll_oneoff_files(store: Store, wasi: Command) -> Result<()> { - // trapping upwrap in poll_oneoff in adapter. - // maybe this is related to the "if fd isnt a stream, request a pollable which completes - // immediately so itll immediately fail" behavior, which i think breaks internal invariant... - run_with_temp_dir(store, wasi).await + if cfg!(windows) { + expect_fail(run_with_temp_dir(store, wasi).await) + } else { + run_with_temp_dir(store, wasi).await + } } async fn run_poll_oneoff_stdio(store: Store, wasi: Command) -> Result<()> { --- src/lib.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ab7c5559251c..1ffc1535857c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1585,7 +1585,6 @@ pub unsafe extern "C" fn poll_oneoff( ) .trapping_unwrap() ); - // Store the pollable handles at the beginning, and the bool results at the // end, so that we don't clobber the bool results when writting the events. let pollables = out as *mut c_void as *mut Pollable; @@ -1653,36 +1652,23 @@ pub unsafe extern "C" fn poll_oneoff( } EVENTTYPE_FD_READ => { - match state + let stream = state .descriptors() - .get_read_stream(subscription.u.u.fd_read.file_descriptor) - { - Ok(stream) => streams::subscribe_to_input_stream(stream), - // If the file descriptor isn't a stream, request a - // pollable which completes immediately so that it'll - // immediately fail. - Err(ERRNO_BADF) => monotonic_clock::subscribe(0, false), - Err(e) => return Err(e), - } + .get_read_stream(subscription.u.u.fd_read.file_descriptor)?; + streams::subscribe_to_input_stream(stream) } EVENTTYPE_FD_WRITE => { - match state + let stream = state .descriptors() - .get_write_stream(subscription.u.u.fd_write.file_descriptor) - { - Ok(stream) => streams::subscribe_to_output_stream(stream), - // If the file descriptor isn't a stream, request a - // pollable which completes immediately so that it'll - // immediately fail. - Err(ERRNO_BADF) => monotonic_clock::subscribe(0, false), - Err(e) => return Err(e), - } + .get_write_stream(subscription.u.u.fd_write.file_descriptor)?; + streams::subscribe_to_output_stream(stream) } _ => return Err(ERRNO_INVAL), }); } + let vec = state.import_alloc.with_buffer( results, nsubscriptions @@ -1776,7 +1762,7 @@ pub unsafe extern "C" fn poll_oneoff( type_ = wasi::EVENTTYPE_FD_WRITE; let ds = state.descriptors(); let desc = ds - .get(subscription.u.u.fd_read.file_descriptor) + .get(subscription.u.u.fd_write.file_descriptor) .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match streams.type_ { From 87f74e9d1a6d647582ea41cd351a13e1b28d90fd Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 27 Apr 2023 15:27:51 -0700 Subject: [PATCH 131/153] adapter: path_ apis return NOTDIR instead of BADF when passed file; fixes path_open_dirfd test. (#152) * adapter: special case for NOTDIR instead of BADF fixes path_open_dirfd test. * get_dir now fails with NOTDIR for non-dir files * get rid of special case * complete test coverage for all path_ functions giving NOTDIR errors --- src/descriptors.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/descriptors.rs b/src/descriptors.rs index 6340da968c64..a9f9bc5bd265 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -324,6 +324,16 @@ impl Descriptors { } } + pub fn get_file_or_dir(&self, fd: Fd) -> Result<&File, Errno> { + match self.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => Ok(file), + _ => Err(wasi::ERRNO_BADF), + } + } + pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { Descriptor::Streams(Streams { @@ -371,6 +381,15 @@ impl Descriptors { ), .. }) => Ok(file), + Descriptor::Streams(Streams { + type_: + StreamType::File( + file @ File { + descriptor_type: _, .. + }, + ), + .. + }) => Err(wasi::ERRNO_NOTDIR), _ => Err(wasi::ERRNO_BADF), } } From 1e04bf294112442d926b1b33a9ade82382a738b5 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 3 May 2023 12:20:41 -0700 Subject: [PATCH 132/153] adapter: StreamType::Unknown is actually Stdio (#161) We have only ever used Unknown for the stdio streams, and I don't expect us to use it for anything else in the future, so rename it. Set the returned filetype to character device: Closes #146. Also, fix some warnings. --- src/descriptors.rs | 22 ++++++---------------- src/lib.rs | 17 ++++++++--------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index a9f9bc5bd265..214794952ba0 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -30,7 +30,7 @@ impl Drop for Descriptor { match &stream.type_ { StreamType::File(file) => filesystem::drop_descriptor(file.fd), StreamType::Socket(_) => unreachable!(), - StreamType::Unknown => {} + StreamType::Stdio => {} } } Descriptor::Closed(_) => {} @@ -106,8 +106,8 @@ impl Streams { #[allow(dead_code)] // until Socket is implemented pub enum StreamType { - /// It's a valid stream but we don't know where it comes from. - Unknown, + /// Stream is used for implementing stdio. + Stdio, /// Streaming data with a file. File(File), @@ -146,19 +146,19 @@ impl Descriptors { d.push(Descriptor::Streams(Streams { input: Cell::new(Some(stdio.stdin)), output: Cell::new(None), - type_: StreamType::Unknown, + type_: StreamType::Stdio, })) .trapping_unwrap(); d.push(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(Some(stdio.stdout)), - type_: StreamType::Unknown, + type_: StreamType::Stdio, })) .trapping_unwrap(); d.push(Descriptor::Streams(Streams { input: Cell::new(None), output: Cell::new(Some(stdio.stderr)), - type_: StreamType::Unknown, + type_: StreamType::Stdio, })) .trapping_unwrap(); @@ -324,16 +324,6 @@ impl Descriptors { } } - pub fn get_file_or_dir(&self, fd: Fd) -> Result<&File, Errno> { - match self.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => Ok(file), - _ => Err(wasi::ERRNO_BADF), - } - } - pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { Descriptor::Streams(Streams { diff --git a/src/lib.rs b/src/lib.rs index 1ffc1535857c..1696d29e0b0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -498,9 +498,9 @@ pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { | Descriptor::Streams(Streams { input, output, - type_: StreamType::Unknown, + type_: StreamType::Stdio, }) => { - let fs_filetype = FILETYPE_UNKNOWN; + let fs_filetype = FILETYPE_CHARACTER_DEVICE; let fs_flags = 0; let mut fs_rights_base = 0; if input.get().is_some() { @@ -540,7 +540,7 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { }) if !file.is_dir() => file, _ => Err(wasi::ERRNO_BADF)?, }; - file.append = (flags & FDFLAGS_APPEND == FDFLAGS_APPEND); + file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND; file.blocking = !(flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK); Ok(()) }) @@ -581,16 +581,15 @@ pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { }; Ok(()) } - // For unknown (effectively, stdio) streams, instead of returning an error, return a - // Filestat with all zero fields and Filetype::Unknown. + // Stdio is all zero fields, except for filetype character device Descriptor::Streams(Streams { - type_: StreamType::Unknown, + type_: StreamType::Stdio, .. }) => { *buf = Filestat { dev: 0, ino: 0, - filetype: FILETYPE_UNKNOWN, + filetype: FILETYPE_CHARACTER_DEVICE, nlink: 0, size: 0, atim: 0, @@ -1749,7 +1748,7 @@ pub unsafe extern "C" fn poll_oneoff( } */ } - StreamType::Unknown => { + StreamType::Stdio => { error = ERRNO_SUCCESS; nbytes = 1; flags = 0; @@ -1766,7 +1765,7 @@ pub unsafe extern "C" fn poll_oneoff( .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match streams.type_ { - StreamType::File(_) | StreamType::Unknown => { + StreamType::File(_) | StreamType::Stdio => { error = ERRNO_SUCCESS; nbytes = 1; flags = 0; From 4f84e641176d6ac693e9ea0d6a7f9b9383d9ae20 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 3 May 2023 14:51:44 -0700 Subject: [PATCH 133/153] Revert #131, renaming `main` back to `run`. (#165) Changing LLVM and/or Rust to avoid special handling of `main` is a fair amount of work, and there could be other toolchains with similar special rules for functions named `main`, so rename the command entrypoint back to `run`. We could potentially re-evaluate this in the future, such as in a preview3 timeframe, but for now, let's go with the simplest thing that works. --- build.rs | 6 ------ src/lib.rs | 17 ++++++++++++++--- src/main.s | 27 --------------------------- 3 files changed, 14 insertions(+), 36 deletions(-) delete mode 100644 src/main.s diff --git a/build.rs b/build.rs index f3514d64c43f..fcd7b7c6dac9 100644 --- a/build.rs +++ b/build.rs @@ -14,12 +14,6 @@ fn main() { out_dir.to_str().unwrap() ); - // LLVM has special handling for `main` so use a .s file to define the main - // function for the adapter. See the comments in src/main.s for details. - if env::var("CARGO_FEATURE_COMMAND").is_ok() { - println!("cargo:rustc-link-arg=src/main.o"); - } - // Some specific flags to `wasm-ld` to inform the shape of this adapter. // Notably we're importing memory from the main module and additionally our // own module has no stack at all since it's specifically allocated at diff --git a/src/lib.rs b/src/lib.rs index 1696d29e0b0b..1198dd3d8e29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub mod bindings { raw_strings, // The generated definition of command will pull in std, so we are defining it // manually below instead - skip: ["main", "get-directories", "get-environment"], + skip: ["run", "get-directories", "get-environment"], }); #[cfg(feature = "reactor")] @@ -43,6 +43,17 @@ pub mod bindings { }); } +#[no_mangle] +#[cfg(feature = "command")] +pub unsafe extern "C" fn run() -> u32 { + #[link(wasm_import_module = "__main_module__")] + extern "C" { + fn _start(); + } + _start(); + 0 +} + // The unwrap/expect methods in std pull panic when they fail, which pulls // in unwinding machinery that we can't use in the adapter. Instead, use this // extension trait to get postfixed upwrap on Option and Result. @@ -197,7 +208,7 @@ impl ImportAlloc { } } -/// This allocator is only used for the `main` entrypoint. +/// This allocator is only used for the `run` entrypoint. /// /// The implementation here is a bump allocator into `State::long_lived_arena` which /// traps when it runs out of data. This means that the total size of @@ -2092,7 +2103,7 @@ struct State { /// Long-lived bump allocated memory arena. /// /// This is used for the cabi_export_realloc to allocate data passed to the - /// `main` entrypoint. Allocations in this arena are safe to use for + /// `run` entrypoint. Allocations in this arena are safe to use for /// the lifetime of the State struct. It may also be used for import allocations /// which need to be long-lived, by using `import_alloc.with_arena`. long_lived_arena: BumpArena, diff --git a/src/main.s b/src/main.s deleted file mode 100644 index 4a252e3f8901..000000000000 --- a/src/main.s +++ /dev/null @@ -1,27 +0,0 @@ -# The component model CLI world exports its entrypoint under the name -# "main". However, LLVM has special handling for functions named `main` -# in order to handle the `main(void)` vs `main(int argc, char **argv)` -# difference on Wasm where the caller needs to know the exact signature. -# To avoid this, define a function with a different name and export it -# as `main`. -# -# To generate the `main.o` file from this `main.s` file, compile with -# `clang --target=wasm32-wasi -c main.s` - - .text - .functype main () -> (i32) - .export_name main, main - .functype _start () -> () - .import_name _start, _start - .import_module _start, __main_module__ - .section .text.main,"",@ - .hidden main - .globl main - .type main,@function -main: - .functype main () -> (i32) - call _start - i32.const 0 - return - end_function - .no_dead_strip main From e176feb34353247e6d739cc45a0d7e52f63ef6b5 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 12 May 2023 07:41:06 -0700 Subject: [PATCH 134/153] prepare adapter directory layout for upstreaming (#172) * delete adapter src/main.o: this was accidentally left out of #165 * move adapter, byte-array, and verify to a new workspace * rename byte-array crate to a name available on crates.io * add a readme for verify, also give it a slightly better name * CI: wit dep check in its own step, verify before publish, trim down publication * reactor-tests: delete deps symlinks * reactor-tests: manage wit with wit-deps * test: dont set default toolchain to nightly * wit-deps lock adapter * wit-deps lock reactor-tests wit-deps doesnt manage these for some reason --- .../.gitignore | 1 + .../Cargo.lock | 456 +++++++++++ .../Cargo.toml | 58 ++ .../wasi-preview1-component-adapter/README.md | 56 ++ .../wasi-preview1-component-adapter/build.rs | 0 .../byte-array-literals/Cargo.toml | 8 + .../byte-array-literals/README.md | 15 + .../byte-array-literals/src/lib.rs | 98 +++ .../src}/descriptors.rs | 0 .../src}/lib.rs | 0 .../src}/macros.rs | 4 +- .../verify/Cargo.toml | 11 + .../verify/README.md | 11 + .../verify/src/main.rs | 104 +++ .../wit/command.wit | 21 + .../wit/deps.lock | 41 + .../wit/deps.toml | 1 + .../wit/deps/clocks/monotonic-clock.wit | 32 + .../wit/deps/clocks/timezone.wit | 61 ++ .../wit/deps/clocks/wall-clock.wit | 41 + .../wit/deps/filesystem/filesystem.wit | 751 ++++++++++++++++++ .../wit/deps/http/incoming-handler.wit | 24 + .../wit/deps/http/outgoing-handler.wit | 18 + .../wit/deps/http/types.wit | 157 ++++ .../wit/deps/io/streams.wit | 213 +++++ .../wit/deps/logging/handler.wit | 32 + .../wit/deps/poll/poll.wit | 39 + .../wit/deps/preview/command-extended.wit | 29 + .../wit/deps/preview/command.wit | 21 + .../wit/deps/preview/proxy.wit | 6 + .../wit/deps/preview/reactor.wit | 21 + .../wit/deps/random/random.wit | 42 + .../wit/deps/sockets/instance-network.wit | 9 + .../wit/deps/sockets/ip-name-lookup.wit | 71 ++ .../wit/deps/sockets/network.wit | 56 ++ .../wit/deps/sockets/tcp-create-socket.wit | 19 + .../wit/deps/sockets/tcp.wit | 188 +++++ .../wit/deps/sockets/udp-create-socket.wit | 19 + .../wit/deps/sockets/udp.wit | 162 ++++ .../wit/deps/wasi-cli-base/environment.wit | 14 + .../wit/deps/wasi-cli-base/exit.wit | 4 + .../wit/deps/wasi-cli-base/preopens.wit | 17 + .../wit/reactor.wit | 21 + src/main.o | Bin 181 -> 0 bytes 44 files changed, 2950 insertions(+), 2 deletions(-) create mode 100644 crates/wasi-preview1-component-adapter/.gitignore create mode 100644 crates/wasi-preview1-component-adapter/Cargo.lock create mode 100644 crates/wasi-preview1-component-adapter/Cargo.toml create mode 100644 crates/wasi-preview1-component-adapter/README.md rename build.rs => crates/wasi-preview1-component-adapter/build.rs (100%) create mode 100644 crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml create mode 100644 crates/wasi-preview1-component-adapter/byte-array-literals/README.md create mode 100644 crates/wasi-preview1-component-adapter/byte-array-literals/src/lib.rs rename {src => crates/wasi-preview1-component-adapter/src}/descriptors.rs (100%) rename {src => crates/wasi-preview1-component-adapter/src}/lib.rs (100%) rename {src => crates/wasi-preview1-component-adapter/src}/macros.rs (95%) create mode 100644 crates/wasi-preview1-component-adapter/verify/Cargo.toml create mode 100644 crates/wasi-preview1-component-adapter/verify/README.md create mode 100644 crates/wasi-preview1-component-adapter/verify/src/main.rs create mode 100644 crates/wasi-preview1-component-adapter/wit/command.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps.lock create mode 100644 crates/wasi-preview1-component-adapter/wit/deps.toml create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/clocks/monotonic-clock.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/clocks/timezone.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/clocks/wall-clock.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/filesystem/filesystem.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/http/incoming-handler.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/http/outgoing-handler.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/http/types.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/io/streams.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/logging/handler.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/poll/poll.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/preview/command-extended.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/preview/command.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/preview/proxy.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/preview/reactor.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/random/random.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/instance-network.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/ip-name-lookup.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/network.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp-create-socket.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/udp-create-socket.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/sockets/udp.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/environment.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/exit.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/preopens.wit create mode 100644 crates/wasi-preview1-component-adapter/wit/reactor.wit delete mode 100644 src/main.o diff --git a/crates/wasi-preview1-component-adapter/.gitignore b/crates/wasi-preview1-component-adapter/.gitignore new file mode 100644 index 000000000000..eb5a316cbd19 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/.gitignore @@ -0,0 +1 @@ +target diff --git a/crates/wasi-preview1-component-adapter/Cargo.lock b/crates/wasi-preview1-component-adapter/Cargo.lock new file mode 100644 index 000000000000..4cdca9e0289e --- /dev/null +++ b/crates/wasi-preview1-component-adapter/Cargo.lock @@ -0,0 +1,456 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byte-array-literals" +version = "0.0.1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "verify-component-adapter" +version = "0.0.1" +dependencies = [ + "anyhow", + "wasmparser 0.92.0", + "wat", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi-preview1-component-adapter" +version = "0.0.1" +dependencies = [ + "byte-array-literals", + "object", + "wasi", + "wasm-encoder 0.25.0", + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eff853c4f09eec94d76af527eddad4e9de13b11d6286a1ef7134bc30135a2b7" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6956efd8a1a2c48a707e9a1b2da729834a0f8e4c58117493b0d9d089cee468" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "wasm-encoder 0.25.0", + "wasmparser 0.102.0", +] + +[[package]] +name = "wasmparser" +version = "0.92.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da34cec2a8c23db906cdf8b26e988d7a7f0d549eb5d51299129647af61a1b37" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +dependencies = [ + "indexmap", + "url", +] + +[[package]] +name = "wast" +version = "57.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.26.0", +] + +[[package]] +name = "wat" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" +dependencies = [ + "wast", +] + +[[package]] +name = "wit-bindgen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7cf57f8786216c5652e1228b25203af2ff523808b5e9d3671894eee2bf7264" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef177b73007d86c720931d0e2ea7e30eb8c9776e58361717743fc1e83cfacfe5" +dependencies = [ + "anyhow", + "wit-component", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdf5b00935b7b52d0e56cae1960f8ac13019a285f5aa762ff6bd7139a5c28a2" +dependencies = [ + "heck", + "wasm-metadata", + "wit-bindgen-core", + "wit-bindgen-rust-lib", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-lib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0a8f4b5fb1820b9d232beb122936425f72ec8fe6acb56e5d8782cfe55083da" +dependencies = [ + "heck", + "wit-bindgen-core", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadf1adf12ed25629b06272c16b335ef8c5a240d0ca64ab508a955ac3b46172c" +dependencies = [ + "anyhow", + "proc-macro2", + "syn 1.0.109", + "wit-bindgen-core", + "wit-bindgen-rust", + "wit-component", +] + +[[package]] +name = "wit-component" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed04310239706efc71cc8b995cb0226089c5b5fd260c3bd800a71486bd3cec97" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "url", + "wasm-encoder 0.25.0", + "wasm-metadata", + "wasmparser 0.102.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f887c3da527a51b321076ebe6a7513026a4757b6d4d144259946552d6fc728b3" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "pulldown-cmark", + "unicode-xid", + "url", +] diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml new file mode 100644 index 000000000000..75b5869451a7 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "wasi-preview1-component-adapter" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +wasi = { version = "0.11.0", default-features = false } +wit-bindgen = { version = "0.4.0", default-features = false, features = ["macros"] } +byte-array-literals = { workspace = true } + +[build-dependencies] +wasm-encoder = "0.25" +object = { version = "0.30.0", default-features = false, features = ["archive"] } + +[lib] +crate-type = ["cdylib"] + +[profile.release] +# Omit any unwinding support. This is currently the default for wasm, though +# that could theoretically change in the future. +panic = 'abort' +opt-level = 's' +strip = 'debuginfo' + +# Make dev look like a release build since this adapter module won't work with +# a debug build that uses data segments and such. +[profile.dev] +panic = 'abort' +incremental = false +opt-level = 's' + +# Omit assertions, which include failure messages which require string +# initializers. +debug-assertions = false + +# Omit integer overflow checks, which include failure messages which require +# string initializers. +overflow-checks = false + +[features] +default = ["reactor"] +reactor = [] +command = [] + +[workspace] +members = [ + "verify", + "byte-array-literals" +] + +[workspace.package] +version = "0.0.1" +edition = "2021" +authors = ["The Wasmtime Project Developers"] + +[workspace.dependencies] +byte-array-literals = { path = "byte-array-literals" } diff --git a/crates/wasi-preview1-component-adapter/README.md b/crates/wasi-preview1-component-adapter/README.md new file mode 100644 index 000000000000..83dc9212ea15 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/README.md @@ -0,0 +1,56 @@ +# `wasi_snapshot_preview1.wasm` + +> **Note**: This repository is a work in progress. This is intended to be an +> internal tool which not everyone has to look at but many might rely on. You +> may need to reach out via issues or +> [Zulip](https://bytecodealliance.zulipchat.com/) to learn more about this +> repository. + +This repository currently contains an implementation of a WebAssembly module: +`wasi_snapshot_preview1.wasm`. This module bridges the `wasi_snapshot_preview1` +ABI to the preview2 ABI of the component model. At this time the preview2 APIs +themselves are not done being specified so a local copy of `wit/*.wit` is used +instead. + +## Building + +This adapter can be built with: + +```sh +$ cargo build --target wasm32-unknown-unknown --release +``` + +And the artifact will be located at +`target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.wasm`. +Alternatively the latest copy of this can be [downloaded from the releases +page][latest-release] + +[latest-release]: https://github.com/bytecodealliance/preview2-prototyping/releases/tag/latest + +## Using + +With a `wasi_snapshot_preview1.wasm` file on-hand you can create a component +from a module that imports WASI functions using the [`wasm-tools` +CLI](https://github.com/bytecodealliance/wasm-tools) + +```sh +$ cat foo.rs +fn main() { + println!("Hello, world!"); +} +$ rustc foo.rs --target wasm32-wasi +$ wasm-tools print foo.wasm | grep '(import' + (import "wasi_snapshot_preview1" "fd_write" (func ... + (import "wasi_snapshot_preview1" "environ_get" (func ... + (import "wasi_snapshot_preview1" "environ_sizes_get" ... + (import "wasi_snapshot_preview1" "proc_exit" (func ... +$ wasm-tools component new foo.wasm --adapt wasi_snapshot_preview1.wasm -o component.wasm + +# Inspect the generated `component.wasm` +$ wasm-tools validate component.wasm --features component-model +$ wasm-tools component wit component.wasm +``` + +Here the `component.wasm` that's generated is a ready-to-run component which +imports wasi preview2 functions and is compatible with the wasi-preview1-using +module internally. diff --git a/build.rs b/crates/wasi-preview1-component-adapter/build.rs similarity index 100% rename from build.rs rename to crates/wasi-preview1-component-adapter/build.rs diff --git a/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml b/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml new file mode 100644 index 000000000000..28af4a52571b --- /dev/null +++ b/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "byte-array-literals" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +proc-macro = true diff --git a/crates/wasi-preview1-component-adapter/byte-array-literals/README.md b/crates/wasi-preview1-component-adapter/byte-array-literals/README.md new file mode 100644 index 000000000000..1568549409c0 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/byte-array-literals/README.md @@ -0,0 +1,15 @@ +# byte-array-literals + +This crate exists to solve a very peculiar problem for the +`wasi-preview1-component-adapter`: we want to use string literals in our +source code, but the resulting binary (when compiled for +wasm32-unknown-unknown) cannot contain any data sections. + +The answer that @sunfishcode discovered is that these string literals, if +represented as an array of u8 literals, these will somehow not end up in the +data section, at least when compiled with opt-level='s' on today's rustc +(1.69.0). So, this crate exists to transform these literals using a proc +macro. + +It is very possible this cheat code will abruptly stop working in some future +compiler, but we'll cross that bridge when we get to it. diff --git a/crates/wasi-preview1-component-adapter/byte-array-literals/src/lib.rs b/crates/wasi-preview1-component-adapter/byte-array-literals/src/lib.rs new file mode 100644 index 000000000000..ea9f013d9f53 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/byte-array-literals/src/lib.rs @@ -0,0 +1,98 @@ +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree}; + +/// Expand a `str` literal into a byte array. +#[proc_macro] +pub fn str(input: TokenStream) -> TokenStream { + let rv = convert_str(input); + + vec![TokenTree::Group(Group::new( + Delimiter::Bracket, + rv.into_iter().collect(), + ))] + .into_iter() + .collect() +} + +/// The same as `str` but appends a `'\n'`. +#[proc_macro] +pub fn str_nl(input: TokenStream) -> TokenStream { + let mut rv = convert_str(input); + + rv.push(TokenTree::Literal(Literal::u8_suffixed(b'\n'))); + + vec![TokenTree::Group(Group::new( + Delimiter::Bracket, + rv.into_iter().collect(), + ))] + .into_iter() + .collect() +} + +fn convert_str(input: TokenStream) -> Vec { + let mut it = input.into_iter(); + + let mut tokens = Vec::new(); + match it.next() { + Some(TokenTree::Literal(l)) => { + for b in to_string(l).into_bytes() { + tokens.push(TokenTree::Literal(Literal::u8_suffixed(b))); + tokens.push(TokenTree::Punct(Punct::new(',', Spacing::Alone))); + } + } + _ => panic!(), + } + + assert!(it.next().is_none()); + tokens +} + +fn to_string(lit: Literal) -> String { + let formatted = lit.to_string(); + + let mut it = formatted.chars(); + assert_eq!(it.next(), Some('"')); + + let mut rv = String::new(); + loop { + match it.next() { + Some('"') => match it.next() { + Some(_) => panic!(), + None => break, + }, + Some('\\') => match it.next() { + Some('x') => { + let hi = it.next().unwrap().to_digit(16).unwrap(); + let lo = it.next().unwrap().to_digit(16).unwrap(); + let v = (hi << 16) | lo; + rv.push(v as u8 as char); + } + Some('u') => { + assert_eq!(it.next(), Some('{')); + let mut c = it.next().unwrap(); + let mut ch = 0; + while let Some(v) = c.to_digit(16) { + ch *= 16; + ch |= v; + c = it.next().unwrap(); + } + assert_eq!(c, '}'); + rv.push(::std::char::from_u32(ch).unwrap()); + } + Some('0') => rv.push('\0'), + Some('\\') => rv.push('\\'), + Some('\"') => rv.push('\"'), + Some('r') => rv.push('\r'), + Some('n') => rv.push('\n'), + Some('t') => rv.push('\t'), + Some(_) => panic!(), + None => panic!(), + }, + Some(c) => rv.push(c), + None => panic!(), + } + } + + rv +} diff --git a/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs similarity index 100% rename from src/descriptors.rs rename to crates/wasi-preview1-component-adapter/src/descriptors.rs diff --git a/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs similarity index 100% rename from src/lib.rs rename to crates/wasi-preview1-component-adapter/src/lib.rs diff --git a/src/macros.rs b/crates/wasi-preview1-component-adapter/src/macros.rs similarity index 95% rename from src/macros.rs rename to crates/wasi-preview1-component-adapter/src/macros.rs index 770968ee411d..13faf6bc62fc 100644 --- a/src/macros.rs +++ b/crates/wasi-preview1-component-adapter/src/macros.rs @@ -14,7 +14,7 @@ macro_rules! eprint { ($arg:tt) => {{ // We have to expand string literals into byte arrays to prevent them // from getting statically initialized. - let message = byte_array::str!($arg); + let message = byte_array_literals::str!($arg); $crate::macros::print(&message); }}; } @@ -25,7 +25,7 @@ macro_rules! eprintln { ($arg:tt) => {{ // We have to expand string literals into byte arrays to prevent them // from getting statically initialized. - let message = byte_array::str_nl!($arg); + let message = byte_array_literals::str_nl!($arg); $crate::macros::print(&message); }}; } diff --git a/crates/wasi-preview1-component-adapter/verify/Cargo.toml b/crates/wasi-preview1-component-adapter/verify/Cargo.toml new file mode 100644 index 000000000000..e27b733011ba --- /dev/null +++ b/crates/wasi-preview1-component-adapter/verify/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "verify-component-adapter" +version.workspace = true +edition.workspace = true +authors.workspace = true +publish = false + +[dependencies] +wasmparser = "0.92.0" +wat = "1.0.62" +anyhow = "1" diff --git a/crates/wasi-preview1-component-adapter/verify/README.md b/crates/wasi-preview1-component-adapter/verify/README.md new file mode 100644 index 000000000000..dc4768b56a0d --- /dev/null +++ b/crates/wasi-preview1-component-adapter/verify/README.md @@ -0,0 +1,11 @@ +# verify-component-adapter + +The `wasi-preview1-component-adapter` crate must compile to a wasm binary that +meets a challenging set of constraints, in order to be used as an adapter by +the `wasm-tools component new` tool. + +There are a limited set of wasm sections allowed in the binary, and a limited +set of wasm modules we allow imports from. + +This crate is a bin target which parses a wasm file and reports an error if it +does not fit in those constraints. diff --git a/crates/wasi-preview1-component-adapter/verify/src/main.rs b/crates/wasi-preview1-component-adapter/verify/src/main.rs new file mode 100644 index 000000000000..ad4861f56c6f --- /dev/null +++ b/crates/wasi-preview1-component-adapter/verify/src/main.rs @@ -0,0 +1,104 @@ +use anyhow::{bail, Result}; +use std::env; +use wasmparser::*; + +const ALLOWED_IMPORT_MODULES: &[&str] = &[ + "wall-clock", + "monotonic-clock", + "timezone", + "filesystem", + "instance-network", + "ip-name-lookup", + "network", + "tcp-create-socket", + "tcp", + "udp-create-socket", + "udp", + "random", + "poll", + "streams", + "environment", + "preopens", + "exit", + "canonical_abi", + "__main_module__", +]; + +fn main() -> Result<()> { + let file = env::args() + .nth(1) + .expect("must pass wasm file as an argument"); + let wasm = wat::parse_file(&file)?; + + let mut validator = Validator::new(); + for payload in Parser::new(0).parse_all(&wasm) { + let payload = payload?; + validator.payload(&payload)?; + match payload { + Payload::Version { encoding, .. } => { + if encoding != Encoding::Module { + bail!("adapter must be a core wasm module, not a component"); + } + } + Payload::End(_) => {} + Payload::TypeSection(_) => {} + Payload::ImportSection(s) => { + for i in s { + let i = i?; + match i.ty { + TypeRef::Func(_) => { + if !ALLOWED_IMPORT_MODULES.contains(&i.module) { + bail!("import from unknown module `{}`", i.module); + } + } + TypeRef::Table(_) => bail!("should not import table"), + TypeRef::Global(_) => bail!("should not import globals"), + TypeRef::Memory(_) => {} + TypeRef::Tag(_) => bail!("unsupported `tag` type"), + } + } + } + Payload::TableSection(_) => {} + Payload::MemorySection(_) => { + bail!("preview1.wasm should import memory"); + } + Payload::GlobalSection(_) => {} + + Payload::ExportSection(_) => {} + + Payload::FunctionSection(_) => {} + + Payload::CodeSectionStart { .. } => {} + Payload::CodeSectionEntry(_) => {} + Payload::CustomSection(_) => {} + + // sections that shouldn't appear in the specially-crafted core wasm + // adapter self we're processing + Payload::DataCountSection { .. } + | Payload::ElementSection(_) + | Payload::DataSection(_) + | Payload::StartSection { .. } + | Payload::TagSection(_) + | Payload::UnknownSection { .. } => { + bail!("unsupported section {payload:?} found in preview1.wasm") + } + + // component-model related things that shouldn't show up + Payload::ModuleSection { .. } + | Payload::ComponentSection { .. } + | Payload::InstanceSection(_) + | Payload::ComponentInstanceSection(_) + | Payload::ComponentAliasSection(_) + | Payload::ComponentCanonicalSection(_) + | Payload::ComponentStartSection(_) + | Payload::ComponentImportSection(_) + | Payload::CoreTypeSection(_) + | Payload::ComponentExportSection(_) + | Payload::ComponentTypeSection(_) => { + bail!("component section found in preview1.wasm") + } + } + } + + Ok(()) +} diff --git a/crates/wasi-preview1-component-adapter/wit/command.wit b/crates/wasi-preview1-component-adapter/wit/command.wit new file mode 100644 index 000000000000..3e929515f0ba --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/command.wit @@ -0,0 +1,21 @@ +default world command { + import wall-clock: clocks.wall-clock + import monotonic-clock: clocks.monotonic-clock + import timezone: clocks.timezone + import filesystem: filesystem.filesystem + import instance-network: sockets.instance-network + import ip-name-lookup: sockets.ip-name-lookup + import network: sockets.network + import tcp-create-socket: sockets.tcp-create-socket + import tcp: sockets.tcp + import udp-create-socket: sockets.udp-create-socket + import udp: sockets.udp + import random: random.random + import poll: poll.poll + import streams: io.streams + import environment: wasi-cli-base.environment + import preopens: wasi-cli-base.preopens + import exit: wasi-cli-base.exit + + export run: func() -> result +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps.lock b/crates/wasi-preview1-component-adapter/wit/deps.lock new file mode 100644 index 000000000000..bfa0b9d4f662 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps.lock @@ -0,0 +1,41 @@ +[clocks] +sha256 = "16155da02fdba4a51515b44dbd1c65e0909bb8c4d383bd32875c777044ce5389" +sha512 = "30efd2697885954f3846322adf94f28a28f958595708407696ef66e8fe5bb62c79525e6f13ec469476cba8f2a12d3d9aef8250b1fe04c90a28542d3245db11f2" + +[filesystem] +sha256 = "a3f57790dfd6af70758df33df82aa064f915eb795b71a190f272f10da9cfc9df" +sha512 = "2b0059ffae0a4b1ce3bd8d489a40d08c1faa229bf1bb4cc8df0a98e0fd13d4694742117b2bc609d7fb75db1ed4aa148d3af7beca6a84866d87d65a1769b1d988" + +[http] +sha256 = "b6a245b8f37f3be93650c1a8ae41c01923f3b7303bbf2d016d8d03a1c92b3c10" +sha512 = "e2e0612213bb1272153b3858b13110a7fee0d89dc97d0021ab5ccccc8822ccc1f1629c8fa8dc582862411b2517ef3f79e7e51986c946654bf6ddd3f390cf36f2" + +[io] +sha256 = "87d8bf336f37184edc9290af9a14f7cac38c8fe087a786e66d08d1a94e48d186" +sha512 = "cdb5b35860c46c637ad37d784dd17f6023cf1c7c689d37973ec7c4094718baee4a16864e64622173b5578eed3950ad73211d9fe48a5b61b951809fdd9242b607" + +[logging] +sha256 = "8c32c7f893c938e0b76cd42e3de97e268dfe8fd0e7525d1f42b7ad10a88b00bb" +sha512 = "e9ce2adf02bb518c179a40b7d7af84bf44b99b2098609a897a114fb5af24946992dd7bd08da02c60d2768963f94414a16165f11343ec053a44b9e586058c812a" + +[poll] +sha256 = "9f2e6b5ea1a017575f96a3c415c737fe31513b043d9b47aefeb21f6c27ab8425" +sha512 = "f65965395742a0290fd9e4e55408c2b5ce3a4eae0ee22be1ff012e3db75910110da21c5f0a61083e043b4c510967a0a73ff4491ae9719e9158543f512dbeea5f" + +[preview] +path = "../../../wit" +sha256 = "007d4902193faef77b2df60714a8dfe8ec243e030c1ce61fb11c52788e3a5fe0" +sha512 = "bd06e8338e7fad0fc2e56ae01c5b184282c66ca79d2040eaec5a11efb14a4002b2a6234baaf354aff3cd57dc99835e4979eb71cdbd5d12800ea289d9a13252f2" +deps = ["clocks", "filesystem", "http", "io", "logging", "poll", "random", "sockets", "wasi-cli-base"] + +[random] +sha256 = "19d57f2262530016ec2b32991db3d8b6705b3a0bc5a7b6ae8fff3bcef0cf1088" +sha512 = "f9eba7dfd858d1edcaa868ce636f69545323bf010c3b0d4a2a1b23584d8027240c592d13e91882617e70a4dbf290754a8697724b5633147d11fa3db7869a2afe" + +[sockets] +sha256 = "d5a51604d53eec88d2735356ce7089a0eeb06886262e7ad2fc74be6b4dd943b2" +sha512 = "99bd86c0c59174bc9dafc559ca7dbab1aa9ed04a9b54ae3f7bb2d974b3a97bae8846acf1d77232edecda0aef5b23b9e4010d53ebf4a1d04292581078763530b7" + +[wasi-cli-base] +sha256 = "a0beee69392d2b55baff0b5f2285f50f717473b1369bdb3fbbfbefbaa3bd6c86" +sha512 = "d80eada98646bfeec1066437f1a4f69d67d117f38407f35c48d8b31667c171656059c5abf638c5b31169119ef5fa73bbf199933ac198f815f58281f124cb4f8d" diff --git a/crates/wasi-preview1-component-adapter/wit/deps.toml b/crates/wasi-preview1-component-adapter/wit/deps.toml new file mode 100644 index 000000000000..c90a145d08ce --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps.toml @@ -0,0 +1 @@ +preview = "../../../wit" diff --git a/crates/wasi-preview1-component-adapter/wit/deps/clocks/monotonic-clock.wit b/crates/wasi-preview1-component-adapter/wit/deps/clocks/monotonic-clock.wit new file mode 100644 index 000000000000..42e2981f579a --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/clocks/monotonic-clock.wit @@ -0,0 +1,32 @@ +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +/// +/// It is intended for measuring elapsed time. +default interface monotonic-clock { + use poll.poll.{pollable} + + /// A timestamp in nanoseconds. + type instant = u64 + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant + + /// Query the resolution of the clock. + resolution: func() -> instant + + /// Create a `pollable` which will resolve once the specified time has been + /// reached. + subscribe: func( + when: instant, + absolute: bool + ) -> pollable +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/clocks/timezone.wit b/crates/wasi-preview1-component-adapter/wit/deps/clocks/timezone.wit new file mode 100644 index 000000000000..63f99cc401b1 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/clocks/timezone.wit @@ -0,0 +1,61 @@ +default interface timezone { + use pkg.wall-clock.{datetime} + + /// A timezone. + /// + /// In timezones that recognize daylight saving time, also known as daylight + /// time and summer time, the information returned from the functions varies + /// over time to reflect these adjustments. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type timezone = u32 + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + display: func(this: timezone, when: datetime) -> timezone-display + + /// The same as `display`, but only return the UTC offset. + utc-offset: func(this: timezone, when: datetime) -> s32 + + /// Dispose of the specified input-stream, after which it may no longer + /// be used. + drop-timezone: func(this: timezone) + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/clocks/wall-clock.wit b/crates/wasi-preview1-component-adapter/wit/deps/clocks/wall-clock.wit new file mode 100644 index 000000000000..89c5a75da68b --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/clocks/wall-clock.wit @@ -0,0 +1,41 @@ +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +default interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/filesystem/filesystem.wit b/crates/wasi-preview1-component-adapter/wit/deps/filesystem/filesystem.wit new file mode 100644 index 000000000000..234ef39a8c23 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/filesystem/filesystem.wit @@ -0,0 +1,751 @@ +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +default interface filesystem { + use io.streams.{input-stream, output-stream} + use clocks.wall-clock.{datetime} + + /// File size or length of a region within a file. + type filesize = u64 + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// Device ID of device containing the file. + device: device, + /// File serial number. + inode: inode, + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + data-access-timestamp: datetime, + /// Last data modification timestamp. + data-modification-timestamp: datetime, + /// Last file status change timestamp. + status-change-timestamp: datetime, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Permissions mode used by `open-at`, `change-file-permissions-at`, and + /// similar. + flags modes { + /// True if the resource is considered readable by the containing + /// filesystem. + readable, + /// True if the resource is considered writeable by the containing + /// filesystem. + writeable, + /// True if the resource is considered executable by the containing + /// filesystem. This does not apply to directories. + executable, + } + + /// Number of hard links to an inode. + type link-count = u64 + + /// Identifier for a device containing a file system. Can be used in + /// combination with `inode` to uniquely identify a file or directory in + /// the filesystem. + type device = u64 + + /// Filesystem object serial number that is unique within its file system. + type inode = u64 + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The serial number of the object referred to by this directory entry. + /// May be none if the inode value is not known. + /// + /// When this is none, libc implementations might do an extra `stat-at` + /// call to retrieve the inode number to fill their `d_ino` fields, so + /// implementations which can set this to a non-none value should do so. + inode: option, + + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type descriptor = u32 + + /// Return a stream for reading from a file. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + this: descriptor, + /// The offset within the file at which to start reading. + offset: filesize, + ) -> input-stream + + /// Return a stream for writing to a file. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + this: descriptor, + /// The offset within the file at which to start writing. + offset: filesize, + ) -> output-stream + + /// Return a stream for appending to a file. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func( + this: descriptor, + ) -> output-stream + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + this: descriptor, + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code> + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func(this: descriptor) -> result<_, error-code> + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func(this: descriptor) -> result + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func(this: descriptor) -> result + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(this: descriptor, size: filesize) -> result<_, error-code> + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + this: descriptor, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + this: descriptor, + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code> + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + this: descriptor, + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func( + this: descriptor + ) -> result + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func(this: descriptor) -> result<_, error-code> + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + this: descriptor, + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code> + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func(this: descriptor) -> result + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code> + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: descriptor, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code> + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + /// Permissions to use when creating a new file. + modes: modes + ) -> result + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + this: descriptor, + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + this: descriptor, + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code> + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + this: descriptor, + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: descriptor, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code> + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + this: descriptor, + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code> + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + this: descriptor, + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code> + + /// Change the permissions of a filesystem object that is not a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-file-permissions-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the filesystem object. + modes: modes, + ) -> result<_, error-code> + + /// Change the permissions of a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" + /// flag. `read` on a directory implies readability and searchability, and + /// `execute` is not valid for directories. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-directory-permissions-at: func( + this: descriptor, + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the directory. + modes: modes, + ) -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. + lock-shared: func(this: descriptor) -> result<_, error-code> + + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. + lock-exclusive: func(this: descriptor) -> result<_, error-code> + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. + try-lock-shared: func(this: descriptor) -> result<_, error-code> + + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. + try-lock-exclusive: func(this: descriptor) -> result<_, error-code> + + /// Release a shared or exclusive lock on an open file. + /// + /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. + unlock: func(this: descriptor) -> result<_, error-code> + + /// Dispose of the specified `descriptor`, after which it may no longer + /// be used. + drop-descriptor: func(this: descriptor) + + /// A stream of directory entries. + /// + /// This [represents a stream of `dir-entry`](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Streams). + type directory-entry-stream = u32 + + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func( + this: directory-entry-stream + ) -> result, error-code> + + /// Dispose of the specified `directory-entry-stream`, after which it may no longer + /// be used. + drop-directory-entry-stream: func(this: directory-entry-stream) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/http/incoming-handler.wit b/crates/wasi-preview1-component-adapter/wit/deps/http/incoming-handler.wit new file mode 100644 index 000000000000..1ecff0aa5f5b --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/http/incoming-handler.wit @@ -0,0 +1,24 @@ +// The `wasi:http/incoming-handler` interface is meant to be exported by +// components and called by the host in response to a new incoming HTTP +// response. +// +// NOTE: in Preview3, this interface will be merged with +// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface +// that takes a `request` parameter and returns a `response` result. +// +default interface incoming-handler { + use pkg.types.{incoming-request, response-outparam} + + // The `handle` function takes an outparam instead of returning its response + // so that the component may stream its response while streaming any other + // request or response bodies. The callee MUST write a response to the + // `response-out` and then finish the response before returning. The `handle` + // function is allowed to continue execution after finishing the response's + // output stream. While this post-response execution is taken off the + // critical path, since there is no return value, there is no way to report + // its success or failure. + handle: func( + request: incoming-request, + response-out: response-outparam + ) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/http/outgoing-handler.wit b/crates/wasi-preview1-component-adapter/wit/deps/http/outgoing-handler.wit new file mode 100644 index 000000000000..abe812ffaba7 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/http/outgoing-handler.wit @@ -0,0 +1,18 @@ +// The `wasi:http/outgoing-handler` interface is meant to be imported by +// components and implemented by the host. +// +// NOTE: in Preview3, this interface will be merged with +// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface +// that takes a `request` parameter and returns a `response` result. +// +default interface outgoing-handler { + use pkg.types.{outgoing-request, request-options, future-incoming-response} + + // The parameter and result types of the `handle` function allow the caller + // to concurrently stream the bodies of the outgoing request and the incoming + // response. + handle: func( + request: outgoing-request, + options: option + ) -> future-incoming-response +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/http/types.wit b/crates/wasi-preview1-component-adapter/wit/deps/http/types.wit new file mode 100644 index 000000000000..bdcf79737273 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/http/types.wit @@ -0,0 +1,157 @@ +// The `wasi:http/types` interface is meant to be imported by components to +// define the HTTP resource types and operations used by the component's +// imported and exported interfaces. +default interface types { + use io.streams.{input-stream, output-stream} + use poll.poll.{pollable} + + // This type corresponds to HTTP standard Methods. + variant method { + get, + head, + post, + put, + delete, + connect, + options, + trace, + patch, + other(string) + } + + // This type corresponds to HTTP standard Related Schemes. + variant scheme { + HTTP, + HTTPS, + other(string) + } + + // TODO: perhaps better align with HTTP semantics? + // This type enumerates the different kinds of errors that may occur when + // initially returning a response. + variant error { + invalid-url(string), + timeout-error(string), + protocol-error(string), + unexpected-error(string) + } + + // This following block defines the `fields` resource which corresponds to + // HTTP standard Fields. Soon, when resource types are added, the `type + // fields = u32` type alias can be replaced by a proper `resource fields` + // definition containing all the functions using the method syntactic sugar. + type fields = u32 + drop-fields: func(fields: fields) + new-fields: func(entries: list>) -> fields + fields-get: func(fields: fields, name: string) -> list + fields-set: func(fields: fields, name: string, value: list) + fields-delete: func(fields: fields, name: string) + fields-append: func(fields: fields, name: string, value: string) + fields-entries: func(fields: fields) -> list> + fields-clone: func(fields: fields) -> fields + + type headers = fields + type trailers = fields + + // The following block defines stream types which corresponds to the HTTP + // standard Contents and Trailers. With Preview3, all of these fields can be + // replaced by a stream>. In the interim, we need to + // build on separate resource types defined by `wasi:io/streams`. The + // `finish-` functions emulate the stream's result value and MUST be called + // exactly once after the final read/write from/to the stream before dropping + // the stream. + type incoming-stream = input-stream + type outgoing-stream = output-stream + finish-incoming-stream: func(s: incoming-stream) -> option + finish-outgoing-stream: func(s: outgoing-stream, trailers: option) + + // The following block defines the `incoming-request` and `outgoing-request` + // resource types that correspond to HTTP standard Requests. Soon, when + // resource types are added, the `u32` type aliases can be replaced by + // proper `resource` type definitions containing all the functions as + // methods. Later, Preview2 will allow both types to be merged together into + // a single `request` type (that uses the single `stream` type mentioned + // above). The `consume` and `write` methods may only be called once (and + // return failure thereafter). + type incoming-request = u32 + type outgoing-request = u32 + drop-incoming-request: func(request: incoming-request) + drop-outgoing-request: func(request: outgoing-request) + incoming-request-method: func(request: incoming-request) -> method + incoming-request-path: func(request: incoming-request) -> string + incoming-request-query: func(request: incoming-request) -> string + incoming-request-scheme: func(request: incoming-request) -> option + incoming-request-authority: func(request: incoming-request) -> string + incoming-request-headers: func(request: incoming-request) -> headers + incoming-request-consume: func(request: incoming-request) -> result + new-outgoing-request: func( + method: method, + path: string, + query: string, + scheme: option, + authority: string, + headers: headers + ) -> outgoing-request + outgoing-request-write: func(request: outgoing-request) -> result + + // Additional optional parameters that can be set when making a request. + record request-options { + // The following timeouts are specific to the HTTP protocol and work + // independently of the overall timeouts passed to `io.poll.poll-oneoff`. + + // The timeout for the initial connect. + connect-timeout-ms: option, + + // The timeout for receiving the first byte of the response body. + first-byte-timeout-ms: option, + + // The timeout for receiving the next chunk of bytes in the response body + // stream. + between-bytes-timeout-ms: option + } + + // The following block defines a special resource type used by the + // `wasi:http/incoming-handler` interface. When resource types are added, this + // block can be replaced by a proper `resource response-outparam { ... }` + // definition. Later, with Preview3, the need for an outparam goes away entirely + // (the `wasi:http/handler` interface used for both incoming and outgoing can + // simply return a `stream`). + type response-outparam = u32 + drop-response-outparam: func(response: response-outparam) + set-response-outparam: func(response: result) -> result + + // This type corresponds to the HTTP standard Status Code. + type status-code = u16 + + // The following block defines the `incoming-response` and `outgoing-response` + // resource types that correspond to HTTP standard Responses. Soon, when + // resource types are added, the `u32` type aliases can be replaced by proper + // `resource` type definitions containing all the functions as methods. Later, + // Preview2 will allow both types to be merged together into a single `response` + // type (that uses the single `stream` type mentioned above). The `consume` and + // `write` methods may only be called once (and return failure thereafter). + type incoming-response = u32 + type outgoing-response = u32 + drop-incoming-response: func(response: incoming-response) + drop-outgoing-response: func(response: outgoing-response) + incoming-response-status: func(response: incoming-response) -> status-code + incoming-response-headers: func(response: incoming-response) -> headers + incoming-response-consume: func(response: incoming-response) -> result + new-outgoing-response: func( + status-code: status-code, + headers: headers + ) -> outgoing-response + outgoing-response-write: func(response: outgoing-response) -> result + + // The following block defines a special resource type used by the + // `wasi:http/outgoing-handler` interface to emulate + // `future>` in advance of Preview3. Given a + // `future-incoming-response`, the client can call the non-blocking `get` + // method to get the result if it is available. If the result is not available, + // the client can call `listen` to get a `pollable` that can be passed to + // `io.poll.poll-oneoff`. + type future-incoming-response = u32 + drop-future-incoming-response: func(f: future-incoming-response) + future-incoming-response-get: func(f: future-incoming-response) -> option> + listen-to-future-incoming-response: func(f: future-incoming-response) -> pollable +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/io/streams.wit b/crates/wasi-preview1-component-adapter/wit/deps/io/streams.wit new file mode 100644 index 000000000000..c1567fd4c1c8 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/io/streams.wit @@ -0,0 +1,213 @@ +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +default interface streams { + use poll.poll.{pollable} + + /// An error type returned from a stream operation. Currently this + /// doesn't provide any additional information. + record stream-error {} + + /// An input bytestream. In the future, this will be replaced by handle + /// types. + /// + /// This conceptually represents a `stream`. It's temporary + /// scaffolding until component-model's async features are ready. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe-to-input-stream` function to obtain a `pollable` which + /// can be polled for using `wasi_poll`. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type input-stream = u32 + + /// Read bytes from a stream. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// stream was reached. The returned list will contain up to `len` bytes; it + /// may return fewer than requested, but not more. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// If `len` is 0, it represents a request to read 0 bytes, which should + /// always succeed, assuming the stream hasn't reached its end yet, and + /// return an empty list. + /// + /// The len here is a `u64`, but some callees may not be able to allocate + /// a buffer as large as that would imply. + /// FIXME: describe what happens if allocation fails. + read: func( + this: input-stream, + /// The maximum number of bytes to read + len: u64 + ) -> result, bool>, stream-error> + + /// Read bytes from a stream, with blocking. + /// + /// This is similar to `read`, except that it blocks until at least one + /// byte can be read. + blocking-read: func( + this: input-stream, + /// The maximum number of bytes to read + len: u64 + ) -> result, bool>, stream-error> + + /// Skip bytes from a stream. + /// + /// This is similar to the `read` function, but avoids copying the + /// bytes into the instance. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// This function returns the number of bytes skipped, along with a bool + /// indicating whether the end of the stream was reached. The returned + /// value will be at most `len`; it may be less. + skip: func( + this: input-stream, + /// The maximum number of bytes to skip. + len: u64, + ) -> result, stream-error> + + /// Skip bytes from a stream, with blocking. + /// + /// This is similar to `skip`, except that it blocks until at least one + /// byte can be consumed. + blocking-skip: func( + this: input-stream, + /// The maximum number of bytes to skip. + len: u64, + ) -> result, stream-error> + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + subscribe-to-input-stream: func(this: input-stream) -> pollable + + /// Dispose of the specified `input-stream`, after which it may no longer + /// be used. + drop-input-stream: func(this: input-stream) + + /// An output bytestream. In the future, this will be replaced by handle + /// types. + /// + /// This conceptually represents a `stream`. It's temporary + /// scaffolding until component-model's async features are ready. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe-to-output-stream` function to obtain a + /// `pollable` which can be polled for using `wasi_poll`. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type output-stream = u32 + + /// Write bytes to a stream. + /// + /// This function returns a `u64` indicating the number of bytes from + /// `buf` that were written; it may be less than the full list. + write: func( + this: output-stream, + /// Data to write + buf: list + ) -> result + + /// Write bytes to a stream, with blocking. + /// + /// This is similar to `write`, except that it blocks until at least one + /// byte can be written. + blocking-write: func( + this: output-stream, + /// Data to write + buf: list + ) -> result + + /// Write multiple zero bytes to a stream. + /// + /// This function returns a `u64` indicating the number of zero bytes + /// that were written; it may be less than `len`. + write-zeroes: func( + this: output-stream, + /// The number of zero bytes to write + len: u64 + ) -> result + + /// Write multiple zero bytes to a stream, with blocking. + /// + /// This is similar to `write-zeroes`, except that it blocks until at least + /// one byte can be written. + blocking-write-zeroes: func( + this: output-stream, + /// The number of zero bytes to write + len: u64 + ) -> result + + /// Read from one stream and write to another. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + /// + /// Unlike other I/O functions, this function blocks until all the data + /// read from the input stream has been written to the output stream. + splice: func( + this: output-stream, + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result, stream-error> + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until at least + /// one byte can be read. + blocking-splice: func( + this: output-stream, + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result, stream-error> + + /// Forward the entire contents of an input stream to an output stream. + /// + /// This function repeatedly reads from the input stream and writes + /// the data to the output stream, until the end of the input stream + /// is reached, or an error is encountered. + /// + /// Unlike other I/O functions, this function blocks until the end + /// of the input stream is seen and all the data has been written to + /// the output stream. + /// + /// This function returns the number of bytes transferred. + forward: func( + this: output-stream, + /// The stream to read from + src: input-stream + ) -> result + + /// Create a `pollable` which will resolve once either the specified stream + /// is ready to accept bytes or the other end of the stream has been closed. + subscribe-to-output-stream: func(this: output-stream) -> pollable + + /// Dispose of the specified `output-stream`, after which it may no longer + /// be used. + drop-output-stream: func(this: output-stream) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/logging/handler.wit b/crates/wasi-preview1-component-adapter/wit/deps/logging/handler.wit new file mode 100644 index 000000000000..c9632b9cc4f8 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/logging/handler.wit @@ -0,0 +1,32 @@ +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +default interface handler { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + + /// Describes messages indicating hazardous situations. + warn, + + /// Describes messages indicating serious errors. + error, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/poll/poll.wit b/crates/wasi-preview1-component-adapter/wit/deps/poll/poll.wit new file mode 100644 index 000000000000..28f08e17d755 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/poll/poll.wit @@ -0,0 +1,39 @@ +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +default interface poll { + /// A "pollable" handle. + /// + /// This is conceptually represents a `stream<_, _>`, or in other words, + /// a stream that one can wait on, repeatedly, but which does not itself + /// produce any data. It's temporary scaffolding until component-model's + /// async features are ready. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// `pollable` lifetimes are not automatically managed. Users must ensure + /// that they do not outlive the resource they reference. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type pollable = u32 + + /// Dispose of the specified `pollable`, after which it may no longer + /// be used. + drop-pollable: func(this: pollable) + + /// Poll for completion on a set of pollables. + /// + /// The "oneoff" in the name refers to the fact that this function must do a + /// linear scan through the entire list of subscriptions, which may be + /// inefficient if the number is large and the same subscriptions are used + /// many times. In the future, this is expected to be obsoleted by the + /// component model async proposal, which will include a scalable waiting + /// facility. + /// + /// Note that the return type would ideally be `list`, but that would + /// be more difficult to polyfill given the current state of `wit-bindgen`. + /// See + /// for details. For now, we use zero to mean "not ready" and non-zero to + /// mean "ready". + poll-oneoff: func(in: list) -> list +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/preview/command-extended.wit b/crates/wasi-preview1-component-adapter/wit/deps/preview/command-extended.wit new file mode 100644 index 000000000000..11a0444eda0d --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/preview/command-extended.wit @@ -0,0 +1,29 @@ +default world command-extended { + import wall-clock: clocks.wall-clock + import monotonic-clock: clocks.monotonic-clock + import timezone: clocks.timezone + import filesystem: filesystem.filesystem + import instance-network: sockets.instance-network + import ip-name-lookup: sockets.ip-name-lookup + import network: sockets.network + import tcp-create-socket: sockets.tcp-create-socket + import tcp: sockets.tcp + import udp-create-socket: sockets.udp-create-socket + import udp: sockets.udp + import random: random.random + import poll: poll.poll + import streams: io.streams + import environment: wasi-cli-base.environment + import preopens: wasi-cli-base.preopens + import exit: wasi-cli-base.exit + + // We should replace all others with `include self.command` + // as soon as the unioning of worlds is available: + // https://github.com/WebAssembly/component-model/issues/169 + import console: logging.handler + import default-outgoing-HTTP: http.outgoing-handler + + export run: func( + args: list, + ) -> result +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/preview/command.wit b/crates/wasi-preview1-component-adapter/wit/deps/preview/command.wit new file mode 100644 index 000000000000..3e929515f0ba --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/preview/command.wit @@ -0,0 +1,21 @@ +default world command { + import wall-clock: clocks.wall-clock + import monotonic-clock: clocks.monotonic-clock + import timezone: clocks.timezone + import filesystem: filesystem.filesystem + import instance-network: sockets.instance-network + import ip-name-lookup: sockets.ip-name-lookup + import network: sockets.network + import tcp-create-socket: sockets.tcp-create-socket + import tcp: sockets.tcp + import udp-create-socket: sockets.udp-create-socket + import udp: sockets.udp + import random: random.random + import poll: poll.poll + import streams: io.streams + import environment: wasi-cli-base.environment + import preopens: wasi-cli-base.preopens + import exit: wasi-cli-base.exit + + export run: func() -> result +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/preview/proxy.wit b/crates/wasi-preview1-component-adapter/wit/deps/preview/proxy.wit new file mode 100644 index 000000000000..2f444758946b --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/preview/proxy.wit @@ -0,0 +1,6 @@ +default world proxy { + import random: random.random + import console: logging.handler + import default-outgoing-HTTP: http.outgoing-handler + export HTTP: http.incoming-handler +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/preview/reactor.wit b/crates/wasi-preview1-component-adapter/wit/deps/preview/reactor.wit new file mode 100644 index 000000000000..2abfa466ba3b --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/preview/reactor.wit @@ -0,0 +1,21 @@ +default world reactor { + import wall-clock: clocks.wall-clock + import monotonic-clock: clocks.monotonic-clock + import timezone: clocks.timezone + import filesystem: filesystem.filesystem + import instance-network: sockets.instance-network + import ip-name-lookup: sockets.ip-name-lookup + import network: sockets.network + import tcp-create-socket: sockets.tcp-create-socket + import tcp: sockets.tcp + import udp-create-socket: sockets.udp-create-socket + import udp: sockets.udp + import random: random.random + import poll: poll.poll + import streams: io.streams + import console: logging.handler + import default-outgoing-HTTP: http.outgoing-handler + import environment: wasi-cli-base.environment + import preopens: wasi-cli-base.preopens + import exit: wasi-cli-base.exit +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/random/random.wit b/crates/wasi-preview1-component-adapter/wit/deps/random/random.wit new file mode 100644 index 000000000000..2080ddfde9dd --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/random/random.wit @@ -0,0 +1,42 @@ +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +default interface random { + /// Return `len` cryptographically-secure pseudo-random bytes. + /// + /// This function must produce data from an adequately seeded + /// cryptographically-secure pseudo-random number generator (CSPRNG), so it + /// must not block, from the perspective of the calling program, and the + /// returned data is always unpredictable. + /// + /// This function must always return fresh pseudo-random data. Deterministic + /// environments must omit this function, rather than implementing it with + /// deterministic data. + get-random-bytes: func(len: u64) -> list + + /// Return a cryptographically-secure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-random-bytes`, represented as a `u64`. + get-random-u64: func() -> u64 + + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-random: func() -> tuple +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/instance-network.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/instance-network.wit new file mode 100644 index 000000000000..b1f5c982d976 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/instance-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +default interface instance-network { + use pkg.network.{network} + + /// Get a handle to the default network. + instance-network: func() -> network + +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/ip-name-lookup.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/ip-name-lookup.wit new file mode 100644 index 000000000000..b594598e0090 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,71 @@ + +default interface ip-name-lookup { + use poll.poll.{pollable} + use pkg.network.{network, error, ip-address, ip-address-family} + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// Parameters: + /// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted + /// to ASCII using IDNA encoding. + /// - `address-family`: If provided, limit the results to addresses of this specific address family. + /// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime + /// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on + /// systems without an active IPv6 interface. Notes: + /// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. + /// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. + /// + /// This function never blocks. It either immediately returns successfully with a `resolve-address-stream` + /// that can be used to (asynchronously) fetch the results. + /// Or it immediately fails whenever `name` is: + /// - empty + /// - an IP address + /// - a syntactically invalid domain name in another way + /// + /// References: + /// - + /// - + /// + resolve-addresses: func(network: network, name: string, address-family: option, include-unavailable: bool) -> result + + + + type resolve-address-stream = u32 + + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// After which, you should release the stream with `drop-resolve-address-stream`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + resolve-next-address: func(this: resolve-address-stream) -> result, error> + + + + /// Dispose of the specified `resolve-address-stream`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-resolve-address-stream: func(this: resolve-address-stream) + + /// Get/set the blocking mode of the stream. + /// + /// By default a stream is in "blocking" mode, meaning that any function blocks and waits for its completion. + /// When switched to "non-blocking" mode, operations that would block return an `again` error. After which + /// the API consumer is expected to call `subscribe` and wait for completion using the wasi-poll module. + /// + /// Note: these functions are here for WASI Preview2 only. + /// They're planned to be removed when `future` is natively supported in Preview3. + non-blocking: func(this: resolve-address-stream) -> result + set-non-blocking: func(this: resolve-address-stream, value: bool) -> result<_, error> + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func(this: resolve-address-stream) -> pollable +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/network.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/network.wit new file mode 100644 index 000000000000..1f3a20d6b8c8 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/network.wit @@ -0,0 +1,56 @@ + +default interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + /// + /// FYI, In the future this will be replaced by handle types. + type network = u32 + + /// Dispose of the specified `network`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-network: func(this: network) + + + + enum error { + unknown, + again, + // TODO ... + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple + type ipv6-address = tuple + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} \ No newline at end of file diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp-create-socket.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp-create-socket.wit new file mode 100644 index 000000000000..571a0197a90e --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,19 @@ + +default interface tcp-create-socket { + use pkg.network.{network, error, ip-address-family} + use pkg.tcp.{tcp-socket} + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// References: + /// - + /// - + /// + create-tcp-socket: func(address-family: ip-address-family) -> result +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp.wit new file mode 100644 index 000000000000..b2f483368f97 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/tcp.wit @@ -0,0 +1,188 @@ + +default interface tcp { + use io.streams.{input-stream, output-stream} + use poll.poll.{pollable} + use pkg.network.{network, error, ip-socket-address, ip-address-family} + + /// A TCP socket handle. + type tcp-socket = u32 + + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Fails when: + /// - the socket is already bound. + /// + /// References + /// - + /// - + bind: func(this: tcp-socket, network: network, local-address: ip-socket-address) -> result<_, error> + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// Fails when: + /// - the socket is already bound to a different network. + /// - the provided network does not allow connections to the specified endpoint. + /// - the socket is already in the Connection or Listener state. + /// - either the remote IP address or port is 0. + /// + /// References + /// - + /// - + connect: func(this: tcp-socket, network: network, remote-address: ip-socket-address) -> result, error> + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Fails when: + /// - the socket is already bound to a different network. + /// - the provided network does not allow listening on the specified address. + /// - the socket is already in the Connection or Listener state. + /// + /// References + /// - + /// - + listen: func(this: tcp-socket, network: network) -> result<_, error> + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// Fails when this socket is not in the Listening state. + /// + /// References: + /// - + /// - + accept: func(this: tcp-socket) -> result, error> + + /// Get the bound local address. + /// + /// Returns an error if the socket is not bound. + /// + /// References + /// - + /// - + local-address: func(this: tcp-socket) -> result + + /// Get the bound remote address. + /// + /// Fails when the socket is not in the Connection state. + /// + /// References + /// - + /// - + remote-address: func(this: tcp-socket) -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func(this: tcp-socket) -> result + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// Implementations are not required to support dual-stack mode. Calling `set-ipv6-only(false)` might fail. + /// + /// Fails when called on an IPv4 socket. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + ipv6-only: func(this: tcp-socket) -> result + set-ipv6-only: func(this: tcp-socket, value: bool) -> result<_, error> + + /// Hints the desired listen queue size. Implementations are free to ignore this. + set-listen-backlog-size: func(this: tcp-socket, value: u64) -> result<_, error> + + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive: func(this: tcp-socket) -> result + set-keep-alive: func(this: tcp-socket, value: bool) -> result<_, error> + + /// Equivalent to the TCP_NODELAY socket option. + no-delay: func(this: tcp-socket) -> result + set-no-delay: func(this: tcp-socket, value: bool) -> result<_, error> + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + unicast-hop-limit: func(this: tcp-socket) -> result + set-unicast-hop-limit: func(this: tcp-socket, value: u8) -> result<_, error> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Fails when this socket is in the Listening state. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + receive-buffer-size: func(this: tcp-socket) -> result + set-receive-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error> + send-buffer-size: func(this: tcp-socket) -> result + set-send-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error> + + /// Get/set the blocking mode of the socket. + /// + /// By default a socket is in "blocking" mode, meaning that any function blocks and waits for its completion. + /// When switched to "non-blocking" mode, operations that would block return an `again` error. After which + /// the API consumer is expected to call `subscribe` and wait for completion using the wasi-poll module. + /// + /// Note: these functions are here for WASI Preview2 only. + /// They're planned to be removed when `future` is natively supported in Preview3. + non-blocking: func(this: tcp-socket) -> result + set-non-blocking: func(this: tcp-socket, value: bool) -> result<_, error> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func(this: tcp-socket) -> pollable + + /// Gracefully shut down the connection. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close the socket. + /// + /// Fails when the socket is not in the Connection state. + /// + /// References + /// - + /// - + shutdown: func(this: tcp-socket, shutdown-type: shutdown-type) -> result<_, error> + + /// Dispose of the specified `tcp-socket`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-tcp-socket: func(this: tcp-socket) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/udp-create-socket.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/udp-create-socket.wit new file mode 100644 index 000000000000..169957c9fe80 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/udp-create-socket.wit @@ -0,0 +1,19 @@ + +default interface udp-create-socket { + use pkg.network.{network, error, ip-address-family} + use pkg.udp.{udp-socket} + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// References: + /// - + /// - + /// + create-udp-socket: func(address-family: ip-address-family) -> result +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/sockets/udp.wit b/crates/wasi-preview1-component-adapter/wit/deps/sockets/udp.wit new file mode 100644 index 000000000000..af8f873b9b6c --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/sockets/udp.wit @@ -0,0 +1,162 @@ + +default interface udp { + use poll.poll.{pollable} + use pkg.network.{network, error, ip-socket-address, ip-address-family} + + + /// A UDP socket handle. + type udp-socket = u32 + + + record datagram { + data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + remote-address: ip-socket-address, + + /// Possible future additions: + /// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO + /// local-interface: u32, // IP_PKTINFO / IP_RECVIF + /// ttl: u8, // IP_RECVTTL + /// dscp: u6, // IP_RECVTOS + /// ecn: u2, // IP_RECVTOS + } + + + + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a connect, send or receive operation will + /// implicitly bind the socket. + /// + /// Fails when: + /// - the socket is already bound. + /// + /// References + /// - + /// - + bind: func(this: udp-socket, network: network, local-address: ip-socket-address) -> result<_, error> + + /// Set the destination address. + /// + /// The local-address is updated based on the best network path to `remote-address`. + /// + /// When a destination address is set: + /// - all receive operations will only return datagrams sent from the provided `remote-address`. + /// - the `send` function can only be used to send to this destination. + /// + /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". + /// + /// Fails when: + /// - the socket is already bound to a different network. + /// + /// References + /// - + /// - + connect: func(this: udp-socket, network: network, remote-address: ip-socket-address) -> result<_, error> + + /// Receive a message. + /// + /// Returns: + /// - The sender address of the datagram + /// - The number of bytes read. + /// + /// Fails when: + /// - the socket is not bound. + /// + /// References + /// - + /// - + /// - + receive: func(this: udp-socket) -> result + + /// Send a message to a specific destination address. + /// + /// The remote address option is required. To send a message to the "connected" peer, + /// call `remote-address` to get their address. + /// + /// Fails when: + /// - the socket is not bound. Unlike POSIX, this function does not perform an implicit bind. + /// - the socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. + /// + /// References + /// - + /// - + /// - + send: func(this: udp-socket, datagram: datagram) -> result<_, error> + + /// Get the current bound address. + /// + /// Returns an error if the socket is not bound. + /// + /// References + /// - + /// - + local-address: func(this: udp-socket) -> result + + /// Get the address set with `connect`. + /// + /// References + /// - + /// - + remote-address: func(this: udp-socket) -> result + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func(this: udp-socket) -> result + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// Implementations are not required to support dual-stack mode, so calling `set-ipv6-only(false)` might fail. + /// + /// Fails when called on an IPv4 socket. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + ipv6-only: func(this: udp-socket) -> result + set-ipv6-only: func(this: udp-socket, value: bool) -> result<_, error> + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + unicast-hop-limit: func(this: udp-socket) -> result + set-unicast-hop-limit: func(this: udp-socket, value: u8) -> result<_, error> + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Fails when this socket is in the Listening state. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + receive-buffer-size: func(this: udp-socket) -> result + set-receive-buffer-size: func(this: udp-socket, value: u64) -> result<_, error> + send-buffer-size: func(this: udp-socket) -> result + set-send-buffer-size: func(this: udp-socket, value: u64) -> result<_, error> + + /// Get/set the blocking mode of the socket. + /// + /// By default a socket is in "blocking" mode, meaning that any function blocks and waits for its completion. + /// When switched to "non-blocking" mode, operations that would block return an `again` error. After which + /// the API consumer is expected to call `subscribe` and wait for completion using the wasi-poll module. + /// + /// Note: these functions are here for WASI Preview2 only. + /// They're planned to be removed when `future` is natively supported in Preview3. + non-blocking: func(this: udp-socket) -> result + set-non-blocking: func(this: udp-socket, value: bool) -> result<_, error> + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func(this: udp-socket) -> pollable + + /// Dispose of the specified `udp-socket`, after which it may no longer be used. + /// + /// Note: this function is scheduled to be removed when Resources are natively supported in Wit. + drop-udp-socket: func(this: udp-socket) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/environment.wit b/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/environment.wit new file mode 100644 index 000000000000..876ea3a0c921 --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/environment.wit @@ -0,0 +1,14 @@ +default interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list> + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/exit.wit b/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/exit.wit new file mode 100644 index 000000000000..2759e9dd989c --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/exit.wit @@ -0,0 +1,4 @@ +default interface wasi-exit { + /// Exit the curerent instance and any linked instances. + exit: func(status: result) +} diff --git a/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/preopens.wit b/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/preopens.wit new file mode 100644 index 000000000000..52a93442078e --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/deps/wasi-cli-base/preopens.wit @@ -0,0 +1,17 @@ +default interface preopens { + use filesystem.filesystem.{descriptor} + use io.streams.{input-stream, output-stream} + + /// Stdio preopens: these are the resources that provide stdin, stdout, and + /// stderr. + record stdio-preopens { + stdin: input-stream, + stdout: output-stream, + stderr: output-stream, + } + + /// Return the set of stdio preopens. + get-stdio: func() -> stdio-preopens + /// Return the set of of preopened directories, and their path. + get-directories: func() -> list> +} diff --git a/crates/wasi-preview1-component-adapter/wit/reactor.wit b/crates/wasi-preview1-component-adapter/wit/reactor.wit new file mode 100644 index 000000000000..2abfa466ba3b --- /dev/null +++ b/crates/wasi-preview1-component-adapter/wit/reactor.wit @@ -0,0 +1,21 @@ +default world reactor { + import wall-clock: clocks.wall-clock + import monotonic-clock: clocks.monotonic-clock + import timezone: clocks.timezone + import filesystem: filesystem.filesystem + import instance-network: sockets.instance-network + import ip-name-lookup: sockets.ip-name-lookup + import network: sockets.network + import tcp-create-socket: sockets.tcp-create-socket + import tcp: sockets.tcp + import udp-create-socket: sockets.udp-create-socket + import udp: sockets.udp + import random: random.random + import poll: poll.poll + import streams: io.streams + import console: logging.handler + import default-outgoing-HTTP: http.outgoing-handler + import environment: wasi-cli-base.environment + import preopens: wasi-cli-base.preopens + import exit: wasi-cli-base.exit +} diff --git a/src/main.o b/src/main.o deleted file mode 100644 index 8f0af8b73feb57955cd6d54dbc4570728c356ddb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmXwxK?=e!6h!Bz!AiTRC-DwqUF!{gg9a%!38YnUWpL$2aN!N~0^ZQ1=xYAVpLtNd zApmp>AwV>sO9P0{=rV0j6?4~GtvnlToIeX7Mn<)kjoTeN%{<|LP<{Yq*EA#KBaCEf wfr|T>mY78iSfDELN>?7iht}@J^qH){n|_-O{>(@E6kd03J72Erl_W^|0&03QG5`Po From 3504c0c8027807572927cc697176fc07316c0eb9 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 12 May 2023 13:55:41 -0700 Subject: [PATCH 135/153] ci: add build-preview1-component-adapter step. prtest:full --- .github/workflows/main.yml | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a1f8428686a..86c0afae40f5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -523,6 +523,46 @@ jobs: env: GH_TOKEN: ${{ github.token }} + build-preview1-component-adapter: + name: Build wasi-preview1-component-adapter + needs: determine + if: needs.determine.outputs.run-full + runs-on: ubuntu-latest + permissions: + deployments: write + contents: write + steps: + - uses: actions/checkout@v3 + - run: rustup update stable && rustup default stable + - run: rustup target add wasm32-wasi wasm32-unknown-unknown + + - name: Install wasm-tools + run: | + curl -L https://github.com/bytecodealliance/wasm-tools/releases/download/wasm-tools-1.0.27/wasm-tools-1.0.27-x86_64-linux.tar.gz | tar xfz - + echo `pwd`/wasm-tools-1.0.27-x86_64-linux >> $GITHUB_PATH + + - run: ./ci/build-wasi-preview1-component-adapter.sh + env: + VERSION: ${{ github.sha }} + + - uses: actions/upload-artifact@v3 + with: + name: wasi_preview1_component_adapter.reactor.wasm + path: crates/wasi-preview1-component-adapter/target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.reactor.wasm + + - uses: actions/upload-artifact@v3 + with: + name: wasi_preview1_component_adapter.command.wasm + path: crates/wasi-preview1-component-adapter/target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.command.wasm + + + # common logic to cancel the entire run if this job fails + - run: gh run cancel ${{ github.run_id }} + if: failure() && github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + + bench: needs: determine if: needs.determine.outputs.run-full From bb5d051584658f24b5a3caa403726b619fb1257a Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 12 May 2023 14:25:36 -0700 Subject: [PATCH 136/153] temporary: always run the adapter build CI step --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 86c0afae40f5..3198064662b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -526,7 +526,7 @@ jobs: build-preview1-component-adapter: name: Build wasi-preview1-component-adapter needs: determine - if: needs.determine.outputs.run-full + # if: needs.determine.outputs.run-full runs-on: ubuntu-latest permissions: deployments: write From 424deb98feb5aac3ae2f01ae7a9c1aa3176729e6 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 12 May 2023 14:27:40 -0700 Subject: [PATCH 137/153] add ci/build-wasi-preview1-component-adapter script --- ci/build-wasi-preview1-component-adapter.sh | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 ci/build-wasi-preview1-component-adapter.sh diff --git a/ci/build-wasi-preview1-component-adapter.sh b/ci/build-wasi-preview1-component-adapter.sh new file mode 100755 index 000000000000..babab0dc0d3e --- /dev/null +++ b/ci/build-wasi-preview1-component-adapter.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -ex + +BASEDIR=$(dirname "$0") +cd ${BASEDIR}/../crates/wasi-preview1-component-adapter/ + +# Debug build, default features (reactor) +cargo build --target wasm32-unknown-unknown +cargo run -p verify-component-adapter -- target/wasm32-unknown-unknown/debug/wasi_preview1_component_adapter.wasm + +# Debug build, command +cargo build --target wasm32-unknown-unknown --no-default-features --features command +cargo run -p verify-component-adapter -- target/wasm32-unknown-unknown/debug/wasi_preview1_component_adapter.wasm + +# Release build, command +cargo build --target wasm32-unknown-unknown --release --no-default-features --features command +cargo run -p verify-component-adapter -- target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm +wasm-tools metadata add --name "wasi_preview1_component_adapter.command.adapter:${VERSION}" target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm -o target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.command.wasm + +# Release build, default features (reactor) +cargo build --target wasm32-unknown-unknown --release +cargo run -p verify-component-adapter -- ./target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm +wasm-tools metadata add --name "wasi_preview1_component_adapter.reactor.adapter:${VERSION}" target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm -o target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.reactor.wasm From 880f94a7e057ef139c2e74e12153d013c9ebc564 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 12 May 2023 14:49:30 -0700 Subject: [PATCH 138/153] put both adapter binaries in one artifact, name it bins-*, publish it --- .github/workflows/main.yml | 9 ++------- .github/workflows/publish-artifacts.yml | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3198064662b7..0fe87375bf15 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -547,13 +547,8 @@ jobs: - uses: actions/upload-artifact@v3 with: - name: wasi_preview1_component_adapter.reactor.wasm - path: crates/wasi-preview1-component-adapter/target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.reactor.wasm - - - uses: actions/upload-artifact@v3 - with: - name: wasi_preview1_component_adapter.command.wasm - path: crates/wasi-preview1-component-adapter/target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.command.wasm + name: bins-wasi-preview1-component-adapter + path: crates/wasi-preview1-component-adapter/target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.*.wasm # common logic to cancel the entire run if this job fails diff --git a/.github/workflows/publish-artifacts.yml b/.github/workflows/publish-artifacts.yml index ea3c3216158d..23d6a263bf12 100644 --- a/.github/workflows/publish-artifacts.yml +++ b/.github/workflows/publish-artifacts.yml @@ -42,7 +42,7 @@ jobs: - run: | mkdir dist mv -t dist bins-*/*.tar.* - mv -t dist bins-*/*.{zip,msi} + mv -t dist bins-*/*.{zip,msi,wasm} - name: Publish Release uses: ./.github/actions/github-release with: From 968a61dbc402fbfc429353f9b4d30b479d1fa556 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 12 May 2023 15:31:32 -0700 Subject: [PATCH 139/153] restore adapter to only building if run-full --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0fe87375bf15..3cd72f3a46de 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -526,7 +526,7 @@ jobs: build-preview1-component-adapter: name: Build wasi-preview1-component-adapter needs: determine - # if: needs.determine.outputs.run-full + if: needs.determine.outputs.run-full runs-on: ubuntu-latest permissions: deployments: write From 19858ddc984cb247f6c5d617001f4cdf0498d692 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 09:59:44 -0700 Subject: [PATCH 140/153] change adapter to be part of root workspace --- .github/workflows/main.yml | 2 +- Cargo.lock | 193 ++++++++++++++++-- Cargo.toml | 21 ++ ci/build-wasi-preview1-component-adapter.sh | 11 +- .../.gitignore | 1 - .../Cargo.toml | 36 ---- 6 files changed, 200 insertions(+), 64 deletions(-) delete mode 100644 crates/wasi-preview1-component-adapter/.gitignore diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3cd72f3a46de..6782eadd058e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -548,7 +548,7 @@ jobs: - uses: actions/upload-artifact@v3 with: name: bins-wasi-preview1-component-adapter - path: crates/wasi-preview1-component-adapter/target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.*.wasm + path: target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.*.wasm # common logic to cancel the entire run if this job fails diff --git a/Cargo.lock b/Cargo.lock index f18b99a22888..9b618f3b509b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,6 +234,10 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byte-array-literals" +version = "10.0.0" + [[package]] name = "byteorder" version = "1.4.3" @@ -673,7 +677,7 @@ dependencies = [ "target-lexicon", "thiserror", "toml", - "wasmparser", + "wasmparser 0.103.0", "wat", ] @@ -849,7 +853,7 @@ dependencies = [ "serde", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.103.0", "wasmtime-types", "wat", ] @@ -1538,6 +1542,9 @@ name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "hermit-abi" @@ -3307,6 +3314,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.9" @@ -3374,6 +3387,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "verify-component-adapter" +version = "10.0.0" +dependencies = [ + "anyhow", + "wasmparser 0.92.0", + "wat", +] + [[package]] name = "version_check" version = "0.9.4" @@ -3497,6 +3519,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "wasi-preview1-component-adapter" +version = "10.0.0" +dependencies = [ + "byte-array-literals", + "object", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-encoder 0.25.0", + "wit-bindgen", +] + [[package]] name = "wasi-tokio" version = "10.0.0" @@ -3586,6 +3619,19 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-metadata" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6956efd8a1a2c48a707e9a1b2da729834a0f8e4c58117493b0d9d089cee468" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "wasm-encoder 0.25.0", + "wasmparser 0.102.0", +] + [[package]] name = "wasm-mutate" version = "0.2.23" @@ -3597,7 +3643,7 @@ dependencies = [ "rand 0.8.5", "thiserror", "wasm-encoder 0.25.0", - "wasmparser", + "wasmparser 0.103.0", ] [[package]] @@ -3611,7 +3657,7 @@ dependencies = [ "indexmap", "leb128", "wasm-encoder 0.25.0", - "wasmparser", + "wasmparser 0.103.0", ] [[package]] @@ -3652,6 +3698,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "wasmparser" +version = "0.92.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da34cec2a8c23db906cdf8b26e988d7a7f0d549eb5d51299129647af61a1b37" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +dependencies = [ + "indexmap", + "url", +] + [[package]] name = "wasmparser" version = "0.103.0" @@ -3678,7 +3743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51befda9d7eefac615a2ef75f42d2f2bd243cdabaa141a8ea0f9ffa3fc79ccf4" dependencies = [ "anyhow", - "wasmparser", + "wasmparser 0.103.0", ] [[package]] @@ -3705,7 +3770,7 @@ dependencies = [ "target-lexicon", "tempfile", "wasi-cap-std-sync", - "wasmparser", + "wasmparser 0.103.0", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", @@ -3821,7 +3886,7 @@ dependencies = [ "tokio", "walkdir", "wasm-encoder 0.26.0", - "wasmparser", + "wasmparser 0.103.0", "wasmtime", "wasmtime-cache", "wasmtime-cli-flags", @@ -3866,7 +3931,7 @@ dependencies = [ "wasmtime", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser", + "wit-parser 0.7.0", ] [[package]] @@ -3889,7 +3954,7 @@ dependencies = [ "object", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.103.0", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -3925,7 +3990,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-encoder 0.26.0", - "wasmparser", + "wasmparser 0.103.0", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -3940,7 +4005,7 @@ dependencies = [ "component-fuzz-util", "env_logger 0.10.0", "libfuzzer-sys", - "wasmparser", + "wasmparser 0.103.0", "wasmprinter", "wasmtime-environ", "wat", @@ -3994,7 +4059,7 @@ dependencies = [ "rand 0.8.5", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.103.0", "wasmtime", "wasmtime-fuzzing", ] @@ -4019,7 +4084,7 @@ dependencies = [ "wasm-smith", "wasm-spec-interpreter", "wasmi", - "wasmparser", + "wasmparser 0.103.0", "wasmprinter", "wasmtime", "wasmtime-wast", @@ -4101,7 +4166,7 @@ dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser", + "wasmparser 0.103.0", ] [[package]] @@ -4187,7 +4252,7 @@ dependencies = [ "gimli", "object", "target-lexicon", - "wasmparser", + "wasmparser 0.103.0", "wasmtime-cranelift-shared", "wasmtime-environ", "winch-codegen", @@ -4200,7 +4265,7 @@ version = "10.0.0" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.7.0", ] [[package]] @@ -4358,14 +4423,14 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.103.0", ] [[package]] name = "winch-environ" version = "0.8.0" dependencies = [ - "wasmparser", + "wasmparser 0.103.0", "wasmtime-environ", "winch-codegen", ] @@ -4411,7 +4476,7 @@ dependencies = [ "similar", "target-lexicon", "toml", - "wasmparser", + "wasmparser 0.103.0", "wasmtime-environ", "wat", "winch-codegen", @@ -4563,6 +4628,96 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7cf57f8786216c5652e1228b25203af2ff523808b5e9d3671894eee2bf7264" +dependencies = [ + "bitflags 1.3.2", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef177b73007d86c720931d0e2ea7e30eb8c9776e58361717743fc1e83cfacfe5" +dependencies = [ + "anyhow", + "wit-component", + "wit-parser 0.6.4", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdf5b00935b7b52d0e56cae1960f8ac13019a285f5aa762ff6bd7139a5c28a2" +dependencies = [ + "heck", + "wasm-metadata", + "wit-bindgen-core", + "wit-bindgen-rust-lib", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-lib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0a8f4b5fb1820b9d232beb122936425f72ec8fe6acb56e5d8782cfe55083da" +dependencies = [ + "heck", + "wit-bindgen-core", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadf1adf12ed25629b06272c16b335ef8c5a240d0ca64ab508a955ac3b46172c" +dependencies = [ + "anyhow", + "proc-macro2", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", + "wit-component", +] + +[[package]] +name = "wit-component" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed04310239706efc71cc8b995cb0226089c5b5fd260c3bd800a71486bd3cec97" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "indexmap", + "log", + "url", + "wasm-encoder 0.25.0", + "wasm-metadata", + "wasmparser 0.102.0", + "wit-parser 0.6.4", +] + +[[package]] +name = "wit-parser" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f887c3da527a51b321076ebe6a7513026a4757b6d4d144259946552d6fc728b3" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "pulldown-cmark", + "unicode-xid", + "url", +] + [[package]] name = "wit-parser" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 5fa228cd6119..f785e1c081dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,8 @@ members = [ "crates/cli-flags", "crates/environ/fuzz", "crates/jit-icache-coherence", + "crates/wasi-preview1-component-adapter", + "crates/wasi-preview1-component-adapter/verify", "crates/winch", "examples/fib-debug/wasm", "examples/wasi/wasm", @@ -174,6 +176,9 @@ winch-environ = { path = "winch/environ", version = "=0.8.0" } winch-filetests = { path = "winch/filetests" } winch-test-macros = { path = "winch/test-macros" } +wasi-preview1-component-adapter = { path = "crates/wasi-preview1-component-adapter", version = "=10.0.0" } +byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-array-literals", version = "=10.0.0" } + target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] } anyhow = "1.0.22" wasmparser = "0.103.0" @@ -267,3 +272,19 @@ harness = false name = "wasi" harness = false +[profile.release.package.wasi-preview1-component-adapter] +opt-level = 's' +strip = 'debuginfo' + +[profile.dev.package.wasi-preview1-component-adapter] +# Make dev look like a release build since this adapter module won't work with +# a debug build that uses data segments and such. +incremental = false +opt-level = 's' +# Omit assertions, which include failure messages which require string +# initializers. +debug-assertions = false +# Omit integer overflow checks, which include failure messages which require +# string initializers. +overflow-checks = false + diff --git a/ci/build-wasi-preview1-component-adapter.sh b/ci/build-wasi-preview1-component-adapter.sh index babab0dc0d3e..ac82f6765518 100755 --- a/ci/build-wasi-preview1-component-adapter.sh +++ b/ci/build-wasi-preview1-component-adapter.sh @@ -1,23 +1,20 @@ #!/usr/bin/env bash set -ex -BASEDIR=$(dirname "$0") -cd ${BASEDIR}/../crates/wasi-preview1-component-adapter/ - # Debug build, default features (reactor) -cargo build --target wasm32-unknown-unknown +cargo build -p wasi-preview1-component-adapter --target wasm32-unknown-unknown cargo run -p verify-component-adapter -- target/wasm32-unknown-unknown/debug/wasi_preview1_component_adapter.wasm # Debug build, command -cargo build --target wasm32-unknown-unknown --no-default-features --features command +cargo build -p wasi-preview1-component-adapter --target wasm32-unknown-unknown --no-default-features --features command cargo run -p verify-component-adapter -- target/wasm32-unknown-unknown/debug/wasi_preview1_component_adapter.wasm # Release build, command -cargo build --target wasm32-unknown-unknown --release --no-default-features --features command +cargo build -p wasi-preview1-component-adapter --target wasm32-unknown-unknown --release --no-default-features --features command cargo run -p verify-component-adapter -- target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm wasm-tools metadata add --name "wasi_preview1_component_adapter.command.adapter:${VERSION}" target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm -o target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.command.wasm # Release build, default features (reactor) -cargo build --target wasm32-unknown-unknown --release +cargo build -p wasi-preview1-component-adapter --target wasm32-unknown-unknown --release cargo run -p verify-component-adapter -- ./target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm wasm-tools metadata add --name "wasi_preview1_component_adapter.reactor.adapter:${VERSION}" target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.wasm -o target/wasm32-unknown-unknown/release/wasi_preview1_component_adapter.reactor.wasm diff --git a/crates/wasi-preview1-component-adapter/.gitignore b/crates/wasi-preview1-component-adapter/.gitignore deleted file mode 100644 index eb5a316cbd19..000000000000 --- a/crates/wasi-preview1-component-adapter/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index 75b5869451a7..81e8136791a1 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -16,43 +16,7 @@ object = { version = "0.30.0", default-features = false, features = ["archive"] [lib] crate-type = ["cdylib"] -[profile.release] -# Omit any unwinding support. This is currently the default for wasm, though -# that could theoretically change in the future. -panic = 'abort' -opt-level = 's' -strip = 'debuginfo' - -# Make dev look like a release build since this adapter module won't work with -# a debug build that uses data segments and such. -[profile.dev] -panic = 'abort' -incremental = false -opt-level = 's' - -# Omit assertions, which include failure messages which require string -# initializers. -debug-assertions = false - -# Omit integer overflow checks, which include failure messages which require -# string initializers. -overflow-checks = false - [features] default = ["reactor"] reactor = [] command = [] - -[workspace] -members = [ - "verify", - "byte-array-literals" -] - -[workspace.package] -version = "0.0.1" -edition = "2021" -authors = ["The Wasmtime Project Developers"] - -[workspace.dependencies] -byte-array-literals = { path = "byte-array-literals" } From a59ed4e7695d1ba72c7f1257c1b74bd57cef3ac9 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 10:00:32 -0700 Subject: [PATCH 141/153] rm deps.lock, toml: wit-deps integration is not implemented --- .../wit/deps.lock | 41 ------------------- .../wit/deps.toml | 1 - 2 files changed, 42 deletions(-) delete mode 100644 crates/wasi-preview1-component-adapter/wit/deps.lock delete mode 100644 crates/wasi-preview1-component-adapter/wit/deps.toml diff --git a/crates/wasi-preview1-component-adapter/wit/deps.lock b/crates/wasi-preview1-component-adapter/wit/deps.lock deleted file mode 100644 index bfa0b9d4f662..000000000000 --- a/crates/wasi-preview1-component-adapter/wit/deps.lock +++ /dev/null @@ -1,41 +0,0 @@ -[clocks] -sha256 = "16155da02fdba4a51515b44dbd1c65e0909bb8c4d383bd32875c777044ce5389" -sha512 = "30efd2697885954f3846322adf94f28a28f958595708407696ef66e8fe5bb62c79525e6f13ec469476cba8f2a12d3d9aef8250b1fe04c90a28542d3245db11f2" - -[filesystem] -sha256 = "a3f57790dfd6af70758df33df82aa064f915eb795b71a190f272f10da9cfc9df" -sha512 = "2b0059ffae0a4b1ce3bd8d489a40d08c1faa229bf1bb4cc8df0a98e0fd13d4694742117b2bc609d7fb75db1ed4aa148d3af7beca6a84866d87d65a1769b1d988" - -[http] -sha256 = "b6a245b8f37f3be93650c1a8ae41c01923f3b7303bbf2d016d8d03a1c92b3c10" -sha512 = "e2e0612213bb1272153b3858b13110a7fee0d89dc97d0021ab5ccccc8822ccc1f1629c8fa8dc582862411b2517ef3f79e7e51986c946654bf6ddd3f390cf36f2" - -[io] -sha256 = "87d8bf336f37184edc9290af9a14f7cac38c8fe087a786e66d08d1a94e48d186" -sha512 = "cdb5b35860c46c637ad37d784dd17f6023cf1c7c689d37973ec7c4094718baee4a16864e64622173b5578eed3950ad73211d9fe48a5b61b951809fdd9242b607" - -[logging] -sha256 = "8c32c7f893c938e0b76cd42e3de97e268dfe8fd0e7525d1f42b7ad10a88b00bb" -sha512 = "e9ce2adf02bb518c179a40b7d7af84bf44b99b2098609a897a114fb5af24946992dd7bd08da02c60d2768963f94414a16165f11343ec053a44b9e586058c812a" - -[poll] -sha256 = "9f2e6b5ea1a017575f96a3c415c737fe31513b043d9b47aefeb21f6c27ab8425" -sha512 = "f65965395742a0290fd9e4e55408c2b5ce3a4eae0ee22be1ff012e3db75910110da21c5f0a61083e043b4c510967a0a73ff4491ae9719e9158543f512dbeea5f" - -[preview] -path = "../../../wit" -sha256 = "007d4902193faef77b2df60714a8dfe8ec243e030c1ce61fb11c52788e3a5fe0" -sha512 = "bd06e8338e7fad0fc2e56ae01c5b184282c66ca79d2040eaec5a11efb14a4002b2a6234baaf354aff3cd57dc99835e4979eb71cdbd5d12800ea289d9a13252f2" -deps = ["clocks", "filesystem", "http", "io", "logging", "poll", "random", "sockets", "wasi-cli-base"] - -[random] -sha256 = "19d57f2262530016ec2b32991db3d8b6705b3a0bc5a7b6ae8fff3bcef0cf1088" -sha512 = "f9eba7dfd858d1edcaa868ce636f69545323bf010c3b0d4a2a1b23584d8027240c592d13e91882617e70a4dbf290754a8697724b5633147d11fa3db7869a2afe" - -[sockets] -sha256 = "d5a51604d53eec88d2735356ce7089a0eeb06886262e7ad2fc74be6b4dd943b2" -sha512 = "99bd86c0c59174bc9dafc559ca7dbab1aa9ed04a9b54ae3f7bb2d974b3a97bae8846acf1d77232edecda0aef5b23b9e4010d53ebf4a1d04292581078763530b7" - -[wasi-cli-base] -sha256 = "a0beee69392d2b55baff0b5f2285f50f717473b1369bdb3fbbfbefbaa3bd6c86" -sha512 = "d80eada98646bfeec1066437f1a4f69d67d117f38407f35c48d8b31667c171656059c5abf638c5b31169119ef5fa73bbf199933ac198f815f58281f124cb4f8d" diff --git a/crates/wasi-preview1-component-adapter/wit/deps.toml b/crates/wasi-preview1-component-adapter/wit/deps.toml deleted file mode 100644 index c90a145d08ce..000000000000 --- a/crates/wasi-preview1-component-adapter/wit/deps.toml +++ /dev/null @@ -1 +0,0 @@ -preview = "../../../wit" From 1a2e833b22d4cbf2b7e641eca422528bafb17a8d Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 10:49:12 -0700 Subject: [PATCH 142/153] use latest wit-bindgen, add wildcard audits for wit-bindgen and wasm-tools crates --- Cargo.lock | 142 +++++++++--------- .../Cargo.toml | 2 +- supply-chain/audits.toml | 91 +++++++++++ 3 files changed, 164 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b618f3b509b..7582f44e783e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,9 +86,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arbitrary" @@ -107,7 +107,7 @@ checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -486,7 +486,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -525,7 +525,7 @@ version = "0.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -1035,7 +1035,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -1046,7 +1046,7 @@ checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -2396,7 +2396,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.92", "version_check", ] @@ -2413,11 +2413,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "c4ec6d5fe0b140acb27c9a0444118cf55bfbb4e0b259739429abb4521dd67c16" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2474,9 +2474,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.18" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -2824,7 +2824,7 @@ checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -3019,6 +3019,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -3027,7 +3038,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", "unicode-xid", ] @@ -3134,7 +3145,7 @@ checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -3197,7 +3208,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -3254,7 +3265,7 @@ checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -3305,6 +3316,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + [[package]] name = "unicode-normalization" version = "0.1.21" @@ -3568,7 +3585,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 1.0.92", "wasm-bindgen-shared", ] @@ -3590,7 +3607,7 @@ checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3621,15 +3638,15 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6956efd8a1a2c48a707e9a1b2da729834a0f8e4c58117493b0d9d089cee468" +checksum = "dbdef99fafff010c57fabb7bc703f0903ec16fcee49207a22dcc4f78ea44562f" dependencies = [ "anyhow", "indexmap", "serde", - "wasm-encoder 0.25.0", - "wasmparser 0.102.0", + "wasm-encoder 0.26.0", + "wasmparser 0.104.0", ] [[package]] @@ -3709,9 +3726,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.102.0" +version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +checksum = "2c437373cac5ea84f1113d648d51f71751ffbe3d90c00ae67618cf20d0b5ee7b" dependencies = [ "indexmap", "url", @@ -3719,9 +3736,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.103.0" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c437373cac5ea84f1113d648d51f71751ffbe3d90c00ae67618cf20d0b5ee7b" +checksum = "6a396af81a7c56ad976131d6a35e4b693b78a1ea0357843bd436b4577e254a7d" dependencies = [ "indexmap", "url", @@ -3926,12 +3943,12 @@ dependencies = [ "component-macro-test-helpers", "proc-macro2", "quote", - "syn", + "syn 1.0.92", "tracing", "wasmtime", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser 0.7.0", + "wit-parser", ] [[package]] @@ -4265,7 +4282,7 @@ version = "10.0.0" dependencies = [ "anyhow", "heck", - "wit-parser 0.7.0", + "wit-parser", ] [[package]] @@ -4354,7 +4371,7 @@ dependencies = [ "proc-macro2", "quote", "shellexpand", - "syn", + "syn 1.0.92", "witx", ] @@ -4364,7 +4381,7 @@ version = "10.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", "wiggle", "wiggle-generate", ] @@ -4460,7 +4477,7 @@ dependencies = [ "glob", "proc-macro2", "quote", - "syn", + "syn 1.0.92", ] [[package]] @@ -4630,30 +4647,30 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7cf57f8786216c5652e1228b25203af2ff523808b5e9d3671894eee2bf7264" +checksum = "ad22d93d3f55847ac4b3df31607a26f35231754ef472382319de032770d8b5bf" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.2.1", "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen-core" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef177b73007d86c720931d0e2ea7e30eb8c9776e58361717743fc1e83cfacfe5" +checksum = "5bc1b5a6e87f16491f2297f75312dc0fb354f8c88c8bece53ea0d3167fc98867" dependencies = [ "anyhow", "wit-component", - "wit-parser 0.6.4", + "wit-parser", ] [[package]] name = "wit-bindgen-rust" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdf5b00935b7b52d0e56cae1960f8ac13019a285f5aa762ff6bd7139a5c28a2" +checksum = "7946a66f1132d3322c29de9d28097bd263f67e1e0909054f91253aa103cdf8be" dependencies = [ "heck", "wasm-metadata", @@ -4664,9 +4681,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-lib" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0a8f4b5fb1820b9d232beb122936425f72ec8fe6acb56e5d8782cfe55083da" +checksum = "0baf7325748c5d363ab6ed3ddbd155c241cfe385410c61f2505ec978a61a2d2c" dependencies = [ "heck", "wit-bindgen-core", @@ -4674,13 +4691,13 @@ dependencies = [ [[package]] name = "wit-bindgen-rust-macro" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadf1adf12ed25629b06272c16b335ef8c5a240d0ca64ab508a955ac3b46172c" +checksum = "42c131da5d2ba7746908e1401d474640371c31ad05281528c2a9e945a87d19be" dependencies = [ "anyhow", "proc-macro2", - "syn", + "syn 2.0.16", "wit-bindgen-core", "wit-bindgen-rust", "wit-component", @@ -4688,41 +4705,26 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed04310239706efc71cc8b995cb0226089c5b5fd260c3bd800a71486bd3cec97" +checksum = "e291ff83cb9c8e59963cc6922bdda77ed8f5517d6835f0c98070c4e7f1ae4996" dependencies = [ "anyhow", "bitflags 1.3.2", "indexmap", "log", "url", - "wasm-encoder 0.25.0", + "wasm-encoder 0.26.0", "wasm-metadata", - "wasmparser 0.102.0", - "wit-parser 0.6.4", + "wasmparser 0.104.0", + "wit-parser", ] [[package]] name = "wit-parser" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f887c3da527a51b321076ebe6a7513026a4757b6d4d144259946552d6fc728b3" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "pulldown-cmark", - "unicode-xid", - "url", -] - -[[package]] -name = "wit-parser" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365273e457e2cacf762df10df4b1e0d4d9912b5332ff4cf2a172760fd67b7ec4" +checksum = "5ca2581061573ef6d1754983d7a9b3ed5871ef859d52708ea9a0f5af32919172" dependencies = [ "anyhow", "id-arena", @@ -4771,7 +4773,7 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.92", "synstructure", ] diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index 81e8136791a1..bfd71593958d 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true [dependencies] wasi = { version = "0.11.0", default-features = false } -wit-bindgen = { version = "0.4.0", default-features = false, features = ["macros"] } +wit-bindgen = { version = "0.6.0", default-features = false, features = ["macros"] } byte-array-literals = { workspace = true } [build-dependencies] diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index fbf81f13b4ad..6bb71ee9a723 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -65,6 +65,19 @@ user-id = 696 start = "2022-02-17" end = "2024-03-10" +[[wildcard-audits.wasm-metadata]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wasm-tools` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + [[wildcard-audits.wasm-mutate]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -150,6 +163,84 @@ I am employed by a member of the Bytecode Alliance and plan to continue doing so and will actively maintain this crate over time. """ +[[wildcard-audits.wit-bindgen]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wit-bindgen` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + +[[wildcard-audits.wit-bindgen-core]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wit-bindgen` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + +[[wildcard-audits.wit-bindgen-rust-lib]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wit-bindgen` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + +[[wildcard-audits.wit-bindgen-rust-macro]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wit-bindgen` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + +[[wildcard-audits.wit-bindgen-rust]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wit-bindgen` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + +[[wildcard-audits.wit-component]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +user-id = 1 +start = "2020-12-11" +end = "2024-04-14" +notes = """ +This is a Bytecode Alliance authored crate maintained in the `wasm-tools` +repository of which I'm one of the primary maintainers and publishers for. +I am employed by a member of the Bytecode Alliance and plan to continue doing +so and will actively maintain this crate over time. +""" + [[wildcard-audits.wit-parser]] who = "Alex Crichton " criteria = "safe-to-deploy" From 7bd2d76df277f209cae916166124fee5ce7e46fd Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 11:11:56 -0700 Subject: [PATCH 143/153] cargo-vet all remaining dependency bumps --- supply-chain/audits.toml | 45 ++++++-- supply-chain/config.toml | 16 --- supply-chain/imports.lock | 225 ++++++++++++++++++++++++++++++++++---- 3 files changed, 238 insertions(+), 48 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 6bb71ee9a723..657d02dc5d1a 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -58,13 +58,6 @@ I am employed by a member of the Bytecode Alliance and plan to continue doing so and will actively maintain this crate over time. """ -[[wildcard-audits.wasm-mutate]] -who = "Nick Fitzgerald " -criteria = "safe-to-deploy" -user-id = 696 -start = "2022-02-17" -end = "2024-03-10" - [[wildcard-audits.wasm-metadata]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -78,6 +71,13 @@ I am employed by a member of the Bytecode Alliance and plan to continue doing so and will actively maintain this crate over time. """ +[[wildcard-audits.wasm-mutate]] +who = "Nick Fitzgerald " +criteria = "safe-to-deploy" +user-id = 696 +start = "2022-02-17" +end = "2024-03-10" + [[wildcard-audits.wasm-mutate]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -189,7 +189,7 @@ I am employed by a member of the Bytecode Alliance and plan to continue doing so and will actively maintain this crate over time. """ -[[wildcard-audits.wit-bindgen-rust-lib]] +[[wildcard-audits.wit-bindgen-rust]] who = "Alex Crichton " criteria = "safe-to-deploy" user-id = 1 @@ -202,7 +202,7 @@ I am employed by a member of the Bytecode Alliance and plan to continue doing so and will actively maintain this crate over time. """ -[[wildcard-audits.wit-bindgen-rust-macro]] +[[wildcard-audits.wit-bindgen-rust-lib]] who = "Alex Crichton " criteria = "safe-to-deploy" user-id = 1 @@ -215,7 +215,7 @@ I am employed by a member of the Bytecode Alliance and plan to continue doing so and will actively maintain this crate over time. """ -[[wildcard-audits.wit-bindgen-rust]] +[[wildcard-audits.wit-bindgen-rust-macro]] who = "Alex Crichton " criteria = "safe-to-deploy" user-id = 1 @@ -291,6 +291,11 @@ nightly feature in the standard library for backtrace integration. No undue here. """ +[[audits.anyhow]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +delta = "1.0.69 -> 1.0.71" + [[audits.arbitrary]] who = "Nick Fitzgerald " criteria = "safe-to-deploy" @@ -1130,6 +1135,11 @@ criteria = "safe-to-deploy" version = "0.3.25" notes = "This crate shells out to the pkg-config executable, but it appears to sanitize inputs reasonably." +[[audits.proc-macro2]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +delta = "1.0.51 -> 1.0.57" + [[audits.pulldown-cmark]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -1140,6 +1150,11 @@ are otherwise not doing other `unsafe` operations. Additionally the crate does not do anything other than markdown rendering as is expected. """ +[[audits.quote]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +delta = "1.0.23 -> 1.0.27" + [[audits.regalloc2]] who = "Jamey Sharp " criteria = "safe-to-deploy" @@ -1291,6 +1306,11 @@ but it's all doing what it says on the tin: being a stable polyfill for strict provenance APIs in the standard library while they're on Nightly. """ +[[audits.syn]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +delta = "1.0.92 -> 2.0.16" + [[audits.system-interface]] who = "Dan Gohman " criteria = "safe-to-deploy" @@ -1393,6 +1413,11 @@ This crate has no unsafe code and does not use `std::*`. Skimming the crate it does not attempt to out of the bounds of what it's already supposed to be doing. """ +[[audits.unicode-ident]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "1.0.8" + [[audits.unicode-normalization]] who = "Alex Crichton " criteria = "safe-to-deploy" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 89e34d9f0f13..ac785564bd2b 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -585,14 +585,6 @@ criteria = "safe-to-deploy" version = "1.0.4" criteria = "safe-to-deploy" -[[exemptions.proc-macro-error-attr]] -version = "1.0.4" -criteria = "safe-to-deploy" - -[[exemptions.proc-macro2]] -version = "1.0.37" -criteria = "safe-to-deploy" - [[exemptions.proptest]] version = "1.0.0" criteria = "safe-to-deploy" @@ -843,14 +835,6 @@ criteria = "safe-to-run" version = "1.15.0" criteria = "safe-to-deploy" -[[exemptions.unicode-width]] -version = "0.1.9" -criteria = "safe-to-deploy" - -[[exemptions.unicode-xid]] -version = "0.2.3" -criteria = "safe-to-deploy" - [[exemptions.uuid]] version = "1.0.0" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index e9e6be045763..fce2d03ef22b 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -22,20 +22,6 @@ user-id = 696 user-login = "fitzgen" user-name = "Nick Fitzgerald" -[[publisher.regalloc2]] -version = "0.6.1" -when = "2023-02-16" -user-id = 3726 -user-login = "cfallin" -user-name = "Chris Fallin" - -[[publisher.regalloc2]] -version = "0.7.0" -when = "2023-04-18" -user-id = 187138 -user-login = "elliottt" -user-name = "Trevor Elliott" - [[publisher.regalloc2]] version = "0.8.1" when = "2023-05-01" @@ -43,6 +29,27 @@ user-id = 3726 user-login = "cfallin" user-name = "Chris Fallin" +[[publisher.unicode-segmentation]] +version = "1.10.1" +when = "2023-01-31" +user-id = 1139 +user-login = "Manishearth" +user-name = "Manish Goregaokar" + +[[publisher.unicode-width]] +version = "0.1.9" +when = "2021-09-16" +user-id = 1139 +user-login = "Manishearth" +user-name = "Manish Goregaokar" + +[[publisher.unicode-xid]] +version = "0.2.3" +when = "2022-05-02" +user-id = 1139 +user-login = "Manishearth" +user-name = "Manish Goregaokar" + [[publisher.wasm-encoder]] version = "0.26.0" when = "2023-04-27" @@ -50,6 +57,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wasm-metadata]] +version = "0.5.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wasm-mutate]] version = "0.2.23" when = "2023-04-13" @@ -71,6 +85,13 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wasmparser]] +version = "0.104.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wasmprinter]] version = "0.2.55" when = "2023-04-13" @@ -92,9 +113,51 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" +[[publisher.wit-bindgen]] +version = "0.6.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + +[[publisher.wit-bindgen-core]] +version = "0.6.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + +[[publisher.wit-bindgen-rust]] +version = "0.6.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + +[[publisher.wit-bindgen-rust-lib]] +version = "0.6.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + +[[publisher.wit-bindgen-rust-macro]] +version = "0.6.0" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + +[[publisher.wit-component]] +version = "0.8.2" +when = "2023-04-27" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + [[publisher.wit-parser]] -version = "0.7.0" -when = "2023-04-13" +version = "0.7.1" +when = "2023-04-27" user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" @@ -104,12 +167,6 @@ who = "Johan Andersson " criteria = "safe-to-deploy" version = "1.0.58" -[[audits.embark-studios.audits.anyhow]] -who = "Johan Andersson " -criteria = "safe-to-deploy" -delta = "1.0.58 -> 1.0.66" -notes = "New unsafe usage, looks sane. Expert maintainer" - [[audits.embark-studios.audits.cty]] who = "Johan Andersson " criteria = "safe-to-deploy" @@ -144,6 +201,12 @@ criteria = "safe-to-run" version = "0.6.2" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" +[[audits.google.audits.proc-macro-error-attr]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "1.0.4" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.static_assertions]] who = "ChromeOS" criteria = "safe-to-run" @@ -181,6 +244,64 @@ who = "David Cook " criteria = "safe-to-deploy" version = "0.2.83" +[[audits.mozilla.wildcard-audits.unicode-segmentation]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +user-id = 1139 +start = "2019-05-15" +end = "2024-05-03" +notes = "All code written or reviewed by Manish" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.wildcard-audits.unicode-width]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +user-id = 1139 +start = "2019-12-05" +end = "2024-05-03" +notes = "All code written or reviewed by Manish" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.wildcard-audits.unicode-xid]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +user-id = 1139 +start = "2019-07-25" +end = "2024-05-03" +notes = "All code written or reviewed by Manish" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.anyhow]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.57 -> 1.0.61" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.anyhow]] +who = "Bobby Holley " +criteria = "safe-to-deploy" +delta = "1.0.58 -> 1.0.57" +notes = "No functional differences, just CI config and docs." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.anyhow]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.61 -> 1.0.62" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.anyhow]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.62 -> 1.0.68" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.anyhow]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.68 -> 1.0.69" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.autocfg]] who = "Josh Stone " criteria = "safe-to-deploy" @@ -366,6 +487,54 @@ criteria = "safe-to-deploy" delta = "1.13.1 -> 1.16.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.proc-macro2]] +who = "Nika Layzell " +criteria = "safe-to-deploy" +version = "1.0.39" +notes = """ +`proc-macro2` acts as either a thin(-ish) wrapper around the std-provided +`proc_macro` crate, or as a fallback implementation of the crate, depending on +where it is used. + +If using this crate on older versions of rustc (1.56 and earlier), it will +temporarily replace the panic handler while initializing in order to detect if +it is running within a `proc_macro`, which could lead to surprising behaviour. +This should not be an issue for more recent compiler versions, which support +`proc_macro::is_available()`. + +The `proc-macro2` crate's fallback behaviour is not identical to the complex +behaviour of the rustc compiler (e.g. it does not perform unicode normalization +for identifiers), however it behaves well enough for its intended use-case +(tests and scripts processing rust code). + +`proc-macro2` does not use unsafe code, however exposes one `unsafe` API to +allow bypassing checks in the fallback implementation when constructing +`Literal` using `from_str_unchecked`. This was intended to only be used by the +`quote!` macro, however it has been removed +(https://github.com/dtolnay/quote/commit/f621fe64a8a501cae8e95ebd6848e637bbc79078), +and is likely completely unused. Even when used, this API shouldn't be able to +cause unsoundness. +""" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.proc-macro2]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.39 -> 1.0.43" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.proc-macro2]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.43 -> 1.0.49" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.proc-macro2]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.49 -> 1.0.51" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.quote]] who = "Nika Layzell " criteria = "safe-to-deploy" @@ -382,6 +551,18 @@ formatter, and runtime logic. """ aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.quote]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.18 -> 1.0.21" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.quote]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.21 -> 1.0.23" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.rayon]] who = "Josh Stone " criteria = "safe-to-deploy" From c2dea4c31f89119e9301ece8491d34b73fea0752 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 11:15:20 -0700 Subject: [PATCH 144/153] cargo deny: allow the Unicode-DFS-2016 license this has similar requirements to OpenSSL: permissive, but the copyright statement must be advertised --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index b67d310264d8..ef68b2105eb5 100644 --- a/deny.toml +++ b/deny.toml @@ -19,6 +19,7 @@ allow = [ "MIT", "MPL-2.0", "OpenSSL", + "Unicode-DFS-2016", "Zlib", ] From e2e9aba4c87424a3352dd754bb80f9c3dc5acdb4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 11:22:34 -0700 Subject: [PATCH 145/153] run-tests: exclude the adapter, which doesnt build for native --- ci/run-tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/run-tests.sh b/ci/run-tests.sh index 67e53eb87b51..e72e9cf3b981 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -7,4 +7,5 @@ cargo test \ --workspace \ --exclude 'wasmtime-wasi-*' \ --exclude wasi-crypto \ + --exclude wasi-preview1-component-adapter \ $@ From 0259833ffb9e9210c61b5e1794b7a0780241a4f3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 11:28:22 -0700 Subject: [PATCH 146/153] adapter: compile_error when not wasm32-unknown-unknown, and note this where the static assertion fails --- crates/wasi-preview1-component-adapter/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index 1198dd3d8e29..d5459baa7623 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -14,6 +14,9 @@ use core::slice; use poll::Pollable; use wasi::*; +#[cfg(any(not(target_arch = "wasm32"), not(target_os = "unknown")))] +compile_error!("This crate should only be built for wasm32-unknown-unknown"); + #[cfg(all(feature = "command", feature = "reactor"))] compile_error!("only one of the `command` and `reactor` features may be selected at a time"); @@ -2189,6 +2192,7 @@ const fn bump_arena_size() -> usize { // Statically assert that the `State` structure is the size of a wasm page. This // mostly guarantees that it's not larger than one page which is relied upon // below. +// If you see a type error here, make sure you are targeting wasm32-unknown-unknown! const _: () = { let _size_assert: [(); PAGE_SIZE] = [(); size_of::>()]; }; From 9c61c7454d971ce558941d3da58722e7617a6ffe Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 12:56:09 -0700 Subject: [PATCH 147/153] exclude adapter from tests build with manifest instead of run-tests.sh --- ci/run-tests.sh | 1 - crates/wasi-preview1-component-adapter/Cargo.toml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run-tests.sh b/ci/run-tests.sh index e72e9cf3b981..67e53eb87b51 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -7,5 +7,4 @@ cargo test \ --workspace \ --exclude 'wasmtime-wasi-*' \ --exclude wasi-crypto \ - --exclude wasi-preview1-component-adapter \ $@ diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index bfd71593958d..ba1aa9478264 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -15,6 +15,7 @@ object = { version = "0.30.0", default-features = false, features = ["archive"] [lib] crate-type = ["cdylib"] +test = false [features] default = ["reactor"] From aeef73c24c7a091d93c812dcdc6863e2146e2871 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 12:57:11 -0700 Subject: [PATCH 148/153] adapter ci step requires submodules checkout now --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6782eadd058e..75b018a4d9e9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -533,6 +533,8 @@ jobs: contents: write steps: - uses: actions/checkout@v3 + with: + submodules: true - run: rustup update stable && rustup default stable - run: rustup target add wasm32-wasi wasm32-unknown-unknown From 1b5f41659d8c56ad2d22d44f5bfb95e6b2f99230 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 13:34:50 -0700 Subject: [PATCH 149/153] adapter and byte-array-literals are publish = false --- crates/wasi-preview1-component-adapter/Cargo.toml | 1 + .../byte-array-literals/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index ba1aa9478264..36a52a192f03 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -3,6 +3,7 @@ name = "wasi-preview1-component-adapter" version.workspace = true authors.workspace = true edition.workspace = true +publish = false [dependencies] wasi = { version = "0.11.0", default-features = false } diff --git a/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml b/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml index 28af4a52571b..a9d08dc9890f 100644 --- a/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/byte-array-literals/Cargo.toml @@ -3,6 +3,7 @@ name = "byte-array-literals" version.workspace = true authors.workspace = true edition.workspace = true +publish = false [lib] proc-macro = true From 651dab199ee040965ecad8e4b9c07d209940ba75 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 14:14:01 -0700 Subject: [PATCH 150/153] delete adatper Cargo.lock --- .../Cargo.lock | 456 ------------------ 1 file changed, 456 deletions(-) delete mode 100644 crates/wasi-preview1-component-adapter/Cargo.lock diff --git a/crates/wasi-preview1-component-adapter/Cargo.lock b/crates/wasi-preview1-component-adapter/Cargo.lock deleted file mode 100644 index 4cdca9e0289e..000000000000 --- a/crates/wasi-preview1-component-adapter/Cargo.lock +++ /dev/null @@ -1,456 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "byte-array-literals" -version = "0.0.1" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown", - "serde", -] - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "object" -version = "0.30.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" -dependencies = [ - "memchr", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "proc-macro2" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pulldown-cmark" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - -[[package]] -name = "quote" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.163" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.163" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "verify-component-adapter" -version = "0.0.1" -dependencies = [ - "anyhow", - "wasmparser 0.92.0", - "wat", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi-preview1-component-adapter" -version = "0.0.1" -dependencies = [ - "byte-array-literals", - "object", - "wasi", - "wasm-encoder 0.25.0", - "wit-bindgen", -] - -[[package]] -name = "wasm-encoder" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eff853c4f09eec94d76af527eddad4e9de13b11d6286a1ef7134bc30135a2b7" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-metadata" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6956efd8a1a2c48a707e9a1b2da729834a0f8e4c58117493b0d9d089cee468" -dependencies = [ - "anyhow", - "indexmap", - "serde", - "wasm-encoder 0.25.0", - "wasmparser 0.102.0", -] - -[[package]] -name = "wasmparser" -version = "0.92.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da34cec2a8c23db906cdf8b26e988d7a7f0d549eb5d51299129647af61a1b37" -dependencies = [ - "indexmap", -] - -[[package]] -name = "wasmparser" -version = "0.102.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" -dependencies = [ - "indexmap", - "url", -] - -[[package]] -name = "wast" -version = "57.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" -dependencies = [ - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.26.0", -] - -[[package]] -name = "wat" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" -dependencies = [ - "wast", -] - -[[package]] -name = "wit-bindgen" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7cf57f8786216c5652e1228b25203af2ff523808b5e9d3671894eee2bf7264" -dependencies = [ - "bitflags", - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef177b73007d86c720931d0e2ea7e30eb8c9776e58361717743fc1e83cfacfe5" -dependencies = [ - "anyhow", - "wit-component", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efdf5b00935b7b52d0e56cae1960f8ac13019a285f5aa762ff6bd7139a5c28a2" -dependencies = [ - "heck", - "wasm-metadata", - "wit-bindgen-core", - "wit-bindgen-rust-lib", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-lib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0a8f4b5fb1820b9d232beb122936425f72ec8fe6acb56e5d8782cfe55083da" -dependencies = [ - "heck", - "wit-bindgen-core", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadf1adf12ed25629b06272c16b335ef8c5a240d0ca64ab508a955ac3b46172c" -dependencies = [ - "anyhow", - "proc-macro2", - "syn 1.0.109", - "wit-bindgen-core", - "wit-bindgen-rust", - "wit-component", -] - -[[package]] -name = "wit-component" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed04310239706efc71cc8b995cb0226089c5b5fd260c3bd800a71486bd3cec97" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "url", - "wasm-encoder 0.25.0", - "wasm-metadata", - "wasmparser 0.102.0", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f887c3da527a51b321076ebe6a7513026a4757b6d4d144259946552d6fc728b3" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "pulldown-cmark", - "unicode-xid", - "url", -] From 44b9d6c3217ef20fcc53aa60c7b8ceea8cf876d4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 14:14:07 -0700 Subject: [PATCH 151/153] cfg static assertion in adapter so it builds for native this is required for cargo test and cargo doc of the whole workspace. --- crates/wasi-preview1-component-adapter/Cargo.toml | 1 - crates/wasi-preview1-component-adapter/src/lib.rs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index 36a52a192f03..eeb8f0dedbb4 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -16,7 +16,6 @@ object = { version = "0.30.0", default-features = false, features = ["archive"] [lib] crate-type = ["cdylib"] -test = false [features] default = ["reactor"] diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index d5459baa7623..998ce47a27f7 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -14,9 +14,6 @@ use core::slice; use poll::Pollable; use wasi::*; -#[cfg(any(not(target_arch = "wasm32"), not(target_os = "unknown")))] -compile_error!("This crate should only be built for wasm32-unknown-unknown"); - #[cfg(all(feature = "command", feature = "reactor"))] compile_error!("only one of the `command` and `reactor` features may be selected at a time"); @@ -2192,7 +2189,7 @@ const fn bump_arena_size() -> usize { // Statically assert that the `State` structure is the size of a wasm page. This // mostly guarantees that it's not larger than one page which is relied upon // below. -// If you see a type error here, make sure you are targeting wasm32-unknown-unknown! +#[cfg(target_arch = "wasm32")] const _: () = { let _size_assert: [(); PAGE_SIZE] = [(); size_of::>()]; }; From 22ccf87d1a14a54b164ccac888fda568a7a6b9ec Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 14:28:19 -0700 Subject: [PATCH 152/153] still need test = false because adapter cant link on native profile includes linker options --- crates/wasi-preview1-component-adapter/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index eeb8f0dedbb4..c2ca4c9c61e6 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -15,6 +15,7 @@ wasm-encoder = "0.25" object = { version = "0.30.0", default-features = false, features = ["archive"] } [lib] +test = false crate-type = ["cdylib"] [features] From 21def7602365c65e4d02785e8c6b4833c7b57820 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 15 May 2023 15:40:50 -0700 Subject: [PATCH 153/153] remove version constraint from adapter and bytearray in workspace dependencies this was apparently messing up ./publish bump --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f785e1c081dd..3b51a55dd603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,8 +176,8 @@ winch-environ = { path = "winch/environ", version = "=0.8.0" } winch-filetests = { path = "winch/filetests" } winch-test-macros = { path = "winch/test-macros" } -wasi-preview1-component-adapter = { path = "crates/wasi-preview1-component-adapter", version = "=10.0.0" } -byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-array-literals", version = "=10.0.0" } +wasi-preview1-component-adapter = { path = "crates/wasi-preview1-component-adapter" } +byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-array-literals" } target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] } anyhow = "1.0.22"