Avoid duplicate unit names when auto-splitting

Fixes #20
This commit is contained in:
Luke Street 2024-01-06 21:57:42 -07:00
parent 85e044463e
commit 58b6d7c7d5
3 changed files with 65 additions and 21 deletions

2
Cargo.lock generated
View File

@ -295,7 +295,7 @@ dependencies = [
[[package]] [[package]]
name = "decomp-toolkit" name = "decomp-toolkit"
version = "0.6.5" version = "0.7.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ar", "ar",

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit." description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
version = "0.6.5" version = "0.7.0"
edition = "2021" edition = "2021"
publish = false publish = false
repository = "https://github.com/encounter/decomp-toolkit" repository = "https://github.com/encounter/decomp-toolkit"

View File

@ -65,18 +65,10 @@ fn split_ctors_dtors(obj: &mut ObjInfo, start: SectionAddress, end: SectionAddre
} }
if ctors_split.is_none() || function_split.is_none() { if ctors_split.is_none() || function_split.is_none() {
let unit = expected_unit.unwrap_or_else(|| { let unit = match expected_unit {
let section_name = function_symbol Some(unit) => unit,
.section None => auto_unit_name(obj, function_symbol, &new_splits)?,
.and_then(|idx| obj.sections.get(idx).map(|s| s.name.clone())) };
.unwrap_or_else(|| "unknown".to_string());
let name =
sanitize_with_options(&function_symbol.name, &sanitise_file_name::Options {
length_limit: 20,
..Default::default()
});
format!("auto_{}_{}", name, section_name.trim_start_matches('.'))
});
log::debug!("Adding splits to unit {}", unit); log::debug!("Adding splits to unit {}", unit);
if ctors_split.is_none() { if ctors_split.is_none() {
@ -251,13 +243,10 @@ fn split_extabindex(obj: &mut ObjInfo, start: SectionAddress) -> Result<()> {
} }
if extabindex_split.is_none() || extab_split.is_none() || function_split.is_none() { if extabindex_split.is_none() || extab_split.is_none() || function_split.is_none() {
let unit = expected_unit.unwrap_or_else(|| { let unit = match expected_unit {
let section_name = function_symbol Some(unit) => unit,
.section None => auto_unit_name(obj, function_symbol, &new_splits)?,
.and_then(|idx| obj.sections.get(idx).map(|s| s.name.clone())) };
.unwrap_or_else(|| "unknown".to_string());
format!("auto_{}_{}", function_symbol.name, section_name.trim_start_matches('.'))
});
log::debug!("Adding splits to unit {}", unit); log::debug!("Adding splits to unit {}", unit);
if extabindex_split.is_none() { if extabindex_split.is_none() {
@ -384,6 +373,14 @@ fn create_gap_splits(obj: &mut ObjInfo) -> Result<()> {
symbol.name, symbol.name,
symbol.address symbol.address
); );
if symbol.address & 3 != 0 {
bail!(
"Need to split at {:#010X} for duplicate symbol name {}, \
but it is not 4-byte aligned. Please split manually.",
symbol.address,
symbol.name,
);
}
new_split_end.address = symbol.address as u32; new_split_end.address = symbol.address as u32;
break; break;
} }
@ -1340,3 +1337,50 @@ pub fn end_for_section(obj: &ObjInfo, section_index: usize) -> Result<SectionAdd
} }
Ok(SectionAddress::new(section_index, section_end)) Ok(SectionAddress::new(section_index, section_end))
} }
/// Generates a unit name for an autogenerated split.
/// The name is based on the symbol name and section name.
/// If the name is not unique, a number is appended to the end.
fn auto_unit_name(
obj: &ObjInfo,
symbol: &ObjSymbol,
new_splits: &BTreeMap<SectionAddress, ObjSplit>,
) -> Result<String> {
let section_name = symbol
.section
.and_then(|idx| obj.sections.get(idx).map(|s| s.name.clone()))
.unwrap_or_else(|| "unknown".to_string());
let name = sanitize_with_options(&symbol.name, &sanitise_file_name::Options {
length_limit: 20,
..Default::default()
});
let mut unit_name = format!("auto_{}_{}", name, section_name.trim_start_matches('.'));
// Ensure the name is unique
if unit_exists(&unit_name, obj, new_splits) {
let mut i = 1;
loop {
let new_unit_name = format!("{}_{}", unit_name, i);
if !unit_exists(&new_unit_name, obj, new_splits) {
unit_name = new_unit_name;
break;
}
i += 1;
}
}
Ok(unit_name)
}
/// Check if a unit name exists in the object or pending splits.
fn unit_exists(
unit_name: &str,
obj: &ObjInfo,
new_splits: &BTreeMap<SectionAddress, ObjSplit>,
) -> bool {
obj.link_order.iter().any(|u| u.name.eq_ignore_ascii_case(unit_name))
|| obj
.sections
.iter()
.flat_map(|(_, s)| s.splits.iter())
.any(|(_, split)| split.unit.eq_ignore_ascii_case(unit_name))
|| new_splits.values().any(|split| split.unit.eq_ignore_ascii_case(unit_name))
}