Fixed capped-segment linking.
This commit is contained in:
parent
035c393500
commit
5aaa0b240c
5 changed files with 250 additions and 3 deletions
|
|
@ -912,6 +912,13 @@ struct Linker {
|
|||
// bytes per text segment; overflow text sections get bank-aligned
|
||||
// bases starting at segmentBankBase.
|
||||
uint32_t segmentCap = 0;
|
||||
// Cap for OVERFLOW segments (seg 2+). --segment-cap exists to bound
|
||||
// how much text stays in the ENTRY bank, which also has to hold
|
||||
// rodata, bss and the heap under the $C000 IO window. Overflow banks
|
||||
// carry text ONLY and have the full 64K free, so applying the entry
|
||||
// bank's tiny budget to them wastes ~50K per bank. 0 = same as
|
||||
// segmentCap (the historical behaviour).
|
||||
uint32_t overflowCap = 0;
|
||||
uint32_t segmentBankBase = 0x040000;
|
||||
std::string manifestPath;
|
||||
// Phase C.2 — opt-in for ExpressLoad-multi-seg cINTERSEG emission.
|
||||
|
|
@ -1295,8 +1302,12 @@ struct Linker {
|
|||
while (true) {
|
||||
if (curSeg > segSizes.size()) segSizes.push_back(0);
|
||||
if (segSizes[curSeg - 1] == 0) break;
|
||||
if (!segmentCap) break;
|
||||
if (segSizes[curSeg - 1] + sz <= segmentCap) break;
|
||||
// Entry bank shares its 64K with rodata/bss/heap;
|
||||
// overflow banks hold text only.
|
||||
uint32_t capHere = (curSeg == 1 || overflowCap == 0)
|
||||
? segmentCap : overflowCap;
|
||||
if (!capHere) break;
|
||||
if (segSizes[curSeg - 1] + sz <= capHere) break;
|
||||
curSeg++;
|
||||
}
|
||||
} else if (curSeg > segSizes.size()) {
|
||||
|
|
@ -2435,6 +2446,9 @@ int main(int argc, char **argv) {
|
|||
} else if (a == "--segment-cap") {
|
||||
if (++i >= argc) usage(argv[0]);
|
||||
linker.segmentCap = parseInt(argv[i++]);
|
||||
} else if (a == "--overflow-cap") {
|
||||
if (++i >= argc) usage(argv[0]);
|
||||
linker.overflowCap = parseInt(argv[i++]);
|
||||
} else if (a == "--segment-bank-base") {
|
||||
if (++i >= argc) usage(argv[0]);
|
||||
linker.segmentBankBase = parseInt(argv[i++]);
|
||||
|
|
|
|||
|
|
@ -92,10 +92,36 @@ static bool expandFarFI(MachineInstr &MI, int FPOff,
|
|||
return true;
|
||||
}
|
||||
case W65816::STAfi: {
|
||||
// Non-A source: the value has to travel through A to reach the
|
||||
// store, so PHA-bracket it exactly like the stack-relative path in
|
||||
// eliminateFrameIndex below. STAfi is documented (and relied on)
|
||||
// as A-preserving for every source class -- W65816StackSlotCleanup
|
||||
// walks past a STAfi to a different slot without invalidating its
|
||||
// "A still holds slot N" fact, on the strength of that bracket.
|
||||
// Without it here, an IMG-source STAfi in a >0xFF frame silently
|
||||
// clobbered A and the cleanup then deleted the *following* reload,
|
||||
// so the next `sta $E0` parked the source's value where the caller
|
||||
// wanted something else. Live symptom: the LDAptr32S inserter
|
||||
// built its far pointer with the pointer's BANK byte in the offset
|
||||
// half, and `lda [$E0],y` read a stray address -- Space Taxi's
|
||||
// stSimTick read a nonzero byte for sim->collisionPhase and never
|
||||
// ran its physics block.
|
||||
//
|
||||
// Unlike the ,s path no offset fixup is needed: the store goes
|
||||
// through the DP frame pointer ($F6), which PHA does not move.
|
||||
Register Src = MI.getOperand(0).getReg();
|
||||
int srcDP = imgRegToDP(Src);
|
||||
if (srcDP >= 0)
|
||||
bool ViaA = srcDP >= 0 || Src == W65816::X || Src == W65816::Y;
|
||||
if (ViaA)
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::PHA));
|
||||
if (srcDP >= 0) {
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::LDA_DP)).addImm(srcDP);
|
||||
} else if (Src == W65816::X) {
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::TXA));
|
||||
} else if (Src == W65816::Y) {
|
||||
// Before the STY_DP/LDY below overwrite Y.
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::TYA));
|
||||
}
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::STY_DP)).addImm(0xFA)
|
||||
.addReg(W65816::Y, RegState::Implicit | RegState::Undef);
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::LDY_Imm16)).addImm(FPOff);
|
||||
|
|
@ -104,6 +130,8 @@ static bool expandFarFI(MachineInstr &MI, int FPOff,
|
|||
.addReg(W65816::A, RegState::Implicit)
|
||||
.addReg(W65816::Y, RegState::Implicit);
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::LDY_DP)).addImm(0xFA);
|
||||
if (ViaA)
|
||||
BuildMI(MBB, II, DL, TII.get(W65816::PLA));
|
||||
return true;
|
||||
}
|
||||
case W65816::STA8fi: {
|
||||
|
|
|
|||
|
|
@ -946,6 +946,20 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
Opc == W65816::EORi16imm || Opc == W65816::EORabs ||
|
||||
Opc == W65816::ANDi16imm || Opc == W65816::ANDabs ||
|
||||
Opc == W65816::ORAi16imm || Opc == W65816::ORAabs ||
|
||||
// ...and the i8 siblings, for exactly the same reason (cf.
|
||||
// LDAi16imm_bank above): none of the six model Defs=[P], so a
|
||||
// branch can never legitimately take its flags from one, and
|
||||
// leaving them out made isFlagDefining's default claim them as
|
||||
// THE test -- the walk stopped early and declined to wrap.
|
||||
// Live symptom: `if (a->f) { if (b & 1) ... }` lowered to
|
||||
// `cmp #0 ; lda n,s ; and #1 ; beq`, where the AND was not a
|
||||
// test at all but the value the successor block consumes. The
|
||||
// BEQ read the AND's Z instead of the CMP's, so the outer test
|
||||
// never fired -- JoeyLib's Space Taxi never cleared its
|
||||
// landed-on-pad latch and the attract demo parked forever.
|
||||
Opc == W65816::EORi8imm ||
|
||||
Opc == W65816::ANDi8imm ||
|
||||
Opc == W65816::ORAi8imm ||
|
||||
// Register transfers — TAX/TXA/TAY/TYA/TXY/TYX update N/Z
|
||||
// based on the transferred value. They're "data movement"
|
||||
// not "comparison"; treat as corruption so the wrap pass
|
||||
|
|
|
|||
112
src/llvm/test/CodeGen/W65816/andi8-flag-corrupt-wrap.mir
Normal file
112
src/llvm/test/CodeGen/W65816/andi8-flag-corrupt-wrap.mir
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Pin: the i8 logic pseudos (ANDi8imm / ORAi8imm / EORi8imm) count as
|
||||
# flag CORRUPTION, not as the flag-defining test, in the PHP/PLP wrap
|
||||
# (W65816StackSlotCleanup Pass -2.5).
|
||||
#
|
||||
# None of ANDi8imm/ORAi8imm/EORi8imm model Defs=[P], so a branch can never
|
||||
# legitimately take its flags from one -- exactly the argument that already
|
||||
# puts their i16 siblings in the corrupter list. While they were missing,
|
||||
# isFlagDefining's default claimed them as THE test: the backward walk from
|
||||
# the Bxx stopped at the AND, saw nothing corrupting between it and the
|
||||
# branch, and declined to wrap. The CMP's flags were then trampled by the
|
||||
# reload sitting in front of the AND.
|
||||
#
|
||||
# Live symptom: `if (a->latch) { if (b & 1) a->latch = 0; }` lowered to
|
||||
# cmp #0 ; lda n,s ; and #1 ; beq
|
||||
# where the AND is not a test at all -- it computes the value the successor
|
||||
# block consumes. The BEQ read the AND's Z instead of the CMP's, so the outer
|
||||
# test never fired. JoeyLib's Space Taxi never cleared its landed-on-pad
|
||||
# latch, the cab never took off again, and the attract demo parked on the
|
||||
# first pad forever instead of playing its recording out.
|
||||
#
|
||||
# The compares below use a NON-ZERO immediate on purpose: after an AND or a
|
||||
# load, `cmp #0` is redundant (both already set Z) and the pass folds it away,
|
||||
# which would make the pin ambiguous about which instruction the branch reads.
|
||||
#
|
||||
# and_i8: the reported shape -- reload + ANDi8imm between CMP and BEQ.
|
||||
# ora_i8: same for ORAi8imm.
|
||||
# no_corrupt: a bare CMP/BEQ pair still gets no wrap (the wrap costs 8 cycles;
|
||||
# don't pay it where nothing corrupts).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @and_i8() { ret void }
|
||||
define void @ora_i8() { ret void }
|
||||
define void @no_corrupt() { ret void }
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: and_i8
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = LDAfi %stack.1, 1
|
||||
# CHECK-NEXT: $a = ANDi8imm killed $a, 1
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BEQ %bb.1, implicit $p
|
||||
name: and_i8
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 2, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.0, 0
|
||||
CMPi16imm killed $a, 3, implicit-def $p
|
||||
$a = LDAfi %stack.1, 0
|
||||
$a = ANDi8imm killed $a, 1
|
||||
BEQ %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: ora_i8
|
||||
# CHECK: CMPi16imm killed $a, 5, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = LDAfi %stack.1, 1
|
||||
# CHECK-NEXT: $a = ORAi8imm killed $a, 1
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BNE %bb.1, implicit $p
|
||||
name: ora_i8
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 2, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.0, 0
|
||||
CMPi16imm killed $a, 5, implicit-def $p
|
||||
$a = LDAfi %stack.1, 0
|
||||
$a = ORAi8imm killed $a, 1
|
||||
BNE %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# Nothing corrupting between the test and the branch: no wrap, no cost.
|
||||
# CHECK-LABEL: name: no_corrupt
|
||||
# CHECK: CMPi16imm killed $a, 7, implicit-def $p
|
||||
# CHECK-NEXT: BEQ %bb.1, implicit $p
|
||||
# CHECK-NOT: PHP
|
||||
name: no_corrupt
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.0, 0
|
||||
CMPi16imm killed $a, 7, implicit-def $p
|
||||
BEQ %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
79
src/llvm/test/CodeGen/W65816/stafi-far-nona-pha-bracket.mir
Normal file
79
src/llvm/test/CodeGen/W65816/stafi-far-nona-pha-bracket.mir
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Pin: a STAfi whose source is an IMGn (or X/Y) register must PHA-bracket the
|
||||
# transfer through A on BOTH frame paths -- the stack-relative one and the far
|
||||
# `($F6),Y` one taken when the slot's offset exceeds 0xFF.
|
||||
#
|
||||
# STAfi is documented, and relied upon, as A-preserving for every source class:
|
||||
# W65816StackSlotCleanup walks past a STAfi to a DIFFERENT slot without
|
||||
# invalidating its "A still holds slot N" fact, on the strength of that bracket.
|
||||
# The far path used to emit a bare `LDA dp`, so an IMG-source STAfi in a >0xFF
|
||||
# frame silently clobbered A and the cleanup then deleted the FOLLOWING reload.
|
||||
# Live symptom: the LDAptr32S inserter built its far pointer with the pointer's
|
||||
# BANK byte in the offset half, so `lda [$E0],y` read a stray address -- JoeyLib's
|
||||
# Space Taxi read a nonzero byte for a zero struct field and never ran its
|
||||
# physics.
|
||||
#
|
||||
# far_img: offset > 0xFF, IMG source -> PHA / LDA dp / store / PLA.
|
||||
# near_img: offset <= 0xFF, IMG source -> the long-standing ,s bracket.
|
||||
# far_acc: offset > 0xFF, A source -> NO bracket (A is the source; the
|
||||
# +7 cycles would be pure cost).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=prologepilog -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @far_img() { ret void }
|
||||
define void @near_img() { ret void }
|
||||
define void @far_acc() { ret void }
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: far_img
|
||||
# CHECK: PHA
|
||||
# CHECK-NEXT: LDA_DP 192
|
||||
# CHECK-NEXT: STY_DP 250
|
||||
# CHECK-NEXT: LDY_Imm16
|
||||
# CHECK-NEXT: STA_DPIndLongY 246
|
||||
# CHECK-NEXT: LDY_DP 250
|
||||
# CHECK-NEXT: PLA
|
||||
name: far_img
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 768, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
STAfi $img8, %stack.0, 0, implicit-def $a
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: near_img
|
||||
# CHECK: PHA
|
||||
# CHECK-NEXT: LDA_DP 192
|
||||
# CHECK-NEXT: STA_StackRel
|
||||
# CHECK-NEXT: PLA
|
||||
name: near_img
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 100, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
STAfi $img8, %stack.0, 0, implicit-def $a
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# A is already the source, so the far store needs no bracket at all.
|
||||
# CHECK-LABEL: name: far_acc
|
||||
# CHECK: STY_DP 250
|
||||
# CHECK-NEXT: LDY_Imm16
|
||||
# CHECK-NEXT: STA_DPIndLongY 246
|
||||
# CHECK-NEXT: LDY_DP 250
|
||||
# CHECK-NOT: PLA
|
||||
name: far_acc
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 768, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
STAfi $a, %stack.0, 0, implicit-def $a
|
||||
RTL
|
||||
...
|
||||
Loading…
Add table
Reference in a new issue