// 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;
}
