Skip to content

Commit 7a77133

Browse files
authored
sort_segments_by_vram_dependency (#512)
* add sort_segements_by_vram_class_dependency * fix spelling * formatting * add docs entry * additional docs clarification
1 parent 23ccdba commit 7a77133

File tree

3 files changed

+55
-0
lines changed

3 files changed

+55
-0
lines changed

docs/Configuration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,10 @@ Generates a discard section like this:
526526

527527
Determines whether to add wildcards for section linking in the linker script (.rodata* for example)
528528

529+
### ld_sort_segments_by_vram_class_dependency
530+
531+
Ensures segments of vram classes with dependencies (`vram_symbol` / `follows_classes`) are written to the linker script AFTER the segments they depend on. This is intended to preserve vram class ordering for shifted builds (e.g., mods) and is not expected to produce a matching build. Disable this when a matching build is desired.
532+
529533
### ld_use_symbolic_vram_addresses
530534

531535
Determines whether to use `follows_vram` (segment option) and `vram_symbol` / `follows_classes` (vram_class options) to calculate vram addresses in the linker script.

src/splat/scripts/split.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from typing import Any, Dict, List, Optional, Set, Tuple, Union
77
from pathlib import Path
88

9+
from collections import defaultdict, deque
10+
911
from .. import __package_name__, __version__
1012
from ..disassembler import disassembler_instance
1113
from ..util import cache_handler, progress_bar, vram_classes, statistics, file_presets
@@ -222,6 +224,47 @@ def calc_segment_dependences(
222224
return vram_class_to_follows_segments
223225

224226

227+
def sort_segments_by_vram_class_dependency(
228+
all_segments: List[Segment],
229+
) -> List[Segment]:
230+
# map all "_VRAM_END" strings to segments
231+
end_sym_to_seg: Dict[str, Segment] = {}
232+
for seg in all_segments:
233+
end_sym_to_seg[get_segment_vram_end_symbol_name(seg)] = seg
234+
235+
# build dependency graph: A -> B means "A must come before B"
236+
graph: Dict[Segment, List[Segment]] = defaultdict(list)
237+
indeg: Dict[Segment, int] = {seg: 0 for seg in all_segments}
238+
239+
for seg in all_segments:
240+
sym = seg.vram_symbol
241+
if sym is None:
242+
continue
243+
dep = end_sym_to_seg.get(sym)
244+
if dep is None or dep is seg:
245+
continue
246+
graph[dep].append(seg)
247+
indeg[seg] += 1
248+
249+
# stable topo sort with queue seeded in original order
250+
q = deque([seg for seg in all_segments if indeg[seg] == 0])
251+
out: List[Segment] = []
252+
253+
while q:
254+
n = q.popleft()
255+
out.append(n)
256+
for m in graph.get(n, []):
257+
indeg[m] -= 1
258+
if indeg[m] == 0:
259+
q.append(m)
260+
261+
assert len(out) == len(all_segments), (
262+
"Encountered cyclic dependency when reordering segments by vram class."
263+
)
264+
265+
return out
266+
267+
225268
def read_target_binary() -> bytes:
226269
rom_bytes = options.opts.target_path.read_bytes()
227270

@@ -317,6 +360,9 @@ def do_split(
317360

318361

319362
def write_linker_script(all_segments: List[Segment]) -> LinkerWriter:
363+
if options.opts.ld_sort_segments_by_vram_class_dependency:
364+
all_segments = sort_segments_by_vram_class_dependency(all_segments)
365+
320366
vram_class_dependencies = calc_segment_dependences(all_segments)
321367
vram_classes_to_search = set(vram_class_dependencies.keys())
322368

src/splat/util/options.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ class SplatOpts:
127127
# `vram_symbol` / `follows_classes` (vram_class options) to calculate vram addresses in the linker script.
128128
# If disabled, this uses the plain integer values for vram addresses defined in the yaml.
129129
ld_use_symbolic_vram_addresses: bool
130+
# Ensures segments of vram classes with dependencies (`vram_symbol` / `follows_classes`) are written to the linker script AFTER the segments they depend on.
131+
ld_sort_segments_by_vram_class_dependency: bool
130132
# Change linker script generation to allow partially linking segments. Requires both `ld_partial_scripts_path` and `ld_partial_build_segments_path` to be set.
131133
ld_partial_linking: bool
132134
# Folder were each intermediary linker script will be written to.
@@ -512,6 +514,9 @@ def parse_include_asm_macro_style() -> Literal["default", "maspsx_hack"]:
512514
ld_sections_allowlist=p.parse_opt("ld_sections_allowlist", list, []),
513515
ld_sections_denylist=p.parse_opt("ld_sections_denylist", list, []),
514516
ld_wildcard_sections=p.parse_opt("ld_wildcard_sections", bool, False),
517+
ld_sort_segments_by_vram_class_dependency=p.parse_opt(
518+
"ld_sort_segments_by_vram_class_dependency", bool, False
519+
),
515520
ld_use_symbolic_vram_addresses=p.parse_opt(
516521
"ld_use_symbolic_vram_addresses", bool, True
517522
),

0 commit comments

Comments
 (0)