Repository: bytecodealliance/wasmtime
Pull request #7763 (2024-01-09 18:03:50)

wasi: Test that pollables may be used multiple times
We didn't have a test that covered using a pollable multiple times in the test-programs, despite relying on that behavior in the preview1 adapter.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->


elliottt: @pchickey and @guybedford, does this look like enough to have caused a test failure in the jco work?
guybedford: Unfortunately I tried this in JCO and it is definitely passing, so I don't think this is capturing a case. JCO pollables will only ever resolve once as they are promise-based, after which they will be always-resolving. @elliottt let me know if I can help debug further at all.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index d6193259b..6ff63d8ae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -664,7 +664,7 @@ dependencies = [
  "target-lexicon",
  "thiserror",
  "toml",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wat",
 ]
 
@@ -843,7 +843,7 @@ dependencies = [
  "serde_derive",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime-types",
  "wat",
 ]
@@ -2607,7 +2607,7 @@ version = "0.0.0"
 dependencies = [
  "cargo_metadata",
  "heck",
- "wit-component 0.19.0",
+ "wit-component 0.19.1",
 ]
 
 [[package]]
@@ -2895,7 +2895,7 @@ name = "verify-component-adapter"
 version = "18.0.0"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wat",
 ]
 
@@ -3002,7 +3002,7 @@ dependencies = [
  "byte-array-literals",
  "object",
  "wasi",
- "wasm-encoder",
+ "wasm-encoder 0.39.0",
  "wit-bindgen",
 ]
 
@@ -3086,11 +3086,21 @@ dependencies = [
  "leb128",
 ]
 
+[[package]]
+name = "wasm-encoder"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d"
+dependencies = [
+ "leb128",
+ "wasmparser 0.119.0",
+]
+
 [[package]]
 name = "wasm-metadata"
-version = "0.10.14"
+version = "0.10.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d835d67708f6374937c550ad8dd1d17c616ae009e3f00d7a0ac9f7825e78c36a"
+checksum = "818931c85b1d197909699d36c509fa89550ccfa0d66932ba3c1726faddb4d0c7"
 dependencies = [
  "anyhow",
  "indexmap 2.0.0",
@@ -3098,35 +3108,35 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.39.0",
+ "wasmparser 0.119.0",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.2.42"
+version = "0.2.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "140f2555b7c16b89592a1cd2f2626612c8c3cba9ff3224f87b00e063264b4b38"
+checksum = "6f95378917a3e9c61e79d41ca4629eb3e0a3ca1714d745c1c2a186439472578c"
 dependencies = [
  "egg",
  "log",
  "rand",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.39.0",
+ "wasmparser 0.119.0",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.13.1"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58273756970c82a1769b11e13a05bd21f95a767e4a2fab03afd0cff0085ae89d"
+checksum = "a419bbbab75f5ad71744256b74fdaeeb9215ea9e9bfc59c477e96c462ee0ef59"
 dependencies = [
  "arbitrary",
  "flagset",
  "indexmap 2.0.0",
  "leb128",
- "wasm-encoder",
+ "wasm-encoder 0.39.0",
 ]
 
 [[package]]
@@ -3177,6 +3187,17 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "wasmparser"
+version = "0.119.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d"
+dependencies = [
+ "bitflags 2.4.1",
+ "indexmap 2.0.0",
+ "semver",
+]
+
 [[package]]
 name = "wasmparser-nostd"
 version = "0.91.0"
@@ -3188,12 +3209,12 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.2.75"
+version = "0.2.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d027eb8294904fc715ac0870cebe6b0271e96b90605ee21511e7565c4ce568c"
+checksum = "cac2a7745372074e5573e365e17100f5a26058740576313784ef03fb900ea8d2"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.119.0",
 ]
 
 [[package]]
@@ -3220,8 +3241,8 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "wasi-cap-std-sync",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.39.0",
+ "wasmparser 0.119.0",
  "wasmtime-cache",
  "wasmtime-component-macro",
  "wasmtime-component-util",
@@ -3352,7 +3373,7 @@ dependencies = [
  "tokio",
  "tracing",
  "walkdir",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime",
  "wasmtime-cache",
  "wasmtime-cli-flags",
@@ -3366,10 +3387,10 @@ dependencies = [
  "wasmtime-wasi-nn",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
- "wast 69.0.1",
+ "wast 70.0.0",
  "wat",
  "windows-sys 0.52.0",
- "wit-component 0.19.0",
+ "wit-component 0.19.1",
 ]
 
 [[package]]
@@ -3422,7 +3443,7 @@ dependencies = [
  "object",
  "target-lexicon",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
@@ -3458,8 +3479,8 @@ dependencies = [
  "serde_derive",
  "target-lexicon",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.39.0",
+ "wasmparser 0.119.0",
  "wasmprinter",
  "wasmtime-component-util",
  "wasmtime-types",
@@ -3474,7 +3495,7 @@ dependencies = [
  "component-fuzz-util",
  "env_logger",
  "libfuzzer-sys",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmprinter",
  "wasmtime-environ",
  "wat",
@@ -3531,7 +3552,7 @@ dependencies = [
  "rand",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime",
  "wasmtime-fuzzing",
 ]
@@ -3551,12 +3572,12 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "v8",
- "wasm-encoder",
+ "wasm-encoder 0.39.0",
  "wasm-mutate",
  "wasm-smith",
  "wasm-spec-interpreter",
  "wasmi",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmprinter",
  "wasmtime",
  "wasmtime-wast",
@@ -3628,7 +3649,7 @@ dependencies = [
  "rand",
  "rustix",
  "sptr",
- "wasm-encoder",
+ "wasm-encoder 0.39.0",
  "wasmtime-asm-macros",
  "wasmtime-environ",
  "wasmtime-fiber",
@@ -3646,7 +3667,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.119.0",
 ]
 
 [[package]]
@@ -3757,7 +3778,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 69.0.1",
+ "wast 70.0.0",
 ]
 
 [[package]]
@@ -3769,7 +3790,7 @@ dependencies = [
  "gimli",
  "object",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime-cranelift-shared",
  "wasmtime-environ",
  "winch-codegen",
@@ -3800,23 +3821,23 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "69.0.1"
+version = "70.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1ee37317321afde358e4d7593745942c48d6d17e0e6e943704de9bbee121e7a"
+checksum = "2ee4bc54bbe1c6924160b9f75e374a1d07532e7580eb632c0ee6cdd109bb217e"
 dependencies = [
  "leb128",
  "memchr",
  "unicode-width",
- "wasm-encoder",
+ "wasm-encoder 0.39.0",
 ]
 
 [[package]]
 name = "wat"
-version = "1.0.82"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aeb338ee8dee4d4cd05e6426683f21c5087dc7cfc8903e839ccf48d43332da3c"
+checksum = "9f0dce8cdc288c717cf01e461a1e451a7b8445d53451123536ba576e423a101a"
 dependencies = [
- "wast 69.0.1",
+ "wast 70.0.0",
 ]
 
 [[package]]
@@ -3941,7 +3962,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime-environ",
 ]
 
@@ -3957,7 +3978,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -3987,7 +4008,7 @@ dependencies = [
  "similar",
  "target-lexicon",
  "toml",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wasmtime-environ",
  "wat",
  "winch-codegen",
@@ -4208,17 +4229,17 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder",
+ "wasm-encoder 0.38.1",
  "wasm-metadata",
- "wasmparser",
+ "wasmparser 0.118.1",
  "wit-parser",
 ]
 
 [[package]]
 name = "wit-component"
-version = "0.19.0"
+version = "0.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b2e0cdd27c1500cb2524810e8f40b50012355a476c1bea5d12d73620125cc04"
+checksum = "429e3c06fba3a7566aab724ae3ffff3152ede5399d44789e7dd11f5421292859"
 dependencies = [
  "anyhow",
  "bitflags 2.4.1",
@@ -4227,17 +4248,17 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder",
+ "wasm-encoder 0.39.0",
  "wasm-metadata",
- "wasmparser",
+ "wasmparser 0.119.0",
  "wit-parser",
 ]
 
 [[package]]
 name = "wit-parser"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15df6b7b28ce94b8be39d8df5cb21a08a4f3b9f33b631aedb4aa5776f785ead3"
+checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be"
 dependencies = [
  "anyhow",
  "id-arena",
diff --git a/Cargo.toml b/Cargo.toml
index c8fe088e6..cec28df6a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -233,15 +233,15 @@ is-terminal = "0.4.0"
 wit-bindgen = { version = "0.15.0", default-features = false }
 
 # wasm-tools family:
-wasmparser = "0.118.1"
-wat = "1.0.82"
-wast = "69.0.1"
-wasmprinter = "0.2.75"
-wasm-encoder = "0.38.1"
-wasm-smith = "0.13.1"
-wasm-mutate = "0.2.42"
-wit-parser = "0.13.0"
-wit-component = "0.19.0"
+wasmparser = "0.119.0"
+wat = "1.0.83"
+wast = "70.0.0"
+wasmprinter = "0.2.76"
+wasm-encoder = "0.39.0"
+wasm-smith = "0.14.0"
+wasm-mutate = "0.2.44"
+wit-parser = "0.13.1"
+wit-component = "0.19.1"
 
 # Non-Bytecode Alliance maintained dependencies:
 # --------------------------
diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs
index e3d9997c4..163574f53 100644
--- a/cranelift/wasm/src/code_translator.rs
+++ b/cranelift/wasm/src/code_translator.rs
@@ -2503,7 +2503,42 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
             state.push1(r);
         }
 
-        Operator::RefI31 | Operator::I31GetS | Operator::I31GetU => {
+        Operator::TryTable { .. } | Operator::ThrowRef => {
+            unimplemented!("exception operators not yet implemented")
+        }
+
+        Operator::RefI31
+        | Operator::I31GetS
+        | Operator::I31GetU
+        | Operator::RefEq
+        | Operator::RefTestNonNull { .. }
+        | Operator::RefTestNullable { .. }
+        | Operator::RefCastNonNull { .. }
+        | Operator::RefCastNullable { .. }
+        | Operator::BrOnCast { .. }
+        | Operator::BrOnCastFail { .. }
+        | Operator::AnyConvertExtern
+        | Operator::ExternConvertAny
+        | Operator::ArrayNew { .. }
+        | Operator::ArrayNewDefault { .. }
+        | Operator::ArrayNewFixed { .. }
+        | Operator::ArrayNewData { .. }
+        | Operator::ArrayNewElem { .. }
+        | Operator::ArrayGet { .. }
+        | Operator::ArrayGetU { .. }
+        | Operator::ArrayGetS { .. }
+        | Operator::ArraySet { .. }
+        | Operator::ArrayLen { .. }
+        | Operator::ArrayFill { .. }
+        | Operator::ArrayCopy { .. }
+        | Operator::ArrayInitData { .. }
+        | Operator::ArrayInitElem { .. }
+        | Operator::StructNew { .. }
+        | Operator::StructNewDefault { .. }
+        | Operator::StructGetS { .. }
+        | Operator::StructGetU { .. }
+        | Operator::StructSet { .. }
+        | Operator::StructGet { .. } => {
             unimplemented!("GC operators not yet implemented")
         }
     };
diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs
index e38b13277..876d01508 100644
--- a/cranelift/wasm/src/sections_translator.rs
+++ b/cranelift/wasm/src/sections_translator.rs
@@ -401,6 +401,7 @@ pub fn parse_name_section<'data>(
             | wasmparser::Name::Memory(_)
             | wasmparser::Name::Element(_)
             | wasmparser::Name::Data(_)
+            | wasmparser::Name::Tag(_)
             | wasmparser::Name::Unknown { .. } => {}
         }
     }
diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs
index 9e8ea7449..bfdba3bc4 100644
--- a/cranelift/wasm/src/translation_utils.rs
+++ b/cranelift/wasm/src/translation_utils.rs
@@ -4,7 +4,7 @@ use crate::WasmResult;
 use core::u32;
 use cranelift_codegen::ir;
 use cranelift_frontend::FunctionBuilder;
-use wasmparser::{FuncValidator, WasmFuncType, WasmModuleResources};
+use wasmparser::{FuncValidator, WasmModuleResources};
 
 /// Get the parameter and result types for the given Wasm blocktype.
 pub fn blocktype_params_results<'a, T>(
@@ -29,11 +29,13 @@ where
         wasmparser::BlockType::FuncType(ty_index) => {
             let ty = validator
                 .resources()
-                .func_type_at(ty_index)
-                .expect("should be valid");
+                .sub_type_at(ty_index)
+                .expect("should be valid")
+                .unwrap_func();
+
             (
-                itertools::Either::Right(ty.inputs()),
-                itertools::Either::Right(ty.outputs()),
+                itertools::Either::Right(ty.params().iter().copied()),
+                itertools::Either::Right(ty.results().iter().copied()),
             )
         }
     });
diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs
index f2f463eaa..0fecd6970 100644
--- a/crates/environ/src/module_environ.rs
+++ b/crates/environ/src/module_environ.rs
@@ -874,6 +874,7 @@ and for re-adding support for interface types you can see this issue:
                 | wasmparser::Name::Memory(_)
                 | wasmparser::Name::Element(_)
                 | wasmparser::Name::Data(_)
+                | wasmparser::Name::Tag(_)
                 | wasmparser::Name::Unknown { .. } => {}
             }
         }
diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs
index a5914ee36..d91de86b8 100644
--- a/crates/fuzzing/src/generators/config.rs
+++ b/crates/fuzzing/src/generators/config.rs
@@ -47,7 +47,8 @@ impl Config {
         // Allow a memory to be generated, but don't let it get too large.
         // Additionally require the maximum size to guarantee that the growth
         // behavior is consistent across engines.
-        config.max_memory_pages = 10;
+        config.max_memory32_pages = 10;
+        config.max_memory64_pages = 10;
         config.memory_max_size_required = true;
 
         // If tables are generated make sure they don't get too large to avoid
@@ -339,11 +340,13 @@ impl<'a> Arbitrary<'a> for Config {
 
             // Ensure the pooling allocator can support the maximal size of
             // memory, picking the smaller of the two to win.
-            if cfg.max_memory_pages < pooling.memory_pages {
-                pooling.memory_pages = cfg.max_memory_pages;
-            } else {
-                cfg.max_memory_pages = pooling.memory_pages;
-            }
+            let min = cfg
+                .max_memory32_pages
+                .min(cfg.max_memory64_pages)
+                .min(pooling.memory_pages);
+            pooling.memory_pages = min;
+            cfg.max_memory32_pages = min;
+            cfg.max_memory64_pages = min;
 
             // If traps are disallowed then memories must have at least one page
             // of memory so if we still are only allowing 0 pages of memory then
@@ -351,7 +354,8 @@ impl<'a> Arbitrary<'a> for Config {
             if cfg.disallow_traps {
                 if pooling.memory_pages == 0 {
                     pooling.memory_pages = 1;
-                    cfg.max_memory_pages = 1;
+                    cfg.max_memory32_pages = 1;
+                    cfg.max_memory64_pages = 1;
                 }
                 // .. additionally update tables
                 if pooling.table_elements == 0 {
diff --git a/crates/fuzzing/src/generators/module.rs b/crates/fuzzing/src/generators/module.rs
index 7ff78c2b7..e27422021 100644
--- a/crates/fuzzing/src/generators/module.rs
+++ b/crates/fuzzing/src/generators/module.rs
@@ -1,21 +1,20 @@
 //! Generate a Wasm module and the configuration for generating it.
 
 use arbitrary::{Arbitrary, Unstructured};
-use wasm_smith::SwarmConfig;
 
 /// Default module-level configuration for fuzzing Wasmtime.
 ///
-/// Internally this uses `wasm-smith`'s own `SwarmConfig` but we further refine
+/// Internally this uses `wasm-smith`'s own `Config` but we further refine
 /// the defaults here as well.
 #[derive(Debug, Clone)]
 pub struct ModuleConfig {
     #[allow(missing_docs)]
-    pub config: SwarmConfig,
+    pub config: wasm_smith::Config,
 }
 
 impl<'a> Arbitrary<'a> for ModuleConfig {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<ModuleConfig> {
-        let mut config = SwarmConfig::arbitrary(u)?;
+        let mut config = wasm_smith::Config::arbitrary(u)?;
 
         // Allow multi-memory but make it unlikely
         if u.ratio(1, 20)? {
diff --git a/crates/fuzzing/src/generators/single_inst_module.rs b/crates/fuzzing/src/generators/single_inst_module.rs
index e42b45224..9a66fcdf1 100644
--- a/crates/fuzzing/src/generators/single_inst_module.rs
+++ b/crates/fuzzing/src/generators/single_inst_module.rs
@@ -314,11 +314,11 @@ static INSTRUCTIONS: &[SingleInstModule] = &[
     inst!(F32Ge, (f32, f32) -> i32),
     inst!(F64Ge, (f64, f64) -> i32),
     // Integer conversions ("to integer").
-    inst!(I32Extend8S, (i32) -> i32, |c| c.config.sign_extension_enabled),
-    inst!(I32Extend16S, (i32) -> i32, |c| c.config.sign_extension_enabled),
-    inst!(I64Extend8S, (i64) -> i64, |c| c.config.sign_extension_enabled),
-    inst!(I64Extend16S, (i64) -> i64, |c| c.config.sign_extension_enabled),
-    inst!(I64Extend32S, (i64) -> i64, |c| c.config.sign_extension_enabled),
+    inst!(I32Extend8S, (i32) -> i32, |c| c.config.sign_extension_ops_enabled),
+    inst!(I32Extend16S, (i32) -> i32, |c| c.config.sign_extension_ops_enabled),
+    inst!(I64Extend8S, (i64) -> i64, |c| c.config.sign_extension_ops_enabled),
+    inst!(I64Extend16S, (i64) -> i64, |c| c.config.sign_extension_ops_enabled),
+    inst!(I64Extend32S, (i64) -> i64, |c| c.config.sign_extension_ops_enabled),
     inst!(I32WrapI64, (i64) -> i32),
     inst!(I64ExtendI32S, (i32) -> i64),
     inst!(I64ExtendI32U, (i32) -> i64),
diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs
index 06796ae50..68a7c3e76 100644
--- a/crates/types/src/lib.rs
+++ b/crates/types/src/lib.rs
@@ -486,6 +486,7 @@ pub trait TypeConvert {
             wasmparser::HeapType::Concrete(i) => self.lookup_heap_type(i),
 
             wasmparser::HeapType::Any
+            | wasmparser::HeapType::Exn
             | wasmparser::HeapType::None
             | wasmparser::HeapType::NoExtern
             | wasmparser::HeapType::NoFunc
diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs
index d6d0f4291..6e891d549 100644
--- a/crates/wasmtime/src/engine/serialization.rs
+++ b/crates/wasmtime/src/engine/serialization.rs
@@ -221,6 +221,7 @@ impl Metadata<'_> {
             function_references,
             gc,
             component_model_values,
+            component_model_nested_names,
 
             // Always on; we don't currently have knobs for these.
             mutable_global: _,
@@ -232,6 +233,7 @@ impl Metadata<'_> {
         assert!(!memory_control);
         assert!(!gc);
         assert!(!component_model_values);
+        assert!(!component_model_nested_names);
 
         Metadata {
             target: engine.compiler().triple().to_string(),
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 3f56c9a97..60fbc5112 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1947,6 +1947,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-encoder]]
+version = "0.39.0"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-metadata]]
 version = "0.10.9"
 when = "2023-10-14"
@@ -1982,6 +1989,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-metadata]]
+version = "0.10.15"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-mutate]]
 version = "0.2.38"
 when = "2023-10-14"
@@ -2010,6 +2024,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-mutate]]
+version = "0.2.44"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasm-smith]]
 version = "0.12.21"
 when = "2023-10-14"
@@ -2045,6 +2066,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasm-smith]]
+version = "0.14.0"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmparser]]
 version = "0.115.0"
 when = "2023-10-14"
@@ -2080,6 +2108,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmparser]]
+version = "0.119.0"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmprinter]]
 version = "0.2.70"
 when = "2023-10-14"
@@ -2115,6 +2150,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wasmprinter]]
+version = "0.2.76"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wasmtime]]
 version = "14.0.2"
 when = "2023-10-26"
@@ -2750,6 +2792,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wast]]
+version = "70.0.0"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wat]]
 version = "1.0.77"
 when = "2023-10-14"
@@ -2785,6 +2834,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wat]]
+version = "1.0.83"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wiggle]]
 version = "14.0.2"
 when = "2023-10-26"
@@ -3161,6 +3217,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-component]]
+version = "0.19.1"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[publisher.wit-parser]]
 version = "0.12.1"
 when = "2023-10-18"
@@ -3182,6 +3245,13 @@ user-id = 1
 user-login = "alexcrichton"
 user-name = "Alex Crichton"
 
+[[publisher.wit-parser]]
+version = "0.13.1"
+when = "2024-01-09"
+user-id = 1
+user-login = "alexcrichton"
+user-name = "Alex Crichton"
+
 [[audits.embark-studios.wildcard-audits.spdx]]
 who = "Jake Shadle <opensource@embark-studios.com>"
 criteria = "safe-to-deploy"


Test patch:
diff --git a/cranelift/wasm/wasmtests/icall-simd.wat b/cranelift/wasm/wasmtests/icall-simd.wat
index d656b265b..ea6ea9d05 100644
--- a/cranelift/wasm/wasmtests/icall-simd.wat
+++ b/cranelift/wasm/wasmtests/icall-simd.wat
@@ -3,5 +3,5 @@
   (func $foo (export "foo") (param i32) (param v128) (result v128)
     (call_indirect (type $ft) (local.get 1) (local.get 0))
   )
-  (table (;0;) 23 23 anyfunc)
+  (table (;0;) 23 23 funcref)
 )
diff --git a/cranelift/wasm/wasmtests/icall.wat b/cranelift/wasm/wasmtests/icall.wat
index f1dde0eaf..d0c0fb42f 100644
--- a/cranelift/wasm/wasmtests/icall.wat
+++ b/cranelift/wasm/wasmtests/icall.wat
@@ -3,5 +3,5 @@
   (func $foo (export "foo") (param i32 f32) (result i32)
     (call_indirect (type $ft) (local.get 1) (local.get 0))
   )
-  (table (;0;) 23 23 anyfunc)
+  (table (;0;) 23 23 funcref)
 )
diff --git a/cranelift/wasm/wasmtests/unreachable_code.wat b/cranelift/wasm/wasmtests/unreachable_code.wat
index 38c1a315c..0fdcc51b3 100644
--- a/cranelift/wasm/wasmtests/unreachable_code.wat
+++ b/cranelift/wasm/wasmtests/unreachable_code.wat
@@ -72,6 +72,6 @@
     end
     drop
   )
-  (table (;0;) 16 anyfunc)
+  (table (;0;) 16 funcref)
   (elem (i32.const 0))
 )
diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs
index 193c2b313..a515415b6 100644
--- a/crates/test-programs/artifacts/build.rs
+++ b/crates/test-programs/artifacts/build.rs
@@ -74,6 +74,7 @@ fn build_and_generate_tests() {
             s if s.starts_with("cli_") => "cli",
             s if s.starts_with("api_") => "api",
             s if s.starts_with("nn_") => "nn",
+            s if s.starts_with("piped_") => "piped",
             // If you're reading this because you hit this panic, either add it
             // to a test suite above or add a new "suite". The purpose of the
             // categorization above is to have a static assertion that tests
diff --git a/crates/test-programs/src/bin/piped_polling.rs b/crates/test-programs/src/bin/piped_polling.rs
new file mode 100644
index 000000000..05f8b35a8
--- /dev/null
+++ b/crates/test-programs/src/bin/piped_polling.rs
@@ -0,0 +1,51 @@
+use test_programs::wasi::cli::{stdin, stdout};
+
+fn main() {
+    match std::env::var("PIPED_SIDE")
+        .expect("piped tests require the PIPED_SIDE env var")
+        .as_str()
+    {
+        "PRODUCER" => producer(),
+        "CONSUMER" => consumer(),
+        side => panic!("unknown piped test side: {side}"),
+    }
+}
+
+fn producer() {
+    let out = stdout::get_stdout();
+    let out_pollable = out.subscribe();
+
+    for i in 1..100 {
+        let message = format!("{i}");
+        loop {
+            let available = out.check_write().unwrap() as usize;
+            if available >= message.len() {
+                break;
+            }
+
+            out_pollable.block();
+            assert!(out_pollable.ready());
+        }
+
+        out.write(message.as_bytes()).unwrap()
+    }
+
+    drop(out_pollable);
+}
+
+fn consumer() {
+    let stdin = stdin::get_stdin();
+    let stdin_pollable = stdin.subscribe();
+
+    for i in 1..100 {
+        let expected = format!("{i}");
+
+        stdin_pollable.block();
+        assert!(stdin_pollable.ready());
+
+        let bytes = stdin.read(expected.len() as u64).unwrap();
+        assert_eq!(&bytes, expected.as_bytes());
+    }
+
+    drop(stdin_pollable);
+}
diff --git a/crates/test-programs/src/bin/piped_simple.rs b/crates/test-programs/src/bin/piped_simple.rs
new file mode 100644
index 000000000..323e46333
--- /dev/null
+++ b/crates/test-programs/src/bin/piped_simple.rs
@@ -0,0 +1,30 @@
+use test_programs::wasi::cli::{stdin, stdout};
+
+fn main() {
+    match std::env::var("PIPED_SIDE")
+        .expect("piped tests require the PIPED_SIDE env var")
+        .as_str()
+    {
+        "PRODUCER" => producer(),
+        "CONSUMER" => consumer(),
+        side => panic!("unknown piped test side: {side}"),
+    }
+}
+
+const CHUNK: &[u8] = &[b'a'; 50];
+
+fn producer() {
+    let out = stdout::get_stdout();
+    let n = out.check_write().unwrap() as usize;
+    assert!(n > CHUNK.len());
+    out.write(CHUNK).unwrap();
+}
+
+fn consumer() {
+    let stdin = stdin::get_stdin();
+    let stdin_pollable = stdin.subscribe();
+    stdin_pollable.block();
+    assert!(stdin_pollable.ready());
+    let bytes = stdin.read(CHUNK.len() as u64).unwrap();
+    assert_eq!(&bytes, CHUNK);
+}
diff --git a/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs b/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs
index 044ebb336..7b2247764 100644
--- a/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs
+++ b/crates/test-programs/src/bin/preview2_stream_pollable_correct.rs
@@ -1,11 +1,27 @@
-use test_programs::wasi::cli::stdin;
-use test_programs::wasi::io::streams;
+use test_programs::wasi::cli::{stdin, stdout};
 
 fn main() {
-    let stdin: streams::InputStream = stdin::get_stdin();
+    let stdin = stdin::get_stdin();
     let stdin_pollable = stdin.subscribe();
     stdin_pollable.block();
     assert!(stdin_pollable.ready(), "after blocking, pollable is ready");
     drop(stdin_pollable);
     drop(stdin);
+
+    // Pollables can be used many times over their lifetime
+    let stdout = stdout::get_stdout();
+    let stdout_pollable = stdout.subscribe();
+
+    let chunk = [b'a'; 50];
+    for _ in 1..10 {
+        stdout_pollable.block();
+        assert!(stdout_pollable.ready(), "after blocking, pollable is ready");
+
+        let n = stdout.check_write().unwrap() as usize;
+        assert!(n >= chunk.len());
+        stdout.write(&chunk).unwrap();
+    }
+
+    drop(stdout_pollable);
+    drop(stdout);
 }
diff --git a/tests/all/cli_tests/rs2wasm-add-func.wat b/tests/all/cli_tests/rs2wasm-add-func.wat
index 89af3ffc4..660cd8743 100644
--- a/tests/all/cli_tests/rs2wasm-add-func.wat
+++ b/tests/all/cli_tests/rs2wasm-add-func.wat
@@ -6,7 +6,7 @@
     local.get 0
     i32.add)
   (func $start (type 0))
-  (table (;0;) 1 1 anyfunc)
+  (table (;0;) 1 1 funcref)
   (memory (;0;) 17)
   (global (;0;) i32 (i32.const 1049114))
   (global (;1;) i32 (i32.const 1049114))
diff --git a/tests/all/invoke_func_via_table.rs b/tests/all/invoke_func_via_table.rs
index 8edb18386..d8a42fa37 100644
--- a/tests/all/invoke_func_via_table.rs
+++ b/tests/all/invoke_func_via_table.rs
@@ -11,7 +11,7 @@ fn test_invoke_func_via_table() -> Result<()> {
       (module
         (func $f (result i64) (i64.const 42))
 
-        (table (export "table") 1 1 anyfunc)
+        (table (export "table") 1 1 funcref)
         (elem (i32.const 0) $f)
       )
     "#;
diff --git a/tests/all/limits.rs b/tests/all/limits.rs
index ea6d02c08..ffcc793ca 100644
--- a/tests/all/limits.rs
+++ b/tests/all/limits.rs
@@ -12,7 +12,7 @@ fn test_limits() -> Result<()> {
         &engine,
         r#"(module
             (memory $m (export "m") 0)
-            (table (export "t") 0 anyfunc)
+            (table (export "t") 0 funcref)
             (func (export "grow") (param i32) (result i32)
               (memory.grow $m (local.get 0)))
            )"#,
@@ -100,7 +100,7 @@ async fn test_limits_async() -> Result<()> {
     let engine = Engine::new(&config).unwrap();
     let module = Module::new(
         &engine,
-        r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
+        r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
     )?;
 
     struct LimitsAsync {
@@ -190,7 +190,7 @@ fn test_limits_memory_only() -> Result<()> {
     let engine = Engine::default();
     let module = Module::new(
         &engine,
-        r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
+        r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
     )?;
 
     let mut store = Store::new(
@@ -276,7 +276,7 @@ fn test_limits_table_only() -> Result<()> {
     let engine = Engine::default();
     let module = Module::new(
         &engine,
-        r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
+        r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
     )?;
 
     let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(5).build());
@@ -323,7 +323,7 @@ fn test_limits_table_only() -> Result<()> {
 #[test]
 fn test_initial_table_limits_exceeded() -> Result<()> {
     let engine = Engine::default();
-    let module = Module::new(&engine, r#"(module (table (export "t") 23 anyfunc))"#)?;
+    let module = Module::new(&engine, r#"(module (table (export "t") 23 funcref))"#)?;
 
     let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(4).build());
     store.limiter(|s| s as &mut dyn ResourceLimiter);
@@ -661,7 +661,7 @@ fn test_custom_table_limiter() -> Result<()> {
     let engine = Engine::default();
     let linker = Linker::new(&engine);
 
-    let module = Module::new(&engine, r#"(module (table (export "t") 0 anyfunc))"#)?;
+    let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#)?;
 
     let context = TableContext {
         elements_used: 0,
@@ -752,7 +752,7 @@ fn custom_limiter_detect_grow_failure() -> Result<()> {
 
     let module = Module::new(
         &engine,
-        r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
+        r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
     )?;
 
     let context = FailureDetector::default();
@@ -864,7 +864,7 @@ async fn custom_limiter_async_detect_grow_failure() -> Result<()> {
 
     let module = Module::new(
         &engine,
-        r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
+        r#"(module (memory (export "m") 0) (table (export "t") 0 funcref))"#,
     )?;
 
     let context = FailureDetector::default();
@@ -1023,7 +1023,7 @@ fn panic_in_table_limiter() {
     let engine = Engine::default();
     let linker = Linker::new(&engine);
 
-    let module = Module::new(&engine, r#"(module (table (export "t") 0 anyfunc))"#).unwrap();
+    let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#).unwrap();
 
     let mut store = Store::new(&engine, Panic);
     store.limiter(|s| s as &mut dyn ResourceLimiter);
@@ -1095,7 +1095,7 @@ async fn panic_in_async_table_limiter() {
     let engine = Engine::new(&config).unwrap();
     let linker = Linker::new(&engine);
 
-    let module = Module::new(&engine, r#"(module (table (export "t") 0 anyfunc))"#).unwrap();
+    let module = Module::new(&engine, r#"(module (table (export "t") 0 funcref))"#).unwrap();
 
     let mut store = Store::new(&engine, Panic);
     store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
@@ -1117,7 +1117,7 @@ fn growth_trap() -> Result<()> {
         &engine,
         r#"(module
             (memory $m (export "m") 0)
-            (table (export "t") 0 anyfunc)
+            (table (export "t") 0 funcref)
             (func (export "grow") (param i32) (result i32)
               (memory.grow $m (local.get 0)))
            )"#,
diff --git a/tests/all/main.rs b/tests/all/main.rs
index e308b4b43..bb8ac88b0 100644
--- a/tests/all/main.rs
+++ b/tests/all/main.rs
@@ -27,6 +27,7 @@ mod memory_creator;
 mod module;
 mod module_serialize;
 mod name;
+mod piped_tests;
 mod pooling_allocator;
 mod relocs;
 mod stack_creator;
diff --git a/tests/all/piped_tests.rs b/tests/all/piped_tests.rs
new file mode 100644
index 000000000..95f007d04
--- /dev/null
+++ b/tests/all/piped_tests.rs
@@ -0,0 +1,72 @@
+#![cfg(not(miri))]
+
+use super::cli_tests::get_wasmtime_command;
+use anyhow::Result;
+use std::process::{ChildStdout, Command, Stdio};
+
+fn producer_command(component_path: &str) -> Result<Command> {
+    let mut cmd = get_wasmtime_command()?;
+    cmd.arg("run")
+        .arg("-Wcomponent-model")
+        .arg("--env")
+        .arg("PIPED_SIDE=PRODUCER")
+        .arg(component_path)
+        .stdout(Stdio::piped());
+    Ok(cmd)
+}
+
+fn consumer_command(component_path: &str, input: ChildStdout) -> Result<Command> {
+    let mut cmd = get_wasmtime_command()?;
+    cmd.arg("run")
+        .arg("-Wcomponent-model")
+        .arg("--env")
+        .arg("PIPED_SIDE=CONSUMER")
+        .arg(component_path)
+        .stdin(input);
+    Ok(cmd)
+}
+
+pub fn run_wasmtime_piped(component_path: &str) -> Result<()> {
+    let mut producer = producer_command(component_path)?.spawn()?;
+    let stdout = producer.stdout.take().unwrap();
+    let mut consumer = consumer_command(component_path, stdout)?.spawn()?;
+
+    let producer = producer.wait()?;
+    if !producer.success() {
+        // make sure the consumer gets killed off
+        if consumer.try_wait().is_err() {
+            consumer.kill().expect("Failed to kill consumer");
+        }
+
+        panic!("Producer failed");
+    }
+
+    assert!(consumer.wait()?.success(), "Consumer failed");
+
+    Ok(())
+}
+
+mod test_programs {
+    use super::run_wasmtime_piped;
+    use test_programs_artifacts::*;
+
+    macro_rules! assert_test_exists {
+        ($name:ident) => {
+            #[allow(unused_imports)]
+            use self::$name as _;
+        };
+    }
+    foreach_piped!(assert_test_exists);
+
+    // Below here is mechanical: there should be one test for every binary in
+    // wasi-tests.
+    #[test]
+    fn piped_simple() {
+        run_wasmtime_piped(PIPED_SIMPLE_COMPONENT).unwrap()
+    }
+
+    #[test]
+    fn piped_polling() {
+        run_wasmtime_piped(PIPED_POLLING_COMPONENT).unwrap()
+    }
+}
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index ea2d64854..9b3ae2764 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -558,7 +558,7 @@ fn call_signature_mismatch() -> Result<()> {
                 (func $bar (param i32))
                 (start $foo)
 
-                (table 1 anyfunc)
+                (table 1 funcref)
                 (elem (i32.const 0) 1)
             )
         "#,
diff --git a/tests/misc_testsuite/rs2wasm-add-func.wast b/tests/misc_testsuite/rs2wasm-add-func.wast
index 89af3ffc4..660cd8743 100644
--- a/tests/misc_testsuite/rs2wasm-add-func.wast
+++ b/tests/misc_testsuite/rs2wasm-add-func.wast
@@ -6,7 +6,7 @@
     local.get 0
     i32.add)
   (func $start (type 0))
-  (table (;0;) 1 1 anyfunc)
+  (table (;0;) 1 1 funcref)
   (memory (;0;) 17)
   (global (;0;) i32 (i32.const 1049114))
   (global (;1;) i32 (i32.const 1049114))