More code generation fixes. 32 bit operations improved.
This commit is contained in:
parent
deb1c96f73
commit
000aa1c079
16 changed files with 1584 additions and 314 deletions
Binary file not shown.
|
|
@ -2921,6 +2921,174 @@ EOF
|
|||
fi
|
||||
rm -f "$cSignedFile" "$oSignedFile" "$binSignedFile"
|
||||
|
||||
# Every i32 predicate (eq ne ult ule ugt uge slt sle sgt sge) on
|
||||
# 16 boundary values (0, 1, 0x7FFF, 0x8000, 0xFFFF, 0x10000,
|
||||
# 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF, mixed signs ...): as a branch,
|
||||
# as a 0/1 value and as an i16 select against registers, and as a
|
||||
# branch and a value against ten constants; then loops whose i32
|
||||
# counters cross a 16-bit boundary. kExp is the host-computed
|
||||
# expected-result bit table (C semantics), so the oracle does not
|
||||
# depend on the compiler under test. Pins the 2026-09-28 i32
|
||||
# compare rewrite (CMP lo / SBC hi chain, lowerI32Cmp): the old
|
||||
# lowering answered 80 of these wrongly (unsigned x >= K / x < K
|
||||
# with a 16-bit K, via StackSlotCleanup dropping the `cmp #0` a
|
||||
# BCC read), and its loops never ended (W65816StackRelToImg
|
||||
# erasing the carry into an i32 += 1's high half).
|
||||
log "check: MAME i32 compares: every predicate on boundary values, loops across 64K"
|
||||
cI32CmpFile="$(mktemp --suffix=.c)"
|
||||
oI32CmpFile="$(mktemp --suffix=.o)"
|
||||
binI32CmpFile="$(mktemp --suffix=.bin)"
|
||||
cat > "$cI32CmpFile" <<'EOF'
|
||||
// Every i32 predicate on 32-bit boundary values, in three forms against
|
||||
// registers (branch, 0/1 value, i16 select) and two against constants
|
||||
// (branch, value), plus loops whose counters cross a 16-bit boundary.
|
||||
// Expected results come from a host-built bit table. Mailbox: 0x025000
|
||||
// = mismatches, 0x025002 = first failing case, 0x025004 = 0xC0DE done.
|
||||
// No <stdint.h> here (the smoke build has no runtime include path);
|
||||
// int is 16 bits and long 32 on this target.
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned long u32;
|
||||
typedef long s32;
|
||||
#define NV 16u
|
||||
#define NK 10u
|
||||
#define NP 10u
|
||||
#define BAR() __asm__ volatile("" ::: "memory")
|
||||
#define NOI __attribute__((noinline)) static
|
||||
static volatile u16 gSink;
|
||||
static const u32 kV[NV] = {
|
||||
0x00000000UL, 0x00000001UL, 0x00000002UL, 0x00007FFFUL,
|
||||
0x00008000UL, 0x0000FFFFUL, 0x00010000UL, 0x00010001UL,
|
||||
0x00018000UL, 0x7FFFFFFFUL, 0x80000000UL, 0x80000001UL,
|
||||
0xFFFE0001UL, 0xFFFF0000UL, 0xFFFF8000UL, 0xFFFFFFFFUL,
|
||||
};
|
||||
static const u8 kExp[520] = {
|
||||
0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00,
|
||||
0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80,
|
||||
0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0x7F, 0xFF,
|
||||
0xFF, 0xFE, 0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0x7F,
|
||||
0xFE, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF0, 0xFF, 0xE0, 0xFF, 0xC0, 0xFF, 0x80, 0xFF, 0x00, 0xFF,
|
||||
0x00, 0xFE, 0x00, 0xFC, 0x00, 0xF8, 0x00, 0xF0, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0xFF, 0xF8, 0xFF, 0xF0, 0xFF, 0xE0, 0xFF, 0xC0, 0xFF, 0x80, 0xFF,
|
||||
0x00, 0xFF, 0x00, 0xFE, 0x00, 0xFC, 0x00, 0xF8, 0x00, 0xF0, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x80,
|
||||
0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x3F, 0x00, 0x7F, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x0F, 0xFF, 0x1F, 0xFF, 0x3F, 0xFF, 0x7F,
|
||||
0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x3F, 0x00, 0x7F, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x01, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x0F, 0xFF, 0x1F, 0xFF, 0x3F, 0xFF, 0x7F, 0xFF, 0xFF,
|
||||
0xFE, 0x03, 0xFC, 0x03, 0xF8, 0x03, 0xF0, 0x03, 0xE0, 0x03, 0xC0, 0x03, 0x80, 0x03, 0x00, 0x03,
|
||||
0x00, 0x02, 0x00, 0x00, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xE3, 0xFF, 0xC3, 0xFF, 0x83, 0xFF, 0x03,
|
||||
0xFF, 0x03, 0xFE, 0x03, 0xFC, 0x03, 0xF8, 0x03, 0xF0, 0x03, 0xE0, 0x03, 0xC0, 0x03, 0x80, 0x03,
|
||||
0x00, 0x03, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF3, 0xFF, 0xE3, 0xFF, 0xC3, 0xFF, 0x83,
|
||||
0x00, 0xFC, 0x01, 0xFC, 0x03, 0xFC, 0x07, 0xFC, 0x0F, 0xFC, 0x1F, 0xFC, 0x3F, 0xFC, 0x7F, 0xFC,
|
||||
0xFF, 0xFC, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x7C,
|
||||
0x01, 0xFC, 0x03, 0xFC, 0x07, 0xFC, 0x0F, 0xFC, 0x1F, 0xFC, 0x3F, 0xFC, 0x7F, 0xFC, 0xFF, 0xFC,
|
||||
0xFF, 0xFD, 0xFF, 0xFF, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC,
|
||||
0x01, 0x00, 0x02, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x02, 0x00, 0x04,
|
||||
0x00, 0x40, 0x00, 0x80, 0xFE, 0xFF, 0xFD, 0xFF, 0xF7, 0xFF, 0xEF, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF,
|
||||
0xFF, 0xFD, 0xFF, 0xFB, 0xFF, 0xBF, 0xFF, 0x7F, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x0F, 0x00,
|
||||
0x1F, 0x00, 0x3F, 0x00, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0x3F, 0xFF, 0x7F, 0x01, 0x00, 0x03, 0x00,
|
||||
0x0F, 0x00, 0x1F, 0x00, 0x3F, 0x00, 0x7F, 0x00, 0xFF, 0x03, 0xFF, 0x07, 0xFF, 0x7F, 0xFF, 0xFF,
|
||||
0xFE, 0xFF, 0xFC, 0xFF, 0xF0, 0xFF, 0xE0, 0xFF, 0xC0, 0xFF, 0x80, 0xFF, 0x00, 0xFC, 0x00, 0xF8,
|
||||
0x00, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFE, 0xFF, 0xF8, 0xFF, 0xF0, 0xFF, 0xE0, 0xFF, 0xC0, 0xFF,
|
||||
0x00, 0xFE, 0x00, 0xFC, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xFC, 0x01, 0xFC, 0x07, 0xFC, 0x0F, 0xFC,
|
||||
0x1F, 0xFC, 0x3F, 0xFC, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x01, 0xFC, 0x03, 0xFC,
|
||||
0x0F, 0xFC, 0x1F, 0xFC, 0x3F, 0xFC, 0x7F, 0xFC, 0xFF, 0xFF, 0x00, 0x04, 0x00, 0x7C, 0x00, 0xFC,
|
||||
0xFE, 0x03, 0xFC, 0x03, 0xF0, 0x03, 0xE0, 0x03, 0xC0, 0x03, 0x80, 0x03, 0x00, 0x00, 0xFF, 0xFB,
|
||||
0xFF, 0x83, 0xFF, 0x03, 0xFF, 0x03, 0xFE, 0x03, 0xF8, 0x03, 0xF0, 0x03, 0xE0, 0x03, 0xC0, 0x03,
|
||||
0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0x83,
|
||||
};
|
||||
#define ALL(M) M(eq, u32, ==) M(ne, u32, !=) M(ult, u32, <) M(ule, u32, <=) M(ugt, u32, >) M(uge, u32, >=) M(slt, s32, <) M(sle, s32, <=) M(sgt, s32, >) M(sge, s32, >=)
|
||||
#define BR(n, T, op) NOI u16 br_##n(u32 a, u32 b) { if ((T)a op (T)b) { BAR(); return 1u; } return 0u; }
|
||||
#define VA(n, T, op) NOI u16 va_##n(u32 a, u32 b) { return (u16)((T)a op (T)b); }
|
||||
#define SE(n, T, op) NOI u16 se_##n(u32 a, u32 b) { return (T)a op (T)b ? 0x5A5Au : 0xA5A5u; }
|
||||
ALL(BR)
|
||||
ALL(VA)
|
||||
ALL(SE)
|
||||
#define KS(M, n, T, op) M(n, T, op, 0, 0x00000000UL) M(n, T, op, 1, 0x00000001UL) M(n, T, op, 2, 0x00007FFFUL) M(n, T, op, 3, 0x00008000UL) M(n, T, op, 4, 0x0000FFFFUL) M(n, T, op, 5, 0x00010000UL) M(n, T, op, 6, 0x7FFFFFFFUL) M(n, T, op, 7, 0x80000000UL) M(n, T, op, 8, 0xFFFF8000UL) M(n, T, op, 9, 0xFFFFFFFFUL)
|
||||
#define BK(n, T, op, i, K) NOI u16 bk_##n##_##i(u32 a) { if ((T)a op (T)(K)) { BAR(); return 1u; } return 0u; }
|
||||
#define VK(n, T, op, i, K) NOI u16 vk_##n##_##i(u32 a) { return (u16)((T)a op (T)(K)); }
|
||||
#define BKALL(n, T, op) KS(BK, n, T, op)
|
||||
#define VKALL(n, T, op) KS(VK, n, T, op)
|
||||
ALL(BKALL)
|
||||
ALL(VKALL)
|
||||
typedef u16 (*Fn2)(u32, u32);
|
||||
typedef u16 (*Fn1)(u32);
|
||||
#define R2(p) (Fn2)p##_eq, (Fn2)p##_ne, (Fn2)p##_ult, (Fn2)p##_ule, (Fn2)p##_ugt, (Fn2)p##_uge, (Fn2)p##_slt, (Fn2)p##_sle, (Fn2)p##_sgt, (Fn2)p##_sge
|
||||
static Fn2 const kBr[NP] = { R2(br) };
|
||||
static Fn2 const kVa[NP] = { R2(va) };
|
||||
static Fn2 const kSe[NP] = { R2(se) };
|
||||
#define RK(p, n) p##_##n##_0, p##_##n##_1, p##_##n##_2, p##_##n##_3, p##_##n##_4, p##_##n##_5, p##_##n##_6, p##_##n##_7, p##_##n##_8, p##_##n##_9,
|
||||
#define RKALL(p) RK(p, eq) RK(p, ne) RK(p, ult) RK(p, ule) RK(p, ugt) RK(p, uge) RK(p, slt) RK(p, sle) RK(p, sgt) RK(p, sge)
|
||||
static Fn1 const kBk[NP * NK] = { RKALL(bk) };
|
||||
static Fn1 const kVk[NP * NK] = { RKALL(vk) };
|
||||
NOI u16 loopUlt(u32 s, u32 e) { u16 n = 0u; u32 i; for (i = s; i < e; i++) { gSink = (u16)i; n++; } return n; }
|
||||
NOI u16 loopUle(u32 s, u32 e) { u16 n = 0u; u32 i; for (i = s; i <= e; i++) { gSink = (u16)i; n++; } return n; }
|
||||
NOI u16 loopNe(u32 s, u32 e) { u16 n = 0u; u32 i; for (i = s; i != e; i++) { gSink = (u16)i; n++; } return n; }
|
||||
NOI u16 loopSlt(u32 s, u32 e) { u16 n = 0u; s32 i; for (i = (s32)s; i < (s32)e; i++) { gSink = (u16)i; n++; } return n; }
|
||||
NOI u16 loopSge(u32 s, u32 e) { u16 n = 0u; s32 i; for (i = (s32)e; i >= (s32)s; i--) { gSink = (u16)i; n++; } return n; }
|
||||
NOI u16 loopUgtDown(u32 s, u32 e) { u16 n = 0u; u32 i; for (i = e; i > s; i--) { gSink = (u16)i; n++; } return n; }
|
||||
static u16 gBad;
|
||||
static u16 gFirst = 0xFFFFu;
|
||||
static void check(u16 got, u16 want, u16 id) {
|
||||
if (got != want) {
|
||||
if (gBad == 0u) {
|
||||
gFirst = id;
|
||||
}
|
||||
gBad++;
|
||||
}
|
||||
}
|
||||
int main(void) {
|
||||
u16 p;
|
||||
u16 i;
|
||||
u16 j;
|
||||
u16 bit = 0u;
|
||||
for (p = 0u; p < NP; p++) {
|
||||
for (i = 0u; i < NV; i++) {
|
||||
for (j = 0u; j < NV; j++) {
|
||||
u16 want = (u16)((kExp[bit >> 3] >> (bit & 7u)) & 1u);
|
||||
check(kBr[p](kV[i], kV[j]), want, bit);
|
||||
check(kVa[p](kV[i], kV[j]), want, (u16)(bit | 0x4000u));
|
||||
check(kSe[p](kV[i], kV[j]) == 0x5A5Au, want, (u16)(bit | 0x8000u));
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (p = 0u; p < NP * NK; p++) {
|
||||
for (i = 0u; i < NV; i++) {
|
||||
u16 want = (u16)((kExp[bit >> 3] >> (bit & 7u)) & 1u);
|
||||
check(kBk[p](kV[i]), want, bit);
|
||||
check(kVk[p](kV[i]), want, (u16)(bit | 0x4000u));
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
check(loopUlt(0x0000FFF0UL, 0x00010010UL), 32u, 0xC000u);
|
||||
check(loopUlt(0x7FFFFFF0UL, 0x80000010UL), 32u, 0xC001u);
|
||||
check(loopUle(0x0000FFFEUL, 0x00010001UL), 4u, 0xC002u);
|
||||
check(loopNe(0x0001FFF8UL, 0x00020008UL), 16u, 0xC003u);
|
||||
check(loopSlt(0xFFFFFFF0UL, 0x00000010UL), 32u, 0xC004u);
|
||||
check(loopSlt(0x7FFFFFE0UL, 0x7FFFFFFFUL), 31u, 0xC005u);
|
||||
check(loopSge(0xFFFFFFFBUL, 0x00000005UL), 11u, 0xC006u);
|
||||
check(loopUgtDown(0x0000FFF8UL, 0x00010008UL), 16u, 0xC007u);
|
||||
*(volatile u16 *)0x025000UL = gBad;
|
||||
*(volatile u16 *)0x025002UL = gFirst;
|
||||
*(volatile u16 *)0x025004UL = 0xC0DEu;
|
||||
for (;;) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -O2 -ffunction-sections -c \
|
||||
"$cI32CmpFile" -o "$oI32CmpFile"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binI32CmpFile" --text-base 0x1000 \
|
||||
"$oCrt0F" "$oLibgccFile" "$oI32CmpFile" \
|
||||
>/dev/null 2>&1
|
||||
if ! MAME_CHECK_FRAME=900 MAME_SECS=20 \
|
||||
bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binI32CmpFile" --check \
|
||||
0x025000=0000 0x025002=ffff 0x025004=c0de >/dev/null 2>&1; then
|
||||
die "MAME: i32 compare predicates/loops wrong (mailbox 0x025000 = mismatches, 0x025002 = first case)"
|
||||
fi
|
||||
rm -f "$cI32CmpFile" "$oI32CmpFile" "$binI32CmpFile"
|
||||
|
||||
# the list, creating an overlapping free entry. A subsequent
|
||||
# malloc could hand out the same memory to two callers.
|
||||
log "check: MAME runs malloc/free coalesce — three blocks freed in alloc order (#100)"
|
||||
|
|
|
|||
|
|
@ -746,6 +746,140 @@ static bool peelPtr32Offset(SelectionDAG &DAG, SDLoc DL, SDValue Ptr,
|
|||
return true;
|
||||
}
|
||||
|
||||
// The integer predicates lowerI32Cmp handles (every one C can produce).
|
||||
static bool isI32CmpCC(ISD::CondCode CC) {
|
||||
switch (CC) {
|
||||
case ISD::SETEQ: case ISD::SETNE:
|
||||
case ISD::SETULT: case ISD::SETULE: case ISD::SETUGT: case ISD::SETUGE:
|
||||
case ISD::SETLT: case ISD::SETLE: case ISD::SETGT: case ISD::SETGE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// An i32 compare reduced to something one flag test can consume. Either an
|
||||
// equivalent i16 compare (L16 CC16 R16; Glue is null), which the caller feeds
|
||||
// back through the i16 BR_CC / SETCC / SELECT_CC paths, or the flags of a
|
||||
// CMP/SBC chain (Glue) that the caller tests with TCC.
|
||||
struct I32CmpLowering {
|
||||
SDValue L16;
|
||||
SDValue R16;
|
||||
ISD::CondCode CC16 = ISD::SETCC_INVALID;
|
||||
SDValue Glue;
|
||||
W65816CC::CondCode TCC = W65816CC::COND_INVALID;
|
||||
};
|
||||
|
||||
// Lower an i32 compare without materialising per-half booleans. The old
|
||||
// lowering built (hiLT | (hiEQ & loLT)) out of three i16 setcc diamonds plus
|
||||
// an AND and an OR, then branched on the result: ~40 instructions and three
|
||||
// stack temporaries for one `i < n`, the dominant cost of every 32-bit loop.
|
||||
//
|
||||
// Ordered predicates use the classic 65xx multi-precision compare:
|
||||
// lda lhs.lo ; cmp rhs.lo ; lda lhs.hi ; sbc rhs.hi
|
||||
// The CMP leaves C = no-borrow of the low halves, the SBC folds that borrow
|
||||
// into the high halves, so C ends up as the unsigned 32-bit (lhs >= rhs).
|
||||
// No branch, no temporary, one BCC/BCS. The SBC's result is dead; only its
|
||||
// C matters, and neither LDA nor any other N/Z-only op between the two (the
|
||||
// reload of lhs.hi) touches C. Signed predicates bias both high halves by
|
||||
// 0x8000 first, which turns signed order into unsigned order (the same trick
|
||||
// normalizeCC uses for i16); against a constant the bias folds away.
|
||||
//
|
||||
// EQ/NE reduce to one i16 test: (lhs.lo ^ rhs.lo) | (lhs.hi ^ rhs.hi) is
|
||||
// zero exactly when the halves match, and an XOR with a constant 0 half
|
||||
// folds, so `x == 5` is just `lda lo ; eor #5 ; ora hi ; beq`.
|
||||
static I32CmpLowering lowerI32Cmp(SelectionDAG &DAG, const SDLoc &DL,
|
||||
SDValue LHS, SDValue RHS,
|
||||
ISD::CondCode CC) {
|
||||
I32CmpLowering R;
|
||||
// CMP / SBC want the comparand (constant or memory) on the right.
|
||||
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
|
||||
std::swap(LHS, RHS);
|
||||
CC = ISD::getSetCCSwappedOperands(CC);
|
||||
}
|
||||
|
||||
if (CC == ISD::SETEQ || CC == ISD::SETNE) {
|
||||
SDValue Zero = DAG.getConstant(0, DL, MVT::i16);
|
||||
SDValue LL = extractWide32Lo(DAG, DL, LHS);
|
||||
SDValue LH = extractWide32Hi(DAG, DL, LHS);
|
||||
// x == 0 / x != 0 needs no XOR at all: (lo | hi) == 0. Hot in
|
||||
// `while (x)` and in every count-down loop.
|
||||
if (isNullConstant(RHS)) {
|
||||
R.L16 = DAG.getNode(ISD::OR, DL, MVT::i16, LL, LH);
|
||||
} else {
|
||||
SDValue RL = extractWide32Lo(DAG, DL, RHS);
|
||||
SDValue RH = extractWide32Hi(DAG, DL, RHS);
|
||||
SDValue XLo = DAG.getNode(ISD::XOR, DL, MVT::i16, LL, RL);
|
||||
SDValue XHi = DAG.getNode(ISD::XOR, DL, MVT::i16, LH, RH);
|
||||
R.L16 = DAG.getNode(ISD::OR, DL, MVT::i16, XLo, XHi);
|
||||
}
|
||||
R.R16 = Zero;
|
||||
R.CC16 = CC;
|
||||
return R;
|
||||
}
|
||||
|
||||
// Fold the non-strict forms against a constant into strict ones
|
||||
// (x <= K is x < K+1), unless K+1 would wrap. Then only the four
|
||||
// predicates the CMP/SBC chain answers directly remain.
|
||||
if (auto *KC = dyn_cast<ConstantSDNode>(RHS)) {
|
||||
uint32_t K = (uint32_t)KC->getZExtValue();
|
||||
bool UMax = K == 0xFFFFFFFFu;
|
||||
bool SMax = K == 0x7FFFFFFFu;
|
||||
ISD::CondCode Strict = ISD::SETCC_INVALID;
|
||||
if (CC == ISD::SETULE && !UMax)
|
||||
Strict = ISD::SETULT;
|
||||
else if (CC == ISD::SETUGT && !UMax)
|
||||
Strict = ISD::SETUGE;
|
||||
else if (CC == ISD::SETLE && !SMax)
|
||||
Strict = ISD::SETLT;
|
||||
else if (CC == ISD::SETGT && !SMax)
|
||||
Strict = ISD::SETGE;
|
||||
if (Strict != ISD::SETCC_INVALID) {
|
||||
CC = Strict;
|
||||
RHS = DAG.getConstant((uint64_t)(K + 1u), DL, MVT::i32);
|
||||
}
|
||||
}
|
||||
// x <= y is y >= x, x > y is y < x: swap rather than add a second branch.
|
||||
switch (CC) {
|
||||
case ISD::SETULE: case ISD::SETUGT: case ISD::SETLE: case ISD::SETGT:
|
||||
std::swap(LHS, RHS);
|
||||
CC = ISD::getSetCCSwappedOperands(CC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SDValue LL = extractWide32Lo(DAG, DL, LHS);
|
||||
SDValue LH = extractWide32Hi(DAG, DL, LHS);
|
||||
SDValue RL = extractWide32Lo(DAG, DL, RHS);
|
||||
SDValue RH = extractWide32Hi(DAG, DL, RHS);
|
||||
// A constant whose low half is 0 makes the low CMP always leave C = 1,
|
||||
// so the SBC is just a CMP of the high halves: the whole compare is the
|
||||
// i16 compare of the high halves with the same predicate (x < 0x30000 is
|
||||
// hi < 3, and signed x < 0 is hi < 0).
|
||||
if (isNullConstant(RL)) {
|
||||
R.L16 = LH;
|
||||
R.R16 = RH;
|
||||
R.CC16 = CC;
|
||||
return R;
|
||||
}
|
||||
|
||||
bool Signed = (CC == ISD::SETLT || CC == ISD::SETGE);
|
||||
if (Signed) {
|
||||
SDValue Bias = DAG.getConstant(0x8000, DL, MVT::i16);
|
||||
LH = DAG.getNode(ISD::XOR, DL, MVT::i16, LH, Bias);
|
||||
RH = DAG.getNode(ISD::XOR, DL, MVT::i16, RH, Bias);
|
||||
}
|
||||
SDValue LoFlags = DAG.getNode(W65816ISD::CMP, DL, MVT::Glue, LL, RL);
|
||||
SDValue HiSub = DAG.getNode(ISD::SUBE, DL,
|
||||
DAG.getVTList(MVT::i16, MVT::Glue), LH, RH,
|
||||
LoFlags);
|
||||
R.Glue = HiSub.getValue(1);
|
||||
R.TCC = (CC == ISD::SETULT || CC == ISD::SETLT) ? W65816CC::COND_LO
|
||||
: W65816CC::COND_HS;
|
||||
return R;
|
||||
}
|
||||
|
||||
SDValue W65816TargetLowering::LowerBR_CC(SDValue Op, SelectionDAG &DAG) const {
|
||||
SDValue Chain = Op.getOperand(0);
|
||||
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(1))->get();
|
||||
|
|
@ -754,64 +888,20 @@ SDValue W65816TargetLowering::LowerBR_CC(SDValue Op, SelectionDAG &DAG) const {
|
|||
SDValue Dest = Op.getOperand(4);
|
||||
SDLoc DL(Op);
|
||||
EVT VT = LHS.getValueType();
|
||||
// i32 BR_CC: synthesize an i16 boolean from per-half compares, then
|
||||
// branch on (bool != 0). Avoids the legalizer's generic Expand that
|
||||
// re-enters our SETCC/BR_CC custom paths in an infinite loop.
|
||||
// i32 BR_CC: a CMP/SBC chain feeding one Bxx, or an equivalent i16
|
||||
// compare that re-enters this function (see lowerI32Cmp).
|
||||
if (VT == MVT::i32) {
|
||||
SDValue LL = extractWide32Lo(DAG, DL, LHS);
|
||||
SDValue LH = extractWide32Hi(DAG, DL, LHS);
|
||||
SDValue RL = extractWide32Lo(DAG, DL, RHS);
|
||||
SDValue RH = extractWide32Hi(DAG, DL, RHS);
|
||||
// Fast path: i32 == 0 / != 0 → (LL | LH) cmp 0. Drops two i16
|
||||
// setcc materializations + an AND + (for NE) an XOR; the BR_CC
|
||||
// can branch directly on the OR-test. Hot in `while (x)` and
|
||||
// any i32-counter loop test.
|
||||
if ((CC == ISD::SETEQ || CC == ISD::SETNE) &&
|
||||
isa<ConstantSDNode>(RHS) &&
|
||||
cast<ConstantSDNode>(RHS)->isZero()) {
|
||||
SDValue Or = DAG.getNode(ISD::OR, DL, MVT::i16, LL, LH);
|
||||
SDValue Z16 = DAG.getConstant(0, DL, MVT::i16);
|
||||
if (!isI32CmpCC(CC)) {
|
||||
reportUnsupported(DAG, DL, "unsupported i32 branch condition code");
|
||||
return Chain; // safe placeholder; the diagnostic fails compilation
|
||||
}
|
||||
I32CmpLowering C = lowerI32Cmp(DAG, DL, LHS, RHS, CC);
|
||||
if (!C.Glue)
|
||||
return DAG.getNode(ISD::BR_CC, DL, MVT::Other, Chain,
|
||||
DAG.getCondCode(CC), Or, Z16, Dest);
|
||||
}
|
||||
SDValue Bool;
|
||||
if (CC == ISD::SETEQ || CC == ISD::SETNE) {
|
||||
SDValue EqLo = DAG.getSetCC(DL, MVT::i16, LL, RL, ISD::SETEQ);
|
||||
SDValue EqHi = DAG.getSetCC(DL, MVT::i16, LH, RH, ISD::SETEQ);
|
||||
Bool = DAG.getNode(ISD::AND, DL, MVT::i16, EqLo, EqHi);
|
||||
if (CC == ISD::SETNE)
|
||||
Bool = DAG.getNode(ISD::XOR, DL, MVT::i16, Bool,
|
||||
DAG.getConstant(1, DL, MVT::i16));
|
||||
} else {
|
||||
// (a CC b) where CC is ordered:
|
||||
// = (hi_a HiStrict hi_b) || (hi_a == hi_b && lo_a LoCC lo_b)
|
||||
// HiStrict is the strict variant of CC (LE -> LT etc.) so the
|
||||
// tie-breaker (hi==hi && lo CC lo) handles the equality case
|
||||
// properly. LoCC is always the unsigned variant of CC because
|
||||
// the low half is unsigned (the high half carries the sign).
|
||||
ISD::CondCode HiCC, LoCCu;
|
||||
switch (CC) {
|
||||
case ISD::SETLT: HiCC = ISD::SETLT; LoCCu = ISD::SETULT; break;
|
||||
case ISD::SETLE: HiCC = ISD::SETLT; LoCCu = ISD::SETULE; break;
|
||||
case ISD::SETGT: HiCC = ISD::SETGT; LoCCu = ISD::SETUGT; break;
|
||||
case ISD::SETGE: HiCC = ISD::SETGT; LoCCu = ISD::SETUGE; break;
|
||||
case ISD::SETULT: HiCC = ISD::SETULT; LoCCu = ISD::SETULT; break;
|
||||
case ISD::SETULE: HiCC = ISD::SETULT; LoCCu = ISD::SETULE; break;
|
||||
case ISD::SETUGT: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGT; break;
|
||||
case ISD::SETUGE: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGE; break;
|
||||
default:
|
||||
reportUnsupported(DAG, DL, "unsupported i32 branch condition code");
|
||||
return Chain; // safe placeholder; the diagnostic fails compilation
|
||||
}
|
||||
SDValue HiOk = DAG.getSetCC(DL, MVT::i16, LH, RH, HiCC);
|
||||
SDValue HiEq = DAG.getSetCC(DL, MVT::i16, LH, RH, ISD::SETEQ);
|
||||
SDValue LoOk = DAG.getSetCC(DL, MVT::i16, LL, RL, LoCCu);
|
||||
SDValue Tie = DAG.getNode(ISD::AND, DL, MVT::i16, HiEq, LoOk);
|
||||
Bool = DAG.getNode(ISD::OR, DL, MVT::i16, HiOk, Tie);
|
||||
}
|
||||
SDValue Zero = DAG.getConstant(0, DL, MVT::i16);
|
||||
return DAG.getNode(ISD::BR_CC, DL, MVT::Other, Chain,
|
||||
DAG.getCondCode(ISD::SETNE), Bool, Zero, Dest);
|
||||
DAG.getCondCode(C.CC16), C.L16, C.R16, Dest);
|
||||
SDValue CCOp = DAG.getTargetConstant(C.TCC, DL, MVT::i8);
|
||||
return DAG.getNode(W65816ISD::BR_CC, DL, MVT::Other, Chain, Dest, CCOp,
|
||||
C.Glue);
|
||||
}
|
||||
|
||||
W65816CC::CondCode TCC = normalizeCC(LHS, RHS, CC, DAG, DL);
|
||||
|
|
@ -908,50 +998,27 @@ SDValue W65816TargetLowering::LowerSETCC(SDValue Op, SelectionDAG &DAG) const {
|
|||
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(2))->get();
|
||||
SDLoc DL(Op);
|
||||
EVT VT = Op.getValueType();
|
||||
// i32 SETCC: split into per-half compares. Result type is i16 (the
|
||||
// legalizer keeps the boolean result type narrow regardless of LHS
|
||||
// width).
|
||||
// i32 SETCC: one select on the CMP/SBC chain's flags, or the equivalent
|
||||
// i16 SETCC (see lowerI32Cmp). Result type is i16 (the legalizer keeps
|
||||
// the boolean result type narrow regardless of LHS width).
|
||||
if (LHS.getValueType() == MVT::i32) {
|
||||
SDValue LL = extractWide32Lo(DAG, DL, LHS);
|
||||
SDValue LH = extractWide32Hi(DAG, DL, LHS);
|
||||
SDValue RL = extractWide32Lo(DAG, DL, RHS);
|
||||
SDValue RH = extractWide32Hi(DAG, DL, RHS);
|
||||
// Fast path: i32 == 0 / != 0 → (LL | LH) cmp 0. One i16 OR + one
|
||||
// i16 setcc instead of two setcc + AND (+ XOR for NE).
|
||||
if ((CC == ISD::SETEQ || CC == ISD::SETNE) &&
|
||||
isa<ConstantSDNode>(RHS) &&
|
||||
cast<ConstantSDNode>(RHS)->isZero()) {
|
||||
SDValue Or = DAG.getNode(ISD::OR, DL, MVT::i16, LL, LH);
|
||||
SDValue Z16 = DAG.getConstant(0, DL, MVT::i16);
|
||||
return DAG.getSetCC(DL, VT, Or, Z16, CC);
|
||||
}
|
||||
if (CC == ISD::SETEQ || CC == ISD::SETNE) {
|
||||
SDValue EqLo = DAG.getSetCC(DL, VT, LL, RL, ISD::SETEQ);
|
||||
SDValue EqHi = DAG.getSetCC(DL, VT, LH, RH, ISD::SETEQ);
|
||||
SDValue Eq = DAG.getNode(ISD::AND, DL, VT, EqLo, EqHi);
|
||||
if (CC == ISD::SETNE)
|
||||
Eq = DAG.getNode(ISD::XOR, DL, VT, Eq, DAG.getConstant(1, DL, VT));
|
||||
return Eq;
|
||||
}
|
||||
ISD::CondCode HiCC, LoCCu;
|
||||
switch (CC) {
|
||||
case ISD::SETLT: HiCC = ISD::SETLT; LoCCu = ISD::SETULT; break;
|
||||
case ISD::SETLE: HiCC = ISD::SETLT; LoCCu = ISD::SETULE; break;
|
||||
case ISD::SETGT: HiCC = ISD::SETGT; LoCCu = ISD::SETUGT; break;
|
||||
case ISD::SETGE: HiCC = ISD::SETGT; LoCCu = ISD::SETUGE; break;
|
||||
case ISD::SETULT: HiCC = ISD::SETULT; LoCCu = ISD::SETULT; break;
|
||||
case ISD::SETULE: HiCC = ISD::SETULT; LoCCu = ISD::SETULE; break;
|
||||
case ISD::SETUGT: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGT; break;
|
||||
case ISD::SETUGE: HiCC = ISD::SETUGT; LoCCu = ISD::SETUGE; break;
|
||||
default:
|
||||
if (!isI32CmpCC(CC)) {
|
||||
reportUnsupported(DAG, DL, "unsupported i32 comparison condition code");
|
||||
return DAG.getUNDEF(VT); // safe placeholder
|
||||
}
|
||||
SDValue HiOk = DAG.getSetCC(DL, VT, LH, RH, HiCC);
|
||||
SDValue HiEq = DAG.getSetCC(DL, VT, LH, RH, ISD::SETEQ);
|
||||
SDValue LoOk = DAG.getSetCC(DL, VT, LL, RL, LoCCu);
|
||||
SDValue Tie = DAG.getNode(ISD::AND, DL, VT, HiEq, LoOk);
|
||||
return DAG.getNode(ISD::OR, DL, VT, HiOk, Tie);
|
||||
I32CmpLowering C = lowerI32Cmp(DAG, DL, LHS, RHS, CC);
|
||||
if (!C.Glue)
|
||||
return DAG.getSetCC(DL, VT, C.L16, C.R16, C.CC16);
|
||||
// SELECT_CC16/8 are the only selects on raw flags; an i32 boolean
|
||||
// (the combiner folds a zext into the setcc) is the i16 one widened.
|
||||
EVT SelVT = VT == MVT::i32 ? EVT(MVT::i16) : VT;
|
||||
SDValue CCOp = DAG.getTargetConstant(C.TCC, DL, MVT::i8);
|
||||
SDValue Ops[] = {DAG.getConstant(1, DL, SelVT),
|
||||
DAG.getConstant(0, DL, SelVT), CCOp, C.Glue};
|
||||
SDValue Bool = DAG.getNode(W65816ISD::SELECT_CC, DL, SelVT, Ops);
|
||||
if (SelVT != VT)
|
||||
Bool = buildWide32(DAG, DL, Bool, DAG.getConstant(0, DL, MVT::i16));
|
||||
return Bool;
|
||||
}
|
||||
SDValue One = DAG.getConstant(1, DL, VT);
|
||||
SDValue Zero = DAG.getConstant(0, DL, VT);
|
||||
|
|
@ -968,11 +1035,20 @@ SDValue W65816TargetLowering::LowerSELECT_CC(SDValue Op,
|
|||
ISD::CondCode CC = cast<CondCodeSDNode>(Op.getOperand(4))->get();
|
||||
SDLoc DL(Op);
|
||||
|
||||
// i32 SELECT_CC: synthesize an i16 boolean from the i32 compare via
|
||||
// LowerSETCC's i32 path, then select between the i32 halves driven
|
||||
// by the boolean. Avoids creating the i32 W65816::CMP we have no
|
||||
// pattern for.
|
||||
// i32 SELECT_CC. An i8/i16 result selects straight on the CMP/SBC
|
||||
// chain's flags (or on the equivalent i16 compare). An i32 result needs
|
||||
// two per-half selects and a glue value has only one consumer, so it
|
||||
// materialises the i16 boolean (LowerSETCC's i32 path) and selects both
|
||||
// halves on that. Never creates an i32 W65816::CMP (no pattern for it).
|
||||
if (LHS.getValueType() == MVT::i32) {
|
||||
if (Op.getValueType() != MVT::i32 && isI32CmpCC(CC)) {
|
||||
I32CmpLowering C = lowerI32Cmp(DAG, DL, LHS, RHS, CC);
|
||||
if (!C.Glue)
|
||||
return DAG.getSelectCC(DL, C.L16, C.R16, TVal, FVal, C.CC16);
|
||||
SDValue CCOp = DAG.getTargetConstant(C.TCC, DL, MVT::i8);
|
||||
SDValue Ops[] = {TVal, FVal, CCOp, C.Glue};
|
||||
return DAG.getNode(W65816ISD::SELECT_CC, DL, Op.getValueType(), Ops);
|
||||
}
|
||||
// Materialise the i16 boolean.
|
||||
SDValue Bool = DAG.getSetCC(DL, MVT::i16, LHS, RHS, CC);
|
||||
SDValue Zero = DAG.getConstant(0, DL, MVT::i16);
|
||||
|
|
|
|||
|
|
@ -278,10 +278,40 @@ W65816RegisterInfo::getPointerRegClass(unsigned Kind) const {
|
|||
return &W65816::PtrRegsRegClass;
|
||||
}
|
||||
|
||||
// The PHPs in MI's block before MI that no PLP has closed yet.
|
||||
static int openPhpDepth(const MachineInstr &MI) {
|
||||
int Open = 0;
|
||||
int Closed = 0;
|
||||
const MachineBasicBlock &MBB = *MI.getParent();
|
||||
for (auto It = MI.getIterator(); It != MBB.begin();) {
|
||||
--It;
|
||||
if (It->getOpcode() == W65816::PLP) {
|
||||
++Closed;
|
||||
} else if (It->getOpcode() == W65816::PHP) {
|
||||
if (Closed > 0)
|
||||
--Closed;
|
||||
else
|
||||
++Open;
|
||||
}
|
||||
}
|
||||
return Open;
|
||||
}
|
||||
|
||||
bool W65816RegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
|
||||
int SPAdj, unsigned FIOperandNum,
|
||||
RegScavenger *RS) const {
|
||||
MachineInstr &MI = *II;
|
||||
// Inside a PHP/PLP flag wrap (W65816StackSlotCleanup Pass -2.5) S sits
|
||||
// one byte lower, so a stack-relative access needs one more byte of
|
||||
// displacement. PEI feeds getSPAdjust() into SPAdj only inside call
|
||||
// sequences, so the wrap's shift is counted here, at the instruction's
|
||||
// final position. SPAdj only ever reaches the SP-relative forms: the
|
||||
// $F6 frame pointer captured S before any PHP and must not move. The
|
||||
// wrap pass used to bump the offset itself, before PEI knew the frame
|
||||
// size, and so also bumped $F6-relative loads of incoming arguments in
|
||||
// large frames (CoreMark's core_bench_state read its `memblock`
|
||||
// argument one byte off and XOR-corrupted memory from address 0 up).
|
||||
SPAdj += openPhpDepth(MI);
|
||||
MachineFunction &MF = *MI.getParent()->getParent();
|
||||
const MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||
const W65816InstrInfo &TII = *MF.getSubtarget<W65816Subtarget>().getInstrInfo();
|
||||
|
|
|
|||
|
|
@ -769,13 +769,6 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
//
|
||||
// Per-MBB analysis: walk forward, tracking running SpAdj.
|
||||
// Inter-MBB: we require ALL paths to a MBB have the same SpAdj.
|
||||
DenseMap<MachineBasicBlock *, int> MBBEntrySpAdj;
|
||||
DenseMap<MachineBasicBlock *, int> MBBExitSpAdj;
|
||||
MBBEntrySpAdj[&MF.front()] = 0;
|
||||
bool SpAdjValid = true;
|
||||
SmallVector<MachineBasicBlock *, 16> Worklist;
|
||||
Worklist.push_back(&MF.front());
|
||||
SmallPtrSet<MachineBasicBlock *, 16> Visited;
|
||||
auto miSpDelta = [](const MachineInstr &MI) -> int {
|
||||
switch (MI.getOpcode()) {
|
||||
case W65816::PHA: case W65816::PHX: case W65816::PHY:
|
||||
|
|
@ -794,37 +787,49 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
// unhandled — they can shift SP arbitrarily. Caller must bail.
|
||||
return 0;
|
||||
};
|
||||
while (!Worklist.empty() && SpAdjValid) {
|
||||
MachineBasicBlock *MBB = Worklist.pop_back_val();
|
||||
if (!Visited.insert(MBB).second) continue;
|
||||
int Sp = MBBEntrySpAdj[MBB];
|
||||
for (MachineInstr &MI : *MBB) {
|
||||
// TCS resets SP to the canonical value for the region. In
|
||||
// well-formed code, the prologue's TCS sets SP for the body
|
||||
// and the epilogue's TCS undoes it. Since stack-rel accesses
|
||||
// don't span the prologue/epilogue boundaries (the SP-shifts
|
||||
// they imply happen ONLY within prologue or epilogue, not
|
||||
// body), treat TCS as "Sp = 0" (canonical body SP).
|
||||
if (MI.getOpcode() == W65816::TCS) {
|
||||
Sp = 0;
|
||||
continue;
|
||||
// Fills Entry with every reachable MBB's SpAdj on entry; false when two
|
||||
// paths reach a block with different shifts. A lambda because the dead
|
||||
// INC_HI_IF_CARRY sweep near the end needs the same logical offsets
|
||||
// after the rewrites in between.
|
||||
auto computeEntrySpAdj =
|
||||
[&](DenseMap<MachineBasicBlock *, int> &Entry) -> bool {
|
||||
Entry.clear();
|
||||
Entry[&MF.front()] = 0;
|
||||
SmallVector<MachineBasicBlock *, 16> Worklist;
|
||||
Worklist.push_back(&MF.front());
|
||||
SmallPtrSet<MachineBasicBlock *, 16> Visited;
|
||||
while (!Worklist.empty()) {
|
||||
MachineBasicBlock *MBB = Worklist.pop_back_val();
|
||||
if (!Visited.insert(MBB).second) continue;
|
||||
int Sp = Entry[MBB];
|
||||
for (MachineInstr &MI : *MBB) {
|
||||
// TCS resets SP to the canonical value for the region. In
|
||||
// well-formed code, the prologue's TCS sets SP for the body
|
||||
// and the epilogue's TCS undoes it. Since stack-rel accesses
|
||||
// don't span the prologue/epilogue boundaries (the SP-shifts
|
||||
// they imply happen ONLY within prologue or epilogue, not
|
||||
// body), treat TCS as "Sp = 0" (canonical body SP).
|
||||
if (MI.getOpcode() == W65816::TCS) {
|
||||
Sp = 0;
|
||||
continue;
|
||||
}
|
||||
Sp += miSpDelta(MI);
|
||||
}
|
||||
Sp += miSpDelta(MI);
|
||||
}
|
||||
MBBExitSpAdj[MBB] = Sp;
|
||||
for (MachineBasicBlock *Succ : MBB->successors()) {
|
||||
auto It = MBBEntrySpAdj.find(Succ);
|
||||
if (It == MBBEntrySpAdj.end()) {
|
||||
MBBEntrySpAdj[Succ] = Sp;
|
||||
Worklist.push_back(Succ);
|
||||
} else if (It->second != Sp) {
|
||||
// Conflicting SP at successor entry → bail.
|
||||
SpAdjValid = false;
|
||||
break;
|
||||
for (MachineBasicBlock *Succ : MBB->successors()) {
|
||||
auto It = Entry.find(Succ);
|
||||
if (It == Entry.end()) {
|
||||
Entry[Succ] = Sp;
|
||||
Worklist.push_back(Succ);
|
||||
} else if (It->second != Sp) {
|
||||
// Conflicting SP at successor entry -> bail.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!SpAdjValid) {
|
||||
return true;
|
||||
};
|
||||
DenseMap<MachineBasicBlock *, int> MBBEntrySpAdj;
|
||||
if (!computeEntrySpAdj(MBBEntrySpAdj)) {
|
||||
ChangedEarly |= elideStoreForwarding(MF);
|
||||
return ChangedEarly;
|
||||
}
|
||||
|
|
@ -833,10 +838,16 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
DenseMap<int64_t, unsigned> ReadCount;
|
||||
DenseMap<int64_t, unsigned> WriteCount;
|
||||
DenseMap<int64_t, SmallVector<MachineInstr *, 8>> AccessSites;
|
||||
// Slots referenced by *_StackRelIndY (ptr32 Layer 2 deref pseudos).
|
||||
// Those uses read the slot as a POINTER via `(d,S),Y` — the addressing
|
||||
// mode REQUIRES stack-rel access; we cannot promote the slot to DP.
|
||||
DenseSet<int64_t> IndYBlocked;
|
||||
// Slots that must stay on the stack. *_StackRelIndY (ptr32 Layer 2
|
||||
// deref pseudos) read the slot as a POINTER via `(d,S),Y` - the
|
||||
// addressing mode REQUIRES stack-rel access. INC_HI_IF_CARRY_StackRel
|
||||
// (W65816I32IncFold's carry into an i32's high half) reads and writes
|
||||
// its slot but is not one of the stack-rel MC ops the rewrite below
|
||||
// knows, so promoting its slot left it bumping the abandoned stack copy,
|
||||
// and the dead-INC sweep further down then erased it outright: every
|
||||
// `i32 += 1` whose high half got promoted silently lost its carry (a
|
||||
// counter stuck below 64K, a far pointer that never crossed its bank).
|
||||
DenseSet<int64_t> PromoteBlocked;
|
||||
auto isStackRelIndY = [](unsigned Op) {
|
||||
switch (Op) {
|
||||
case W65816::LDA_StackRelIndY: case W65816::STA_StackRelIndY:
|
||||
|
|
@ -870,10 +881,11 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
AccessSites[LogicalOff].push_back(&MI);
|
||||
SiteSpAdj[&MI] = Sp;
|
||||
}
|
||||
} else if (isStackRelIndY(MI.getOpcode())) {
|
||||
} else if (isStackRelIndY(MI.getOpcode()) ||
|
||||
MI.getOpcode() == W65816::INC_HI_IF_CARRY_StackRel) {
|
||||
if (MI.getNumOperands() >= 1 && MI.getOperand(0).isImm()) {
|
||||
int64_t Off = MI.getOperand(0).getImm();
|
||||
IndYBlocked.insert(Off + Sp);
|
||||
PromoteBlocked.insert(Off + Sp);
|
||||
}
|
||||
}
|
||||
Sp += miSpDelta(MI);
|
||||
|
|
@ -956,9 +968,10 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (OffsetToDp.size() >= kMaxPromote) break;
|
||||
// Skip arg slots (offset >= frame_size + 4 from canonical SP).
|
||||
if (Off >= ArgSlotMinOff) continue;
|
||||
// Skip slots used as Layer 2 ptr32 deref source: those have
|
||||
// `(d,S),Y` uses that require stack-rel addressing.
|
||||
if (IndYBlocked.count(Off)) continue;
|
||||
// Skip slots used as Layer 2 ptr32 deref source (their `(d,S),Y`
|
||||
// uses require stack-rel addressing) or bumped by an
|
||||
// INC_HI_IF_CARRY_StackRel (see PromoteBlocked).
|
||||
if (PromoteBlocked.count(Off)) continue;
|
||||
// Skip already-used DP slots ($D0..$DE).
|
||||
while (NextDpIdx < 8 && UsedDp.test(NextDpIdx)) ++NextDpIdx;
|
||||
if (NextDpIdx >= 8) break;
|
||||
|
|
@ -1575,7 +1588,8 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (Fwd->isCall()) { CallIt = Fwd; break; }
|
||||
if (isStackRelOp(Fwd->getOpcode())) {
|
||||
StackRelOps.push_back(Fwd);
|
||||
} else if (Fwd->getOpcode() == W65816::PHA ||
|
||||
} else if (Fwd->getOpcode() == W65816::INC_HI_IF_CARRY_StackRel ||
|
||||
Fwd->getOpcode() == W65816::PHA ||
|
||||
Fwd->getOpcode() == W65816::PHX ||
|
||||
Fwd->getOpcode() == W65816::PHY ||
|
||||
Fwd->getOpcode() == W65816::PUSH16 ||
|
||||
|
|
@ -1583,7 +1597,9 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
Fwd->getOpcode() == W65816::PLA ||
|
||||
Fwd->getOpcode() == W65816::PLX ||
|
||||
Fwd->getOpcode() == W65816::PLY) {
|
||||
// Additional stack ops between push and call — bail.
|
||||
// Additional stack ops between push and call - bail. So does
|
||||
// an INC_HI_IF_CARRY_StackRel: its slot operand would need the
|
||||
// same -2 as the stack-rel ops, which StackRelOps can't hold.
|
||||
CallIt = MBB.end();
|
||||
break;
|
||||
}
|
||||
|
|
@ -2830,77 +2846,76 @@ bool W65816StackRelToImg::runOnMachineFunction(MachineFunction &MF) {
|
|||
// - LDA_StackRel H, ADC_StackRel H, etc. (any read)
|
||||
// - LDA_StackRelIndY H, STA_StackRelIndY H, etc. (used as pointer)
|
||||
// If clean, erase the INC and all STA_StackRel H stores.
|
||||
//
|
||||
// Slots are compared as LOGICAL offsets (raw offset + the SP shift at
|
||||
// that point, step 3's canonical form). Matching raw offsets missed a
|
||||
// read of H inside a PHP/PLP wrap, which is `lda H+1,s`: the sweep then
|
||||
// erased a live carry AND the store that initialised H, so the runtime's
|
||||
// strcpy walked its source through an uninitialised bank word after the
|
||||
// first byte (it only worked while the previous callee happened to leave
|
||||
// the right bank in that stack byte). A 16-bit read at L overlaps the
|
||||
// slots at L-1 and L+1 too, so those count as read. A function that
|
||||
// takes a stack address (TSX, or a TSC that is not a frame adjust) may
|
||||
// read H through a pointer and is left alone.
|
||||
{
|
||||
DenseSet<int64_t> SlotsTouched;
|
||||
bool TakesStackAddr = false;
|
||||
for (MachineBasicBlock &MBB : MF) {
|
||||
for (MachineInstr &MI : MBB) {
|
||||
if (MI.getOpcode() != W65816::INC_HI_IF_CARRY_StackRel) continue;
|
||||
if (MI.getNumOperands() < 1 || !MI.getOperand(0).isImm()) continue;
|
||||
SlotsTouched.insert(MI.getOperand(0).getImm());
|
||||
for (auto It = MBB.begin(), E = MBB.end(); It != E; ++It) {
|
||||
if (It->getOpcode() == W65816::TSX) {
|
||||
TakesStackAddr = true;
|
||||
break;
|
||||
}
|
||||
if (It->getOpcode() != W65816::TSC) continue;
|
||||
// Frame adjust: TSC ; CLC|SEC ; ADC|SBC #n ; TCS.
|
||||
auto N = std::next(It);
|
||||
bool FrameAdjust =
|
||||
N != E && (N->getOpcode() == W65816::CLC ||
|
||||
N->getOpcode() == W65816::SEC) &&
|
||||
++N != E && (N->getOpcode() == W65816::ADC_Imm16 ||
|
||||
N->getOpcode() == W65816::SBC_Imm16) &&
|
||||
++N != E && N->getOpcode() == W65816::TCS;
|
||||
if (!FrameAdjust) {
|
||||
TakesStackAddr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (TakesStackAddr) break;
|
||||
}
|
||||
for (int64_t H : SlotsTouched) {
|
||||
// Scan the entire function for any READ of slot H or USE of H as
|
||||
// an indirect-Y base.
|
||||
bool readSomewhere = false;
|
||||
DenseMap<MachineBasicBlock *, int> SweepEntrySp;
|
||||
if (!TakesStackAddr && computeEntrySpAdj(SweepEntrySp)) {
|
||||
DenseSet<int64_t> IncSlots;
|
||||
DenseSet<int64_t> ReadSlots;
|
||||
// INC_HI_IF_CARRY_StackRel and STA_StackRel sites, with their
|
||||
// logical slot: the candidates for erasure.
|
||||
SmallVector<std::pair<MachineInstr *, int64_t>, 8> Writes;
|
||||
for (MachineBasicBlock &MBB : MF) {
|
||||
for (const MachineInstr &MI : MBB) {
|
||||
unsigned Op = MI.getOpcode();
|
||||
int64_t Imm = -1;
|
||||
if (MI.getNumOperands() >= 1 && MI.getOperand(0).isImm())
|
||||
Imm = MI.getOperand(0).getImm();
|
||||
if (Imm != H) continue;
|
||||
switch (Op) {
|
||||
// Reads of slot H via stack-rel direct.
|
||||
case W65816::LDA_StackRel:
|
||||
case W65816::ADC_StackRel: case W65816::SBC_StackRel:
|
||||
case W65816::AND_StackRel: case W65816::ORA_StackRel:
|
||||
case W65816::EOR_StackRel: case W65816::CMP_StackRel:
|
||||
// Uses of slot H as an indirect-Y base.
|
||||
case W65816::LDA_StackRelIndY: case W65816::STA_StackRelIndY:
|
||||
case W65816::ADC_StackRelIndY: case W65816::SBC_StackRelIndY:
|
||||
case W65816::AND_StackRelIndY: case W65816::ORA_StackRelIndY:
|
||||
case W65816::EOR_StackRelIndY: case W65816::CMP_StackRelIndY:
|
||||
readSomewhere = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
int Sp = SweepEntrySp.lookup(&MBB);
|
||||
for (MachineInstr &MI : MBB) {
|
||||
if (MI.getOpcode() == W65816::TCS) {
|
||||
Sp = 0;
|
||||
continue;
|
||||
}
|
||||
// Also catch indirect uses of slot H-1 (the IndY reads 2 bytes
|
||||
// at H-1, H). Conservative.
|
||||
if (Imm == H - 1) {
|
||||
switch (Op) {
|
||||
case W65816::LDA_StackRelIndY: case W65816::STA_StackRelIndY:
|
||||
case W65816::ADC_StackRelIndY: case W65816::SBC_StackRelIndY:
|
||||
case W65816::AND_StackRelIndY: case W65816::ORA_StackRelIndY:
|
||||
case W65816::EOR_StackRelIndY: case W65816::CMP_StackRelIndY:
|
||||
readSomewhere = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
unsigned Op = MI.getOpcode();
|
||||
if (MI.getNumOperands() >= 1 && MI.getOperand(0).isImm()) {
|
||||
int64_t L = MI.getOperand(0).getImm() + Sp;
|
||||
if (Op == W65816::INC_HI_IF_CARRY_StackRel) {
|
||||
IncSlots.insert(L);
|
||||
Writes.push_back({&MI, L});
|
||||
} else if (Op == W65816::STA_StackRel) {
|
||||
Writes.push_back({&MI, L});
|
||||
} else if (isStackRelOp(Op) || isStackRelIndY(Op)) {
|
||||
ReadSlots.insert(L - 1);
|
||||
ReadSlots.insert(L);
|
||||
ReadSlots.insert(L + 1);
|
||||
}
|
||||
}
|
||||
if (readSomewhere) break;
|
||||
}
|
||||
if (readSomewhere) break;
|
||||
}
|
||||
if (readSomewhere) continue;
|
||||
// Slot H is dead. Erase the INC_HI_IF_CARRY and all STA_StackRel
|
||||
// H stores.
|
||||
SmallVector<MachineInstr *, 4> ToErase;
|
||||
for (MachineBasicBlock &MBB : MF) {
|
||||
for (MachineInstr &MI : MBB) {
|
||||
unsigned Op = MI.getOpcode();
|
||||
if (MI.getNumOperands() < 1 || !MI.getOperand(0).isImm()) continue;
|
||||
if (MI.getOperand(0).getImm() != H) continue;
|
||||
if (Op == W65816::INC_HI_IF_CARRY_StackRel ||
|
||||
Op == W65816::STA_StackRel) {
|
||||
ToErase.push_back(&MI);
|
||||
}
|
||||
Sp += miSpDelta(MI);
|
||||
}
|
||||
}
|
||||
for (MachineInstr *MI : ToErase) {
|
||||
MI->eraseFromParent();
|
||||
for (auto &W : Writes) {
|
||||
if (!IncSlots.count(W.second) || ReadSlots.count(W.second))
|
||||
continue;
|
||||
W.first->eraseFromParent();
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -799,10 +799,8 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
// helpers (broadening it to UsesFPRel broke dadd's i64-ABI libcall
|
||||
// flow — the STAfi pseudos in non-VLA large-frame functions don't
|
||||
// need to be marked corrupting for the wrap-detection walk).
|
||||
// UsesFPRel: broader FrameLowering-matching predicate used by the
|
||||
// pseudo-bump's offset-routing check (FP-rel ops must NOT be bumped,
|
||||
// SP-rel ops MUST be bumped; we replicate eliminateFrameIndex's
|
||||
// routing decision below to choose).
|
||||
// UsesFPRel: broader FrameLowering-matching predicate (a frame that may
|
||||
// get the $F6 frame pointer); Pass 1c below keys off it.
|
||||
bool VLAFunc = MF.getFrameInfo().hasVarSizedObjects();
|
||||
bool UsesFPRel = MF.getFrameInfo().hasVarSizedObjects() ||
|
||||
MF.getFrameInfo().estimateStackSize(MF) > 200;
|
||||
|
|
@ -1018,14 +1016,12 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
Opc == W65816::LDX_AbsY || Opc == W65816::LDX_DPY ||
|
||||
Opc == W65816::LDY_AbsX || Opc == W65816::LDY_DPX;
|
||||
};
|
||||
auto isStackRel = [](unsigned Opc) {
|
||||
// Stack-relative ops read/write at S+disp. PHP decrements S by 1,
|
||||
// so any STA/LDA d,S between PHP and PLP would land at the wrong
|
||||
// address (off by 1). We must keep these OUTSIDE the wrap.
|
||||
// Includes both the post-lowered MC opcodes (LDA_StackRel etc.)
|
||||
// AND the pseudo *fi opcodes (LDAfi etc.) — eliminateFrameIndex
|
||||
// hasn't run yet when the wrap pass executes, so it's the pseudos
|
||||
// that are actually in the IR.
|
||||
auto isStackRelMC = [](unsigned Opc) {
|
||||
// Already-lowered stack-relative MC ops: they read/write at S+disp,
|
||||
// so inside a PHP/PLP wrap their disp needs one more byte. The
|
||||
// frame-index pseudos (LDAfi, STAfi, ADDframe, ...) are left to
|
||||
// eliminateFrameIndex, which adds the wrap's byte itself (see the
|
||||
// wrap below).
|
||||
return Opc == W65816::STA_StackRel ||
|
||||
Opc == W65816::STA_StackRelIndY ||
|
||||
Opc == W65816::LDA_StackRel ||
|
||||
|
|
@ -1035,21 +1031,7 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
Opc == W65816::AND_StackRel ||
|
||||
Opc == W65816::ORA_StackRel ||
|
||||
Opc == W65816::EOR_StackRel ||
|
||||
Opc == W65816::CMP_StackRel ||
|
||||
Opc == W65816::LDAfi ||
|
||||
Opc == W65816::LDAfi_indY ||
|
||||
Opc == W65816::STAfi ||
|
||||
Opc == W65816::STAfi_indY ||
|
||||
Opc == W65816::STA8fi ||
|
||||
Opc == W65816::ADCfi ||
|
||||
Opc == W65816::ADCEfi ||
|
||||
Opc == W65816::SBCfi ||
|
||||
Opc == W65816::SBCEfi ||
|
||||
Opc == W65816::ANDfi ||
|
||||
Opc == W65816::ORAfi ||
|
||||
Opc == W65816::EORfi ||
|
||||
Opc == W65816::CMPfi ||
|
||||
Opc == W65816::ADDframe;
|
||||
Opc == W65816::CMP_StackRel;
|
||||
};
|
||||
// The subset of corrupters that also rewrite C/V, and so must be
|
||||
// wrapped even for a BCC/BCS/BVC/BVS. Only ADDframe today: every
|
||||
|
|
@ -1103,76 +1085,31 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (It == MBB.begin()) break;
|
||||
}
|
||||
if (!Test || !FirstCorrupt) continue;
|
||||
// Stack-relative ops inside the wrap need their displacements
|
||||
// bumped by +1 to compensate for PHP's S decrement. Without
|
||||
// this, `lda 5,s` between PHP and PLP reads at (orig_S-1)+5
|
||||
// = orig_S+4, one byte too low. The pseudo *fi ops carry an
|
||||
// ImmOffset operand that gets folded into the final disp by
|
||||
// eliminateFrameIndex; bumping ImmOffset by 1 produces the
|
||||
// right post-lowered disp. For already-lowered MC ops
|
||||
// (LDA_StackRel etc), bump the disp operand directly.
|
||||
// Stack-relative ops inside the wrap need one more byte of
|
||||
// displacement to compensate for PHP's S decrement. Without it,
|
||||
// `lda 5,s` between PHP and PLP reads at (orig_S-1)+5 = orig_S+4,
|
||||
// one byte too low. Already-lowered MC ops (LDA_StackRel etc.)
|
||||
// get their disp operand bumped here.
|
||||
//
|
||||
// CAVEAT for FP-relative functions (see UsesFPRel declaration above):
|
||||
// FI accesses go through FP-relative addressing (eliminateFrameIndex
|
||||
// routes through expandFarFI when FrameLowering captured FP). FP
|
||||
// was captured BEFORE PHP, so (FP),Y reads aren't affected by PHP's
|
||||
// S decrement. Don't bump pseudo *fi ImmOffsets in that case
|
||||
// (already-lowered MC StackRel ops still need the bump — those are
|
||||
// SP-rel).
|
||||
// The frame-index pseudos (LDAfi, STAfi, ADDframe, ...) are NOT:
|
||||
// eliminateFrameIndex adds the open-PHP depth at the instruction's
|
||||
// final position (see openPhpDepth in W65816RegisterInfo.cpp), and
|
||||
// only to the SP-relative forms. This pass runs before PEI, when
|
||||
// neither the frame size nor the local slots' offsets exist yet, so
|
||||
// it cannot tell which accesses PEI will route through the $F6 frame
|
||||
// pointer (unaffected by PHP); bumping them all put every
|
||||
// $F6-relative access inside a wrap one byte off. (Replicating the
|
||||
// routing test here with the pre-PEI offsets was tried and
|
||||
// misclassified incoming arguments in large frames.)
|
||||
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
|
||||
DebugLoc DL = Test->getDebugLoc();
|
||||
BuildMI(MBB, FirstCorrupt->getIterator(), DL, TII->get(W65816::PHP));
|
||||
for (auto It = FirstCorrupt->getIterator();
|
||||
It != std::next(LastCorrupt->getIterator()); ++It) {
|
||||
if (It->isDebugInstr() || !isStackRel(It->getOpcode())) continue;
|
||||
// Pseudo *fi ops: operand layout is (def, FI, ImmOffset, ...).
|
||||
// Bump the Imm at index 2. MC StackRel ops: operand 0 is the
|
||||
// disp Imm (set by eliminateFrameIndex); bump that.
|
||||
unsigned Opc = It->getOpcode();
|
||||
bool IsPseudo = Opc == W65816::LDAfi || Opc == W65816::LDAfi_indY ||
|
||||
Opc == W65816::STAfi || Opc == W65816::STAfi_indY ||
|
||||
Opc == W65816::STA8fi ||
|
||||
Opc == W65816::ADCfi || Opc == W65816::ADCEfi ||
|
||||
Opc == W65816::SBCfi || Opc == W65816::SBCEfi ||
|
||||
Opc == W65816::ANDfi || Opc == W65816::ORAfi ||
|
||||
Opc == W65816::EORfi || Opc == W65816::CMPfi ||
|
||||
Opc == W65816::ADDframe;
|
||||
// For pseudo *fi ops in FP-rel functions: only SOME will end up
|
||||
// SP-rel after PEI (offsets in [0,255]); the rest go through
|
||||
// expandFarFI → `[$F6],Y`. FP-rel access is unaffected by PHP's
|
||||
// S decrement and must NOT be bumped; SP-rel access IS affected
|
||||
// and MUST be bumped. Replicate eliminateFrameIndex's offset
|
||||
// calculation here to decide. Without this, large-frame
|
||||
// functions that mix both addressing modes (e.g. sha256-style
|
||||
// i32-libcall loops) get their FP-rel pseudos bumped, which
|
||||
// shifts reads/writes by one byte and corrupts state at
|
||||
// iteration N proportional to the i32-libcall count.
|
||||
// ADDframe has no FP-relative expansion (eliminateFrameIndex always
|
||||
// emits TSC + ADC #disp), so it is SP-relative in EVERY function and
|
||||
// always needs the bump - exempting it here would leave its address
|
||||
// one byte low inside the wrap in a large-frame function.
|
||||
if (IsPseudo && UsesFPRel && Opc != W65816::ADDframe) {
|
||||
const MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||
if (It->getOperand(1).isFI()) {
|
||||
int FI = It->getOperand(1).getIndex();
|
||||
int FrameOffset = MFI.getObjectOffset(FI);
|
||||
int ImmOffset = It->getOperand(2).isImm()
|
||||
? (int)It->getOperand(2).getImm() : 0;
|
||||
int LoweredOff = FrameOffset + ImmOffset +
|
||||
(int)MFI.getStackSize();
|
||||
if (FrameOffset < 0) LoweredOff += 1;
|
||||
// Out-of-range or VLA → FP-rel → no bump.
|
||||
if (LoweredOff < 0 || LoweredOff > 0xFF ||
|
||||
MFI.hasVarSizedObjects())
|
||||
continue;
|
||||
// Else SP-rel: fall through and bump ImmOffset.
|
||||
}
|
||||
}
|
||||
unsigned ImmIdx = IsPseudo ? 2 : 0;
|
||||
if (ImmIdx < It->getNumOperands() && It->getOperand(ImmIdx).isImm()) {
|
||||
int64_t v = It->getOperand(ImmIdx).getImm();
|
||||
It->getOperand(ImmIdx).setImm(v + 1);
|
||||
}
|
||||
if (It->isDebugInstr() || !isStackRelMC(It->getOpcode())) continue;
|
||||
// Operand 0 is the disp.
|
||||
if (It->getNumOperands() > 0 && It->getOperand(0).isImm())
|
||||
It->getOperand(0).setImm(It->getOperand(0).getImm() + 1);
|
||||
}
|
||||
BuildMI(MBB, std::next(LastCorrupt->getIterator()), DL,
|
||||
TII->get(W65816::PLP));
|
||||
|
|
@ -1704,7 +1641,20 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
for (auto It = std::next(Cmp->getIterator());
|
||||
It != Cmp->getParent()->end(); ++It) {
|
||||
if (It->isDebugInstr()) continue;
|
||||
if (It->isBranch() || It->isReturn()) break;
|
||||
// The earlier A-modifier only stands in for the CMP's N/Z.
|
||||
// `cmp #0` also sets C (always 1) and clears V, so a BCC/BCS
|
||||
// (or BVC/BVS) after it must keep it: without it the branch
|
||||
// reads whatever carry an older op left. The pre-2026-09-28 i32
|
||||
// compare lowering produced `hi <u 0` as `lda hi ; cmp #0 ; bcc`,
|
||||
// and this erase turned every unsigned `x >= K` / `x < K` with a
|
||||
// 16-bit K into a coin toss on a stale carry.
|
||||
if (It->isBranch() || It->isReturn()) {
|
||||
unsigned BrOpc = It->getOpcode();
|
||||
if (BrOpc == W65816::BCC || BrOpc == W65816::BCS ||
|
||||
BrOpc == W65816::BVC || BrOpc == W65816::BVS)
|
||||
SafeToErase = false;
|
||||
break;
|
||||
}
|
||||
// PHP/PLP-wrap-aware: only safe when LDAfi-expansion sets N/Z
|
||||
// reliably (SP-rel functions, not FP-rel).
|
||||
if (ssCleanupSPRelOnly && It->getOpcode() == W65816::PHP) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
#include "W65816InstrInfo.h"
|
||||
#include "W65816Subtarget.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/CodeGen/MachineDominators.h"
|
||||
#include "llvm/CodeGen/MachineFunction.h"
|
||||
|
|
@ -468,8 +469,16 @@ bool W65816StackSlotMerge::runOnMachineFunction(MachineFunction &MF) {
|
|||
DenseMap<int64_t, SmallVector<MachineInstr *, 4>> Stas;
|
||||
DenseMap<int64_t, SmallVector<MachineInstr *, 4>> Ldas;
|
||||
DenseMap<int64_t, unsigned> AllRefs; // STA + LDA + ADC + ... count
|
||||
// Slots an INC_HI_IF_CARRY_StackRel bumps. That pseudo writes its slot
|
||||
// without being an STA (so the twin check below can't see the write)
|
||||
// and is not a stack-rel MC op (so the rename would skip it): merging
|
||||
// such a slot would leave the carry landing in the abandoned one.
|
||||
DenseSet<int64_t> IncSlots;
|
||||
for (MachineBasicBlock &MBB : MF) {
|
||||
for (MachineInstr &MI : MBB) {
|
||||
if (MI.getOpcode() == W65816::INC_HI_IF_CARRY_StackRel &&
|
||||
MI.getNumOperands() >= 1 && MI.getOperand(0).isImm())
|
||||
IncSlots.insert(MI.getOperand(0).getImm());
|
||||
int64_t Off;
|
||||
if (!srAccess(MI, Off)) continue;
|
||||
AllRefs[Off]++;
|
||||
|
|
@ -520,6 +529,7 @@ bool W65816StackSlotMerge::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (Renames.count(X) || Renames.count(Y)) continue;
|
||||
// Arg-slot guard: skip slots with no STAs (caller-passed args).
|
||||
if (Stas[X].empty() || Stas[Y].empty()) continue;
|
||||
if (IncSlots.count(X) || IncSlots.count(Y)) continue;
|
||||
|
||||
// Validate that every STA X has a twin STA Y.
|
||||
bool allPaired = true;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Pin: an ADDframe (LEA of a stack slot) scheduled between a CMP and the
|
||||
# branch that consumes it must be PHP/PLP-wrapped by the stack-slot-cleanup
|
||||
# pass (Pass -2.5), and its ImmOffset bumped by one for PHP's S decrement.
|
||||
# pass (Pass -2.5), and after PEI its displacement is one byte larger for
|
||||
# PHP's S decrement (eliminateFrameIndex adds it; the PEI prefix below).
|
||||
#
|
||||
# ADDframe expands at PEI to `TSC; CLC; ADC #disp`, which rewrites N/Z, C
|
||||
# and V. It carries no Defs=[P] and is rematerializable, so Machine Copy
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
# carry branch needs the wrap too (LDA-like ops never did, and still don't).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup,prologepilog -o - %s | FileCheck %s --check-prefix=PEI
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @nz() { ret void }
|
||||
|
|
@ -21,9 +23,16 @@
|
|||
...
|
||||
---
|
||||
# CHECK-LABEL: name: nz
|
||||
# PEI-LABEL: name: nz
|
||||
# PEI: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# PEI-NEXT: PHP
|
||||
# PEI-NEXT: TSC
|
||||
# PEI-NEXT: CLC
|
||||
# PEI-NEXT: ADC_Imm16 2,
|
||||
# PEI-NEXT: PLP
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = ADDframe %stack.1, 1
|
||||
# CHECK-NEXT: $a = ADDframe %stack.1, 0
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BEQ %bb.1, implicit $p
|
||||
name: nz
|
||||
|
|
@ -48,7 +57,7 @@ body: |
|
|||
# CHECK-LABEL: name: carry
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = ADDframe %stack.1, 1
|
||||
# CHECK-NEXT: $a = ADDframe %stack.1, 0
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BCC %bb.1, implicit $p
|
||||
name: carry
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@
|
|||
# no_corrupt: a bare CMP/BEQ pair still gets no wrap (the wrap costs 8 cycles;
|
||||
# don't pay it where nothing corrupts).
|
||||
#
|
||||
# The wrapped reload keeps its offset: eliminateFrameIndex adds the byte
|
||||
# PHP pushed (php-wrap-frame-index.mir).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
|
|
@ -38,7 +41,7 @@
|
|||
# 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 = LDAfi %stack.1, 0
|
||||
# CHECK-NEXT: $a = ANDi8imm killed $a, 1
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BEQ %bb.1, implicit $p
|
||||
|
|
@ -65,7 +68,7 @@ body: |
|
|||
# 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 = LDAfi %stack.1, 0
|
||||
# CHECK-NEXT: $a = ORAi8imm killed $a, 1
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BNE %bb.1, implicit $p
|
||||
|
|
|
|||
85
src/llvm/test/CodeGen/W65816/cmp0-carry-branch.mir
Normal file
85
src/llvm/test/CodeGen/W65816/cmp0-carry-branch.mir
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Pin: W65816StackSlotCleanup Pass 1c drops `CMPi16imm $a, 0` after an op
|
||||
# that already set N/Z from A, but only when the branch it feeds reads N or
|
||||
# Z. `cmp #0` also sets C (always 1), so before a BCC/BCS the CMP is the
|
||||
# carry's only source and has to stay.
|
||||
#
|
||||
# The pre-2026-09-28 i32 compare lowering emitted exactly this shape for the
|
||||
# high half of an unsigned compare against a 16-bit constant (`hi <u 0`,
|
||||
# i.e. `lda hi ; cmp #0 ; bcc`). With the CMP erased the BCC tested a stale
|
||||
# carry, and `x >= 0x8000`, `x > 1`, `x < 0xFFFF` ... on a uint32_t took
|
||||
# the wrong branch whenever x had a non-zero high half.
|
||||
#
|
||||
# keep_bcc: CMP #0 before BCC stays.
|
||||
# keep_bcs: CMP #0 before BCS stays.
|
||||
# drop_beq: CMP #0 before BEQ still goes (the LDA's Z is the same).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @keep_bcc() { ret void }
|
||||
define void @keep_bcs() { ret void }
|
||||
define void @drop_beq() { ret void }
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: keep_bcc
|
||||
# CHECK: $a = LDAfi %stack.0, 0
|
||||
# CHECK-NEXT: CMPi16imm killed $a, 0, implicit-def $p
|
||||
# CHECK-NEXT: BCC %bb.1, implicit $p
|
||||
name: keep_bcc
|
||||
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, 0, implicit-def $p
|
||||
BCC %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: keep_bcs
|
||||
# CHECK: $a = LDAfi %stack.0, 0
|
||||
# CHECK-NEXT: CMPi16imm killed $a, 0, implicit-def $p
|
||||
# CHECK-NEXT: BCS %bb.1, implicit $p
|
||||
name: keep_bcs
|
||||
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, 0, implicit-def $p
|
||||
BCS %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: drop_beq
|
||||
# CHECK: $a = LDAfi %stack.0, 0
|
||||
# CHECK-NOT: CMPi16imm
|
||||
# CHECK: BEQ %bb.1, implicit $p
|
||||
name: drop_beq
|
||||
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, 0, implicit-def $p
|
||||
BEQ %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
632
src/llvm/test/CodeGen/W65816/i32-compare-chain.ll
Normal file
632
src/llvm/test/CodeGen/W65816/i32-compare-chain.ll
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
; Pin: an i32 compare lowers to the 65xx multi-precision compare, not to
|
||||
; materialised per-half booleans.
|
||||
;
|
||||
; Ordered predicates: lda lhs.lo ; cmp rhs.lo ; lda lhs.hi ; sbc rhs.hi
|
||||
; then ONE bcc/bcs (or one SELECT_CC diamond for a value). The CMP leaves
|
||||
; C = no-borrow of the low halves, the SBC folds it into the high halves.
|
||||
; Signed predicates first bias both high halves by 0x8000 (a constant's
|
||||
; bias folds: 0x1234 -> sbc #0x9234). x <= K / x > K become x < K+1 /
|
||||
; x >= K+1; reg/reg <= and > swap operands. A constant whose low half is
|
||||
; 0 reduces to an i16 compare of the high halves (x < 0x10000 is hi < 1,
|
||||
; signed x < 0 is hi < 0).
|
||||
;
|
||||
; eq / ne: (lo ^ rhs.lo) | (hi ^ rhs.hi) tested against 0, and against a
|
||||
; constant 0 just lo | hi.
|
||||
;
|
||||
; The old lowering built (hiLT | (hiEQ & loLT)) from three i16 setcc
|
||||
; diamonds and branched on the OR: every `i < n` in a 32-bit loop paid for
|
||||
; three materialised booleans and a PHP/PLP. CHECK-NOT php pins that.
|
||||
;
|
||||
; RUN: llc -mtriple=w65816 -O2 < %s | FileCheck %s
|
||||
|
||||
target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S16"
|
||||
|
||||
@sink = global i16 0
|
||||
|
||||
define void @br_eq(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_eq:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor 0xa, s
|
||||
; CHECK: eor 0x8, s
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: bne
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp eq i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @br_ne(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_ne:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor 0xa, s
|
||||
; CHECK: eor 0x8, s
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: beq
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ne i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; a.lo arrives in A, a.hi in X, b on the stack.
|
||||
define void @br_ult(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_ult:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: stx 0xd0
|
||||
; CHECK-NEXT: cmp 0x4, s
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc 0x6, s
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ult i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; a <= b is b >= a: b is loaded first.
|
||||
define void @br_ule(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_ule:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: lda 0x8, s
|
||||
; CHECK-NEXT: cmp 0x1, s
|
||||
; CHECK: lda 0xa, s
|
||||
; CHECK-NEXT: sbc 0x3, s
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ule i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @br_ugt(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_ugt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: lda 0x8, s
|
||||
; CHECK-NEXT: cmp 0x1, s
|
||||
; CHECK: lda 0xa, s
|
||||
; CHECK-NEXT: sbc 0x3, s
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ugt i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @br_uge(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_uge:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: stx 0xd0
|
||||
; CHECK-NEXT: cmp 0x4, s
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc 0x6, s
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp uge i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; Signed: both high halves biased by 0x8000, then the unsigned chain.
|
||||
define void @br_slt(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_slt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: cmp 0xa, s
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp slt i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @br_sle(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_sle:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: eor
|
||||
; CHECK: lda 0xa, s
|
||||
; CHECK-NEXT: cmp
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sle i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @br_sgt(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_sgt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: eor
|
||||
; CHECK: lda 0xa, s
|
||||
; CHECK-NEXT: cmp
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sgt i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @br_sge(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_sge:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: cmp 0xa, s
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sge i32 %a, %b
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; Against a constant (0x12345678): immediate operands, x <= K as x < K+1.
|
||||
define void @brk_eq(i32 %a) {
|
||||
; CHECK-LABEL: brk_eq:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x1234
|
||||
; CHECK: eor #0x5678
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: bne
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp eq i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_ne(i32 %a) {
|
||||
; CHECK-LABEL: brk_ne:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x1234
|
||||
; CHECK: eor #0x5678
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: beq
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ne i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_ult(i32 %a) {
|
||||
; CHECK-LABEL: brk_ult:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp #0x5678
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc #0x1234
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ult i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_ule(i32 %a) {
|
||||
; CHECK-LABEL: brk_ule:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp #0x5679
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc #0x1234
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ule i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_ugt(i32 %a) {
|
||||
; CHECK-LABEL: brk_ugt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp #0x5679
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc #0x1234
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ugt i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_uge(i32 %a) {
|
||||
; CHECK-LABEL: brk_uge:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp #0x5678
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc #0x1234
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp uge i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_slt(i32 %a) {
|
||||
; CHECK-LABEL: brk_slt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: cmp #0x5678
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc #0x9234
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp slt i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_sle(i32 %a) {
|
||||
; CHECK-LABEL: brk_sle:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: cmp #0x5679
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc #0x9234
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sle i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_sgt(i32 %a) {
|
||||
; CHECK-LABEL: brk_sgt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: cmp #0x5679
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc #0x9234
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sgt i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_sge(i32 %a) {
|
||||
; CHECK-LABEL: brk_sge:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x8000
|
||||
; CHECK: cmp #0x5678
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc #0x9234
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sge i32 %a, 305419896
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; Against 0 and -1, and constants whose low half is 0: no chain at all.
|
||||
define void @brk_eq0(i32 %a) {
|
||||
; CHECK-LABEL: brk_eq0:
|
||||
; CHECK-NOT: eor
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: ora 0x1, s
|
||||
; CHECK-NEXT: beq
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp eq i32 %a, 0
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_slt0(i32 %a) {
|
||||
; CHECK-LABEL: brk_slt0:
|
||||
; CHECK-NOT: sbc
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: eor #0x8000
|
||||
; CHECK-NEXT: cmp #0x8000
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp slt i32 %a, 0
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; x > -1 is x >= 0: the high half's sign alone.
|
||||
define void @brk_sgtm1(i32 %a) {
|
||||
; CHECK-LABEL: brk_sgtm1:
|
||||
; CHECK-NOT: sbc
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: eor #0x8000
|
||||
; CHECK-NEXT: cmp #0x8000
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sgt i32 %a, -1
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @brk_ult64k(i32 %a) {
|
||||
; CHECK-LABEL: brk_ult64k:
|
||||
; CHECK-NOT: sbc
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: cmp #0x1
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ult i32 %a, 65536
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; x > 0x1FFFF is x >= 0x20000 is hi >= 2.
|
||||
define void @brk_ugt1ffff(i32 %a) {
|
||||
; CHECK-LABEL: brk_ugt1ffff:
|
||||
; CHECK-NOT: sbc
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: cmp #0x2
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ugt i32 %a, 131071
|
||||
br i1 %c, label %t, label %f
|
||||
t:
|
||||
store volatile i16 1, ptr @sink
|
||||
ret void
|
||||
f:
|
||||
store volatile i16 2, ptr @sink
|
||||
ret void
|
||||
}
|
||||
|
||||
; A value: one diamond on the chain's C, not three.
|
||||
define i16 @set_ult(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: set_ult:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp 0x4, s
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc 0x6, s
|
||||
; CHECK-NEXT: lda #0x1
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: lda #0x0
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ult i32 %a, %b
|
||||
%z = zext i1 %c to i16
|
||||
ret i16 %z
|
||||
}
|
||||
|
||||
define i16 @set_sge(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: set_sge:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp
|
||||
; CHECK: sbc
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sge i32 %a, %b
|
||||
%z = zext i1 %c to i16
|
||||
ret i16 %z
|
||||
}
|
||||
|
||||
; The combiner folds the zext into an i32-typed SETCC: the i16 boolean is
|
||||
; widened with a zero high half (this used to be "Cannot select").
|
||||
define i32 @set32_ult(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: set32_ult:
|
||||
; CHECK: cmp 0x8, s
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc 0xa, s
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: ldx #0x0
|
||||
; CHECK: rtl
|
||||
%c = icmp ult i32 %a, %b
|
||||
%z = zext i1 %c to i32
|
||||
ret i32 %z
|
||||
}
|
||||
|
||||
define i16 @sel16_slt(i32 %a, i32 %b, i16 %x, i16 %y) {
|
||||
; CHECK-LABEL: sel16_slt:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: cmp 0xc, s
|
||||
; CHECK-NEXT: lda
|
||||
; CHECK-NEXT: sbc
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK: rtl
|
||||
%c = icmp slt i32 %a, %b
|
||||
%r = select i1 %c, i16 %x, i16 %y
|
||||
ret i16 %r
|
||||
}
|
||||
|
||||
define i32 @sel32_uge(i32 %a, i32 %b, i32 %x, i32 %y) {
|
||||
; CHECK-LABEL: sel32_uge:
|
||||
; CHECK: cmp 0xa, s
|
||||
; CHECK-NEXT: lda 0xd0
|
||||
; CHECK-NEXT: sbc 0xc, s
|
||||
; CHECK-NEXT: bcs
|
||||
; CHECK: rtl
|
||||
%c = icmp uge i32 %a, %b
|
||||
%r = select i1 %c, i32 %x, i32 %y
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
; The loop shape from JoeyLib's old sfxCopyUnsigned: the latch's i < n is
|
||||
; the chain and one bcc back to the top, and every i32 += 1 keeps the
|
||||
; carry into its high half (the bne skips over it).
|
||||
define void @loop_ult(ptr %dst, i32 %n) {
|
||||
; CHECK-LABEL: loop_ult:
|
||||
; CHECK: [[TOP:.LBB[0-9_]+]]: ; %body
|
||||
; CHECK-NOT: php
|
||||
; CHECK: inc 0xd2
|
||||
; CHECK-NEXT: bne
|
||||
; CHECK-NEXT: lda 0x3, s
|
||||
; CHECK-NEXT: inc a
|
||||
; CHECK-NEXT: sta 0x3, s
|
||||
; CHECK: inc 0xd4
|
||||
; CHECK-NEXT: bne
|
||||
; CHECK-NEXT: lda 0x5, s
|
||||
; CHECK-NEXT: inc a
|
||||
; CHECK-NEXT: sta 0x5, s
|
||||
; CHECK: lda 0xd4
|
||||
; CHECK-NEXT: cmp 0xc, s
|
||||
; CHECK-NEXT: lda 0x5, s
|
||||
; CHECK-NEXT: sbc 0xe, s
|
||||
; CHECK-NEXT: bcc [[TOP]]
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%z = icmp eq i32 %n, 0
|
||||
br i1 %z, label %done, label %body
|
||||
body:
|
||||
%i = phi i32 [ 0, %entry ], [ %inc, %body ]
|
||||
%p = getelementptr inbounds i8, ptr %dst, i32 %i
|
||||
store i8 1, ptr %p
|
||||
%inc = add nuw i32 %i, 1
|
||||
%c = icmp ult i32 %inc, %n
|
||||
br i1 %c, label %body, label %done
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
229
src/llvm/test/CodeGen/W65816/i32-inc-carry-slot.mir
Normal file
229
src/llvm/test/CodeGen/W65816/i32-inc-carry-slot.mir
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# Pin: a stack slot bumped by INC_HI_IF_CARRY_StackRel (W65816I32IncFold's
|
||||
# carry into the high half of an `i32 += 1`) is never promoted to a DP IMG
|
||||
# slot by W65816StackRelToImg, nor renamed by W65816StackSlotMerge.
|
||||
#
|
||||
# The pseudo reads and writes its slot but is not one of the eight
|
||||
# stack-rel MC ops those passes scan. StackRelToImg promoted the hot
|
||||
# high-half slot anyway, rewrote every LDA of it to DP, left the INC
|
||||
# bumping the abandoned stack copy, and its dead-INC sweep then saw a slot
|
||||
# nothing reads via stack-rel and erased the INC outright. Every such
|
||||
# `i32 += 1` lost its carry: the loop below is the latch of JoeyLib's old
|
||||
# sfxCopyUnsigned, whose far pointers stopped crossing their bank and whose
|
||||
# 32-bit counter stuck below 64K. StackSlotMerge had the same blind spot:
|
||||
# it renamed the slot everywhere except in the INC.
|
||||
#
|
||||
# promote_keeps_carry: slots 7 (ptr lo) and 1 (i lo) may go to DP; slots 3
|
||||
# and 5 (the high halves) must stay on the stack with
|
||||
# both INC_HI_IF_CARRY_StackRel intact.
|
||||
# merge_keeps_carry: `LDA 3 ; STA 5` and `LDA 5 ; STA 3` make slots 3 and
|
||||
# 5 look like one PHI value, except that slot 3 is
|
||||
# also bumped by the INC: no rename.
|
||||
# sweep_keeps_wrapped: StackRelToImg's dead-INC sweep. The only read of
|
||||
# slot 5 sits inside a PHP/PLP wrap, so it is spelled
|
||||
# `LDA_StackRel 6`; matching raw offsets called slot 5
|
||||
# unread and erased the INC and the entry STA that
|
||||
# initialised it (the runtime's strcpy, whose source
|
||||
# bank word then came from stack garbage).
|
||||
# sweep_drops_unread: a slot nothing reads still loses its INC and STAs.
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stackrel-to-img -o - %s | FileCheck %s --check-prefix=IMG
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-merge -o - %s | FileCheck %s --check-prefix=MERGE
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @promote_keeps_carry() { ret void }
|
||||
define void @merge_keeps_carry() { ret void }
|
||||
define void @sweep_keeps_wrapped() { ret void }
|
||||
define void @sweep_drops_unread() { ret void }
|
||||
...
|
||||
---
|
||||
# IMG-LABEL: name: promote_keeps_carry
|
||||
# IMG: bb.2:
|
||||
# IMG: LDA_StackRel 3
|
||||
# IMG: LDA_StackRel 3
|
||||
# IMG: LDA_StackRel 5
|
||||
# IMG: INC_DP
|
||||
# IMG-NEXT: INC_HI_IF_CARRY_StackRel 3
|
||||
# IMG: INC_DP
|
||||
# IMG-NEXT: INC_HI_IF_CARRY_StackRel 5
|
||||
# IMG: LDA_StackRel 5
|
||||
# IMG-NEXT: SBC_StackRel 14
|
||||
# IMG-NEXT: BCC %bb.2
|
||||
name: promote_keeps_carry
|
||||
tracksRegLiveness: true
|
||||
frameInfo:
|
||||
stackSize: 8
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.3, %bb.1
|
||||
liveins: $a, $x
|
||||
STX_DP 208
|
||||
STA_StackRel 7, implicit $a
|
||||
LDA_StackRel 12, implicit-def $a
|
||||
ORA_StackRel 14, implicit $a, implicit-def $a
|
||||
BEQ %bb.3, implicit $p
|
||||
|
||||
bb.1:
|
||||
successors: %bb.2
|
||||
$a = LDAi16imm 0
|
||||
STA_StackRel 5, implicit $a
|
||||
LDA_DP 208
|
||||
STA_StackRel 3, implicit $a
|
||||
$a = LDAi16imm 0
|
||||
STA_StackRel 1, implicit $a
|
||||
LDY_Imm16 0, implicit-def $y
|
||||
|
||||
bb.2:
|
||||
successors: %bb.2, %bb.3
|
||||
liveins: $y
|
||||
LDA_StackRel 7, implicit-def $a
|
||||
STA_DP 224, implicit $a
|
||||
LDA_StackRel 3, implicit-def $a
|
||||
STA_DP 226, implicit $a
|
||||
$a = LDAi16imm 1
|
||||
SEP 32
|
||||
STA_DPIndLongY 224, implicit $a, implicit $y
|
||||
REP 32
|
||||
LDA_StackRel 3, implicit-def $a
|
||||
STA_DP 230, implicit $a
|
||||
LDA_StackRel 5, implicit-def $a
|
||||
STA_DP 232, implicit $a
|
||||
LDA_StackRel 7, implicit-def $a
|
||||
$a = INA_PSEUDO $a
|
||||
STA_StackRel 7, implicit $a
|
||||
INC_HI_IF_CARRY_StackRel 3, implicit-def $a
|
||||
LDA_StackRel 1, implicit-def $a
|
||||
$a = INA_PSEUDO $a
|
||||
STA_StackRel 1, implicit $a
|
||||
INC_HI_IF_CARRY_StackRel 5, implicit-def $a
|
||||
LDA_StackRel 1, implicit-def $a
|
||||
CMP_StackRel 12, implicit $a, implicit-def $p
|
||||
LDA_StackRel 5, implicit-def $a
|
||||
SBC_StackRel 14, implicit $a, implicit-def $a, implicit $p, implicit-def $p
|
||||
BCC %bb.2, implicit $p
|
||||
|
||||
bb.3:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# MERGE-LABEL: name: merge_keeps_carry
|
||||
# MERGE: bb.1:
|
||||
# MERGE: LDA_StackRel 3
|
||||
# MERGE-NEXT: STA_StackRel 5
|
||||
# MERGE: LDA_StackRel 5
|
||||
# MERGE-NEXT: STA_StackRel 3
|
||||
# MERGE: INC_HI_IF_CARRY_StackRel 3
|
||||
# MERGE: LDA_StackRel 3
|
||||
# MERGE-NEXT: SBC_StackRel 14
|
||||
name: merge_keeps_carry
|
||||
tracksRegLiveness: true
|
||||
frameInfo:
|
||||
stackSize: 8
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1
|
||||
$a = LDAi16imm 0
|
||||
STA_StackRel 3, implicit $a
|
||||
STA_StackRel 5, implicit $a
|
||||
$a = LDAi16imm 0
|
||||
STA_StackRel 1, implicit $a
|
||||
|
||||
bb.1:
|
||||
successors: %bb.1, %bb.2
|
||||
LDA_StackRel 3, implicit-def $a
|
||||
STA_StackRel 5, implicit $a
|
||||
STA_DP 226, implicit $a
|
||||
LDA_StackRel 5, implicit-def $a
|
||||
STA_StackRel 3, implicit $a
|
||||
LDA_StackRel 1, implicit-def $a
|
||||
$a = INA_PSEUDO $a
|
||||
STA_StackRel 1, implicit $a
|
||||
INC_HI_IF_CARRY_StackRel 3, implicit-def $a
|
||||
LDA_StackRel 1, implicit-def $a
|
||||
CMP_StackRel 12, implicit $a, implicit-def $p
|
||||
LDA_StackRel 3, implicit-def $a
|
||||
SBC_StackRel 14, implicit $a, implicit-def $a, implicit $p, implicit-def $p
|
||||
BCC %bb.1, implicit $p
|
||||
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# IMG-LABEL: name: sweep_keeps_wrapped
|
||||
# IMG: STA_StackRel 5
|
||||
# IMG: bb.1:
|
||||
# IMG: INC_HI_IF_CARRY_StackRel 5
|
||||
# IMG: PHP
|
||||
# IMG-NEXT: LDA_StackRel 6
|
||||
# IMG-NEXT: PLP
|
||||
name: sweep_keeps_wrapped
|
||||
tracksRegLiveness: true
|
||||
frameInfo:
|
||||
stackSize: 14
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1
|
||||
liveins: $a, $x
|
||||
LDA_StackRel 18, implicit-def $a
|
||||
STA_StackRel 7, implicit $a
|
||||
LDA_StackRel 20, implicit-def $a
|
||||
STA_StackRel 13, implicit $a
|
||||
STA_StackRel 5, implicit $a
|
||||
|
||||
bb.1:
|
||||
successors: %bb.1, %bb.2
|
||||
LDA_StackRel 7, implicit-def $a
|
||||
$a = INA_PSEUDO $a
|
||||
STA_StackRel 7, implicit $a
|
||||
INC_HI_IF_CARRY_StackRel 5, implicit-def $a
|
||||
LDA_StackRel 7, implicit-def $a
|
||||
STA_DP 224, implicit $a
|
||||
LDA_StackRel 13, implicit-def $a
|
||||
STA_DP 226, implicit $a
|
||||
LDY_Imm16 0, implicit-def $y
|
||||
LDA_DPIndLongY 224, implicit-def $a, implicit $y
|
||||
$a = ANDi16imm killed $a, 255
|
||||
PHP
|
||||
LDA_StackRel 6, implicit-def $a
|
||||
PLP
|
||||
STA_StackRel 13, implicit $a
|
||||
BNE %bb.1, implicit $p
|
||||
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# IMG-LABEL: name: sweep_drops_unread
|
||||
# IMG: bb.0:
|
||||
# IMG-NOT: StackRel 5
|
||||
# IMG: bb.1:
|
||||
# IMG-NOT: INC_HI_IF_CARRY_StackRel
|
||||
# IMG: RTL
|
||||
name: sweep_drops_unread
|
||||
tracksRegLiveness: true
|
||||
frameInfo:
|
||||
stackSize: 14
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1
|
||||
liveins: $a, $x
|
||||
LDA_StackRel 18, implicit-def $a
|
||||
STA_StackRel 7, implicit $a
|
||||
LDA_StackRel 20, implicit-def $a
|
||||
STA_StackRel 5, implicit $a
|
||||
|
||||
bb.1:
|
||||
successors: %bb.1, %bb.2
|
||||
LDA_StackRel 7, implicit-def $a
|
||||
$a = INA_PSEUDO $a
|
||||
STA_StackRel 7, implicit $a
|
||||
INC_HI_IF_CARRY_StackRel 5, implicit-def $a
|
||||
LDA_StackRel 7, implicit-def $a
|
||||
STA_DP 224, implicit $a
|
||||
LDY_Imm16 0, implicit-def $y
|
||||
LDA_DPIndLongY 224, implicit-def $a, implicit $y
|
||||
$a = ANDi16imm killed $a, 255
|
||||
BNE %bb.1, implicit $p
|
||||
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
60
src/llvm/test/CodeGen/W65816/php-wrap-frame-index.mir
Normal file
60
src/llvm/test/CodeGen/W65816/php-wrap-frame-index.mir
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Pin: inside a PHP/PLP flag wrap (W65816StackSlotCleanup Pass -2.5) a
|
||||
# stack-relative frame access gets one more byte of displacement, and a
|
||||
# $F6-relative one (large frame) does not.
|
||||
#
|
||||
# PHP lowers S by one, so `lda d,s` inside the wrap must read d+1. The $F6
|
||||
# frame pointer was captured from S in the prologue, before any PHP, so
|
||||
# `lda [$F6],y` must keep its offset. The wrap pass runs before PEI and used
|
||||
# to bump every frame pseudo's offset itself; it could not know which ones
|
||||
# PEI would route through $F6 (the frame size and the local offsets do not
|
||||
# exist yet), and its guess called incoming arguments of a large frame
|
||||
# stack-relative. CoreMark's core_bench_state then reloaded its `memblock`
|
||||
# argument one byte off inside a wrap, started its corruption loop near
|
||||
# address 0 and XOR-scribbled memory until the emulator died. The byte is
|
||||
# now added by eliminateFrameIndex from the open PHPs before the access.
|
||||
#
|
||||
# wrap_fp_arg: 262-byte frame. The argument (fixed-stack.0) lives at
|
||||
# $F6+266 inside the wrap too; the local stack.1 moves from
|
||||
# `1,s` to `2,s`.
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup,prologepilog -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @wrap_fp_arg() { ret void }
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: wrap_fp_arg
|
||||
# CHECK: SBC_Imm16 262
|
||||
# CHECK: STA_DP 246
|
||||
# CHECK: LDA_StackRel 1,
|
||||
# CHECK-NEXT: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: STY_DP 250
|
||||
# CHECK-NEXT: LDY_Imm16 266,
|
||||
# CHECK-NEXT: LDA_DPIndLongY 246,
|
||||
# CHECK-NEXT: LDY_DP 250
|
||||
# CHECK-NEXT: LDA_StackRel 2,
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BEQ %bb.1
|
||||
name: wrap_fp_arg
|
||||
tracksRegLiveness: false
|
||||
fixedStack:
|
||||
- { id: 0, offset: 4, size: 2, alignment: 1, isImmutable: true }
|
||||
stack:
|
||||
- { id: 0, size: 260, alignment: 1 }
|
||||
- { id: 1, size: 2, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.1, 0
|
||||
CMPi16imm killed $a, 3, implicit-def $p
|
||||
$a = LDAfi %fixed-stack.0, 0
|
||||
$a = LDAfi %stack.1, 0
|
||||
BEQ %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
$a = LDAfi %stack.0, 0
|
||||
RTL
|
||||
...
|
||||
|
|
@ -20,6 +20,9 @@
|
|||
# LDA_DPIndLongY, not a pseudo) between CMP and BEQ; the wrap
|
||||
# starts at the index load in front of it.
|
||||
#
|
||||
# The wrapped reload keeps its offset: eliminateFrameIndex adds the byte
|
||||
# PHP pushed (php-wrap-frame-index.mir).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
|
|
@ -31,7 +34,7 @@
|
|||
# CHECK-LABEL: name: srl8a
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = LDAfi %stack.1, 1
|
||||
# CHECK-NEXT: $a = LDAfi %stack.1, 0
|
||||
# CHECK-NEXT: $a = SRL8A killed $a
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: STAfi killed $a, %stack.2, 0
|
||||
|
|
@ -61,7 +64,7 @@ body: |
|
|||
# CHECK-LABEL: name: shl8a
|
||||
# CHECK: CMPi16imm killed $a, 5, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = LDAfi %stack.1, 1
|
||||
# CHECK-NEXT: $a = LDAfi %stack.1, 0
|
||||
# CHECK-NEXT: $a = SHL8A killed $a
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BEQ %bb.1, implicit $p
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
"num": 1,
|
||||
"name": "SEG1",
|
||||
"base": "0x001000",
|
||||
"size": 44929,
|
||||
"bss_start": "0x00d800",
|
||||
"size": 41548,
|
||||
"bss_start": "0x00d000",
|
||||
"bss_size": 6268,
|
||||
"image": "coreMark.bin",
|
||||
"entry_offset": "0x0000"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"num": 2,
|
||||
"name": "SEG2",
|
||||
"base": "0x040000",
|
||||
"size": 16546,
|
||||
"size": 16362,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "coreMark.seg2.bin",
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
"num": 1,
|
||||
"name": "SEG1",
|
||||
"base": "0x001000",
|
||||
"size": 25189,
|
||||
"bss_start": "0x008400",
|
||||
"size": 44548,
|
||||
"bss_start": "0x00e200",
|
||||
"bss_size": 4248,
|
||||
"image": "luaTest.bin",
|
||||
"entry_offset": "0x0000"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"num": 2,
|
||||
"name": "SEG2",
|
||||
"base": "0x040000",
|
||||
"size": 41006,
|
||||
"size": 43204,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "luaTest.seg2.bin",
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
"num": 3,
|
||||
"name": "SEG3",
|
||||
"base": "0x050000",
|
||||
"size": 43429,
|
||||
"size": 41056,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "luaTest.seg3.bin",
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
"num": 4,
|
||||
"name": "SEG4",
|
||||
"base": "0x060000",
|
||||
"size": 26491,
|
||||
"size": 28810,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "luaTest.seg4.bin",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue