Repository: bytecodealliance/wasmtime
Pull request #9356 (2024-10-01 21:10:29)

Allow alignments that eat the remaining allocation
The preview1 adapter has an implementation of cabi_realloc that facilitates calling component imports that return dynamically allocated results, like lists and strings. However, when returning a zero-length value to a buffer that's been allocated with zero bytes available, a trap will occur. This is because the alignment check requires that there is space left over after alignment has been added to the base pointer, which in the case of an empty buffer, will not be possible.

This PR fixes the issue by relaxing the alignment check to require that the amount required to align the base does not exceed the length of the buffer, but may consume the entire allocation.


alexcrichton: Would it be possible to add a test for this as well? Or is that out-of-scope in the WASI APIs we have bound?

Patch:
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index b7464ff1f..1806df36f 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -448,7 +448,7 @@ impl BumpAlloc {
             unreachable!("invalid alignment");
         }
         let align_offset = self.base.align_offset(align);
-        if align_offset >= self.len {
+        if align_offset > self.len {
             unreachable!("failed to allocate")
         }
         self.len -= align_offset;
diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs
index e85c277e1..21add3328 100644
--- a/crates/wasmtime/src/runtime/component/component.rs
+++ b/crates/wasmtime/src/runtime/component/component.rs
@@ -618,6 +618,30 @@ impl Component {
         self.inner.code.code_memory().mmap().image_range()
     }
 
+    /// Force initialization of copy-on-write images to happen here-and-now
+    /// instead of when they're requested during first instantiation.
+    ///
+    /// When [copy-on-write memory
+    /// initialization](crate::Config::memory_init_cow) is enabled then Wasmtime
+    /// will lazily create the initialization image for a component. This method
+    /// can be used to explicitly dictate when this initialization happens.
+    ///
+    /// Note that this largely only matters on Linux when memfd is used.
+    /// Otherwise the copy-on-write image typically comes from disk and in that
+    /// situation the creation of the image is trivial as the image is always
+    /// sourced from disk. On Linux, though, when memfd is used a memfd is
+    /// created and the initialization image is written to it.
+    ///
+    /// Also note that this method is not required to be called, it's available
+    /// as a performance optimization if required but is otherwise handled
+    /// automatically.
+    pub fn initialize_copy_on_write_image(&self) -> Result<()> {
+        for (_, module) in self.inner.static_modules.iter() {
+            module.initialize_copy_on_write_image()?;
+        }
+        Ok(())
+    }
+
     /// Looks up a specific export of this component by `name` optionally nested
     /// within the `instance` provided.
     ///


Test patch:
diff --git a/crates/test-programs/src/bin/cli_stdin_empty.rs b/crates/test-programs/src/bin/cli_stdin_empty.rs
new file mode 100644
index 000000000..c5df2f090
--- /dev/null
+++ b/crates/test-programs/src/bin/cli_stdin_empty.rs
@@ -0,0 +1,16 @@
+use test_programs::preview1::STDIN_FD;
+
+fn main() {
+    let mut buffer = [0_u8; 0];
+
+    unsafe {
+        wasi::fd_read(
+            STDIN_FD,
+            &[wasi::Iovec {
+                buf: buffer.as_mut_ptr(),
+                buf_len: 0,
+            }],
+        )
+        .expect("empty read should succeed");
+    }
+}
diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs
index 7f8b6b2f2..797fe4c64 100644
--- a/tests/all/cli_tests.rs
+++ b/tests/all/cli_tests.rs
@@ -1160,6 +1160,27 @@ mod test_programs {
         Ok(())
     }
 
+    #[test]
+    fn cli_stdin_empty() -> Result<()> {
+        let mut child = get_wasmtime_command()?
+            .args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT])
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .stdin(Stdio::piped())
+            .spawn()?;
+        child
+            .stdin
+            .take()
+            .unwrap()
+            .write_all(b"not to be read")
+            .unwrap();
+        let output = child.wait_with_output()?;
+        println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+        println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+        assert!(output.status.success());
+        Ok(())
+    }
+
     #[test]
     fn cli_stdin() -> Result<()> {
         let mut child = get_wasmtime_command()?