Skip to content

Commit 63e5f28

Browse files
avrabeclaude
andauthored
fix(fuse): coalesce + index-remap the name section under --preserve-names (#328) (#333)
`--preserve-names` copied every input module's `name` custom section verbatim into the fused module. With multiple modules that produced (a) DUPLICATE `name` sections — llvm-dwarfdump rejects the whole module (`out of order section type: 0`) — and (b) function-name entries carrying each source module's PRE-fusion indices, so names labelled the wrong fused functions. `wasm-tools validate` passes, so it was silent. Same class as #222 (producers), never applied to `name`. Fix (the #222 pattern + index remapping): - `MergedModule` gains `fused_function_names: BTreeMap<u32, String>`. - At the per-module custom-section merge (merger.rs), `name` is no longer copied verbatim; `accumulate_remapped_function_names` parses it and remaps each function-name entry from `(comp, module, orig_idx)` into the fused index space via `function_index_map` (already populated by the preceding function merge). Entries with no mapping (dead/internalized) are dropped — never a wrong index (LS-D-1). Non-function subsections are dropped in this pass. - At encode (`encode_output`, `preserve_names` branch) exactly ONE coalesced `name` section is emitted from the accumulator, in the LLVM-required order. So the output carries a single `name` section whose indices are all in the fused space. Pinned by `accumulate_remapped_function_names_remaps_and_drops_unmapped`. Tier-5 (merger.rs, adapter/): needs a Mythos discover pass before merge. Refs #328. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 15e25ad commit 63e5f28

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

meld-core/src/adapter/fact.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12883,6 +12883,7 @@ mod tests {
1288312883
elements: Vec::new(),
1288412884
data_segments: Vec::new(),
1288512885
custom_sections: Vec::new(),
12886+
fused_function_names: std::collections::BTreeMap::new(),
1288612887
function_index_map: std::collections::HashMap::new(),
1288712888
memory_index_map: std::collections::HashMap::new(),
1288812889
table_index_map: std::collections::HashMap::new(),

meld-core/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,23 @@ impl Fuser {
18121812
data: std::borrow::Cow::Borrowed(contents),
18131813
});
18141814
}
1815+
1816+
// #328: emit ONE coalesced `name` section (function names) whose
1817+
// indices are already remapped into the fused space. This
1818+
// replaces the old verbatim per-module `name` copies (duplicate
1819+
// sections → llvm-dwarfdump rejects; stale indices → wrong
1820+
// labels). The merger no longer routes `name` into
1821+
// `custom_sections`, so this is the sole emitter under
1822+
// `preserve_names`.
1823+
if self.config.preserve_names && !merged.fused_function_names.is_empty() {
1824+
let mut fnames = wasm_encoder::NameMap::new();
1825+
for (idx, fname) in &merged.fused_function_names {
1826+
fnames.append(*idx, fname);
1827+
}
1828+
let mut names = wasm_encoder::NameSection::new();
1829+
names.functions(&fnames);
1830+
module.section(&names);
1831+
}
18151832
}
18161833

18171834
// Remapped DWARF (DwarfHandling::Remap): a single `.debug_*` set

meld-core/src/merger.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ pub struct MergedModule {
8888
/// Custom sections
8989
pub custom_sections: Vec<(String, Vec<u8>)>,
9090

91+
/// #328: fused function names accumulated from every input module's
92+
/// `name` section, with each function index already remapped into the
93+
/// fused function-index space (`function_index_map`). Emitted as ONE
94+
/// coalesced `name` section under `preserve_names` — replacing the old
95+
/// verbatim per-module copies (duplicate sections + stale indices).
96+
/// `BTreeMap` keeps the fused indices ascending (the order the
97+
/// name-section function-name subsection expects).
98+
pub fused_function_names: std::collections::BTreeMap<u32, String>,
99+
91100
/// Index mapping for function references
92101
pub function_index_map: HashMap<(usize, usize, u32), u32>,
93102

@@ -989,6 +998,7 @@ impl Merger {
989998
elements: Vec::new(),
990999
data_segments: Vec::new(),
9911000
custom_sections: Vec::new(),
1001+
fused_function_names: std::collections::BTreeMap::new(),
9921002
function_index_map: HashMap::new(),
9931003
memory_index_map: HashMap::new(),
9941004
table_index_map: HashMap::new(),
@@ -2165,6 +2175,24 @@ impl Merger {
21652175

21662176
// Merge custom sections
21672177
for (name, data) in &module.custom_sections {
2178+
// #328: the `name` section carries function indices in THIS
2179+
// module's index space. Copying it verbatim produces duplicate
2180+
// `name` sections (llvm-dwarfdump rejects the module) whose
2181+
// indices point at the wrong fused functions. Instead, remap its
2182+
// function-name entries into the fused index space and accumulate
2183+
// them; a single coalesced `name` section is emitted at encode
2184+
// time under `preserve_names`. Function merge above has already
2185+
// populated `function_index_map` for this module.
2186+
if name == "name" {
2187+
accumulate_remapped_function_names(
2188+
data,
2189+
comp_idx,
2190+
mod_idx,
2191+
&merged.function_index_map,
2192+
&mut merged.fused_function_names,
2193+
);
2194+
continue;
2195+
}
21682196
merged.custom_sections.push((name.clone(), data.clone()));
21692197
}
21702198

@@ -3551,10 +3579,75 @@ fn compute_unresolved_import_assignments(
35513579
(counts, assignments, dedup_info)
35523580
}
35533581

3582+
/// #328: parse a WASM `name` custom section and accumulate its
3583+
/// function-name entries into `out`, remapping each function index from
3584+
/// this module's index space `(comp_idx, mod_idx, orig_idx)` into the
3585+
/// fused index space via `function_index_map`.
3586+
///
3587+
/// Entries with no mapping (a dead / internalized function) are dropped —
3588+
/// the coalesced section must never carry a wrong index (LS-D-1: correct or
3589+
/// nothing). Non-function subsections (module / local / label / type / …
3590+
/// names) are ignored in this pass: their indices would also need
3591+
/// remapping, and dropping them keeps the emitted section correct rather
3592+
/// than plausibly-wrong. A malformed section is dropped whole.
3593+
fn accumulate_remapped_function_names(
3594+
data: &[u8],
3595+
comp_idx: usize,
3596+
mod_idx: usize,
3597+
function_index_map: &HashMap<(usize, usize, u32), u32>,
3598+
out: &mut std::collections::BTreeMap<u32, String>,
3599+
) {
3600+
let reader = wasmparser::NameSectionReader::new(wasmparser::BinaryReader::new(data, 0));
3601+
for subsection in reader {
3602+
let Ok(subsection) = subsection else {
3603+
return; // malformed — never guess
3604+
};
3605+
if let wasmparser::Name::Function(namemap) = subsection {
3606+
for naming in namemap {
3607+
let Ok(naming) = naming else { continue };
3608+
if let Some(&fused) = function_index_map.get(&(comp_idx, mod_idx, naming.index)) {
3609+
out.insert(fused, naming.name.to_string());
3610+
}
3611+
}
3612+
}
3613+
}
3614+
}
3615+
35543616
#[cfg(test)]
35553617
mod tests {
35563618
use super::*;
35573619

3620+
/// #328: the name-section remap accumulates function names under their
3621+
/// FUSED indices, and drops entries whose source index isn't in the
3622+
/// fused map (dead/internalized) — never emitting a wrong index.
3623+
#[test]
3624+
fn accumulate_remapped_function_names_remaps_and_drops_unmapped() {
3625+
// A `name` section body with a function-name subsection (id 1)
3626+
// naming source func 0 -> "foo" and source func 5 -> "bar".
3627+
// Layout: [subsec_id=1][size=0x0b][count=2][idx=0,len=3,"foo"][idx=5,len=3,"bar"]
3628+
let data: Vec<u8> = vec![
3629+
0x01, 0x0b, 0x02, 0x00, 0x03, b'f', b'o', b'o', 0x05, 0x03, b'b', b'a', b'r',
3630+
];
3631+
// Fused index map for (comp 0, module 0): 0 -> 10; source 5 is
3632+
// intentionally unmapped (should be dropped).
3633+
let mut map: HashMap<(usize, usize, u32), u32> = HashMap::new();
3634+
map.insert((0, 0, 0), 10);
3635+
3636+
let mut out: std::collections::BTreeMap<u32, String> = std::collections::BTreeMap::new();
3637+
accumulate_remapped_function_names(&data, 0, 0, &map, &mut out);
3638+
3639+
assert_eq!(
3640+
out.get(&10).map(String::as_str),
3641+
Some("foo"),
3642+
"source 0 -> fused 10"
3643+
);
3644+
assert_eq!(
3645+
out.len(),
3646+
1,
3647+
"unmapped source func 5 (\"bar\") must be dropped, not emitted at a wrong index"
3648+
);
3649+
}
3650+
35583651
#[test]
35593652
fn test_convert_memory_type() {
35603653
let mem = MemoryType {
@@ -3670,6 +3763,7 @@ mod tests {
36703763
elements: Vec::new(),
36713764
data_segments: Vec::new(),
36723765
custom_sections: Vec::new(),
3766+
fused_function_names: std::collections::BTreeMap::new(),
36733767
function_index_map: HashMap::new(),
36743768
memory_index_map: HashMap::new(),
36753769
table_index_map: HashMap::new(),
@@ -5724,6 +5818,7 @@ mod tests {
57245818
elements: Vec::new(),
57255819
data_segments: Vec::new(),
57265820
custom_sections: Vec::new(),
5821+
fused_function_names: std::collections::BTreeMap::new(),
57275822
function_index_map: std::collections::HashMap::new(),
57285823
memory_index_map: std::collections::HashMap::new(),
57295824
table_index_map: std::collections::HashMap::new(),

0 commit comments

Comments
 (0)