diff --git a/src/cc65/codeoptutil.c b/src/cc65/codeoptutil.c index b098845b4..6f4288921 100644 --- a/src/cc65/codeoptutil.c +++ b/src/cc65/codeoptutil.c @@ -1123,6 +1123,8 @@ void AddOpLow (StackOpData* D, opc_t OPC, LoadInfo* LI) InsertEntry (D, X, D->IP++); if (LI->A.LoadEntry->OPC == OP65_JSR) { + /* This should only happen in one known ldaxysp case. */ + CHECK (CE_IsCallTo (LI->A.LoadEntry, "ldaxysp")); /* opc (c_sp),y */ X = NewCodeEntry (OPC, AM65_ZP_INDY, "c_sp", 0, D->OpEntry->LI); } else { @@ -1133,7 +1135,7 @@ void AddOpLow (StackOpData* D, opc_t OPC, LoadInfo* LI) } - /* In both cases, we can remove the load */ + /* In both cases, we may try removing the load */ LI->A.Flags |= LI_REMOVE; } else { @@ -1188,6 +1190,8 @@ void AddOpHigh (StackOpData* D, opc_t OPC, LoadInfo* LI, int KeepResult) InsertEntry (D, X, D->IP++); if (LI->X.LoadEntry->OPC == OP65_JSR) { + /* This should only happen in one known ldaxysp case. */ + CHECK (CE_IsCallTo (LI->X.LoadEntry, "ldaxysp")); /* opc (c_sp),y */ X = NewCodeEntry (OPC, AM65_ZP_INDY, "c_sp", 0, D->OpEntry->LI); } else { @@ -1197,10 +1201,8 @@ void AddOpHigh (StackOpData* D, opc_t OPC, LoadInfo* LI, int KeepResult) InsertEntry (D, X, D->IP++); } - /* If this is the right hand side, we can remove the load. */ - if (LI == &D->Rhs) { - LI->X.Flags |= LI_REMOVE; - } + /* In both cases, we may try removing the load */ + LI->X.Flags |= LI_REMOVE; } else { @@ -1227,13 +1229,71 @@ void AddOpHigh (StackOpData* D, opc_t OPC, LoadInfo* LI, int KeepResult) void RemoveRegLoads (StackOpData* D, LoadInfo* LI) /* Remove register load insns */ { - if ((LI->A.Flags & LI_REMOVE) == LI_REMOVE) { - if (LI->A.LoadIndex >= 0 && - (LI->A.LoadEntry->Flags & CEF_DONT_REMOVE) == 0) { - DelEntry (D, LI->A.LoadIndex); - LI->A.LoadEntry = 0; - } + int CanRemoveA; + int CanRemoveX; + + /* When either A or X load insn is a call to ldaxysp runtime, the load + ** affects both and must be treated as a single A/X unit. A request to + ** remove the runtime call for just one (A or X) load is not valid in that + ** case. Either both must be removable+removed, or the other load is + ** independent, or ldaxysp runtime call must remain. + */ + CanRemoveA = (LI->A.Flags & LI_REMOVE) == LI_REMOVE && + LI->A.LoadEntry != 0 && + (LI->A.LoadEntry->Flags & CEF_DONT_REMOVE) == 0; + + CanRemoveX = (LI->X.Flags & LI_REMOVE) == LI_REMOVE && + LI->X.LoadEntry != 0 && + (LI->X.LoadEntry->Flags & CEF_DONT_REMOVE) == 0; + + /* The only runtime calls removable as load insns are calls to ldaxysp. */ + CHECK (!CanRemoveA || LI->A.LoadEntry->OPC != OP65_JSR || + CE_IsCallTo (LI->A.LoadEntry, "ldaxysp")); + CHECK (!CanRemoveX || LI->X.LoadEntry->OPC != OP65_JSR || + CE_IsCallTo (LI->X.LoadEntry, "ldaxysp")); + + /* When the A load insn affects X reg (e.g. ldaxysp runtime), and the X + ** load cannot be removed, we cannot remove the A load either. + */ + if (CanRemoveA && (LI->A.LoadEntry->Chg & REG_X) != 0 && !CanRemoveX) { + /* A load is not removable */ + LI->A.LoadEntry->Flags |= CEF_DONT_REMOVE; + CanRemoveA = 0; + } + + /* When the X load insn affects A reg (e.g. ldaxysp runtime), and the A + ** load cannot be removed, we cannot remove the X load either. + */ + if (CanRemoveX && (LI->X.LoadEntry->Chg & REG_A) != 0 && !CanRemoveA) { + /* X load is not removable */ + LI->X.LoadEntry->Flags |= CEF_DONT_REMOVE; + CanRemoveX = 0; + } + + /* A load removal demand which cannot be satisifed is a fatal condition */ + if (((LI->A.Flags & LI_MUST_REMOVE) != 0 && !CanRemoveA) || + ((LI->X.Flags & LI_MUST_REMOVE) != 0 && !CanRemoveX)) { + Internal ("Cannot remove a load instruction which must be removed"); + } + + /* A request to remove a "change" insn (ChgIndex) when there is no + ** corresponing load insn (LoadIndex) is never valid. + */ + CHECK ((LI->A.Flags & LI_REMOVE) == 0 || LI->A.ChgIndex < 0 || + LI->A.LoadIndex >= 0); + CHECK ((LI->X.Flags & LI_REMOVE) == 0 || LI->X.ChgIndex < 0 || + LI->X.LoadIndex >= 0); + + if (CanRemoveA) { + + CHECK (LI->A.LoadIndex >= 0); + DelEntry (D, LI->A.LoadIndex); + + /* Only remove the Y load if it is not shared with a non-removable + ** X load. A shared load will be removed here otherwise. + */ if (LI->A.LoadYIndex >= 0 && + (LI->A.LoadYIndex != LI->X.LoadYIndex || CanRemoveX) && (LI->A.LoadYEntry->Flags & CEF_DONT_REMOVE) == 0) { DelEntry (D, LI->A.LoadYIndex); } @@ -1250,12 +1310,16 @@ void RemoveRegLoads (StackOpData* D, LoadInfo* LI) LI->A.LoadYEntry->Flags |= CEF_DONT_REMOVE; } - if ((LI->X.Flags & LI_REMOVE) == LI_REMOVE) { - if (LI->X.LoadIndex >= 0 && - (LI->X.LoadEntry->Flags & CEF_DONT_REMOVE) == 0) { - DelEntry (D, LI->X.LoadIndex); - LI->X.LoadEntry = 0; - } + /* The X load may have already been removed above if it were a runtime + ** call (i.e. "jsr ldaxysp"), so need to check again. + */ + if (CanRemoveX && LI->X.LoadIndex >= 0) { + + DelEntry (D, LI->X.LoadIndex); + + /* Only remove the Y load if it is not shared with non-removable A load. + ** If it is shared and still needed, it was flaged non-removable above. + */ if (LI->X.LoadYIndex >= 0 && (LI->X.LoadYEntry->Flags & CEF_DONT_REMOVE) == 0) { DelEntry (D, LI->X.LoadYIndex); diff --git a/src/cc65/codeoptutil.h b/src/cc65/codeoptutil.h index 06a9d4ce3..c62ff28f3 100644 --- a/src/cc65/codeoptutil.h +++ b/src/cc65/codeoptutil.h @@ -70,6 +70,7 @@ typedef enum { LI_USED_BY_Y = 0x4000, /* Content used by RegY */ LI_SP = 0x8000, /* Content on stack */ LI_LOAD_INSN = 0x010000, /* Is a load insn */ + LI_MUST_REMOVE = 0x020000, /* Load must be removed */ } LI_FLAGS; /* Structure that tells us how to load the lhs values */ diff --git a/src/cc65/coptstop.c b/src/cc65/coptstop.c index 0bc533297..fdc1f96da 100644 --- a/src/cc65/coptstop.c +++ b/src/cc65/coptstop.c @@ -436,9 +436,9 @@ static unsigned Opt_toseqax_tosneax (StackOpData* D, const char* BoolTransformer X = NewCodeEntry (OP65_CMP, LoadA->AM, LoadA->Arg, 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= (LI_REMOVE | LI_MUST_REMOVE); + D->Rhs.A.Flags |= (LI_REMOVE | LI_MUST_REMOVE); } else if (RhsIsRemovable (D)) { @@ -454,6 +454,10 @@ static unsigned Opt_toseqax_tosneax (StackOpData* D, const char* BoolTransformer /* Add operand for high byte */ AddOpHigh (D, OP65_CMP, &D->Rhs, 0); + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; + } else { /* Implement the op via a temp ZP location */ @@ -1018,9 +1022,9 @@ static unsigned Opt_tosgeax (StackOpData* D) X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the tosgeax function */ RemoveRemainders (D); @@ -1075,9 +1079,9 @@ static unsigned Opt_tosltax (StackOpData* D) X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the tosltax function */ RemoveRemainders (D); @@ -1158,9 +1162,9 @@ static unsigned Opt_tossubax (StackOpData* D) /* Add code for high operand */ AddOpHigh (D, OP65_SBC, &D->Rhs, 1); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the tossubax function */ RemoveRemainders (D); @@ -1200,9 +1204,9 @@ static unsigned Opt_tosugeax (StackOpData* D) X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the tosugeax function */ RemoveRemainders (D); @@ -1246,9 +1250,9 @@ static unsigned Opt_tosugtax (StackOpData* D) X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolugt", 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the operator function */ RemoveRemainders (D); @@ -1292,9 +1296,9 @@ static unsigned Opt_tosuleax (StackOpData* D) X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolule", 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the operator function */ RemoveRemainders (D); @@ -1326,9 +1330,9 @@ static unsigned Opt_tosultax (StackOpData* D) X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolult", 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs load entries must be removed because Lhs must be in effect */ + D->Rhs.X.Flags |= LI_MUST_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the operator function */ RemoveRemainders (D); @@ -1396,8 +1400,8 @@ static unsigned Opt_a_tosbitwise (StackOpData* D, opc_t OPC) X = NewCodeEntry (OPC, D->Rhs.A.LoadEntry->AM, D->Rhs.A.LoadEntry->Arg, 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Rhs load entries must be removed */ - D->Rhs.A.Flags |= LI_REMOVE; + /* Rhs A load must be removed because Lhs must be in effect */ + D->Rhs.A.Flags |= (LI_REMOVE | LI_MUST_REMOVE); } else if (RegIsDirectNonStackLoaded (&D->Lhs.A)) { @@ -1419,12 +1423,10 @@ static unsigned Opt_a_tosbitwise (StackOpData* D, opc_t OPC) InsertEntry (D, X, D->IP++); } - /* ### Bug to come: there are dangerous Rhs X removal attempts here. - ** Runtime function calls can be "loads", and must be treated as a - ** single A/X unit, so removal of X load alone is not valid. - ** This can be solved with an additional "removable Rhs" test, or - ** by simply not forcing the LI_REMOVE flag and letting other optimizers - ** remove any unnecessary loads. + /* Note: Eager Rhs X removals here can sometimes produce slightly worse + ** code after all other optimizers have run. This may need a general + ** study of which is better: eager removal, or letting other optimizers + ** take care of unneeded loads. */ /* Do high-byte operation only when its result is used */ if ((GetRegInfo (D->Code, D->IP, REG_X) & REG_X) != 0) { @@ -1433,10 +1435,12 @@ static unsigned Opt_a_tosbitwise (StackOpData* D, opc_t OPC) /* Since this is a "same X" EOR, the result is always 0. */ X = NewCodeEntry (OP65_LDX, AM65_IMM, MakeHexArg (0), 0, D->Rhs.X.ChgEntry->LI); InsertEntry (D, X, D->IP++); + + /* Rhs X load may be removed (but this is not a demand) */ D->Rhs.X.Flags |= LI_REMOVE; } } else { - /* Rhs load entries may be removed */ + /* Rhs X load may be removed (but this is not a demand) */ D->Rhs.X.Flags |= LI_REMOVE; } @@ -1464,7 +1468,7 @@ static unsigned Opt_a_toscmpbool (StackOpData* D, const char* BoolTransformer) /* Rhs low-byte load must be removed and hi-byte load may be removed */ D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; } else if (RegIsDirectLoaded (&D->Lhs.A)) { /* If the lhs is direct (but not stack relative), encode compares with lhs, @@ -1604,7 +1608,9 @@ static unsigned Opt_a_tosicmp (StackOpData* D) InsertEntry (D, X, D->IP++); } - /* RHS may be removed */ + /* RHS may be removed, but it is not a demand because Lhs was + ** reloaded at Op and is in effect. + */ D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_REMOVE; } @@ -1703,9 +1709,11 @@ static unsigned Opt_a_tossub (StackOpData* D) InsertEntry (D, X, D->IP++); } - /* Rhs load entries must be removed */ + /* Rhs low-byte load must be removed (because Lhs must be in effect) + ** and hi-byte load may be removed. + */ D->Rhs.X.Flags |= LI_REMOVE; - D->Rhs.A.Flags |= LI_REMOVE; + D->Rhs.A.Flags |= LI_MUST_REMOVE; /* Remove the push and the call to the tossubax function */ RemoveRemainders (D); diff --git a/test/val/bug1374.c b/test/val/bug1374.c index b55e75fee..86f0223a9 100644 --- a/test/val/bug1374.c +++ b/test/val/bug1374.c @@ -24,6 +24,15 @@ int test1b(void) return (z == 0x89ab) ? 0 : 1; } +int test1c(void) +{ + uint8_t x = 0x95; + uint8_t y = 0xab; + uint16_t z = (x * 0) | y; + printf("%x\n", z); + return (z == 0x00ab) ? 0 : 1; +} + int test2(void) { uint16_t x = 0x8900; @@ -33,6 +42,15 @@ int test2(void) return (z == 0x89ab) ? 0 : 1; } +int test2c(void) +{ + uint16_t x = 0x6795; + uint8_t y = 0xab; + uint16_t z = (x * 0) | y; + printf("%x\n", z); + return (z == 0x00ab) ? 0 : 1; +} + int test3(void) { uint16_t x = 0x89; @@ -69,6 +87,15 @@ int test4b(void) return (z == 0x89ab) ? 0 : 1; } +int test4c(void) +{ + uint8_t x = 0x95; + uint16_t y = 0xabcd; + uint16_t z = (x * 0) | y; + printf("%x\n", z); + return (z == 0xabcd) ? 0 : 1; +} + int main(void) { res |= test1(); @@ -78,6 +105,9 @@ int main(void) res |= test1b(); res |= test3b(); res |= test4b(); + res |= test1c(); + res |= test2c(); + res |= test4c(); printf("res: %d\n", res); return res; } diff --git a/test/val/bug2461.c b/test/val/bug2461.c index e8340b224..77a8dff57 100644 --- a/test/val/bug2461.c +++ b/test/val/bug2461.c @@ -2,13 +2,22 @@ /* Note: The values for MASK1, MASK2, the return values of GarbleAX and the * arguments for CALC() are carefully chosen to elicit the bug. + * CALCLX() errors appear with cc65 -Osi optimizations. */ #include #define MASK1 0x000FU #define MASK2 0x00FFU -#define CALC(num, op) (((num) & (~MASK1)) op ((num) & MASK2)) +#define CALCLA(num, op) (((num) & (~MASK1)) op ((num) & MASK2)) +/* + 0x100 here invokes g_inc(), case CF_INT: if (val <= 0x300), when +** CodeSizeFactor >= 200; then g_inc() produces a single "inx" +*/ +#define CALCLX(num, op) (((num) + 0x100) op ((num) & MASK2)) +#define CALCRX(num, op) (((num) & MASK2) op ((num) << 8)) + +#define CALCX(num, op) (((num) + 0x100) op ((num) & (~MASK1))) + static unsigned Failures = 0; static unsigned TestCount = 0; @@ -16,33 +25,157 @@ static unsigned TestCount = 0; unsigned GarbleAX(void) { static const unsigned Garbage[] = { - 0x1234, 0x0000, 0x1234, 0x1234 + 0x1234, 0x1234, 0x0000, 0x1234, 0x1234, /* Lhs A: Add, Sub, And, Or, Xor */ + 0x0057, 0x0057, 0x0037, 0x0057, /* Lhs A: Eq, Neq, Gte, Lte */ + + 0x1234, 0x1234, 0x2003, 0x1002, 0x5678, /* Lhs X: Add, Sub, And, Or, Xor */ + 0x0101, 0xFF00, 0xFF00, 0xFF00, /* Lhs X: Eq, Neq, Gte, Lte */ + + 0x1234, 0x1234, 0xFFFF, 0xFFFF, 0xFFFF, /* Rhs X: Add, Sub, And, Or, Xor */ }; return Garbage[TestCount - 1]; } -unsigned WrongAdd(unsigned num) +unsigned LhsAAdd(unsigned num) { unsigned ret=GarbleAX(); - return CALC(num, +); + return CALCLA(num, +); } -unsigned WrongAnd(unsigned num) +unsigned LhsASub(unsigned num) { unsigned ret=GarbleAX(); - return CALC(num, &); + return CALCLA(num, -); } -unsigned WrongOr(unsigned num) +unsigned LhsAAnd(unsigned num) { unsigned ret=GarbleAX(); - return CALC(num, |); + return CALCLA(num, &); } -unsigned WrongXor(unsigned num) +unsigned LhsAOr(unsigned num) { unsigned ret=GarbleAX(); - return CALC(num, ^); + return CALCLA(num, |); +} + +unsigned LhsAXor(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLA(num, ^); +} + + +unsigned LhsAEq(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLA(num, ==); +} + +unsigned LhsANeq(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLA(num, !=); +} + +unsigned LhsAGte(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLA(num, >=); +} + +unsigned LhsALte(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLA(num, <=); +} + + +unsigned LhsXAdd(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCX(num, +); +} + +unsigned LhsXSub(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCX(num, -); +} + +unsigned LhsXAnd(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCX(num, &); +} + +unsigned LhsXOr(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLX(num, |); +} + +unsigned LhsXXor(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLX(num, ^); +} + + +unsigned LhsXEq(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLX(num, ==); +} + +unsigned LhsXNeq(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCX(num, !=); +} + +unsigned LhsXGte(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLX(num, >=); +} + +unsigned LhsXLte(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCLX(num, <=); +} + + +unsigned RhsXAdd(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCRX(num, +); +} + +unsigned RhsXSub(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCRX(num, -); +} + +unsigned RhsXAnd(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCRX(num, &); +} + +unsigned RhsXOr(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCRX(num, |); +} + +unsigned RhsXXor(unsigned num) +{ + unsigned ret=GarbleAX(); + return CALCRX(num, ^); } void Test(unsigned (*F)(unsigned), unsigned Num, unsigned Ref) @@ -58,10 +191,39 @@ void Test(unsigned (*F)(unsigned), unsigned Num, unsigned Ref) int main(void) { - Test(WrongAdd, 0x4715, CALC(0x4715, +)); - Test(WrongAnd, 0x4715, CALC(0x4715, &)); - Test(WrongOr, 0x4715, CALC(0x4715, |)); - Test(WrongXor, 0x4715, CALC(0x4715, ^)); + /* Test 1+ */ + Test(LhsAAdd, 0x4715, CALCLA(0x4715, +)); + Test(LhsASub, 0x4715, CALCLA(0x4715, -)); + Test(LhsAAnd, 0x4715, CALCLA(0x4715, &)); + Test(LhsAOr, 0x4715, CALCLA(0x4715, |)); + Test(LhsAXor, 0x4715, CALCLA(0x4715, ^)); + + /* Test 6+ */ + Test(LhsAEq, 0x4750, CALCLA(0x4750, ==)); + Test(LhsANeq, 0x4750, CALCLA(0x4750, !=)); + Test(LhsAGte, 0x4750, CALCLA(0x4750, >=)); + Test(LhsALte, 0x4750, CALCLA(0x4750, <=)); + + /* Test 10+ */ + Test(LhsXAdd, 0x3F15, CALCX(0x3F15, +)); + Test(LhsXSub, 0x3F15, CALCX(0x3F15, -)); + Test(LhsXAnd, 0x3F15, CALCX(0x3F15, &)); + Test(LhsXOr, 0x3F15, CALCLX(0x3F15, |)); + Test(LhsXXor, 0x3F15, CALCLX(0x3F15, ^)); + + /* Test 15+ */ + Test(LhsXEq, 0xFF50, CALCLX(0xFF50, ==)); + Test(LhsXNeq, 0xFF50, CALCX(0xFF50, !=)); + Test(LhsXGte, 0xFF50, CALCLX(0xFF50, >=)); + Test(LhsXLte, 0xFF50, CALCLX(0xFF50, <=)); + + /* Test 19+ */ + Test(RhsXAdd, 0x3F15, CALCRX(0x3F15, +)); + Test(RhsXSub, 0x3F15, CALCRX(0x3F15, -)); + Test(RhsXAnd, 0x3F15, CALCRX(0x3F15, &)); + Test(RhsXOr, 0x3F15, CALCRX(0x3F15, |)); + Test(RhsXXor, 0x3F15, CALCRX(0x3F15, ^)); + printf("Failures: %u\n", Failures); return Failures; } diff --git a/test/val/pr2951.c b/test/val/pr2951.c new file mode 100644 index 000000000..de56c1f05 --- /dev/null +++ b/test/val/pr2951.c @@ -0,0 +1,244 @@ + +/* bug #2942/#2947: OptStackOps() sometimes removes Lhs loads shared with Rhs. +** +** These are synthetic, extreme reuse test cases: AX unchanged by pushax. +** These do not occur with the current codegen, runtime and optimizers, but +** may occur in the future. If the runtime or codegen change sufficiently to +** break these tests, they can be deleted. +*/ + +#include +#include + +int fail = 0; + +#define TRASH_AX 0x55AA + +int stat_val; + +int stat_add_full(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = AX + AX */ + asm("jsr pushax"); + asm("jsr tosaddax"); + + return __AX__; +} + +int stat_xor_full(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = AX ^ AX */ + asm("jsr pushax"); + asm("jsr tosxorax"); + + return __AX__; +} + +int stat_xor_and(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = AX ^ (AX & 0xFFF0) */ + asm("jsr pushax"); + asm("and #$F0"); + asm("jsr tosxorax"); + + return __AX__; +} + +int stat_or_inx(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = AX | (AX + 0x100) */ + asm("jsr pushax"); + asm("inx"); + asm("jsr tosorax"); + + return __AX__; +} + +int stat_and_add(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = (AX & 0xFFF0) + (AX & 0xFFF0) */ + asm("and #$F0"); + asm("jsr pushax"); + asm("jsr tosaddax"); + + return __AX__; +} + +int stat_inx_add(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = (AX + 0x100) + (AX + 0x100) */ + asm("inx"); + asm("jsr pushax"); + asm("jsr tosaddax"); + + return __AX__; +} + +int stat_inx_or(int trash) +{ + (void)trash; + + __AX__ = stat_val; + /* AX = (AX + 0x100) | (AX + 0x100) */ + asm("inx"); + asm("jsr pushax"); + asm("jsr tosorax"); + + return __AX__; +} + + +int loc_add_full(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = AX + AX */ + asm("jsr pushax"); + asm("jsr tosaddax"); + + return __AX__; +} + +int loc_xor_full(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = AX ^ AX */ + asm("jsr pushax"); + asm("jsr tosxorax"); + + return __AX__; +} + +int loc_xor_and(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = AX ^ (AX & 0xFFF0) */ + asm("jsr pushax"); + asm("and #$F0"); + asm("jsr tosxorax"); + + return __AX__; +} + +int loc_or_inx(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = AX | (AX + 0x100) */ + asm("jsr pushax"); + asm("inx"); + asm("jsr tosorax"); + + return __AX__; +} + +int loc_and_add(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = (AX & 0xFFF0) + (AX & 0xFFF0) */ + asm("and #$F0"); + asm("jsr pushax"); + asm("jsr tosaddax"); + + return __AX__; +} + +int loc_inx_add(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = (AX + 0x100) + (AX + 0x100) */ + asm("inx"); + asm("jsr pushax"); + asm("jsr tosaddax"); + + return __AX__; +} + +int loc_inx_or(int val, int trash) +{ + (void)trash; + + __AX__ = val; + /* AX = (AX + 0x100) | (AX + 0x100) */ + asm("inx"); + asm("jsr pushax"); + asm("jsr tosorax"); + + return __AX__; +} + + +void test_stat(int (*func)(int /*trash*/), int num, int ref, const char* expr) +{ + int res; + + stat_val = num; + res = func(TRASH_AX); + if (res != ref) { + printf("Fail (stat): %s -> 0x%x\n", expr, res); + ++fail; + } +} + +void test_loc(int (*func)(int, int /*trash*/), int num, int ref, const char* expr) +{ + int res; + + res = func(num, TRASH_AX); + if (res != ref) { + printf("Fail (loc): %s -> 0x%x\n", expr, res); + ++fail; + } +} + +#define TEST_STATIC(func, num, ref) test_stat(func, num, (ref), #ref) +#define TEST_LOCAL(func, num, ref) test_loc(func, num, (ref), #ref) + +int main(void) +{ + TEST_STATIC(stat_add_full, 0x321, 0x321 + 0x321); + TEST_STATIC(stat_xor_full, 0x321, 0x321 ^ 0x321); + TEST_STATIC(stat_xor_and, 0x321, 0x321 ^ (0x321 & 0xFFF0)); + TEST_STATIC(stat_or_inx, 0x321, 0x321 | (0x321 + 0x100)); + TEST_STATIC(stat_and_add, 0x321, (0x321 & 0xFFF0) + (0x321 & 0xFFF0)); + TEST_STATIC(stat_inx_add, 0x321, (0x321 + 0x100) + (0x321 + 0x100)); + TEST_STATIC(stat_inx_or, 0x321, (0x321 + 0x100) | (0x321 + 0x100)); + + TEST_LOCAL(loc_add_full, 0x321, 0x321 + 0x321); + TEST_LOCAL(loc_xor_full, 0x321, 0x321 ^ 0x321); + TEST_LOCAL(loc_xor_and, 0x321, 0x321 ^ (0x321 & 0xFFF0)); + TEST_LOCAL(loc_or_inx, 0x321, 0x321 | (0x321 + 0x100)); + TEST_LOCAL(loc_and_add, 0x321, (0x321 & 0xFFF0) + (0x321 & 0xFFF0)); + TEST_LOCAL(loc_inx_add, 0x321, (0x321 + 0x100) + (0x321 + 0x100)); + TEST_LOCAL(loc_inx_or, 0x321, (0x321 + 0x100) | (0x321 + 0x100)); + + return fail == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +}