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
197 changes: 147 additions & 50 deletions meld-core/src/adapter/fact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ 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> {
use wasm_encoder::EntityType;
let mut 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);
}
func_idx += 1;
}
}
map
}

/// FACT-style adapter generator
pub struct FactStyleGenerator {
#[allow(dead_code)]
Expand All @@ -58,22 +79,23 @@ impl FactStyleGenerator {
site: &AdapterSite,
merged: &MergedModule,
_adapter_idx: usize,
resource_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);
let options = self.analyze_call_site(site, merged, resource_imports);

// Generate the adapter function body
let (type_idx, body) = if site.crosses_memory && options.needs_transcoding() {
self.generate_transcoding_adapter(site, merged, &options)?
} else if site.crosses_memory {
self.generate_memory_copy_adapter(site, merged, &options)?
} else {
self.generate_direct_adapter(site, merged)?
self.generate_direct_adapter(site, merged, &options)?
};

Ok(AdapterFunction {
Expand All @@ -89,7 +111,12 @@ impl FactStyleGenerator {
}

/// Analyze a call site to determine adapter options
fn analyze_call_site(&self, site: &AdapterSite, merged: &MergedModule) -> AdapterOptions {
fn analyze_call_site(
&self,
site: &AdapterSite,
merged: &MergedModule,
resource_imports: &std::collections::HashMap<(String, String), u32>,
) -> AdapterOptions {
let mut options = AdapterOptions::default();

// Determine memory indices
Expand Down Expand Up @@ -162,6 +189,35 @@ impl FactStyleGenerator {
options.callee_post_return = Some(merged_pr_idx);
}

// Resolve resource BORROW params → [resource-rep] merged function indices.
//
// Per the canonical ABI spec, `borrow<T>` params where T is defined by
// the callee receive the representation (raw pointer), not the handle.
// The adapter must call resource.rep(handle) → rep for these.
//
// `own<T>` params receive the handle directly — the callee's core
// function calls from_handle/resource.rep internally, so the adapter
// must NOT convert them (that would cause double conversion).
//
// Results are never converted — own results have resource.new called
// by the callee's core function, and borrows cannot appear in results.
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));
} else {
log::debug!(
"Resource rep import not found: ({}, {})",
op.import_module,
op.import_field
);
}
}

options
}

Expand All @@ -170,9 +226,9 @@ impl FactStyleGenerator {
&self,
site: &AdapterSite,
merged: &MergedModule,
options: &AdapterOptions,
) -> Result<(u32, Function)> {
let target_func = self.resolve_target_function(site, merged)?;
let options = self.analyze_call_site(site, merged);

// Find the target function's type (convert wasm index to array position)
let type_idx = merged
Expand All @@ -186,36 +242,45 @@ impl FactStyleGenerator {
let result_types: Vec<wasm_encoder::ValType> =
func_type.map(|t| t.results.clone()).unwrap_or_default();

// If post-return is specified, we need scratch locals to save results
let has_post_return = options.callee_post_return.is_some();
let has_resource_ops = !options.resource_rep_calls.is_empty();

if has_post_return && result_count > 0 {
// Need locals to save results across the post-return call
let locals: Vec<(u32, wasm_encoder::ValType)> =
result_types.iter().map(|t| (1u32, *t)).collect();
let mut func = Function::new(locals);
if has_resource_ops || (has_post_return && result_count > 0) {
let mut locals: Vec<(u32, wasm_encoder::ValType)> = Vec::new();
let result_base = param_count as u32;

// Load all parameters and call target
for i in 0..param_count {
func.instruction(&Instruction::LocalGet(i as u32));
if has_post_return && result_count > 0 {
locals.extend(result_types.iter().map(|t| (1u32, *t)));
}
func.instruction(&Instruction::Call(target_func));
let mut func = Function::new(locals);

// Save results to locals (pop in reverse order)
for i in (0..result_count).rev() {
func.instruction(&Instruction::LocalSet(result_base + i as u32));
// 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));
}

// Call post-return with the saved results
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
for i in 0..param_count {
func.instruction(&Instruction::LocalGet(i as u32));
}
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
func.instruction(&Instruction::Call(target_func));

// Push saved results back onto stack
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
if has_post_return && result_count > 0 {
// Save results to locals (pop in reverse order)
for i in (0..result_count).rev() {
func.instruction(&Instruction::LocalSet(result_base + i as u32));
}
// Call post-return with saved results
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
}
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
// Push saved results back onto stack
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
}
} else if has_post_return {
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
}

func.instruction(&Instruction::End);
Expand All @@ -230,7 +295,6 @@ impl FactStyleGenerator {
func.instruction(&Instruction::Call(target_func));

if has_post_return {
// No results to save, just call post-return
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
}

Expand Down Expand Up @@ -324,33 +388,41 @@ impl FactStyleGenerator {
// If memories are the same, just do direct call (with post-return if needed)
if options.caller_memory == options.callee_memory {
let has_post_return = options.callee_post_return.is_some();
let has_resource_ops = !options.resource_rep_calls.is_empty();

if has_post_return && result_count > 0 {
// Need scratch locals to save results across post-return call
let locals: Vec<(u32, wasm_encoder::ValType)> =
result_types.iter().map(|t| (1u32, *t)).collect();
let mut func = Function::new(locals);
if has_resource_ops || (has_post_return && result_count > 0) {
let mut locals: Vec<(u32, wasm_encoder::ValType)> = Vec::new();
let result_base = param_count as u32;

for i in 0..param_count {
func.instruction(&Instruction::LocalGet(i as u32));
if has_post_return && result_count > 0 {
locals.extend(result_types.iter().map(|t| (1u32, *t)));
}
func.instruction(&Instruction::Call(target_func));
let mut func = Function::new(locals);

// Save results to locals (pop in reverse order)
for i in (0..result_count).rev() {
func.instruction(&Instruction::LocalSet(result_base + i as u32));
// 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));
}

// Call post-return with saved results
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
for i in 0..param_count {
func.instruction(&Instruction::LocalGet(i as u32));
}
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
func.instruction(&Instruction::Call(target_func));

// Push saved results back onto stack
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
if has_post_return && result_count > 0 {
for i in (0..result_count).rev() {
func.instruction(&Instruction::LocalSet(result_base + i as u32));
}
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
}
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
for i in 0..result_count {
func.instruction(&Instruction::LocalGet(result_base + i as u32));
}
} else if has_post_return {
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
}

func.instruction(&Instruction::End);
Expand All @@ -363,7 +435,6 @@ impl FactStyleGenerator {
func.instruction(&Instruction::Call(target_func));

if has_post_return {
// No results to save, just call post-return
func.instruction(&Instruction::Call(options.callee_post_return.unwrap()));
}

Expand All @@ -390,16 +461,19 @@ impl FactStyleGenerator {
let needs_post_return_save =
!needs_result_copy && options.callee_post_return.is_some() && result_count > 0;

// We need result-save locals for post-return AND/OR conditional result copy
// We need result-save locals for post-return or conditional result copy.
let needs_result_save =
(needs_post_return_save || needs_conditional_result_copy) && result_count > 0;

// If no copying and no post-return save needed, direct call
let has_resource_ops = !options.resource_rep_calls.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
{
let mut func = Function::new([]);
for i in 0..param_count {
Expand Down Expand Up @@ -443,6 +517,13 @@ 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));
}

// Assign scratch local indices (after params)
let base = param_count as u32;
// For outbound copy:
Expand Down Expand Up @@ -655,7 +736,7 @@ impl FactStyleGenerator {
func.instruction(&Instruction::LocalGet(callee_ret_len_local));
}

// Post-return and/or conditional result copy for non-result-copy case
// Post-return and/or conditional result copy
if !needs_result_copy
&& (needs_conditional_result_copy || options.callee_post_return.is_some())
{
Expand Down Expand Up @@ -814,6 +895,13 @@ 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 1: Outbound copy of ALL pointer pairs (caller → callee) ---
if let Some(callee_realloc) = options
.callee_realloc
Expand Down Expand Up @@ -953,6 +1041,7 @@ impl FactStyleGenerator {
// load/store instructions based on the canonical ABI memory layout.
let result_layouts = &site.requirements.result_copy_layouts;
let return_area_slots = &site.requirements.return_area_slots;

if let Some(caller_realloc) = options
.caller_realloc
.filter(|_| !result_ptr_offsets.is_empty())
Expand Down Expand Up @@ -1462,6 +1551,13 @@ 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));
}

// Generate transcoding logic based on encoding pair

match (
Expand Down Expand Up @@ -2350,10 +2446,11 @@ impl AdapterGenerator for FactStyleGenerator {
merged: &MergedModule,
graph: &DependencyGraph,
) -> Result<Vec<AdapterFunction>> {
let resource_imports = build_resource_import_map(merged);
let mut adapters = Vec::new();

for (idx, site) in graph.adapter_sites.iter().enumerate() {
let adapter = self.generate_adapter(site, merged, idx)?;
let adapter = self.generate_adapter(site, merged, idx, &resource_imports)?;
adapters.push(adapter);
}

Expand Down
20 changes: 20 additions & 0 deletions meld-core/src/adapter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,25 @@ pub struct AdapterOptions {
/// Post-return function index in merged module (if any).
/// 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])`.
///
/// 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.
///
/// `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.
///
/// 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)>,
}

impl Default for AdapterOptions {
Expand All @@ -136,6 +155,7 @@ impl Default for AdapterOptions {
callee_realloc: None,
returns_pointer_pair: false,
callee_post_return: None,
resource_rep_calls: Vec::new(),
}
}
}
Loading
Loading