recompilation: remap SectionAbsolute relocs to relocatable sections

When splat marks a fragment-internal symbol as undefined (e.g.
'D_8140DD78 = 0x8140DD78;' in undefined_syms_auto.ld), the elf parser
records target_section = SectionAbsolute and target_section_offset =
the symbol's literal vram. Previously the recompilation walker treated
this as a non-relocatable reference and emitted the link-time literal
as a lui+addiu pair. At runtime the containing fragment loads at a
non-canonical address, so the access misses by the relocation delta
and lands in unwritten memory.

Same pattern as the prior bss-remap (28de57f) and unsorted-relocs
(506b9fc) fixes: producer/consumer asymmetry where some references
get RELOC and others bake in the link-time literal.

Fix: after the bss → parent remap, walk the registered relocatable
sections; if the absolute value falls inside one, redirect
reloc_section to that section index and use bss_remap_offset_adjustment
to subtract the new section's vram base. Downstream target_relocatable
check then treats it correctly and emits RELOC_HI16/LO16.

Verified on PokemonStadiumRecomp: the attract-path G_DL target
0x8140DD78 (a Gfx array in fragment34, used by fragment62) now emits
as RELOC_HI16(147, 0xDD78) instead of literal 0x8141<<16. Stadium
attract advances past the prior send_dl=1157 freeze; environment
renders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley 2026-05-03 11:43:50 -07:00
parent b792e74a3e
commit bc00a039f7

View File

@ -197,6 +197,45 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
reloc_section = find_bss_it->second;
}
// SectionAbsolute → relocatable-section remap. When splat marks
// a symbol as undefined (e.g. D_8140DD78 = 0x8140DD78 lives in
// undefined_syms_auto.ld), the elf parser writes
// target_section = SectionAbsolute and target_section_offset =
// the symbol's literal vram. Without remapping, the HI16/LO16
// emit path below treats it as a non-relocatable reference and
// bakes in the link-time literal. At runtime, when the symbol's
// containing fragment is loaded at a non-canonical address, the
// access misses by the relocation delta — same bug pattern as
// the BSS-remap case (producer/consumer asymmetry: properly
// resolved peers get RELOC, this one gets a literal).
//
// Walk the registered relocatable sections; if the absolute
// value falls inside one, redirect the reloc to that section.
// The downstream relocatable check then treats it correctly.
if (reloc_section == N64Recomp::SectionAbsolute && !reloc.reference_symbol) {
const uint32_t abs_value = uint32_t(reloc.target_section_offset);
for (size_t si = 0; si < context.sections.size(); si++) {
const auto& s = context.sections[si];
if (!s.relocatable) continue;
const uint32_t s_start = uint32_t(s.ram_addr);
const uint32_t s_end = s_start + uint32_t(s.size);
if (abs_value >= s_start && abs_value < s_end) {
reloc_section = uint32_t(si);
// The downstream code computes
// reloc_target_section_offset =
// reloc.target_section_offset + bss_remap_offset_adjustment;
// For ABS, reloc.target_section_offset is the literal
// vram (e.g. 0x8140DD78). To get the offset within
// the new section base s_start, subtract s_start by
// adding -s_start into the same adjustment slot
// (bss/abs paths are mutually exclusive — bss_remap
// is 0 here because target wasn't a bss section).
bss_remap_offset_adjustment = uint32_t(0) - s_start;
break;
}
}
}
// Check if the relocation references a relocatable section.
bool target_relocatable = false;
if (!reloc.reference_symbol && reloc_section != N64Recomp::SectionAbsolute) {