// Regression test for the W65816 SepRepCleanup PHI-copy-hoist fix // (W65816SepRepCleanup.cpp gap conflict-check). // // Reported by an external user targeting the SNES (gist gingerbeardman/ // 74e25c6a). SepRepCleanup's "sink LDA;STA out of a PHP/PLP flag-wrap" // peephole hoisted a wrap's trailing `STA_StackRel N` (staging a select's // TRUE value `y`) ABOVE an `AND_StackRel N` in the gap (the select // predicate's boolean operand) -- its conflict check only treated // LDA_StackRel as a slot read, missing AND/ORA/EOR/ADC/SBC/CMP_StackRel. // The AND then read `y` instead of the boolean, so `best` wrongly became // `y` at every optimized -O level. // // course_support_piece(&gCourse, 32, 250, 8, 0, 0) must return 0x00037FFF // (best stays 0x7FFF, pid = 3). The buggy backend returned 0x00030082 // (best clobbered with y = 130). course_support_piece MUST stay // out-of-line (external + 2 call sites) -- a single inlined call site // changes register allocation and hides the bug. Verified in MAME. typedef short fx_i16; typedef unsigned short fx_u16; typedef unsigned char fx_u8; typedef signed char fx_i8; typedef unsigned long fx_u32; // 32-bit on w65816 #define NO_GROUND 0x7FFF #define MASK_NONE 0x7FFF #define FLAT_SLOPED 0x7FFE #define CTILE 16 #define CTSHIFT 4 #define CTMASK 15 #define PC_GAP 0 #define PC_CRUMBLE 2 #define PIECE_SHIFT 4 #define fx_shr16(n, k) ((fx_i16)((fx_i16)(n) >> (k))) #define fx_abs16(n) ((fx_i16)((n) < 0 ? -(n) : (n))) #define rd_i16(p, i) (((const volatile fx_i16 *)(p))[(i)]) #define rd_u16(p, i) (((const volatile fx_u16 *)(p))[(i)]) #define rd_u8(p, i) (((const volatile fx_u8 *)(p))[(i)]) typedef struct { fx_i16 start_tcol, end_tcol; fx_i16 start_col, end_col; const fx_u16 *tmask; const fx_i16 *tbase; const fx_u8 *ground_piece; const fx_i16 *col_surf_flat; } course_layer_t; typedef struct { int nlayers; const course_layer_t *layers; const fx_i16 *masks; const fx_u8 *collapsed; fx_i16 max_px; } course_t; static __attribute__((noinline)) fx_i16 wall_clamp_px(const course_t *c, fx_i16 px) { if (px < 0) { return 0; } if (px > c->max_px) { return c->max_px; } return px; } static fx_i16 layer_probe(const course_t *c, const course_layer_t *L, fx_i16 px, fx_i16 col, int li, fx_u8 *piece_out) { (void)li; fx_i16 ret; fx_u8 outpiece; fx_i16 tcol = fx_shr16(px, CTSHIFT); if (tcol < L->start_tcol || tcol >= L->end_tcol) { ret = NO_GROUND; outpiece = PC_GAP; } else { int ti = tcol - L->start_tcol; int in_col = (col >= L->start_col && col < L->end_col); int gi = col - L->start_col; const fx_u16 *tmask = L->tmask; const fx_i16 *tbase = L->tbase; const fx_u8 *gp = L->ground_piece; const fx_i16 *masks = c->masks; const fx_u8 *collapsed = c->collapsed; const fx_i16 *csf = L->col_surf_flat; fx_u8 mi = (fx_u8)(px & CTMASK); fx_u8 gpv = PC_GAP; { fx_i16 fy = rd_i16(csf, ti); if (fy != FLAT_SLOPED) { if (in_col) gpv = rd_u8(gp, gi); if (piece_out) *piece_out = in_col ? gpv : PC_GAP; return fy; } } if (in_col) gpv = rd_u8(gp, gi); fx_u16 id = rd_u16(tmask, ti); if (id == 0) ret = NO_GROUND; else if (collapsed && in_col && collapsed[col] && gpv == PC_CRUMBLE) ret = NO_GROUND; else { fx_i16 off = rd_i16(masks, (int)id * CTILE + mi); ret = (off == MASK_NONE) ? NO_GROUND : (fx_i16)(rd_i16(tbase, ti) + off); } outpiece = in_col ? gpv : PC_GAP; } if (piece_out) *piece_out = outpiece; return ret; } fx_u32 course_support_piece(const course_t *c, fx_i16 px, fx_i16 contactY, fx_i8 grab, fx_i16 belowFrom, fx_i16 *below_out) { fx_i16 best = NO_GROUND, bestd = (fx_i16)(grab + 1), piece_bestd = 0x7FFF; fx_u8 pid = PC_GAP; fx_i16 below = NO_GROUND; fx_i16 qpx = wall_clamp_px(c, px); fx_i16 col = fx_shr16(qpx, PIECE_SHIFT); for (int li = 0; li < c->nlayers; li++) { const course_layer_t *L = &c->layers[li]; fx_u8 pc; fx_i16 y = layer_probe(c, L, qpx, col, li, &pc); if (y != NO_GROUND) { fx_i16 d = fx_abs16((fx_i16)(y - contactY)); if (d <= grab && d < bestd) { best = y; bestd = d; } if (d < piece_bestd) { piece_bestd = d; pid = pc; } if (below_out) { if (y >= belowFrom && y < below) below = y; } else if (d == 0) return (fx_u32)(fx_u16)y | ((fx_u32)pc << 16); } } if (below_out) *below_out = below; return (fx_u32)(fx_u16)best | ((fx_u32)pid << 16); } #define FS FLAT_SLOPED static const fx_u16 tmask0[16] = {0,1,2,3, 1,2,3,0, 1,2,3,1, 2,3,0,1}; static const fx_i16 tbase0[16] = {100,110,120,130, 140,150,160,170, 180,190,200,210, 220,230,240,250}; static const fx_u8 gp0[16] = {1,2,3,4, 5,6,7,8, 1,2,3,4, 5,6,7,8}; static const fx_i16 csf0[16] = {FS,250,FS,260, FS,FS,250,FS, 300,FS,FS,250, FS,FS,FS,FS}; static const fx_u16 tmask1[16] = {1,0,2,0, 3,1,0,2, 0,3,1,0, 2,0,3,1}; static const fx_i16 tbase1[16] = {200,205,210,215, 220,225,230,235, 240,245,250,255, 260,265,270,275}; static const fx_u8 gp1[16] = {2,2,2,2, 2,2,2,2, 3,3,3,3, 1,1,1,1}; static const fx_i16 csf1[16] = {FS,FS,FS,FS, 250,FS,FS,FS, FS,240,FS,FS, FS,FS,250,FS}; static const fx_u16 tmask2[16] = {3,3,0,1, 2,3,0,1, 2,3,0,1, 2,3,0,0}; static const fx_i16 tbase2[16] = {150,150,150,150, 160,160,160,160, 170,170,170,170, 180,180,180,180}; static const fx_u8 gp2[16] = {4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7}; static const fx_i16 csf2[16] = {FS,FS,FS,FS, FS,FS,FS,FS, FS,FS,FS,FS, FS,FS,FS,FS}; #undef FS static const fx_i16 masks[64] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,1,2,3, 4,5,6,7, MASK_NONE,0,1,2, 3,4,5,6, 10,11,MASK_NONE,13, 14,15,16,17, 18,MASK_NONE,20,21, 22,23,24,25, MASK_NONE,1,2,MASK_NONE, 4,5,6,7, 8,9,10,11, 12,13,14,15 }; static const fx_u8 collapsed[16] = {0,0,1,0, 0,1,0,0, 1,0,0,1, 0,0,1,0}; static const course_layer_t layers[3] = { { 0,16, 0,16, tmask0, tbase0, gp0, csf0 }, { 0,16, 0,16, tmask1, tbase1, gp1, csf1 }, { 0,16, 0,16, tmask2, tbase2, gp2, csf2 }, }; static const course_t gCourse = { 3, layers, masks, collapsed, 255 }; // Two call sites keep course_support_piece out-of-line (it is far over the // inline threshold), reproducing the real function's register pressure. int main(void) { fx_u32 r = course_support_piece(&gCourse, 32, 250, 8, 0, 0); fx_u32 r2 = course_support_piece(&gCourse, 48, 260, 8, 0, 0); *(volatile unsigned short *)0x025000 = (unsigned short)(r & 0xFFFF); *(volatile unsigned short *)0x025002 = (unsigned short)((r >> 16) & 0xFFFF); *(volatile unsigned short *)0x025004 = (unsigned short)(r2 & 0xFFFF); while (1) {} return 0; }