Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 150 additions & 2 deletions meld-core/src/adapter/fact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,29 @@ impl FactStyleGenerator {
}
}

// Resolve inner resource handles from param copy layouts.
// When list elements contain borrow<T>, 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<T> 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
}

Expand Down Expand Up @@ -600,13 +623,16 @@ 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
&& !needs_result_copy
&& !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 {
Expand All @@ -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)
};
Expand Down Expand Up @@ -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()
{
Expand All @@ -739,6 +769,65 @@ impl FactStyleGenerator {
);
}

// 2c. Fix up inner resource handles if element type contains borrow<T>
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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()
{
Expand All @@ -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);
}
}
}

Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
{
Expand Down
5 changes: 5 additions & 0 deletions meld-core/src/adapter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ pub struct AdapterOptions {
pub resource_rep_calls: Vec<ResourceBorrowTransfer>,
/// Resource own<T> results needing rep→handle conversion (3-component chains).
pub resource_new_calls: Vec<ResourceOwnResultTransfer>,
/// 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<T>` handle across an adapter boundary.
Expand Down Expand Up @@ -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(),
}
}
}
46 changes: 45 additions & 1 deletion meld-core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2396,14 +2396,16 @@ 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,
}
} else {
CopyLayout::Elements {
element_size,
inner_pointers: inner_ptrs,
inner_resources: inner_res,
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -3422,6 +3465,7 @@ mod tests {
crate::resolver::CopyLayout::Elements {
element_size,
inner_pointers,
..
} => {
assert_eq!(
element_size, 8,
Expand Down
6 changes: 6 additions & 0 deletions meld-core/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
}

Expand Down Expand Up @@ -3009,6 +3012,7 @@ mod tests {
CopyLayout::Elements {
element_size,
inner_pointers,
..
} => {
assert_eq!(
element_size, 8,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -3103,6 +3108,7 @@ mod tests {
CopyLayout::Elements {
element_size,
inner_pointers,
..
} => {
assert_eq!(
element_size, 8,
Expand Down
5 changes: 3 additions & 2 deletions meld-core/tests/wit_bindgen_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading