You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
canon task.return receives its result as parameters, so MAX_FLAT_PARAMS (16) applies. A result flattening to more than 16 core values is passed indirectly — one i32 pointer to a buffer the guest lowered the value into:
— flattened with MAX_FLAT_PARAMS, vendor/component-model/design/mvp/CanonicalABI.md
Wado emits the flat parameters regardless of count, so the core import it declares disagrees with the canonical signature the component builds, and the WIR pipeline fails validation.
expected is what the guest declared; found is the canonical signature — a single out-pointer, i.e. the indirect form.
Where the cap is missing
compute_export_flat_return_types (synthesis/cm_binding/types.rs) flattens the whole result with no bound, and every consumer takes the list at face value: record_task_return_flat_params (synthesis/cm_binding.rs) types the shared NIR import from it, and both emission sites — push_result_task_return_epilogue (synthesis/cm_binding/export_adapter.rs) and the task return expansion (synthesis/cm_binding/task_return.rs) — pass one argument per slot. Nothing anywhere compares the count against 16.
Notes for the fix
Both halves of the indirect form already exist and are used by the synchronous lift:
lowering the value into a buffer — synthesize_lower_wasi_type_to_memory (synthesis/cm_binding/lower.rs), the same call push_sync_return_epilogue makes for a multi-value sync result;
So the work is the gate itself: pick the form on the flat-slot count, and thread the single-i32 signature through the import typing.
Found while fixing #1708 — that issue anticipated the indirect variant as a case the reclamation walk would have to handle, on the assumption the form was lowered. It is not, so there is no buffer to reclaim yet.
Summary
canon task.returnreceives its result as parameters, soMAX_FLAT_PARAMS(16) applies. A result flattening to more than 16 core values is passed indirectly — onei32pointer to a buffer the guest lowered the value into:Wado emits the flat parameters regardless of count, so the core import it declares disagrees with the canonical signature the component builds, and the WIR pipeline fails validation.
Reproduction
wado build --lib -O0:expectedis what the guest declared;foundis the canonical signature — a single out-pointer, i.e. the indirect form.Where the cap is missing
compute_export_flat_return_types(synthesis/cm_binding/types.rs) flattens the whole result with no bound, and every consumer takes the list at face value:record_task_return_flat_params(synthesis/cm_binding.rs) types the shared NIR import from it, and both emission sites —push_result_task_return_epilogue(synthesis/cm_binding/export_adapter.rs) and thetask returnexpansion (synthesis/cm_binding/task_return.rs) — pass one argument per slot. Nothing anywhere compares the count against 16.Notes for the fix
Both halves of the indirect form already exist and are used by the synchronous lift:
synthesize_lower_wasi_type_to_memory(synthesis/cm_binding/lower.rs), the same callpush_sync_return_epiloguemakes for a multi-value sync result;synthesize_free_cm_value(synthesis/cm_binding/cm_free.rs), the memory walkpost-returnuses.task.returnlifts eagerly, so the buffer is freed right after the call, exactly as the flat slots are today (Async-lifted exports leak theirtask.returnpayload: nothing frees the lowered buffer #1708).So the work is the gate itself: pick the form on the flat-slot count, and thread the single-
i32signature through the import typing.Found while fixing #1708 — that issue anticipated the indirect variant as a case the reclamation walk would have to handle, on the assumption the form was lowered. It is not, so there is no buffer to reclaim yet.