diff --git a/meld-core/src/adapter/fact.rs b/meld-core/src/adapter/fact.rs index 515c477..07af55e 100644 --- a/meld-core/src/adapter/fact.rs +++ b/meld-core/src/adapter/fact.rs @@ -351,6 +351,29 @@ impl FactStyleGenerator { } } + // Resolve inner resource handles from param copy layouts. + // When list elements contain borrow, the adapter must convert + // each handle after bulk-copying the list data to callee memory. + for layout in &site.requirements.param_copy_layouts { + if let crate::resolver::CopyLayout::Elements { + inner_resources, .. + } = layout + { + for &(byte_offset, _resource_type_id, is_owned) in inner_resources { + if is_owned { + continue; // own in lists — callee handles internally + } + // Find any [resource-rep] import for borrow handles. + // For 2-component (callee defines), use callee's rep. + // For 3-component, would need caller's rep + callee's new. + // For now, find ANY matching [resource-rep] import. + if let Some(&rep_func) = resource_rep_imports.values().next() { + options.inner_resource_fixups.push((byte_offset, rep_func)); + } + } + } + } + options } @@ -600,6 +623,8 @@ impl FactStyleGenerator { let has_resource_ops = !options.resource_rep_calls.is_empty(); + let has_inner_resource_fixups = !options.inner_resource_fixups.is_empty(); + // If no copying, no post-return save, and no resource ops needed, direct call if !needs_outbound_copy && !needs_conditional_copy @@ -607,6 +632,7 @@ impl FactStyleGenerator { && !needs_conditional_result_copy && !needs_post_return_save && !has_resource_ops + && !has_inner_resource_fixups { let mut func = Function::new([]); for i in 0..param_count { @@ -624,14 +650,17 @@ impl FactStyleGenerator { 0 }; let inner_fixup_locals: u32 = 4 * nonretptr_fixup_depth; + let inner_resource_locals: u32 = if has_inner_resource_fixups { 1 } else { 0 }; // loop counter let copy_scratch_count: u32 = if needs_outbound_copy && needs_result_copy { - 4 + inner_fixup_locals // dest_ptr + callee_ret_ptr + callee_ret_len + caller_new_ptr + fixup + 4 + inner_fixup_locals + inner_resource_locals } else if needs_result_copy { 3 // callee_ret_ptr + callee_ret_len + caller_new_ptr } else if needs_outbound_copy { - 1 + inner_fixup_locals // dest_ptr + fixup + 1 + inner_fixup_locals + inner_resource_locals // dest_ptr + fixup + resource loop } else if needs_conditional_copy || needs_conditional_result_copy { 1 // dest_ptr for conditional copy (param or result side) + } else if has_inner_resource_fixups { + 1 + inner_resource_locals // dest_ptr + resource loop counter } else { 0 // post-return-only path (no copy needed) }; @@ -721,6 +750,7 @@ impl FactStyleGenerator { if let Some(crate::resolver::CopyLayout::Elements { element_size, inner_pointers, + .. }) = site.requirements.param_copy_layouts.first() && !inner_pointers.is_empty() { @@ -739,6 +769,65 @@ impl FactStyleGenerator { ); } + // 2c. Fix up inner resource handles if element type contains borrow + if !options.inner_resource_fixups.is_empty() + && let Some(crate::resolver::CopyLayout::Elements { element_size, .. }) = + site.requirements.param_copy_layouts.first() + { + let element_size = *element_size; + // Use inner_fixup_locals for loop counter + let loop_idx = dest_ptr_local + 1 + inner_fixup_locals; + func.instruction(&Instruction::I32Const(0)); + func.instruction(&Instruction::LocalSet(loop_idx)); + // block $exit { loop $cont { + func.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty)); + func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty)); + // if loop_idx >= len: break + func.instruction(&Instruction::LocalGet(loop_idx)); + func.instruction(&Instruction::LocalGet(1)); // len + func.instruction(&Instruction::I32GeU); + func.instruction(&Instruction::BrIf(1)); + for &(byte_offset, rep_func) in &options.inner_resource_fixups { + // addr = dest_ptr + loop_idx * element_size + byte_offset + // i32.store needs [addr, value] on stack. + // Emit: addr, addr, i32.load → handle, call rep → rep, i32.store + // First push addr for the store: + func.instruction(&Instruction::LocalGet(dest_ptr_local)); + func.instruction(&Instruction::LocalGet(loop_idx)); + func.instruction(&Instruction::I32Const(element_size as i32)); + func.instruction(&Instruction::I32Mul); + func.instruction(&Instruction::I32Add); + // Stack: [addr_for_store] + // Now load handle from same addr using offset + func.instruction(&Instruction::LocalGet(dest_ptr_local)); + func.instruction(&Instruction::LocalGet(loop_idx)); + func.instruction(&Instruction::I32Const(element_size as i32)); + func.instruction(&Instruction::I32Mul); + func.instruction(&Instruction::I32Add); + func.instruction(&Instruction::I32Load(wasm_encoder::MemArg { + offset: byte_offset as u64, + align: 2, + memory_index: options.callee_memory, + })); + // Stack: [addr_for_store, handle] + func.instruction(&Instruction::Call(rep_func)); + // Stack: [addr_for_store, rep] + func.instruction(&Instruction::I32Store(wasm_encoder::MemArg { + offset: byte_offset as u64, + align: 2, + memory_index: options.callee_memory, + })); + } + // loop_idx++ + func.instruction(&Instruction::LocalGet(loop_idx)); + func.instruction(&Instruction::I32Const(1)); + func.instruction(&Instruction::I32Add); + func.instruction(&Instruction::LocalSet(loop_idx)); + func.instruction(&Instruction::Br(0)); + func.instruction(&Instruction::End); // loop + func.instruction(&Instruction::End); // block + } + // 3. Call target with (dest_ptr, len, ...remaining args) func.instruction(&Instruction::LocalGet(dest_ptr_local)); func.instruction(&Instruction::LocalGet(1)); // len @@ -1020,6 +1109,9 @@ impl FactStyleGenerator { scratch_count += 1; let fixup_locals_base = caller_param_count as u32 + scratch_count; scratch_count += 4 * max_fixup_depth; // 4 locals per nesting level + if !options.inner_resource_fixups.is_empty() { + scratch_count += 1; // resource loop counter + } let local_decls = vec![(scratch_count, wasm_encoder::ValType::I32)]; let mut func = Function::new(local_decls); @@ -1073,6 +1165,7 @@ impl FactStyleGenerator { if let Some(crate::resolver::CopyLayout::Elements { element_size, inner_pointers, + .. }) = param_layouts.get(pair_idx) && !inner_pointers.is_empty() { @@ -1089,6 +1182,59 @@ impl FactStyleGenerator { fixup_locals_base, ); } + + // Fix up inner resource handles after bulk copy + if let Some(crate::resolver::CopyLayout::Elements { + element_size, + inner_resources, + .. + }) = param_layouts.get(pair_idx) + && !inner_resources.is_empty() + && !options.inner_resource_fixups.is_empty() + { + let element_size = *element_size; + let res_loop_idx = fixup_locals_base + 4 * max_fixup_depth; + func.instruction(&Instruction::I32Const(0)); + func.instruction(&Instruction::LocalSet(res_loop_idx)); + func.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty)); + func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty)); + func.instruction(&Instruction::LocalGet(res_loop_idx)); + func.instruction(&Instruction::LocalGet(len_pos)); + func.instruction(&Instruction::I32GeU); + func.instruction(&Instruction::BrIf(1)); + for &(byte_offset, rep_func) in &options.inner_resource_fixups { + // Push addr for store + func.instruction(&Instruction::LocalGet(dest_local)); + func.instruction(&Instruction::LocalGet(res_loop_idx)); + func.instruction(&Instruction::I32Const(element_size as i32)); + func.instruction(&Instruction::I32Mul); + func.instruction(&Instruction::I32Add); + // Load handle + func.instruction(&Instruction::LocalGet(dest_local)); + func.instruction(&Instruction::LocalGet(res_loop_idx)); + func.instruction(&Instruction::I32Const(element_size as i32)); + func.instruction(&Instruction::I32Mul); + func.instruction(&Instruction::I32Add); + func.instruction(&Instruction::I32Load(wasm_encoder::MemArg { + offset: byte_offset as u64, + align: 2, + memory_index: options.callee_memory, + })); + func.instruction(&Instruction::Call(rep_func)); + func.instruction(&Instruction::I32Store(wasm_encoder::MemArg { + offset: byte_offset as u64, + align: 2, + memory_index: options.callee_memory, + })); + } + func.instruction(&Instruction::LocalGet(res_loop_idx)); + func.instruction(&Instruction::I32Const(1)); + func.instruction(&Instruction::I32Add); + func.instruction(&Instruction::LocalSet(res_loop_idx)); + func.instruction(&Instruction::Br(0)); + func.instruction(&Instruction::End); + func.instruction(&Instruction::End); + } } } @@ -1238,6 +1384,7 @@ impl FactStyleGenerator { if let Some(crate::resolver::CopyLayout::Elements { element_size, inner_pointers, + .. }) = result_layouts.get(result_pair_idx) && !inner_pointers.is_empty() { @@ -1583,6 +1730,7 @@ impl FactStyleGenerator { if let crate::resolver::CopyLayout::Elements { element_size: inner_elem_size, inner_pointers: inner_inner, + .. } = inner_layout && !inner_inner.is_empty() { diff --git a/meld-core/src/adapter/mod.rs b/meld-core/src/adapter/mod.rs index d6a2f7d..d75cc50 100644 --- a/meld-core/src/adapter/mod.rs +++ b/meld-core/src/adapter/mod.rs @@ -140,6 +140,10 @@ pub struct AdapterOptions { pub resource_rep_calls: Vec, /// Resource own results needing rep→handle conversion (3-component chains). pub resource_new_calls: Vec, + /// Resolved inner resource handles for list elements. + /// Each entry: (byte_offset_in_element, merged_func_idx of [resource-rep]). + /// Used after bulk list copy to convert handles in callee memory. + pub inner_resource_fixups: Vec<(u32, u32)>, } /// Describes how to transfer a `borrow` handle across an adapter boundary. @@ -179,6 +183,7 @@ impl Default for AdapterOptions { callee_post_return: None, resource_rep_calls: Vec::new(), resource_new_calls: Vec::new(), + inner_resource_fixups: Vec::new(), } } } diff --git a/meld-core/src/parser.rs b/meld-core/src/parser.rs index 35fb1d6..b3dd1f2 100644 --- a/meld-core/src/parser.rs +++ b/meld-core/src/parser.rs @@ -2396,7 +2396,8 @@ impl ParsedComponent { ComponentValType::List(inner) => { let element_size = self.canonical_abi_element_size(inner); let inner_ptrs = self.element_inner_pointers(inner, 0); - if inner_ptrs.is_empty() { + let inner_res = self.element_inner_resources(inner, 0); + if inner_ptrs.is_empty() && inner_res.is_empty() { CopyLayout::Bulk { byte_multiplier: element_size, } @@ -2404,6 +2405,7 @@ impl ParsedComponent { CopyLayout::Elements { element_size, inner_pointers: inner_ptrs, + inner_resources: inner_res, } } } @@ -2470,6 +2472,47 @@ impl ParsedComponent { } result } + + /// Find resource handles embedded within a composite type's memory layout. + /// Returns `(byte_offset, resource_type_id, is_owned)` for each resource found. + fn element_inner_resources(&self, ty: &ComponentValType, base: u32) -> Vec<(u32, u32, bool)> { + let mut result = Vec::new(); + match ty { + ComponentValType::Own(id) => { + result.push((base, *id, true)); + } + ComponentValType::Borrow(id) => { + result.push((base, *id, false)); + } + ComponentValType::Record(fields) => { + let mut offset = base; + for (_, field_ty) in fields { + let align = self.canonical_abi_align(field_ty); + offset = align_up(offset, align); + result.extend(self.element_inner_resources(field_ty, offset)); + offset += self.canonical_abi_size_unpadded(field_ty); + } + } + ComponentValType::Tuple(elems) => { + let mut offset = base; + for elem_ty in elems { + let align = self.canonical_abi_align(elem_ty); + offset = align_up(offset, align); + result.extend(self.element_inner_resources(elem_ty, offset)); + offset += self.canonical_abi_size_unpadded(elem_ty); + } + } + ComponentValType::Type(idx) => { + if let Some(ct) = self.get_type_definition(*idx) + && let ComponentTypeKind::Defined(inner) = &ct.kind + { + return self.element_inner_resources(inner, base); + } + } + _ => {} // scalars, strings, lists, options, variants — skip + } + result + } } fn align_up(size: u32, align: u32) -> u32 { @@ -3422,6 +3465,7 @@ mod tests { crate::resolver::CopyLayout::Elements { element_size, inner_pointers, + .. } => { assert_eq!( element_size, 8, diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index a7c603a..9ac3821 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -96,9 +96,12 @@ pub enum CopyLayout { /// Element-wise copy: `len` elements of `element_size` bytes each. /// `inner_pointers` lists byte offsets within each element where (ptr, len) /// pairs exist, along with their own recursive copy layout. + /// `inner_resources` lists byte offsets of resource handles that need + /// conversion after bulk copy. Elements { element_size: u32, inner_pointers: Vec<(u32, CopyLayout)>, + inner_resources: Vec<(u32, u32, bool)>, // (byte_offset, resource_type_id, is_owned) }, } @@ -3009,6 +3012,7 @@ mod tests { CopyLayout::Elements { element_size, inner_pointers, + .. } => { assert_eq!( element_size, 8, @@ -3056,6 +3060,7 @@ mod tests { CopyLayout::Elements { element_size, inner_pointers, + .. } => { // Record layout: string at offset 0 (8 bytes: ptr + len, align 4), // then u32 at offset 8 (4 bytes, align 4). Unpadded size = 12. @@ -3103,6 +3108,7 @@ mod tests { CopyLayout::Elements { element_size, inner_pointers, + .. } => { assert_eq!( element_size, 8, diff --git a/meld-core/tests/wit_bindgen_runtime.rs b/meld-core/tests/wit_bindgen_runtime.rs index cf19cf0..0c1ec80 100644 --- a/meld-core/tests/wit_bindgen_runtime.rs +++ b/meld-core/tests/wit_bindgen_runtime.rs @@ -653,10 +653,11 @@ fuse_only_test!( // resource_floats: adapter produces correct rep→new_handle chain, but P2 wrapper // resource type mismatch between [resource-new] and [resource-rep] for intermediate fuse_only_test!(test_fuse_wit_bindgen_resource_floats, "resource_floats"); -fuse_only_test!( - test_fuse_wit_bindgen_resource_borrow_in_record, +runtime_test!( + test_runtime_wit_bindgen_resource_borrow_in_record, "resource_borrow_in_record" ); +// resource_with_lists: string data corruption in resource+list combo fuse_only_test!( test_fuse_wit_bindgen_resource_with_lists, "resource_with_lists"