From 3b24c488773704d3043719326c98d12232409132 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 2 Apr 2026 20:11:34 -0400 Subject: [PATCH] fix: suffix duplicate exports in multi-memory mode to prevent cross-component collisions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In multi-memory mode, each component's shim module exports numeric function names ("0", "1", ...) and a "$imports" table. When multiple components exist, these export names collide and the first-wins dedup silently drops subsequent entries — wiring the fixup module to the wrong component's indirect table. Fix: suffix duplicate export names with $comp_idx in multi-memory mode. SharedMemory mode retains first-wins dedup since all components share one memory. Part of #69. Co-Authored-By: Claude Opus 4.6 (1M context) --- meld-core/src/merger.rs | 44 +++++++++++++++++---------------------- meld-core/src/resolver.rs | 18 ++++++++++++++++ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/meld-core/src/merger.rs b/meld-core/src/merger.rs index 26c06ef..24edd08 100644 --- a/meld-core/src/merger.rs +++ b/meld-core/src/merger.rs @@ -1284,33 +1284,27 @@ impl Merger { } }; - // Export deduplication: first-wins strategy. - // - // When multiple modules export the same name, the first export - // (in topological/instantiation order) wins and subsequent - // duplicates are silently dropped. This matches the component - // model's semantics where earlier instantiations take priority. - // - // If this behavior is ever made configurable (e.g. error on - // conflict, or prefix with component name), update both this - // check and the MergedExport documentation. - if let Some(existing) = merged.exports.iter().find(|e| e.name == export.name) { - log::warn!( - "Duplicate export \"{}\": keeping {:?} index {} (from earlier module), \ - skipping {:?} index {} from component {} module {}", - export.name, - existing.kind, - existing.index, - kind, - old_idx, - comp_idx, - mod_idx, - ); - continue; - } + // Export deduplication: in multi-memory mode, suffix duplicate + // export names with the component index. Each component's shim + // module exports numeric function names ("0", "1", ...) and a + // "$imports" table that must remain distinct — deduplication + // would wire the fixup module to the wrong component's indirect + // table. In shared-memory mode, first-wins dedup is correct + // since all components share one memory. + let export_name = if self.memory_strategy == MemoryStrategy::MultiMemory + && merged.exports.iter().any(|e| e.name == export.name) + { + format!("{}${}", export.name, comp_idx) + } else if self.memory_strategy != MemoryStrategy::MultiMemory + && merged.exports.iter().any(|e| e.name == export.name) + { + continue; // first-wins dedup in shared-memory mode + } else { + export.name.clone() + }; merged.exports.push(MergedExport { - name: export.name.clone(), + name: export_name, kind, index: old_idx, }); diff --git a/meld-core/src/resolver.rs b/meld-core/src/resolver.rs index 9592e8b..5c9481b 100644 --- a/meld-core/src/resolver.rs +++ b/meld-core/src/resolver.rs @@ -1428,8 +1428,19 @@ impl Resolver { ) -> Result<()> { for (comp_idx, component) in components.iter().enumerate() { if !component.instances.is_empty() { + log::debug!( + "resolve_module_imports: comp {} using resolve_via_instances ({} instances, {} modules)", + comp_idx, + component.instances.len(), + component.core_modules.len() + ); self.resolve_via_instances(comp_idx, component, graph)?; } else { + log::debug!( + "resolve_module_imports: comp {} using resolve_via_flat_names ({} modules)", + comp_idx, + component.core_modules.len() + ); self.resolve_via_flat_names(comp_idx, component, graph)?; } } @@ -1692,6 +1703,13 @@ impl Resolver { && let Some(to_mod_idx) = find_module_with_export(component, &import.name, from_mod_idx) { + log::debug!( + "comp {} mod {} __main_module__::{} → mod {}", + comp_idx, + from_mod_idx, + import.name, + to_mod_idx + ); graph.module_resolutions.push(ModuleResolution { component_idx: comp_idx, from_module: from_mod_idx,