Skip to content

Commit 71fba5b

Browse files
authored
Merge pull request #723 from wado-lang/claude/wasi-p3-update-eOVtf
Fix CM binding codegen for HTTP and streaming functions
2 parents 3aa8508 + 16e97ff commit 71fba5b

File tree

509 files changed

+40734
-1439
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

509 files changed

+40734
-1439
lines changed

wado-compiler/src/codegen/component.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ pub fn build_component(project: &Project, core_module: &[u8], wir_module: &WirMo
162162
Some("http-response-new"),
163163
ctx.comp_func_idx("http-response-new"),
164164
[
165+
CanonicalOption::Async,
165166
CanonicalOption::Memory(ctx.memory_idx()),
166167
CanonicalOption::Realloc(ctx.core_func_idx("realloc")),
167168
],
@@ -1730,8 +1731,16 @@ fn import_http_types_for_service(
17301731
// Type generation starts after the SubResource exports.
17311732
// CmInstanceTypeGen emits error-code and its payload structs
17321733
// on demand when the parameter/return types are processed.
1734+
// Use interface hint to disambiguate types shared across packages
1735+
// (e.g., ErrorCode exists in http, filesystem, sockets).
17331736
let resource_count = http_resources.len() as u32;
1734-
let mut type_gen = CmInstanceTypeGen::new(resource_count);
1737+
let http_version = project
1738+
.wasi_registry
1739+
.get_package_version("http")
1740+
.expect("WASI HTTP version not found in registry");
1741+
let http_types_interface = format!("wasi:http/types@{http_version}");
1742+
let mut type_gen =
1743+
CmInstanceTypeGen::with_interface_hint(resource_count, &http_types_interface);
17351744
let resource_exports: IndexMap<&str, u32> = http_resources
17361745
.iter()
17371746
.enumerate()

wado-compiler/src/component_model.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,33 @@ impl WasiRegistry {
718718
self.variants.get(name).map(|(_, cases)| cases.as_slice())
719719
}
720720

721+
/// Get the variant cases by interface path + name for disambiguation.
722+
/// Tries interface-qualified key first, falls back to unqualified name.
723+
pub fn get_variant_cases_by_interface(
724+
&self,
725+
interface_path: &str,
726+
name: &str,
727+
) -> Option<&[CmVariantCase]> {
728+
let full_key = format!("{interface_path}#{name}");
729+
self.variants
730+
.get(&full_key)
731+
.or_else(|| self.variants.get(name))
732+
.map(|(_, cases)| cases.as_slice())
733+
}
734+
735+
/// Get the CM variant name by interface path + name for disambiguation.
736+
pub fn get_variant_cm_name_by_interface(
737+
&self,
738+
interface_path: &str,
739+
name: &str,
740+
) -> Option<&str> {
741+
let full_key = format!("{interface_path}#{name}");
742+
self.variants
743+
.get(&full_key)
744+
.or_else(|| self.variants.get(name))
745+
.map(|(cm_name, _)| cm_name.as_str())
746+
}
747+
721748
/// Check if a type name is a registered struct (WIT record)
722749
pub fn is_struct(&self, name: &str) -> bool {
723750
self.structs.contains_key(name)
@@ -1161,13 +1188,27 @@ use wasm_encoder::{ComponentValType, InstanceType, PrimitiveValType, TypeBounds}
11611188
pub struct CmInstanceTypeGen {
11621189
next_idx: u32,
11631190
cache: IndexMap<String, u32>,
1191+
/// Optional interface path hint for disambiguating types with the same name
1192+
/// across different WASI interfaces (e.g., "wasi:http/types@..." to select
1193+
/// HTTP's `ErrorCode` over filesystem's `ErrorCode`).
1194+
interface_hint: Option<String>,
11641195
}
11651196

11661197
impl CmInstanceTypeGen {
11671198
pub fn new(start_idx: u32) -> Self {
11681199
Self {
11691200
next_idx: start_idx,
11701201
cache: IndexMap::default(),
1202+
interface_hint: None,
1203+
}
1204+
}
1205+
1206+
/// Create a new generator with an interface hint for type disambiguation.
1207+
pub fn with_interface_hint(start_idx: u32, interface_hint: &str) -> Self {
1208+
Self {
1209+
next_idx: start_idx,
1210+
cache: IndexMap::default(),
1211+
interface_hint: Some(interface_hint.to_string()),
11711212
}
11721213
}
11731214

@@ -1461,8 +1502,21 @@ impl CmInstanceTypeGen {
14611502
self.cache.insert(cache_key, idx);
14621503
ComponentValType::Type(idx)
14631504
} else if wasi_registry.is_variant(name) {
1464-
let cm_name = wasi_registry.get_variant_cm_name(name).unwrap().to_string();
1465-
let cases = wasi_registry.get_variant_cases(name).unwrap().to_vec();
1505+
let (cm_name, cases) = if let Some(hint) = &self.interface_hint {
1506+
let cm = wasi_registry
1507+
.get_variant_cm_name_by_interface(hint, name)
1508+
.unwrap()
1509+
.to_string();
1510+
let cs = wasi_registry
1511+
.get_variant_cases_by_interface(hint, name)
1512+
.unwrap()
1513+
.to_vec();
1514+
(cm, cs)
1515+
} else {
1516+
let cm = wasi_registry.get_variant_cm_name(name).unwrap().to_string();
1517+
let cs = wasi_registry.get_variant_cases(name).unwrap().to_vec();
1518+
(cm, cs)
1519+
};
14661520
let idx = self.define_variant(
14671521
instance_type,
14681522
&cm_name,

wado-compiler/src/synthesis/cm_binding.rs

Lines changed: 7 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2676,71 +2676,13 @@ fn synthesize_adapter(
26762676
// The binding's return type to the Wado caller:
26772677
let adapter_return_type;
26782678

2679-
// Streaming path: functions with Stream params that callers must write to
2680-
// before the subtask completes. For functions returning tuples with Future
2681-
// (like read_via_stream -> [Stream, Future]), the async path lifts the full
2682-
// tuple from outptr after wait_for_subtask.
2683-
let is_streaming_func = !func_info.is_async && func_info.has_streaming_param();
2684-
if is_streaming_func {
2685-
// Streaming function (sync function with stream params, e.g. write_via_stream).
2686-
// canon lower with async option returns a packed subtask handle, but for sync
2687-
// functions the subtask completes immediately (Returned status). The actual
2688-
// return value (Future handle) is written to the outptr by the canon lower.
2689-
//
2690-
// We wait for the subtask (no-op for Returned), read the Future handle from
2691-
// outptr, free outptr, and return the Future handle. The caller writes stream
2692-
// data and then drops the Future to wait for completion.
2693-
if let Some((outptr_local, outptr_size, outptr_align)) = async_outptr_info {
2694-
let subtask_local = next_local;
2695-
local_types.push(TypeTable::I32);
2696-
next_local += 1;
2697-
body_stmts.push(let_stmt(
2698-
"__subtask",
2699-
subtask_local,
2700-
TypeTable::I32,
2701-
raw_call_expr,
2702-
));
2703-
body_stmts.push(expr_stmt(internal_call(
2704-
"wait_for_subtask",
2705-
vec![local_ref(subtask_local, "__subtask", TypeTable::I32)],
2706-
TypeTable::UNIT,
2707-
)));
2708-
// Read Future handle from outptr
2709-
let future_handle_local = next_local;
2710-
local_types.push(TypeTable::I32);
2711-
next_local += 1;
2712-
body_stmts.push(let_stmt(
2713-
"__future_handle",
2714-
future_handle_local,
2715-
TypeTable::I32,
2716-
builtin_call(
2717-
"i32_load",
2718-
vec![local_ref(outptr_local, "__async_outptr", TypeTable::I32)],
2719-
TypeTable::I32,
2720-
),
2721-
));
2722-
// Free outptr
2723-
body_stmts.push(expr_stmt(builtin_call(
2724-
"realloc",
2725-
vec![
2726-
local_ref(outptr_local, "__async_outptr", TypeTable::I32),
2727-
i32_const(outptr_size as i32),
2728-
i32_const(outptr_align as i32),
2729-
i32_const(0),
2730-
],
2731-
TypeTable::I32,
2732-
)));
2733-
body_stmts.push(return_stmt(Some(local_ref(
2734-
future_handle_local,
2735-
"__future_handle",
2736-
TypeTable::I32,
2737-
))));
2738-
} else {
2739-
// No outptr (shouldn't happen for streaming functions with return type)
2740-
body_stmts.push(return_stmt(Some(raw_call_expr)));
2741-
}
2742-
adapter_return_type = TypeTable::I32;
2743-
} else if needs_async_lower {
2679+
// Async/streaming path: functions lowered with `async` canon option.
2680+
// This covers both truly async functions (func_info.is_async) and sync
2681+
// functions with streaming params (Stream/Future) that require async lowering.
2682+
// Non-async functions with streaming params complete synchronously (RETURNED
2683+
// status), so wait_for_subtask is a no-op. The result is always written to the
2684+
// outptr and lifted via synthesize_lift based on the return type metadata.
2685+
if needs_async_lower {
27442686
// WASI P3 async calling convention: the lowered function returns a subtask
27452687
// handle (i32). 0 = completed synchronously; non-zero = async task in-flight.
27462688
// In both cases, the result is written to the async results buffer in linear memory.

wado-compiler/tests/fixtures.golden/allocator_debug_poison.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wado-compiler/tests/fixtures.golden/allocator_free_rewind.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wado-compiler/tests/fixtures.golden/allocator_grow.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wado-compiler/tests/fixtures.golden/anon_struct_basic.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wado-compiler/tests/fixtures.golden/anon_struct_nested.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wado-compiler/tests/fixtures.golden/anon_struct_shorthand.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wado-compiler/tests/fixtures.golden/anon_struct_structural_eq.wir.wado

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)