diff --git a/meld-core/src/adapter/fact.rs b/meld-core/src/adapter/fact.rs index 7340294..231d36e 100644 --- a/meld-core/src/adapter/fact.rs +++ b/meld-core/src/adapter/fact.rs @@ -42,23 +42,39 @@ fn alignment_for_encoding(encoding: StringEncoding) -> i32 { /// Build a lookup from `(module, field)` → merged function index for resource imports. /// -/// Scans the merged module's imports to find `[resource-rep]` function imports -/// and records their merged function indices. -fn build_resource_import_map( - merged: &MergedModule, -) -> std::collections::HashMap<(String, String), u32> { +/// Scans the merged module's imports to find `[resource-rep]` and `[resource-new]` +/// function imports and records their merged function indices. +type ResourceImportMap = std::collections::HashMap<(String, String), u32>; + +fn build_resource_import_maps(merged: &MergedModule) -> (ResourceImportMap, ResourceImportMap) { use wasm_encoder::EntityType; - let mut map = std::collections::HashMap::new(); + let mut rep_map = std::collections::HashMap::new(); + let mut new_map = std::collections::HashMap::new(); let mut func_idx = 0u32; for imp in &merged.imports { if matches!(imp.entity_type, EntityType::Function(_)) { if imp.name.starts_with("[resource-rep]") { - map.insert((imp.module.clone(), imp.name.clone()), func_idx); + rep_map.insert((imp.module.clone(), imp.name.clone()), func_idx); + } else if imp.name.starts_with("[resource-new]") { + new_map.insert((imp.module.clone(), imp.name.clone()), func_idx); } func_idx += 1; } } - map + (rep_map, new_map) +} + +/// Emit Phase 0: convert borrow resource handles for each `ResourceBorrowTransfer`. +fn emit_resource_borrow_phase0(func: &mut Function, transfers: &[super::ResourceBorrowTransfer]) { + for t in transfers { + func.instruction(&Instruction::LocalGet(t.param_idx)); + func.instruction(&Instruction::Call(t.rep_func)); + if let Some(new_func) = t.new_func { + // 3-component chain: rep → new handle in callee's table + func.instruction(&Instruction::Call(new_func)); + } + func.instruction(&Instruction::LocalSet(t.param_idx)); + } } /// FACT-style adapter generator @@ -79,7 +95,8 @@ impl FactStyleGenerator { site: &AdapterSite, merged: &MergedModule, _adapter_idx: usize, - resource_imports: &std::collections::HashMap<(String, String), u32>, + resource_rep_imports: &std::collections::HashMap<(String, String), u32>, + resource_new_imports: &std::collections::HashMap<(String, String), u32>, ) -> Result { let name = format!( "$adapter_{}_{}_to_{}_{}", @@ -87,7 +104,8 @@ impl FactStyleGenerator { ); // Determine adapter options based on call site - let options = self.analyze_call_site(site, merged, resource_imports); + let options = + self.analyze_call_site(site, merged, resource_rep_imports, resource_new_imports); // Generate the adapter function body let (type_idx, body) = if site.crosses_memory && options.needs_transcoding() { @@ -115,7 +133,8 @@ impl FactStyleGenerator { &self, site: &AdapterSite, merged: &MergedModule, - resource_imports: &std::collections::HashMap<(String, String), u32>, + resource_rep_imports: &std::collections::HashMap<(String, String), u32>, + resource_new_imports: &std::collections::HashMap<(String, String), u32>, ) -> AdapterOptions { let mut options = AdapterOptions::default(); @@ -201,20 +220,82 @@ impl FactStyleGenerator { // // Results are never converted — own results have resource.new called // by the callee's core function, and borrows cannot appear in results. + // Build caller resource params index by flat_idx for quick lookup + let caller_ops: std::collections::HashMap = site + .requirements + .caller_resource_params + .iter() + .map(|op| (op.flat_idx, op)) + .collect(); + for op in &site.requirements.resource_params { if op.is_owned { continue; // own: callee handles conversion internally } - if let Some(&func_idx) = - resource_imports.get(&(op.import_module.clone(), op.import_field.clone())) - { - options.resource_rep_calls.push((op.flat_idx, func_idx)); + + if op.callee_defines_resource { + // 2-component case: callee defines the resource. + // Use callee's [resource-rep] which returns rep directly. + if let Some(&rep_func) = + resource_rep_imports.get(&(op.import_module.clone(), op.import_field.clone())) + { + options + .resource_rep_calls + .push(super::ResourceBorrowTransfer { + param_idx: op.flat_idx, + rep_func, + new_func: None, + }); + } } else { - log::debug!( - "Resource rep import not found: ({}, {})", - op.import_module, - op.import_field - ); + // 3-component case: callee doesn't define the resource. + // Use caller's [resource-rep] to convert handle → rep, + // then callee's [resource-new] to create handle in callee's table. + // + // Find the caller's [resource-rep] by matching the resource name + // from the callee's import field (e.g., "float" from "[resource-rep]float"). + let resource_name = op + .import_field + .strip_prefix("[resource-rep]") + .unwrap_or(&op.import_field); + + // Look for a [resource-rep] import from a DIFFERENT module than callee's + let caller_rep = resource_rep_imports + .iter() + .find(|((module, field), _)| { + field.ends_with(resource_name) + && field.starts_with("[resource-rep]") + && *module != op.import_module + }) + .or_else(|| { + // Fallback: try caller_resource_params if available + caller_ops.get(&op.flat_idx).and_then(|caller_op| { + resource_rep_imports.get_key_value(&( + caller_op.import_module.clone(), + caller_op.import_field.clone(), + )) + }) + }); + + if let Some((_, &rep_func)) = caller_rep { + let new_field = op.import_field.replace("[resource-rep]", "[resource-new]"); + let new_func = resource_new_imports + .get(&(op.import_module.clone(), new_field)) + .copied(); + options + .resource_rep_calls + .push(super::ResourceBorrowTransfer { + param_idx: op.flat_idx, + rep_func, + new_func, + }); + } else { + log::debug!( + "No caller resource rep found for {} at flat_idx {}", + resource_name, + op.flat_idx + ); + } } } @@ -253,12 +334,8 @@ impl FactStyleGenerator { } let mut func = Function::new(locals); - // Phase 0: Convert borrow resource handles → representations - for &(param_idx, rep_func) in &options.resource_rep_calls { - func.instruction(&Instruction::LocalGet(param_idx)); - func.instruction(&Instruction::Call(rep_func)); - func.instruction(&Instruction::LocalSet(param_idx)); - } + // Phase 0: Convert borrow resource handles + emit_resource_borrow_phase0(&mut func, &options.resource_rep_calls); for i in 0..param_count { func.instruction(&Instruction::LocalGet(i as u32)); @@ -398,12 +475,8 @@ impl FactStyleGenerator { } let mut func = Function::new(locals); - // Phase 0: Convert borrow resource handles → representations - for &(param_idx, rep_func) in &options.resource_rep_calls { - func.instruction(&Instruction::LocalGet(param_idx)); - func.instruction(&Instruction::Call(rep_func)); - func.instruction(&Instruction::LocalSet(param_idx)); - } + // Phase 0: Convert borrow resource handles + emit_resource_borrow_phase0(&mut func, &options.resource_rep_calls); for i in 0..param_count { func.instruction(&Instruction::LocalGet(i as u32)); @@ -517,12 +590,8 @@ impl FactStyleGenerator { let mut func = Function::new(local_decls); - // Phase 0: Convert borrow resource handles → representations - for &(param_idx, rep_func) in &options.resource_rep_calls { - func.instruction(&Instruction::LocalGet(param_idx)); - func.instruction(&Instruction::Call(rep_func)); - func.instruction(&Instruction::LocalSet(param_idx)); - } + // Phase 0: Convert borrow resource handles + emit_resource_borrow_phase0(&mut func, &options.resource_rep_calls); // Assign scratch local indices (after params) let base = param_count as u32; @@ -895,12 +964,8 @@ impl FactStyleGenerator { let local_decls = vec![(scratch_count, wasm_encoder::ValType::I32)]; let mut func = Function::new(local_decls); - // Phase 0: Convert borrow resource handles → representations - for &(param_idx, rep_func) in &options.resource_rep_calls { - func.instruction(&Instruction::LocalGet(param_idx)); - func.instruction(&Instruction::Call(rep_func)); - func.instruction(&Instruction::LocalSet(param_idx)); - } + // Phase 0: Convert borrow resource handles + emit_resource_borrow_phase0(&mut func, &options.resource_rep_calls); // --- Phase 1: Outbound copy of ALL pointer pairs (caller → callee) --- if let Some(callee_realloc) = options @@ -1551,12 +1616,8 @@ impl FactStyleGenerator { } let mut func = Function::new(local_decls); - // Phase 0: Convert borrow resource handles → representations - for &(param_idx, rep_func) in &options.resource_rep_calls { - func.instruction(&Instruction::LocalGet(param_idx)); - func.instruction(&Instruction::Call(rep_func)); - func.instruction(&Instruction::LocalSet(param_idx)); - } + // Phase 0: Convert borrow resource handles + emit_resource_borrow_phase0(&mut func, &options.resource_rep_calls); // Generate transcoding logic based on encoding pair @@ -2446,11 +2507,17 @@ impl AdapterGenerator for FactStyleGenerator { merged: &MergedModule, graph: &DependencyGraph, ) -> Result> { - let resource_imports = build_resource_import_map(merged); + let (resource_rep_imports, resource_new_imports) = build_resource_import_maps(merged); let mut adapters = Vec::new(); for (idx, site) in graph.adapter_sites.iter().enumerate() { - let adapter = self.generate_adapter(site, merged, idx, &resource_imports)?; + let adapter = self.generate_adapter( + site, + merged, + idx, + &resource_rep_imports, + &resource_new_imports, + )?; adapters.push(adapter); } diff --git a/meld-core/src/adapter/mod.rs b/meld-core/src/adapter/mod.rs index 0bf8b11..62a7b5a 100644 --- a/meld-core/src/adapter/mod.rs +++ b/meld-core/src/adapter/mod.rs @@ -124,24 +124,33 @@ pub struct AdapterOptions { /// Called after results have been copied back, to allow callee cleanup. pub callee_post_return: Option, - /// Resource borrow params needing handle→representation conversion. - /// Each entry: `(flat_param_idx, merged_func_idx of [resource-rep])`. + /// Resource borrow params needing handle conversion. /// - /// Per the canonical ABI spec, `borrow` params where T is defined by - /// the callee receive the **representation** (raw pointer), not the handle. - /// The `lower_borrow` function has `if cx.inst is t.rt.impl: return rep`. - /// So the adapter must call `[resource-rep]R(handle)` to convert before - /// forwarding to the callee's core function. + /// Per the canonical ABI, `borrow` params need different handling + /// depending on who defines the resource: /// - /// `own` params always receive a **handle** (table index) because - /// `lower_own` unconditionally creates a handle entry. The callee's core - /// function calls `from_handle` / `[resource-rep]` internally, so the - /// adapter must NOT convert own params. + /// - **Callee defines T**: adapter calls `[resource-rep](handle) → rep` + /// and passes rep to callee (which expects raw pointer via identity lift). /// - /// Results are never converted by the adapter — the callee's core function - /// already calls `[resource-new]R` internally for own results, and borrows - /// cannot appear in results. - pub resource_rep_calls: Vec<(u32, u32)>, + /// - **Neither defines T** (3-component chain): adapter calls caller's + /// `[resource-rep](handle) → rep`, then callee's `[resource-new](rep) → new_handle`, + /// and passes new_handle to callee (which expects a handle in its own table). + /// + /// `own` params are never converted (callee calls from_handle internally). + pub resource_rep_calls: Vec, +} + +/// Describes how to transfer a `borrow` handle across an adapter boundary. +#[derive(Debug, Clone)] +pub struct ResourceBorrowTransfer { + /// The flat parameter index holding the handle + pub param_idx: u32, + /// Merged function index of `[resource-rep]` to convert handle → rep + pub rep_func: u32, + /// If the callee doesn't define the resource, the merged function index + /// of `[resource-new]` to convert rep → new handle in callee's table. + /// None when the callee defines the resource (rep is passed directly). + pub new_func: Option, } impl Default for AdapterOptions { diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index 3318a7c..44cc410 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -156,6 +156,12 @@ pub struct ResolvedResourceOp { pub import_module: String, /// Import field name (e.g., `"[resource-rep]y"`) pub import_field: String, + /// Whether the callee defines this resource type. When false, the adapter + /// must use the caller's `[resource-rep]` (to extract the rep from the + /// caller's handle table) followed by the callee's `[resource-new]` (to + /// create a handle in the callee's table). When true, the adapter only + /// needs the callee's `[resource-rep]` (which returns rep directly). + pub callee_defines_resource: bool, } /// Requirements for an adapter function @@ -209,7 +215,12 @@ pub struct AdapterRequirements { pub return_area_slots: Vec, /// Resource-typed parameters needing handle→representation conversion. /// The adapter calls `[resource-rep]` for each before forwarding to callee. + /// These are resolved against the CALLEE's resource map. pub resource_params: Vec, + /// Caller-side resource params. When `callee_defines_resource` is false + /// for a param, the adapter should use the caller's `[resource-rep]` instead + /// of the callee's. Indexed by the same flat_idx as `resource_params`. + pub caller_resource_params: Vec, /// Resource-typed results needing representation→handle conversion. /// The adapter calls `[resource-new]` for each before returning to caller. pub resource_results: Vec, @@ -867,6 +878,7 @@ fn resolve_resource_positions( resource_map: &HashMap<(u32, &'static str), (String, String)>, positions: &[crate::parser::ResourcePosition], field_prefix: &'static str, + callee_type_defs: &[crate::parser::ComponentTypeDef], ) -> Vec { let mut resolved = Vec::new(); for pos in positions { @@ -889,12 +901,20 @@ fn resolve_resource_positions( } }); if let Some((module_name, field_name)) = entry { + // Check if the callee defines this resource type. + // Import(_) means the callee imports the resource from another component. + // Defined means the callee's own type section defines it. + let callee_defines_resource = callee_type_defs + .get(pos.resource_type_id as usize) + .map(|def| !matches!(def, crate::parser::ComponentTypeDef::Import(_))) + .unwrap_or(true); // default to true (SR-25 behavior) resolved.push(ResolvedResourceOp { flat_idx: pos.flat_idx, byte_offset: pos.byte_offset, is_owned: pos.is_owned, import_module: module_name.clone(), import_field: field_name.clone(), + callee_defines_resource, }); } else { log::debug!( @@ -1818,6 +1838,7 @@ impl Resolver { let mut per_func_matched = false; let callee_lift_info = to_component.lift_info_by_core_func(); let callee_resource_map = build_resource_type_to_import(to_component); + let caller_resource_map = build_resource_type_to_import(from_component); // Provenance-based maps for correct core func index lookup. // These account for interleaved canon lower / alias entries. let callee_export_to_core = build_module_export_to_core_func(to_component); @@ -1916,6 +1937,7 @@ impl Resolver { &to_component .resource_param_positions(comp_params), "[resource-rep]", + &to_component.component_type_defs, ); requirements.resource_results = resolve_resource_positions( @@ -1923,6 +1945,16 @@ impl Resolver { &to_component .resource_result_positions(results), "[resource-new]", + &to_component.component_type_defs, + ); + // Caller-side resource params for 3-component chains + requirements.caller_resource_params = + resolve_resource_positions( + &caller_resource_map, + &to_component + .resource_param_positions(comp_params), + "[resource-rep]", + &from_component.component_type_defs, ); } } @@ -2081,12 +2113,24 @@ impl Resolver { &callee_resource_map, &to_component.resource_param_positions(comp_params), "[resource-rep]", + &to_component.component_type_defs, ); requirements.resource_results = resolve_resource_positions( &callee_resource_map, &to_component.resource_result_positions(results), "[resource-new]", + &to_component.component_type_defs, ); + // Caller-side resource params for 3-component chains + let caller_resource_map = + build_resource_type_to_import(from_component); + requirements.caller_resource_params = + resolve_resource_positions( + &caller_resource_map, + &to_component.resource_param_positions(comp_params), + "[resource-rep]", + &from_component.component_type_defs, + ); } } diff --git a/meld-core/tests/wit_bindgen_runtime.rs b/meld-core/tests/wit_bindgen_runtime.rs index a80cead..e5bb12a 100644 --- a/meld-core/tests/wit_bindgen_runtime.rs +++ b/meld-core/tests/wit_bindgen_runtime.rs @@ -650,8 +650,7 @@ fuse_only_test!( test_fuse_wit_bindgen_resource_aggregates, "resource_aggregates" ); -// resource_floats: 3-component chain — adapter uses callee's [resource-rep] but handle -// is in caller's resource table (needs caller-side resource map lookup) +// resource_floats: 3-component borrow forwarding needs caller→callee handle transfer fuse_only_test!(test_fuse_wit_bindgen_resource_floats, "resource_floats"); fuse_only_test!( test_fuse_wit_bindgen_resource_borrow_in_record,