Repository: bytecodealliance/wasm-tools
Pull request #2084 (2025-03-07 00:47:10)

Enhance dummy async module generation
This commit enhances the `--async-callback` and `--async-stackful` arguments to the `wasm-tools component embed` subcommand when paired with `--dummy-names legacy`. Specifically the dummy module generated now additionally includes imports for various component model intrinsics such as waitable-set management, future management, `task.return`, etc.  The goal of this commit is to stress `wit-component`'s processing of these intrinsics during normal fuzzing.

After the dummy module support was updated the `roundtrip_wit` fuzzer was run locally for awhile and a number of bugs popped out. This commit additionally includes various fixes and test cases. All tests "pass" before this commit because intrinsics weren't generated in dummy mode, but tests all fail without the `wit-component` fixes but with the dummy module support.

alexcrichton: My plan is to make a point release after this is merged.

Patch:
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 9271f5d0..4f5cafc1 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -1206,9 +1206,8 @@ impl ComponentState {
                 CanonicalOption::UTF8
                 | CanonicalOption::UTF16
                 | CanonicalOption::CompactUTF16
-                | CanonicalOption::Memory(_)
-                | CanonicalOption::Realloc(_) => continue,
-
+                | CanonicalOption::Memory(_) => continue,
+                CanonicalOption::Realloc(_) => "realloc",
                 CanonicalOption::PostReturn(_) => "post-return",
                 CanonicalOption::Async => "async",
                 CanonicalOption::Callback(_) => "callback",
diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs
index 452ab21f..80daa051 100644
--- a/crates/wit-component/src/dummy.rs
+++ b/crates/wit-component/src/dummy.rs
@@ -1,4 +1,4 @@
-use wit_parser::abi::WasmType;
+use wit_parser::abi::{AbiVariant, WasmType};
 use wit_parser::{
     Function, LiftLowerAbi, ManglingAndAbi, Resolve, ResourceIntrinsic, TypeDefKind, TypeId,
     WasmExport, WasmExportKind, WasmImport, WorldId, WorldItem, WorldKey,
@@ -12,81 +12,42 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId, mangling: ManglingAndAbi)
     for (name, import) in world.imports.iter() {
         match import {
             WorldItem::Function(func) => {
-                let sig = resolve.wasm_signature(mangling.import_variant(), func);
-
-                let (module, name) = resolve.wasm_import_name(
-                    mangling,
-                    WasmImport::Func {
-                        interface: None,
-                        func,
-                    },
-                );
-
-                wat.push_str(&format!("(import {module:?} {name:?} (func"));
-                push_tys(&mut wat, "param", &sig.params);
-                push_tys(&mut wat, "result", &sig.results);
-                wat.push_str("))\n");
+                push_imported_func(&mut wat, resolve, None, func, mangling);
             }
             WorldItem::Interface { id: import, .. } => {
                 for (_, func) in resolve.interfaces[*import].functions.iter() {
-                    let sig = resolve.wasm_signature(mangling.import_variant(), func);
-
-                    let (module, name) = resolve.wasm_import_name(
-                        mangling,
-                        WasmImport::Func {
-                            interface: Some(name),
-                            func,
-                        },
-                    );
-                    wat.push_str(&format!("(import {module:?} {name:?} (func"));
-                    push_tys(&mut wat, "param", &sig.params);
-                    push_tys(&mut wat, "result", &sig.results);
-                    wat.push_str("))\n");
+                    push_imported_func(&mut wat, resolve, Some(name), func, mangling);
                 }
                 for (_, ty) in resolve.interfaces[*import].types.iter() {
-                    push_resource_func_imports(&mut wat, resolve, Some(name), *ty, mangling);
+                    push_imported_type_intrinsics(&mut wat, resolve, Some(name), *ty, mangling);
                 }
             }
             WorldItem::Type(id) => {
-                push_resource_func_imports(&mut wat, resolve, None, *id, mangling);
+                push_imported_type_intrinsics(&mut wat, resolve, None, *id, mangling);
             }
         }
     }
 
-    // Import any resource-related functions for exports.
+    if mangling.is_async() {
+        push_root_async_intrinsics(&mut wat);
+    }
+
+    // Append any intrinsics which are imported but used in exported items
+    // (e.g. resources)
     for (name, export) in world.exports.iter() {
-        let export = match export {
-            WorldItem::Interface { id, .. } => *id,
-            _ => continue,
-        };
-        for resource in resolve.interfaces[export].types.values().copied() {
-            let ty = &resolve.types[resource];
-            match ty.kind {
-                TypeDefKind::Resource => {}
-                _ => continue,
+        match export {
+            WorldItem::Function(func) => {
+                push_exported_func_intrinsics(&mut wat, resolve, None, func, mangling);
             }
-            let intrinsics = [
-                (ResourceIntrinsic::ExportedDrop, "(func (param i32))"),
-                (
-                    ResourceIntrinsic::ExportedNew,
-                    "(func (param i32) (result i32))",
-                ),
-                (
-                    ResourceIntrinsic::ExportedRep,
-                    "(func (param i32) (result i32))",
-                ),
-            ];
-            for (intrinsic, sig) in intrinsics {
-                let (module, name) = resolve.wasm_import_name(
-                    mangling.sync(),
-                    WasmImport::ResourceIntrinsic {
-                        interface: Some(name),
-                        resource,
-                        intrinsic,
-                    },
-                );
-                wat.push_str(&format!("(import {module:?} {name:?} {sig})\n"));
+            WorldItem::Interface { id: export, .. } => {
+                for (_, func) in resolve.interfaces[*export].functions.iter() {
+                    push_exported_func_intrinsics(&mut wat, resolve, Some(name), func, mangling);
+                }
+                for (_, ty) in resolve.interfaces[*export].types.iter() {
+                    push_exported_type_intrinsics(&mut wat, resolve, Some(name), *ty, mangling);
+                }
             }
+            WorldItem::Type(_) => {}
         }
     }
 
@@ -99,23 +60,8 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId, mangling: ManglingAndAbi)
                 for (_, func) in resolve.interfaces[*export].functions.iter() {
                     push_func_export(&mut wat, resolve, Some(name), func, mangling);
                 }
-
-                // Feign destructors for any resource that this interface
-                // exports
-                for resource in resolve.interfaces[*export].types.values().copied() {
-                    let ty = &resolve.types[resource];
-                    match ty.kind {
-                        TypeDefKind::Resource => {}
-                        _ => continue,
-                    }
-                    let name = resolve.wasm_export_name(
-                        mangling,
-                        WasmExport::ResourceDtor {
-                            interface: name,
-                            resource,
-                        },
-                    );
-                    wat.push_str(&format!("(func (export {name:?}) (param i32))"));
+                for (_, ty) in resolve.interfaces[*export].types.iter() {
+                    push_exported_resource_functions(&mut wat, resolve, name, *ty, mangling);
                 }
             }
             WorldItem::Type(_) => {}
@@ -128,107 +74,318 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId, mangling: ManglingAndAbi)
     wat.push_str(&format!(
         "(func (export {realloc:?}) (param i32 i32 i32 i32) (result i32) unreachable)\n"
     ));
+
     let initialize = resolve.wasm_export_name(mangling, WasmExport::Initialize);
     wat.push_str(&format!("(func (export {initialize:?}))"));
     wat.push_str(")\n");
 
     return wat::parse_str(&wat).unwrap();
+}
+
+fn push_imported_func(
+    wat: &mut String,
+    resolve: &Resolve,
+    interface: Option<&WorldKey>,
+    func: &Function,
+    mangling: ManglingAndAbi,
+) {
+    let sig = resolve.wasm_signature(mangling.import_variant(), func);
 
-    fn push_resource_func_imports(
-        wat: &mut String,
-        resolve: &Resolve,
-        interface: Option<&WorldKey>,
-        resource: TypeId,
-        mangling: ManglingAndAbi,
-    ) {
-        let ty = &resolve.types[resource];
-        match ty.kind {
-            TypeDefKind::Resource => {}
-            _ => return,
+    let (module, name) = resolve.wasm_import_name(mangling, WasmImport::Func { interface, func });
+    wat.push_str(&format!("(import {module:?} {name:?} (func"));
+    push_tys(wat, "param", &sig.params);
+    push_tys(wat, "result", &sig.results);
+    wat.push_str("))\n");
+
+    if mangling.is_async() {
+        push_imported_future_and_stream_intrinsics(wat, resolve, "", interface, func);
+    }
+}
+
+fn push_imported_type_intrinsics(
+    wat: &mut String,
+    resolve: &Resolve,
+    interface: Option<&WorldKey>,
+    resource: TypeId,
+    mangling: ManglingAndAbi,
+) {
+    let ty = &resolve.types[resource];
+    match ty.kind {
+        TypeDefKind::Resource => {
+            let (module, name) = resolve.wasm_import_name(
+                // Force using a sync ABI here at this time as support for async
+                // resource drop isn't implemented yet.
+                mangling.sync(),
+                WasmImport::ResourceIntrinsic {
+                    interface,
+                    resource,
+                    intrinsic: ResourceIntrinsic::ImportedDrop,
+                },
+            );
+            wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))"));
+
+            if mangling.is_async() {
+                // TODO: when wit-component supports async resource drop,
+                // implement it here too.
+                // let name = format!("[async-lower]{name}");
+                // wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))"));
+            }
         }
-        let (module, name) = resolve.wasm_import_name(
-            // Force using a sync ABI here at this time as support for async
-            // resource drop isn't implemented yet.
-            mangling.sync(),
-            WasmImport::ResourceIntrinsic {
-                interface,
-                resource,
-                intrinsic: ResourceIntrinsic::ImportedDrop,
-            },
-        );
-        wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))"));
+
+        // No other types with intrinsics at this time (futures/streams are
+        // relative to where they show up in function types.
+        _ => {}
     }
+}
 
-    fn push_func_export(
-        wat: &mut String,
-        resolve: &Resolve,
-        interface: Option<&WorldKey>,
-        func: &Function,
-        mangling: ManglingAndAbi,
-    ) {
-        let sig = resolve.wasm_signature(mangling.export_variant(), func);
-        let name = resolve.wasm_export_name(
-            mangling,
-            WasmExport::Func {
-                interface,
-                func,
-                kind: WasmExportKind::Normal,
-            },
-        );
-        wat.push_str(&format!("(func (export \"{name}\")"));
-        push_tys(wat, "param", &sig.params);
-        push_tys(wat, "result", &sig.results);
-        wat.push_str(" unreachable)\n");
-
-        match mangling {
-            ManglingAndAbi::Standard32 | ManglingAndAbi::Legacy(LiftLowerAbi::Sync) => {
-                let name = resolve.wasm_export_name(
-                    mangling,
-                    WasmExport::Func {
-                        interface,
-                        func,
-                        kind: WasmExportKind::PostReturn,
-                    },
-                );
-                wat.push_str(&format!("(func (export \"{name}\")"));
-                push_tys(wat, "param", &sig.results);
-                wat.push_str(")\n");
+fn push_exported_func_intrinsics(
+    wat: &mut String,
+    resolve: &Resolve,
+    interface: Option<&WorldKey>,
+    func: &Function,
+    mangling: ManglingAndAbi,
+) {
+    if !mangling.is_async() {
+        return;
+    }
+
+    // For exported async functions, generate a `task.return` intrinsic.
+    let module = match interface {
+        Some(key) => format!("[export]{}", resolve.name_world_key(key)),
+        None => "[export]$root".to_string(),
+    };
+    let name = format!("[task-return]{}", func.name);
+    let mut func_tmp = func.clone();
+    func_tmp.params = Vec::new();
+    func_tmp.result = None;
+    if let Some(ty) = func.result {
+        func_tmp.params.push(("x".to_string(), ty));
+    }
+    let sig = resolve.wasm_signature(AbiVariant::GuestImport, &func_tmp);
+    wat.push_str(&format!("(import {module:?} {name:?} (func"));
+    push_tys(wat, "param", &sig.params);
+    push_tys(wat, "result", &sig.results);
+    wat.push_str("))\n");
+
+    push_imported_future_and_stream_intrinsics(wat, resolve, "[export]", interface, func);
+}
+
+fn push_imported_future_and_stream_intrinsics(
+    wat: &mut String,
+    resolve: &Resolve,
+    module_prefix: &str,
+    interface: Option<&WorldKey>,
+    func: &Function,
+) {
+    let module = match interface {
+        Some(key) => format!("{module_prefix}{}", resolve.name_world_key(key)),
+        None => format!("{module_prefix}$root"),
+    };
+    let name = &func.name;
+
+    for (i, id) in func
+        .find_futures_and_streams(resolve)
+        .into_iter()
+        .enumerate()
+    {
+        match &resolve.types[id].kind {
+            TypeDefKind::Future(_) => {
+                wat.push_str(&format!(
+                    r#"
+(import {module:?} "[future-new-{i}]{name}" (func (result i32)))
+(import {module:?} "[future-read-{i}]{name}" (func (param i32 i32) (result i32)))
+(import {module:?} "[future-write-{i}]{name}" (func (param i32 i32) (result i32)))
+(import {module:?} "[future-cancel-read-{i}]{name}" (func (param i32) (result i32)))
+(import {module:?} "[future-cancel-write-{i}]{name}" (func (param i32) (result i32)))
+(import {module:?} "[future-close-readable-{i}]{name}" (func (param i32 i32)))
+(import {module:?} "[future-close-writable-{i}]{name}" (func (param i32 i32)))
+(import {module:?} "[async-lower][future-read-{i}]{name}" (func (param i32 i32) (result i32)))
+(import {module:?} "[async-lower][future-write-{i}]{name}" (func (param i32 i32) (result i32)))
+(import {module:?} "[async-lower][future-cancel-read-{i}]{name}" (func (param i32) (result i32)))
+(import {module:?} "[async-lower][future-cancel-write-{i}]{name}" (func (param i32) (result i32)))
+"#
+                ));
+            }
+            TypeDefKind::Stream(_) => {
+                wat.push_str(&format!(
+                    r#"
+(import {module:?} "[stream-new-{i}]{name}" (func (result i32)))
+(import {module:?} "[stream-read-{i}]{name}" (func (param i32 i32 i32) (result i32)))
+(import {module:?} "[stream-write-{i}]{name}" (func (param i32 i32 i32) (result i32)))
+(import {module:?} "[stream-cancel-read-{i}]{name}" (func (param i32) (result i32)))
+(import {module:?} "[stream-cancel-write-{i}]{name}" (func (param i32) (result i32)))
+(import {module:?} "[stream-close-readable-{i}]{name}" (func (param i32 i32)))
+(import {module:?} "[stream-close-writable-{i}]{name}" (func (param i32 i32)))
+(import {module:?} "[async-lower][stream-read-{i}]{name}" (func (param i32 i32 i32) (result i32)))
+(import {module:?} "[async-lower][stream-write-{i}]{name}" (func (param i32 i32 i32) (result i32)))
+(import {module:?} "[async-lower][stream-cancel-read-{i}]{name}" (func (param i32) (result i32)))
+(import {module:?} "[async-lower][stream-cancel-write-{i}]{name}" (func (param i32) (result i32)))
+"#
+                ));
             }
-            ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback) => {
-                let name = resolve.wasm_export_name(
-                    mangling,
-                    WasmExport::Func {
+            _ => unreachable!(),
+        }
+    }
+}
+
+fn push_exported_type_intrinsics(
+    wat: &mut String,
+    resolve: &Resolve,
+    interface: Option<&WorldKey>,
+    resource: TypeId,
+    mangling: ManglingAndAbi,
+) {
+    let ty = &resolve.types[resource];
+    match ty.kind {
+        TypeDefKind::Resource => {
+            let intrinsics = [
+                (ResourceIntrinsic::ExportedDrop, "(func (param i32))"),
+                (
+                    ResourceIntrinsic::ExportedNew,
+                    "(func (param i32) (result i32))",
+                ),
+                (
+                    ResourceIntrinsic::ExportedRep,
+                    "(func (param i32) (result i32))",
+                ),
+            ];
+            for (intrinsic, sig) in intrinsics {
+                let (module, name) = resolve.wasm_import_name(
+                    mangling.sync(),
+                    WasmImport::ResourceIntrinsic {
                         interface,
-                        func,
-                        kind: WasmExportKind::Callback,
+                        resource,
+                        intrinsic,
                     },
                 );
-                wat.push_str(&format!(
-                    "(func (export \"{name}\") (param i32 i32 i32 i32) (result i32) unreachable)\n"
-                ));
+                wat.push_str(&format!("(import {module:?} {name:?} {sig})\n"));
             }
-            ManglingAndAbi::Legacy(LiftLowerAbi::AsyncStackful) => {}
         }
+
+        // No other types with intrinsics at this time (futures/streams
+        // relative to where they are in a function).
+        _ => {}
+    }
+}
+
+fn push_exported_resource_functions(
+    wat: &mut String,
+    resolve: &Resolve,
+    interface: &WorldKey,
+    resource: TypeId,
+    mangling: ManglingAndAbi,
+) {
+    let ty = &resolve.types[resource];
+    match ty.kind {
+        TypeDefKind::Resource => {}
+        _ => return,
     }
+    // Feign destructors for any resource that this interface
+    // exports
+    let name = resolve.wasm_export_name(
+        mangling,
+        WasmExport::ResourceDtor {
+            interface,
+            resource,
+        },
+    );
+    wat.push_str(&format!("(func (export {name:?}) (param i32))"));
+}
 
-    fn push_tys(dst: &mut String, desc: &str, params: &[WasmType]) {
-        if params.is_empty() {
-            return;
+fn push_func_export(
+    wat: &mut String,
+    resolve: &Resolve,
+    interface: Option<&WorldKey>,
+    func: &Function,
+    mangling: ManglingAndAbi,
+) {
+    let sig = resolve.wasm_signature(mangling.export_variant(), func);
+    let name = resolve.wasm_export_name(
+        mangling,
+        WasmExport::Func {
+            interface,
+            func,
+            kind: WasmExportKind::Normal,
+        },
+    );
+    wat.push_str(&format!("(func (export \"{name}\")"));
+    push_tys(wat, "param", &sig.params);
+    push_tys(wat, "result", &sig.results);
+    wat.push_str(" unreachable)\n");
+
+    match mangling {
+        ManglingAndAbi::Standard32 | ManglingAndAbi::Legacy(LiftLowerAbi::Sync) => {
+            let name = resolve.wasm_export_name(
+                mangling,
+                WasmExport::Func {
+                    interface,
+                    func,
+                    kind: WasmExportKind::PostReturn,
+                },
+            );
+            wat.push_str(&format!("(func (export \"{name}\")"));
+            push_tys(wat, "param", &sig.results);
+            wat.push_str(")\n");
         }
-        dst.push_str(" (");
-        dst.push_str(desc);
-        for ty in params {
-            dst.push(' ');
-            match ty {
-                WasmType::I32 => dst.push_str("i32"),
-                WasmType::I64 => dst.push_str("i64"),
-                WasmType::F32 => dst.push_str("f32"),
-                WasmType::F64 => dst.push_str("f64"),
-                WasmType::Pointer => dst.push_str("i32"),
-                WasmType::PointerOrI64 => dst.push_str("i64"),
-                WasmType::Length => dst.push_str("i32"),
-            }
+        ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback) => {
+            let name = resolve.wasm_export_name(
+                mangling,
+                WasmExport::Func {
+                    interface,
+                    func,
+                    kind: WasmExportKind::Callback,
+                },
+            );
+            wat.push_str(&format!(
+                "(func (export \"{name}\") (param i32 i32 i32 i32) (result i32) unreachable)\n"
+            ));
+        }
+        ManglingAndAbi::Legacy(LiftLowerAbi::AsyncStackful) => {}
+    }
+}
+
+fn push_tys(dst: &mut String, desc: &str, params: &[WasmType]) {
+    if params.is_empty() {
+        return;
+    }
+    dst.push_str(" (");
+    dst.push_str(desc);
+    for ty in params {
+        dst.push(' ');
+        match ty {
+            WasmType::I32 => dst.push_str("i32"),
+            WasmType::I64 => dst.push_str("i64"),
+            WasmType::F32 => dst.push_str("f32"),
+            WasmType::F64 => dst.push_str("f64"),
+            WasmType::Pointer => dst.push_str("i32"),
+            WasmType::PointerOrI64 => dst.push_str("i64"),
+            WasmType::Length => dst.push_str("i32"),
         }
-        dst.push(')');
     }
+    dst.push(')');
+}
+
+fn push_root_async_intrinsics(dst: &mut String) {
+    dst.push_str(
+        r#"
+(import "$root" "[backpressure-set]" (func (param i32)))
+(import "$root" "[waitable-set-new]" (func (result i32)))
+(import "$root" "[waitable-set-wait]" (func (param i32 i32) (result i32)))
+(import "$root" "[async-lower][waitable-set-wait]" (func (param i32 i32) (result i32)))
+(import "$root" "[waitable-set-poll]" (func (param i32 i32) (result i32)))
+(import "$root" "[async-lower][waitable-set-poll]" (func (param i32 i32) (result i32)))
+(import "$root" "[waitable-set-drop]" (func (param i32)))
+(import "$root" "[waitable-join]" (func (param i32 i32)))
+(import "$root" "[yield]" (func))
+(import "$root" "[async-lower][yield]" (func))
+(import "$root" "[subtask-drop]" (func (param i32)))
+(import "$root" "[error-context-new-utf8]" (func (param i32 i32) (result i32)))
+(import "$root" "[error-context-new-utf16]" (func (param i32 i32) (result i32)))
+(import "$root" "[error-context-new-latin1+utf16]" (func (param i32 i32) (result i32)))
+(import "$root" "[error-context-debug-message-utf8]" (func (param i32 i32)))
+(import "$root" "[error-context-debug-message-utf16]" (func (param i32 i32)))
+(import "$root" "[error-context-debug-message-latin1+utf16]" (func (param i32 i32)))
+(import "$root" "[error-context-drop]" (func (param i32)))
+"#,
+    );
 }
diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs
index 54f03f19..c7bc1fb3 100644
--- a/crates/wit-component/src/encoding.rs
+++ b/crates/wit-component/src/encoding.rs
@@ -1348,7 +1348,13 @@ impl<'a> EncodingState<'a> {
                     encoding,
                     for_module,
                 } => {
-                    let mut encoder = self.root_export_type_encoder(*interface);
+                    // See `Import::ExportedTaskReturn` handling for why this
+                    // encoder is treated specially.
+                    let mut encoder = if interface.is_none() {
+                        self.root_import_type_encoder(*interface)
+                    } else {
+                        self.root_export_type_encoder(*interface)
+                    };
                     let result = match result {
                         Some(ty) => Some(encoder.encode_valtype(resolve, ty)?),
                         None => None,
@@ -1387,8 +1393,23 @@ impl<'a> EncodingState<'a> {
     /// encoder to use.
     fn payload_type_index(&mut self, info: &PayloadInfo) -> Result<u32> {
         let resolve = &self.info.encoder.metadata.resolve;
-        let ComponentValType::Type(type_index) = if info.imported {
-            self.root_import_type_encoder(info.interface)
+        // What exactly is selected here as the encoder is a bit unusual here.
+        // If the interface is imported, an import encoder is used. An import
+        // encoder is also used though if `info` is exported and
+        // `info.interface` is `None`, meaning that this is for a function that
+        // is in the top-level of a world. At the top level of a world all
+        // types are imported.
+        //
+        // Additionally for the import encoder the interface passed in is
+        // `None`, not `info.interface`. Notably this means that references to
+        // named types will be aliased from their imported versions, which is
+        // what we want here.
+        //
+        // Finally though exports do use `info.interface`. Honestly I'm not
+        // really entirely sure why. Fuzzing is happy though, and truly
+        // everything must be ok if the fuzzers are happy, right?
+        let ComponentValType::Type(type_index) = if info.imported || info.interface.is_none() {
+            self.root_import_type_encoder(None)
         } else {
             self.root_export_type_encoder(info.interface)
         }
@@ -1619,7 +1640,16 @@ impl<'a> EncodingState<'a> {
             Import::ExportedTaskReturn(key, interface, func, result) => {
                 let (options, _sig) = task_return_options_and_type(resolve, *result);
                 if options.is_empty() {
-                    let mut encoder = self.root_export_type_encoder(*interface);
+                    // Note that an "import type encoder" is used here despite
+                    // this being for an exported function if the `interface`
+                    // is none, meaning that this is for a top-level world
+                    // function. In that situation all types that can be
+                    // referred to are imported, not exported.
+                    let mut encoder = if interface.is_none() {
+                        self.root_import_type_encoder(*interface)
+                    } else {
+                        self.root_export_type_encoder(*interface)
+                    };
 
                     let result = match result {
                         Some(ty) => Some(encoder.encode_valtype(resolve, ty)?),
diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs
index 4adc1812..912b6951 100644
--- a/crates/wit-component/src/validation.rs
+++ b/crates/wit-component/src/validation.rs
@@ -608,12 +608,9 @@ impl ImportMap {
             }
 
             if Some(name) == names.yield_() {
-                validate_not_async()?;
                 let expected = FuncType::new([], []);
                 validate_func_sig(name, &expected, ty)?;
-                return Ok(Import::Yield {
-                    async_: abi == AbiVariant::GuestImportAsync,
-                });
+                return Ok(Import::Yield { async_ });
             }
 
             if Some(name) == names.subtask_drop() {
@@ -817,7 +814,7 @@ impl ImportMap {
                 // calculating the expected signature based of `func.result`.
                 return Ok(Some(Import::ExportedTaskReturn(
                     key,
-                    None,
+                    id,
                     func.name.clone(),
                     func.result,
                 )));
diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs
index 97673d4e..d8551db1 100644
--- a/crates/wit-parser/src/lib.rs
+++ b/crates/wit-parser/src/lib.rs
@@ -1042,6 +1042,15 @@ impl ManglingAndAbi {
             | Self::Legacy(LiftLowerAbi::AsyncStackful) => Self::Legacy(LiftLowerAbi::Sync),
         }
     }
+
+    /// Returns whether this is an async ABI
+    pub fn is_async(&self) -> bool {
+        match self {
+            Self::Standard32 | Self::Legacy(LiftLowerAbi::Sync) => false,
+            Self::Legacy(LiftLowerAbi::AsyncCallback)
+            | Self::Legacy(LiftLowerAbi::AsyncStackful) => true,
+        }
+    }
 }
 
 impl Function {
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index aa64565b..8950dcd1 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -3898,7 +3898,7 @@ fn update_stability(from: &Stability, into: &mut Stability) -> Result<()> {
 }
 
 /// Compares the two attributes and if the `from` is more stable than the `into` then
-/// it will elevate the `into` to the same stability.  
+/// it will elevate the `into` to the same stability.
 /// This should be used after its already been confirmed that the types are the same and
 /// should be apart of the component because versions/features are enabled.
 fn merge_stability(from: &Stability, into: &mut Stability) -> Result<()> {
diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs
index 504097dc..bf9833f3 100644
--- a/fuzz/src/roundtrip_wit.rs
+++ b/fuzz/src/roundtrip_wit.rs
@@ -35,7 +35,6 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
     // to avoid timing out this fuzzer with asan enabled.
     let mut decoded_bindgens = Vec::new();
     for (id, world) in resolve.worlds.iter().take(20) {
-        log::debug!("embedding world {} as in a dummy module", world.name);
         let mangling = match u.int_in_range(0..=3)? {
             0 => ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
             1 => ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback),
@@ -43,6 +42,10 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
             3 => ManglingAndAbi::Standard32,
             _ => unreachable!(),
         };
+        log::debug!(
+            "embedding world {} as in a dummy module with abi {mangling:?}",
+            world.name
+        );
         let mut dummy = wit_component::dummy_module(&resolve, id, mangling);
         wit_component::embed_component_metadata(&mut dummy, &resolve, id, StringEncoding::UTF8)
             .unwrap();
diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs
index 18549374..9baf414d 100644
--- a/src/bin/wasm-tools/component.rs
+++ b/src/bin/wasm-tools/component.rs
@@ -364,6 +364,9 @@ impl EmbedOpts {
         }
 
         let mut wasm = if self.dummy {
+            if self.async_callback || self.async_stackful {
+                bail!("non-legacy mangling not yet supported when generating async dummy modules");
+            }
             wit_component::dummy_module(&resolve, world, ManglingAndAbi::Standard32)
         } else if let Some(mangling) = self.dummy_names {
             wit_component::dummy_module(


Test patch:
diff --git a/crates/wit-component/tests/components/async-builtins/component.wat b/crates/wit-component/tests/components/async-builtins/component.wat
index b5718fa1..da1b9368 100644
--- a/crates/wit-component/tests/components/async-builtins/component.wat
+++ b/crates/wit-component/tests/components/async-builtins/component.wat
@@ -46,7 +46,7 @@
     (type (;0;) (func (param i32 i32) (result i32)))
     (type (;1;) (func (param i32 i32)))
     (type (;2;) (func (param i32 i32)))
-    (table (;0;) 9 9 funcref)
+    (table (;0;) 10 10 funcref)
     (export "0" (func $waitable-set.wait))
     (export "1" (func $waitable-set.poll))
     (export "2" (func $error-new))
@@ -56,6 +56,7 @@
     (export "6" (func $"#func6 error-debug-message"))
     (export "7" (func $"#func7 error-debug-message"))
     (export "8" (func $task-return-foo))
+    (export "9" (func $"#func9 task-return-foo"))
     (export "$imports" (table 0))
     (func $waitable-set.wait (;0;) (type 0) (param i32 i32) (result i32)
       local.get 0
@@ -111,6 +112,12 @@
       i32.const 8
       call_indirect (type 2)
     )
+    (func $"#func9 task-return-foo" (@name "task-return-foo") (;9;) (type 2) (param i32 i32)
+      local.get 0
+      local.get 1
+      i32.const 9
+      call_indirect (type 2)
+    )
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
     )
@@ -128,8 +135,9 @@
     (import "" "6" (func (;6;) (type 1)))
     (import "" "7" (func (;7;) (type 1)))
     (import "" "8" (func (;8;) (type 2)))
-    (import "" "$imports" (table (;0;) 9 9 funcref))
-    (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8)
+    (import "" "9" (func (;9;) (type 2)))
+    (import "" "$imports" (table (;0;) 10 10 funcref))
+    (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9)
     (@producers
       (processed-by "wit-component" "$CARGO_PKG_VERSION")
     )
@@ -171,8 +179,9 @@
   (core instance (;2;)
     (export "[task-return]foo" (func 15))
   )
+  (alias core export 0 "9" (core func (;16;)))
   (core instance (;3;)
-    (export "[task-return]foo" (func 15))
+    (export "[task-return]foo" (func 16))
   )
   (core instance (;4;) (instantiate 0
       (with "$root" (instance 1))
@@ -182,39 +191,41 @@
   )
   (alias core export 4 "memory" (core memory (;0;)))
   (alias core export 0 "$imports" (core table (;0;)))
-  (core func (;16;) (canon waitable-set.wait (memory 0)))
-  (core func (;17;) (canon waitable-set.poll (memory 0)))
-  (core func (;18;) (canon error-context.new (memory 0) string-encoding=utf8))
-  (core func (;19;) (canon error-context.new (memory 0) string-encoding=utf16))
-  (core func (;20;) (canon error-context.new (memory 0) string-encoding=latin1+utf16))
-  (alias core export 4 "cabi_realloc" (core func (;21;)))
-  (core func (;22;) (canon error-context.debug-message (memory 0) (realloc 21) string-encoding=utf8))
-  (core func (;23;) (canon error-context.debug-message (memory 0) (realloc 21) string-encoding=utf16))
-  (core func (;24;) (canon error-context.debug-message (memory 0) (realloc 21) string-encoding=latin1+utf16))
-  (core func (;25;) (canon task.return (result string) (memory 0) string-encoding=utf8))
+  (core func (;17;) (canon waitable-set.wait (memory 0)))
+  (core func (;18;) (canon waitable-set.poll (memory 0)))
+  (core func (;19;) (canon error-context.new (memory 0) string-encoding=utf8))
+  (core func (;20;) (canon error-context.new (memory 0) string-encoding=utf16))
+  (core func (;21;) (canon error-context.new (memory 0) string-encoding=latin1+utf16))
+  (alias core export 4 "cabi_realloc" (core func (;22;)))
+  (core func (;23;) (canon error-context.debug-message (memory 0) (realloc 22) string-encoding=utf8))
+  (core func (;24;) (canon error-context.debug-message (memory 0) (realloc 22) string-encoding=utf16))
+  (core func (;25;) (canon error-context.debug-message (memory 0) (realloc 22) string-encoding=latin1+utf16))
+  (core func (;26;) (canon task.return (result string) (memory 0) string-encoding=utf8))
+  (core func (;27;) (canon task.return (result string) (memory 0) string-encoding=utf8))
   (core instance (;5;)
     (export "$imports" (table 0))
-    (export "0" (func 16))
-    (export "1" (func 17))
-    (export "2" (func 18))
-    (export "3" (func 19))
-    (export "4" (func 20))
-    (export "5" (func 22))
-    (export "6" (func 23))
-    (export "7" (func 24))
-    (export "8" (func 25))
+    (export "0" (func 17))
+    (export "1" (func 18))
+    (export "2" (func 19))
+    (export "3" (func 20))
+    (export "4" (func 21))
+    (export "5" (func 23))
+    (export "6" (func 24))
+    (export "7" (func 25))
+    (export "8" (func 26))
+    (export "9" (func 27))
   )
   (core instance (;6;) (instantiate 2
       (with "" (instance 5))
     )
   )
   (type (;0;) (func (param "s" string) (result string)))
-  (alias core export 4 "[async-lift-stackful]foo" (core func (;26;)))
-  (func (;0;) (type 0) (canon lift (core func 26) (memory 0) (realloc 21) string-encoding=utf8 async))
+  (alias core export 4 "[async-lift-stackful]foo" (core func (;28;)))
+  (func (;0;) (type 0) (canon lift (core func 28) (memory 0) (realloc 22) string-encoding=utf8 async))
   (export (;1;) "foo" (func 0))
   (type (;1;) (func (param "s" string) (result string)))
-  (alias core export 4 "[async-lift-stackful]foo:foo/bar#foo" (core func (;27;)))
-  (func (;2;) (type 1) (canon lift (core func 27) (memory 0) (realloc 21) string-encoding=utf8 async))
+  (alias core export 4 "[async-lift-stackful]foo:foo/bar#foo" (core func (;29;)))
+  (func (;2;) (type 1) (canon lift (core func 29) (memory 0) (realloc 22) string-encoding=utf8 async))
   (component (;0;)
     (type (;0;) (func (param "s" string) (result string)))
     (import "import-func-foo" (func (;0;) (type 0)))
diff --git a/crates/wit-component/tests/components/async-streams-and-futures/component.wat b/crates/wit-component/tests/components/async-streams-and-futures/component.wat
index 558a3ca2..37fc7d71 100644
--- a/crates/wit-component/tests/components/async-streams-and-futures/component.wat
+++ b/crates/wit-component/tests/components/async-streams-and-futures/component.wat
@@ -458,30 +458,27 @@
     (export "[future-close-readable-1]foo" (func 42))
     (export "[future-close-writable-1]foo" (func 43))
   )
-  (type (;8;) (stream string))
-  (core func (;44;) (canon stream.new 8))
+  (core func (;44;) (canon stream.new 3))
   (alias core export 0 "14" (core func (;45;)))
   (alias core export 0 "15" (core func (;46;)))
-  (core func (;47;) (canon stream.cancel-read 8))
-  (core func (;48;) (canon stream.cancel-write 8))
-  (core func (;49;) (canon stream.close-readable 8))
-  (core func (;50;) (canon stream.close-writable 8))
-  (type (;9;) (future u32))
-  (core func (;51;) (canon future.new 9))
+  (core func (;47;) (canon stream.cancel-read 3))
+  (core func (;48;) (canon stream.cancel-write 3))
+  (core func (;49;) (canon stream.close-readable 3))
+  (core func (;50;) (canon stream.close-writable 3))
+  (core func (;51;) (canon future.new 1))
   (alias core export 0 "16" (core func (;52;)))
   (alias core export 0 "17" (core func (;53;)))
-  (core func (;54;) (canon future.cancel-read 9))
-  (core func (;55;) (canon future.cancel-write 9))
-  (core func (;56;) (canon future.close-readable 9))
-  (core func (;57;) (canon future.close-writable 9))
-  (type (;10;) (future 9))
-  (core func (;58;) (canon future.new 10))
+  (core func (;54;) (canon future.cancel-read 1))
+  (core func (;55;) (canon future.cancel-write 1))
+  (core func (;56;) (canon future.close-readable 1))
+  (core func (;57;) (canon future.close-writable 1))
+  (core func (;58;) (canon future.new 2))
   (alias core export 0 "18" (core func (;59;)))
   (alias core export 0 "19" (core func (;60;)))
-  (core func (;61;) (canon future.cancel-read 10))
-  (core func (;62;) (canon future.cancel-write 10))
-  (core func (;63;) (canon future.close-readable 10))
-  (core func (;64;) (canon future.close-writable 10))
+  (core func (;61;) (canon future.cancel-read 2))
+  (core func (;62;) (canon future.cancel-write 2))
+  (core func (;63;) (canon future.close-readable 2))
+  (core func (;64;) (canon future.close-writable 2))
   (core instance (;3;)
     (export "[stream-new-2]foo" (func 44))
     (export "[stream-read-2]foo" (func 45))
@@ -505,30 +502,30 @@
     (export "[future-close-readable-1]foo" (func 63))
     (export "[future-close-writable-1]foo" (func 64))
   )
-  (type (;11;) (stream string))
-  (core func (;65;) (canon stream.new 11))
+  (type (;8;) (stream string))
+  (core func (;65;) (canon stream.new 8))
   (alias core export 0 "20" (core func (;66;)))
   (alias core export 0 "21" (core func (;67;)))
-  (core func (;68;) (canon stream.cancel-read 11))
-  (core func (;69;) (canon stream.cancel-write 11))
-  (core func (;70;) (canon stream.close-readable 11))
-  (core func (;71;) (canon stream.close-writable 11))
-  (type (;12;) (future u32))
-  (core func (;72;) (canon future.new 12))
+  (core func (;68;) (canon stream.cancel-read 8))
+  (core func (;69;) (canon stream.cancel-write 8))
+  (core func (;70;) (canon stream.close-readable 8))
+  (core func (;71;) (canon stream.close-writable 8))
+  (type (;9;) (future u32))
+  (core func (;72;) (canon future.new 9))
   (alias core export 0 "22" (core func (;73;)))
   (alias core export 0 "23" (core func (;74;)))
-  (core func (;75;) (canon future.cancel-read 12))
-  (core func (;76;) (canon future.cancel-write 12))
-  (core func (;77;) (canon future.close-readable 12))
-  (core func (;78;) (canon future.close-writable 12))
-  (type (;13;) (future 12))
-  (core func (;79;) (canon future.new 13))
+  (core func (;75;) (canon future.cancel-read 9))
+  (core func (;76;) (canon future.cancel-write 9))
+  (core func (;77;) (canon future.close-readable 9))
+  (core func (;78;) (canon future.close-writable 9))
+  (type (;10;) (future 9))
+  (core func (;79;) (canon future.new 10))
   (alias core export 0 "24" (core func (;80;)))
   (alias core export 0 "25" (core func (;81;)))
-  (core func (;82;) (canon future.cancel-read 13))
-  (core func (;83;) (canon future.cancel-write 13))
-  (core func (;84;) (canon future.close-readable 13))
-  (core func (;85;) (canon future.close-writable 13))
+  (core func (;82;) (canon future.cancel-read 10))
+  (core func (;83;) (canon future.cancel-write 10))
+  (core func (;84;) (canon future.close-readable 10))
+  (core func (;85;) (canon future.close-writable 10))
   (core instance (;4;)
     (export "[stream-new-2]foo" (func 65))
     (export "[stream-read-2]foo" (func 66))
@@ -577,18 +574,18 @@
   (core func (;98;) (canon future.write 6 (memory 0)))
   (core func (;99;) (canon future.read 7 (memory 0)))
   (core func (;100;) (canon future.write 7 (memory 0)))
-  (core func (;101;) (canon stream.read 8 (memory 0) (realloc 86) string-encoding=utf8))
-  (core func (;102;) (canon stream.write 8 (memory 0) string-encoding=utf8))
-  (core func (;103;) (canon future.read 9 (memory 0)))
-  (core func (;104;) (canon future.write 9 (memory 0)))
-  (core func (;105;) (canon future.read 10 (memory 0)))
-  (core func (;106;) (canon future.write 10 (memory 0)))
-  (core func (;107;) (canon stream.read 11 (memory 0) (realloc 86) string-encoding=utf8))
-  (core func (;108;) (canon stream.write 11 (memory 0) string-encoding=utf8))
-  (core func (;109;) (canon future.read 12 (memory 0)))
-  (core func (;110;) (canon future.write 12 (memory 0)))
-  (core func (;111;) (canon future.read 13 (memory 0)))
-  (core func (;112;) (canon future.write 13 (memory 0)))
+  (core func (;101;) (canon stream.read 3 (memory 0) (realloc 86) string-encoding=utf8))
+  (core func (;102;) (canon stream.write 3 (memory 0) string-encoding=utf8))
+  (core func (;103;) (canon future.read 1 (memory 0)))
+  (core func (;104;) (canon future.write 1 (memory 0)))
+  (core func (;105;) (canon future.read 2 (memory 0)))
+  (core func (;106;) (canon future.write 2 (memory 0)))
+  (core func (;107;) (canon stream.read 8 (memory 0) (realloc 86) string-encoding=utf8))
+  (core func (;108;) (canon stream.write 8 (memory 0) string-encoding=utf8))
+  (core func (;109;) (canon future.read 9 (memory 0)))
+  (core func (;110;) (canon future.write 9 (memory 0)))
+  (core func (;111;) (canon future.read 10 (memory 0)))
+  (core func (;112;) (canon future.write 10 (memory 0)))
   (core instance (;6;)
     (export "$imports" (table 0))
     (export "0" (func 87))
@@ -625,9 +622,9 @@
   (alias core export 5 "[async-lift-stackful]foo" (core func (;113;)))
   (func (;2;) (type 4) (canon lift (core func 113) async))
   (export (;3;) "foo" (func 2))
-  (type (;14;) (func (param "x" 13) (param "y" u32) (result 11)))
+  (type (;11;) (func (param "x" 10) (param "y" u32) (result 8)))
   (alias core export 5 "[async-lift-stackful]foo:foo/bar#foo" (core func (;114;)))
-  (func (;4;) (type 14) (canon lift (core func 114) async))
+  (func (;4;) (type 11) (canon lift (core func 114) async))
   (component (;0;)
     (type (;0;) (future u32))
     (type (;1;) (future 0))
diff --git a/tests/cli/dummy-async-export-future-with-named-type.wit b/tests/cli/dummy-async-export-future-with-named-type.wit
new file mode 100644
index 00000000..7f9764fa
--- /dev/null
+++ b/tests/cli/dummy-async-export-future-with-named-type.wit
@@ -0,0 +1,12 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        validate -f component-model-async
+
+package a:b;
+
+world c {
+  export i: interface {
+    flags name { a, b, c }
+    f: func(p: future<name>);
+  }
+}
diff --git a/tests/cli/dummy-async-future-export-with-named-type.wit b/tests/cli/dummy-async-future-export-with-named-type.wit
new file mode 100644
index 00000000..72f77c1b
--- /dev/null
+++ b/tests/cli/dummy-async-future-export-with-named-type.wit
@@ -0,0 +1,11 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        component wit
+
+package a:b;
+
+world c {
+  enum name { a, b, c }
+
+  export f: func(a: future<name>);
+}
diff --git a/tests/cli/dummy-async-future-export-with-named-type.wit.stdout b/tests/cli/dummy-async-future-export-with-named-type.wit.stdout
new file mode 100644
index 00000000..41035914
--- /dev/null
+++ b/tests/cli/dummy-async-future-export-with-named-type.wit.stdout
@@ -0,0 +1,11 @@
+package root:component;
+
+world root {
+  enum name {
+    a,
+    b,
+    c,
+  }
+
+  export f: func(a: future<name>);
+}
diff --git a/tests/cli/dummy-async-future-with-flags.wit b/tests/cli/dummy-async-future-with-flags.wit
new file mode 100644
index 00000000..a36ae9bb
--- /dev/null
+++ b/tests/cli/dummy-async-future-with-flags.wit
@@ -0,0 +1,13 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        validate -f component-model-async
+
+package y:name;
+
+world name {
+  export name: interface {
+    flags name { a, b, c }
+
+    f: func(name: future<name>);
+  }
+}
diff --git a/tests/cli/dummy-async-indirect-task-return-with-named-type.wit b/tests/cli/dummy-async-indirect-task-return-with-named-type.wit
new file mode 100644
index 00000000..759e8314
--- /dev/null
+++ b/tests/cli/dummy-async-indirect-task-return-with-named-type.wit
@@ -0,0 +1,11 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        component wit
+
+package x:name;
+
+world name1 {
+  enum name { a, b, c }
+
+  export f: func() -> tuple<string, name>;
+}
diff --git a/tests/cli/dummy-async-indirect-task-return-with-named-type.wit.stdout b/tests/cli/dummy-async-indirect-task-return-with-named-type.wit.stdout
new file mode 100644
index 00000000..5ceb3af4
--- /dev/null
+++ b/tests/cli/dummy-async-indirect-task-return-with-named-type.wit.stdout
@@ -0,0 +1,11 @@
+package root:component;
+
+world root {
+  enum name {
+    a,
+    b,
+    c,
+  }
+
+  export f: func() -> tuple<string, name>;
+}
diff --git a/tests/cli/dummy-async-resource-with-stream.wit b/tests/cli/dummy-async-resource-with-stream.wit
new file mode 100644
index 00000000..8ec09a29
--- /dev/null
+++ b/tests/cli/dummy-async-resource-with-stream.wit
@@ -0,0 +1,13 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        validate -f component-model-async
+
+package a:b;
+
+world c {
+  import i: interface {
+    resource r {
+      f: func(s: stream<r>);
+    }
+  }
+}
diff --git a/tests/cli/dummy-async-resource.wit b/tests/cli/dummy-async-resource.wit
new file mode 100644
index 00000000..5a6b8fff
--- /dev/null
+++ b/tests/cli/dummy-async-resource.wit
@@ -0,0 +1,14 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        validate -f component-model-async
+
+package a:b;
+
+world c {
+  export name: interface {
+    resource n {
+      constructor();
+    }
+  }
+}
+
diff --git a/tests/cli/dummy-async-valid-component1.wit b/tests/cli/dummy-async-valid-component1.wit
new file mode 100644
index 00000000..27bd9d0d
--- /dev/null
+++ b/tests/cli/dummy-async-valid-component1.wit
@@ -0,0 +1,11 @@
+// RUN: component embed % --dummy-names legacy --async-callback | \
+//        component new | \
+//        component wit
+
+package root:root;
+
+world root {
+  type xe = u32;
+
+  export name: func() -> xe;
+}
diff --git a/tests/cli/dummy-async-valid-component1.wit.stdout b/tests/cli/dummy-async-valid-component1.wit.stdout
new file mode 100644
index 00000000..68e186ea
--- /dev/null
+++ b/tests/cli/dummy-async-valid-component1.wit.stdout
@@ -0,0 +1,7 @@
+package root:component;
+
+world root {
+  type xe = u32;
+
+  export name: func() -> xe;
+}
diff --git a/tests/cli/dummy-async.wit b/tests/cli/dummy-async.wit
new file mode 100644
index 00000000..4b8e682d
--- /dev/null
+++ b/tests/cli/dummy-async.wit
@@ -0,0 +1,5 @@
+// FAIL: component embed --dummy --async-stackful % -t
+
+package a:b;
+
+world foo {}
diff --git a/tests/cli/dummy-async.wit.stderr b/tests/cli/dummy-async.wit.stderr
new file mode 100644
index 00000000..1435860e
--- /dev/null
+++ b/tests/cli/dummy-async.wit.stderr
@@ -0,0 +1,1 @@
+error: non-legacy mangling not yet supported when generating async dummy modules
diff --git a/tests/cli/dummy-async2.wit b/tests/cli/dummy-async2.wit
new file mode 100644
index 00000000..de60f091
--- /dev/null
+++ b/tests/cli/dummy-async2.wit
@@ -0,0 +1,5 @@
+// FAIL: component embed --dummy-names standard32 --async-stackful % -t
+
+package a:b;
+
+world foo {}
diff --git a/tests/cli/dummy-async2.wit.stderr b/tests/cli/dummy-async2.wit.stderr
new file mode 100644
index 00000000..1435860e
--- /dev/null
+++ b/tests/cli/dummy-async2.wit.stderr
@@ -0,0 +1,1 @@
+error: non-legacy mangling not yet supported when generating async dummy modules
diff --git a/tests/local/component-model-async/task-builtins.wast b/tests/local/component-model-async/task-builtins.wast
index d13d5088..45e83755 100644
--- a/tests/local/component-model-async/task-builtins.wast
+++ b/tests/local/component-model-async/task-builtins.wast
@@ -46,17 +46,25 @@
   )
   "cannot specify `post-return` option on `task.return`")
 
+(assert_invalid
+  (component
+    (core module $m
+      (func (export "r") (param i32 i32 i32 i32) (result i32) unreachable)
+    )
+    (core instance $m (instantiate $m))
+    (core func $task-return (canon task.return (result u32) (realloc (func $m "r"))))
+  )
+  "cannot specify `realloc` option on `task.return`")
+
 (component
   (core module $m
     (memory (export "m") 1)
-    (func (export "r") (param i32 i32 i32 i32) (result i32) unreachable)
   )
   (core instance $i (instantiate $m))
   (core func (canon task.return (result u32) string-encoding=utf8))
   (core func (canon task.return (result u32) string-encoding=utf16))
   (core func (canon task.return (result u32) string-encoding=latin1+utf16))
   (core func (canon task.return (result u32) (memory $i "m")))
-  (core func (canon task.return (result u32) (realloc (func $i "r"))))
 )
 
 ;; waitable-set.new
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast.json b/tests/snapshots/local/component-model-async/task-builtins.wast.json
index dfe9b963..260e12f3 100644
--- a/tests/snapshots/local/component-model-async/task-builtins.wast.json
+++ b/tests/snapshots/local/component-model-async/task-builtins.wast.json
@@ -42,106 +42,113 @@
       "text": "cannot specify `post-return` option on `task.return`"
     },
     {
-      "type": "module",
-      "line": 49,
+      "type": "assert_invalid",
+      "line": 50,
       "filename": "task-builtins.6.wasm",
-      "module_type": "binary"
+      "module_type": "binary",
+      "text": "cannot specify `realloc` option on `task.return`"
     },
     {
       "type": "module",
-      "line": 63,
+      "line": 59,
       "filename": "task-builtins.7.wasm",
       "module_type": "binary"
     },
     {
-      "type": "assert_invalid",
+      "type": "module",
       "line": 71,
       "filename": "task-builtins.8.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 79,
+      "filename": "task-builtins.9.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `waitable-set.new` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 80,
-      "filename": "task-builtins.9.wasm",
+      "line": 88,
+      "filename": "task-builtins.10.wasm",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 92,
-      "filename": "task-builtins.10.wasm",
+      "line": 100,
+      "filename": "task-builtins.11.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `waitable-set.wait` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 105,
-      "filename": "task-builtins.11.wasm",
+      "line": 113,
+      "filename": "task-builtins.12.wasm",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 117,
-      "filename": "task-builtins.12.wasm",
+      "line": 125,
+      "filename": "task-builtins.13.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `waitable-set.poll` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 130,
-      "filename": "task-builtins.13.wasm",
+      "line": 138,
+      "filename": "task-builtins.14.wasm",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 138,
-      "filename": "task-builtins.14.wasm",
+      "line": 146,
+      "filename": "task-builtins.15.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `waitable-set.drop` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 147,
-      "filename": "task-builtins.15.wasm",
+      "line": 155,
+      "filename": "task-builtins.16.wasm",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 155,
-      "filename": "task-builtins.16.wasm",
+      "line": 163,
+      "filename": "task-builtins.17.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `waitable.join` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 164,
-      "filename": "task-builtins.17.wasm",
+      "line": 172,
+      "filename": "task-builtins.18.wasm",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 174,
-      "filename": "task-builtins.18.wasm",
+      "line": 182,
+      "filename": "task-builtins.19.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `yield` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 185,
-      "filename": "task-builtins.19.wasm",
+      "line": 193,
+      "filename": "task-builtins.20.wasm",
       "module_type": "binary"
     },
     {
       "type": "assert_invalid",
-      "line": 195,
-      "filename": "task-builtins.20.wasm",
+      "line": 203,
+      "filename": "task-builtins.21.wasm",
       "module_type": "binary",
       "text": "type mismatch for export `subtask.drop` of module instantiation argument ``"
     },
     {
       "type": "module",
-      "line": 207,
-      "filename": "task-builtins.21.wasm",
+      "line": 215,
+      "filename": "task-builtins.22.wasm",
       "module_type": "binary"
     }
   ]
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/9.print b/tests/snapshots/local/component-model-async/task-builtins.wast/10.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/9.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/10.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/11.print b/tests/snapshots/local/component-model-async/task-builtins.wast/12.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/11.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/12.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/13.print b/tests/snapshots/local/component-model-async/task-builtins.wast/14.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/13.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/14.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/15.print b/tests/snapshots/local/component-model-async/task-builtins.wast/16.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/15.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/16.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/17.print b/tests/snapshots/local/component-model-async/task-builtins.wast/18.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/17.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/18.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/19.print b/tests/snapshots/local/component-model-async/task-builtins.wast/20.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/19.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/20.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/21.print b/tests/snapshots/local/component-model-async/task-builtins.wast/22.print
similarity index 100%
rename from tests/snapshots/local/component-model-async/task-builtins.wast/21.print
rename to tests/snapshots/local/component-model-async/task-builtins.wast/22.print
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/6.print b/tests/snapshots/local/component-model-async/task-builtins.wast/6.print
deleted file mode 100644
index 86207d78..00000000
--- a/tests/snapshots/local/component-model-async/task-builtins.wast/6.print
+++ /dev/null
@@ -1,19 +0,0 @@
-(component
-  (core module $m (;0;)
-    (type (;0;) (func (param i32 i32 i32 i32) (result i32)))
-    (memory (;0;) 1)
-    (export "m" (memory 0))
-    (export "r" (func 0))
-    (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32)
-      unreachable
-    )
-  )
-  (core instance $i (;0;) (instantiate $m))
-  (core func (;0;) (canon task.return (result u32) string-encoding=utf8))
-  (core func (;1;) (canon task.return (result u32) string-encoding=utf16))
-  (core func (;2;) (canon task.return (result u32) string-encoding=latin1+utf16))
-  (alias core export $i "m" (core memory (;0;)))
-  (core func (;3;) (canon task.return (result u32) (memory 0)))
-  (alias core export $i "r" (core func (;4;)))
-  (core func (;5;) (canon task.return (result u32) (realloc 4)))
-)
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/7.print b/tests/snapshots/local/component-model-async/task-builtins.wast/7.print
index 967b3b8c..48e7d0ad 100644
--- a/tests/snapshots/local/component-model-async/task-builtins.wast/7.print
+++ b/tests/snapshots/local/component-model-async/task-builtins.wast/7.print
@@ -1,14 +1,12 @@
 (component
   (core module $m (;0;)
-    (type (;0;) (func (result i32)))
-    (import "" "waitable-set.new" (func (;0;) (type 0)))
-  )
-  (core func $waitable-set-new (;0;) (canon waitable-set.new))
-  (core instance (;0;)
-    (export "waitable-set.new" (func $waitable-set-new))
-  )
-  (core instance $i (;1;) (instantiate $m
-      (with "" (instance 0))
-    )
+    (memory (;0;) 1)
+    (export "m" (memory 0))
   )
+  (core instance $i (;0;) (instantiate $m))
+  (core func (;0;) (canon task.return (result u32) string-encoding=utf8))
+  (core func (;1;) (canon task.return (result u32) string-encoding=utf16))
+  (core func (;2;) (canon task.return (result u32) string-encoding=latin1+utf16))
+  (alias core export $i "m" (core memory (;0;)))
+  (core func (;3;) (canon task.return (result u32) (memory 0)))
 )
diff --git a/tests/snapshots/local/component-model-async/task-builtins.wast/8.print b/tests/snapshots/local/component-model-async/task-builtins.wast/8.print
new file mode 100644
index 00000000..967b3b8c
--- /dev/null
+++ b/tests/snapshots/local/component-model-async/task-builtins.wast/8.print
@@ -0,0 +1,14 @@
+(component
+  (core module $m (;0;)
+    (type (;0;) (func (result i32)))
+    (import "" "waitable-set.new" (func (;0;) (type 0)))
+  )
+  (core func $waitable-set-new (;0;) (canon waitable-set.new))
+  (core instance (;0;)
+    (export "waitable-set.new" (func $waitable-set-new))
+  )
+  (core instance $i (;1;) (instantiate $m
+      (with "" (instance 0))
+    )
+  )
+)