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
171 changes: 119 additions & 52 deletions meld-core/src/adapter/fact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -79,15 +95,17 @@ 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<AdapterFunction> {
let name = format!(
"$adapter_{}_{}_to_{}_{}",
site.from_component, site.from_module, site.to_component, site.to_module
);

// 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() {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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<u32, &crate::resolver::ResolvedResourceOp> = 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<T>: 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]<name> 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
);
}
}
}

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -2446,11 +2507,17 @@ impl AdapterGenerator for FactStyleGenerator {
merged: &MergedModule,
graph: &DependencyGraph,
) -> Result<Vec<AdapterFunction>> {
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);
}

Expand Down
39 changes: 24 additions & 15 deletions meld-core/src/adapter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,33 @@ pub struct AdapterOptions {
/// Called after results have been copied back, to allow callee cleanup.
pub callee_post_return: Option<u32>,

/// 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<T>` 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<T>` params need different handling
/// depending on who defines the resource:
///
/// `own<T>` 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<T>` params are never converted (callee calls from_handle internally).
pub resource_rep_calls: Vec<ResourceBorrowTransfer>,
}

/// Describes how to transfer a `borrow<T>` 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<u32>,
}

impl Default for AdapterOptions {
Expand Down
Loading
Loading