Compare commits

...

10 Commits

Author SHA1 Message Date
Colin Leroy-Mira
d20d99b32b Fix reverse lowercase on Apple][
Some checks failed
Snapshot Build / Build, Test, and Snapshot (Linux) (push) Has been skipped
Snapshot Build / Build (Windows) (push) Has been cancelled
Fixes #2831.
Thanks to Alex Volkov for the patch
2026-04-30 19:41:39 +02:00
Bob Andrews
8fcee1b552 Merge pull request #2958 from geon/master
Fixed typos. marcos -> macros
2026-04-19 17:52:36 +02:00
Victor Widell
c31565cd5b Fixed typos. marco -> macro 2026-04-19 17:20:54 +02:00
Bob Andrews
a545b4fc3e Merge pull request #2951 from avolkov76/anv-bug-2942
Prevent inadvertent removals of `ldaxysp` runtime calls in `OptStackOps()`
2026-04-07 13:36:12 +02:00
Alex Volkov
69c17a502b Additional synthetic, extreme register load reuse tests future-proofing issues #2942 and #2947. 2026-04-01 18:27:28 -04:00
Alex Volkov
de78048319 Generalize the shared, non-removable reg load tests in RemoveRegLoads() to "instruction affects the other register" 2026-03-29 17:05:20 -04:00
Alex Volkov
321e47f0f3 Rhs X removals in coptstop.c should no longer be dangerous. 2026-03-26 18:32:04 -04:00
Alex Volkov
6027487c30 Expand bug2461 tests to other operators and Lhs/Rhs conditions 2026-03-26 18:22:39 -04:00
Alex Volkov
00a1f1d447 Clearly delineate load instruction "must remove" conditions from "may remove" with a new LI_MUST_REMOVE flag. A demand to remove a non-removable load is fatal. 2026-03-25 19:14:14 -04:00
Alex Volkov
c9b885b144 Prevent the removal of a ldaxysp runtime call as a "load instruction" for only the A or the X side, when the other is non-removable. Fixes issue #2942 / #2461. 2026-03-25 19:08:18 -04:00
7 changed files with 555 additions and 74 deletions

View File

@@ -216,7 +216,7 @@ SMC AdaptUnderlineWidth, { NOP }
<sect1>Accessing arguments<p> <sect1>Accessing arguments<p>
These marcos are determined to get, set and change arguments of instructions: These macros are determined to get, set and change arguments of instructions:
<descrip> <descrip>
@@ -458,7 +458,7 @@ SMC GetData, { LDA SMC_AbsAdr }
<sect1>Operational macros<p> <sect1>Operational macros<p>
These marcos are determined to let read/modify/write opcodes work on parts of These macros are determined to let read/modify/write opcodes work on parts of
SMC instructions. SMC instructions.
<descrip> <descrip>
@@ -504,7 +504,7 @@ SMC GetPageData, { LDA SourceData, X }
<sect1>Scope macros<p> <sect1>Scope macros<p>
These marcos are determined to export and import SMC labels out of the current These macros are determined to export and import SMC labels out of the current
file scope. Please handle with care! If you cannot abstain from leaving the file scope. Please handle with care! If you cannot abstain from leaving the
file scope, you should at least document the exported SMC lines very well. On file scope, you should at least document the exported SMC lines very well. On
import side no checking is available if the SMC line is correct accessed (e.g. import side no checking is available if the SMC line is correct accessed (e.g.

View File

@@ -109,16 +109,14 @@ newline:
: jmp VTABZ : jmp VTABZ
putchar: putchar:
.ifdef __APPLE2ENH__
ldy INVFLG
cpy #$FF ; Normal character display mode?
beq putchardirect
cmp #$E0 ; Lowercase? cmp #$E0 ; Lowercase?
bcc mask and INVFLG ; Apply normal, inverse, flash
and #$7F ; Inverse lowercase bcc putchardirect ; Not lowercase, no special handling
bra putchardirect .ifndef __APPLE2ENH__
bit machinetype
bpl putchardirect ; Mask normally for ][/+ ; done
.endif .endif
mask: and INVFLG ; Apply normal, inverse, flash ora #$40 ; Restore lowercase bit for //e and up
putchardirect: putchardirect:
tax tax

View File

@@ -1123,6 +1123,8 @@ void AddOpLow (StackOpData* D, opc_t OPC, LoadInfo* LI)
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
if (LI->A.LoadEntry->OPC == OP65_JSR) { 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 */ /* opc (c_sp),y */
X = NewCodeEntry (OPC, AM65_ZP_INDY, "c_sp", 0, D->OpEntry->LI); X = NewCodeEntry (OPC, AM65_ZP_INDY, "c_sp", 0, D->OpEntry->LI);
} else { } 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; LI->A.Flags |= LI_REMOVE;
} else { } else {
@@ -1188,6 +1190,8 @@ void AddOpHigh (StackOpData* D, opc_t OPC, LoadInfo* LI, int KeepResult)
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
if (LI->X.LoadEntry->OPC == OP65_JSR) { 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 */ /* opc (c_sp),y */
X = NewCodeEntry (OPC, AM65_ZP_INDY, "c_sp", 0, D->OpEntry->LI); X = NewCodeEntry (OPC, AM65_ZP_INDY, "c_sp", 0, D->OpEntry->LI);
} else { } else {
@@ -1197,7 +1201,7 @@ void AddOpHigh (StackOpData* D, opc_t OPC, LoadInfo* LI, int KeepResult)
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
} }
/* In both cases, we can remove the load */ /* In both cases, we may try removing the load */
LI->X.Flags |= LI_REMOVE; LI->X.Flags |= LI_REMOVE;
} else { } else {
@@ -1225,12 +1229,71 @@ void AddOpHigh (StackOpData* D, opc_t OPC, LoadInfo* LI, int KeepResult)
void RemoveRegLoads (StackOpData* D, LoadInfo* LI) void RemoveRegLoads (StackOpData* D, LoadInfo* LI)
/* Remove register load insns */ /* Remove register load insns */
{ {
if ((LI->A.Flags & LI_REMOVE) == LI_REMOVE) { int CanRemoveA;
if (LI->A.LoadIndex >= 0 && int CanRemoveX;
(LI->A.LoadEntry->Flags & CEF_DONT_REMOVE) == 0) {
DelEntry (D, LI->A.LoadIndex); /* 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 && if (LI->A.LoadYIndex >= 0 &&
(LI->A.LoadYIndex != LI->X.LoadYIndex || CanRemoveX) &&
(LI->A.LoadYEntry->Flags & CEF_DONT_REMOVE) == 0) { (LI->A.LoadYEntry->Flags & CEF_DONT_REMOVE) == 0) {
DelEntry (D, LI->A.LoadYIndex); DelEntry (D, LI->A.LoadYIndex);
} }
@@ -1247,11 +1310,16 @@ void RemoveRegLoads (StackOpData* D, LoadInfo* LI)
LI->A.LoadYEntry->Flags |= CEF_DONT_REMOVE; LI->A.LoadYEntry->Flags |= CEF_DONT_REMOVE;
} }
if ((LI->X.Flags & LI_REMOVE) == LI_REMOVE) { /* The X load may have already been removed above if it were a runtime
if (LI->X.LoadIndex >= 0 && ** call (i.e. "jsr ldaxysp"), so need to check again.
(LI->X.LoadEntry->Flags & CEF_DONT_REMOVE) == 0) { */
DelEntry (D, LI->X.LoadIndex); 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 && if (LI->X.LoadYIndex >= 0 &&
(LI->X.LoadYEntry->Flags & CEF_DONT_REMOVE) == 0) { (LI->X.LoadYEntry->Flags & CEF_DONT_REMOVE) == 0) {
DelEntry (D, LI->X.LoadYIndex); DelEntry (D, LI->X.LoadYIndex);

View File

@@ -70,6 +70,7 @@ typedef enum {
LI_USED_BY_Y = 0x4000, /* Content used by RegY */ LI_USED_BY_Y = 0x4000, /* Content used by RegY */
LI_SP = 0x8000, /* Content on stack */ LI_SP = 0x8000, /* Content on stack */
LI_LOAD_INSN = 0x010000, /* Is a load insn */ LI_LOAD_INSN = 0x010000, /* Is a load insn */
LI_MUST_REMOVE = 0x020000, /* Load must be removed */
} LI_FLAGS; } LI_FLAGS;
/* Structure that tells us how to load the lhs values */ /* Structure that tells us how to load the lhs values */

View File

@@ -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); X = NewCodeEntry (OP65_CMP, LoadA->AM, LoadA->Arg, 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= (LI_REMOVE | LI_MUST_REMOVE);
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= (LI_REMOVE | LI_MUST_REMOVE);
} else if (RhsIsRemovable (D)) { } else if (RhsIsRemovable (D)) {
@@ -454,6 +454,10 @@ static unsigned Opt_toseqax_tosneax (StackOpData* D, const char* BoolTransformer
/* Add operand for high byte */ /* Add operand for high byte */
AddOpHigh (D, OP65_CMP, &D->Rhs, 0); 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 { } else {
/* Implement the op via a temp ZP location */ /* 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); X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the tosgeax function */ /* Remove the push and the call to the tosgeax function */
RemoveRemainders (D); RemoveRemainders (D);
@@ -1075,9 +1079,9 @@ static unsigned Opt_tosltax (StackOpData* D)
X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI); X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the tosltax function */ /* Remove the push and the call to the tosltax function */
RemoveRemainders (D); RemoveRemainders (D);
@@ -1158,9 +1162,9 @@ static unsigned Opt_tossubax (StackOpData* D)
/* Add code for high operand */ /* Add code for high operand */
AddOpHigh (D, OP65_SBC, &D->Rhs, 1); AddOpHigh (D, OP65_SBC, &D->Rhs, 1);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the tossubax function */ /* Remove the push and the call to the tossubax function */
RemoveRemainders (D); RemoveRemainders (D);
@@ -1200,9 +1204,9 @@ static unsigned Opt_tosugeax (StackOpData* D)
X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI); X = NewCodeEntry (OP65_ROL, AM65_ACC, "a", 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the tosugeax function */ /* Remove the push and the call to the tosugeax function */
RemoveRemainders (D); RemoveRemainders (D);
@@ -1246,9 +1250,9 @@ static unsigned Opt_tosugtax (StackOpData* D)
X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolugt", 0, D->OpEntry->LI); X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolugt", 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the operator function */ /* Remove the push and the call to the operator function */
RemoveRemainders (D); RemoveRemainders (D);
@@ -1292,9 +1296,9 @@ static unsigned Opt_tosuleax (StackOpData* D)
X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolule", 0, D->OpEntry->LI); X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolule", 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the operator function */ /* Remove the push and the call to the operator function */
RemoveRemainders (D); RemoveRemainders (D);
@@ -1326,9 +1330,9 @@ static unsigned Opt_tosultax (StackOpData* D)
X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolult", 0, D->OpEntry->LI); X = NewCodeEntry (OP65_JSR, AM65_ABS, "boolult", 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs load entries must be removed because Lhs must be in effect */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_MUST_REMOVE;
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_MUST_REMOVE;
/* Remove the push and the call to the operator function */ /* Remove the push and the call to the operator function */
RemoveRemainders (D); 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); X = NewCodeEntry (OPC, D->Rhs.A.LoadEntry->AM, D->Rhs.A.LoadEntry->Arg, 0, D->OpEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs load entries must be removed */ /* Rhs A load must be removed because Lhs must be in effect */
D->Rhs.A.Flags |= LI_REMOVE; D->Rhs.A.Flags |= (LI_REMOVE | LI_MUST_REMOVE);
} else if (RegIsDirectNonStackLoaded (&D->Lhs.A)) { } 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++); InsertEntry (D, X, D->IP++);
} }
/* ### Bug to come: there are dangerous Rhs X removal attempts here. /* Note: Eager Rhs X removals here can sometimes produce slightly worse
** Runtime function calls can be "loads", and must be treated as a ** code after all other optimizers have run. This may need a general
** single A/X unit, so removal of X load alone is not valid. ** study of which is better: eager removal, or letting other optimizers
** This can be solved with an additional "removable Rhs" test, or ** take care of unneeded loads.
** by simply not forcing the LI_REMOVE flag and letting other optimizers
** remove any unnecessary loads.
*/ */
/* Do high-byte operation only when its result is used */ /* Do high-byte operation only when its result is used */
if ((GetRegInfo (D->Code, D->IP, REG_X) & REG_X) != 0) { 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. */ /* 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); X = NewCodeEntry (OP65_LDX, AM65_IMM, MakeHexArg (0), 0, D->Rhs.X.ChgEntry->LI);
InsertEntry (D, X, D->IP++); InsertEntry (D, X, D->IP++);
/* Rhs X load may be removed (but this is not a demand) */
D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.X.Flags |= LI_REMOVE;
} }
} else { } 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; 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 */ /* Rhs low-byte load must be removed and hi-byte load may be removed */
D->Rhs.X.Flags |= LI_REMOVE; 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)) { } else if (RegIsDirectLoaded (&D->Lhs.A)) {
/* If the lhs is direct (but not stack relative), encode compares with lhs, /* 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++); 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.A.Flags |= LI_REMOVE;
D->Rhs.X.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++); 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.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 */ /* Remove the push and the call to the tossubax function */
RemoveRemainders (D); RemoveRemainders (D);

View File

@@ -2,13 +2,22 @@
/* Note: The values for MASK1, MASK2, the return values of GarbleAX and the /* Note: The values for MASK1, MASK2, the return values of GarbleAX and the
* arguments for CALC() are carefully chosen to elicit the bug. * arguments for CALC() are carefully chosen to elicit the bug.
* CALCLX() errors appear with cc65 -Osi optimizations.
*/ */
#include <stdio.h> #include <stdio.h>
#define MASK1 0x000FU #define MASK1 0x000FU
#define MASK2 0x00FFU #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 Failures = 0;
static unsigned TestCount = 0; static unsigned TestCount = 0;
@@ -16,33 +25,157 @@ static unsigned TestCount = 0;
unsigned GarbleAX(void) unsigned GarbleAX(void)
{ {
static const unsigned Garbage[] = { 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]; return Garbage[TestCount - 1];
} }
unsigned WrongAdd(unsigned num) unsigned LhsAAdd(unsigned num)
{ {
unsigned ret=GarbleAX(); unsigned ret=GarbleAX();
return CALC(num, +); return CALCLA(num, +);
} }
unsigned WrongAnd(unsigned num) unsigned LhsASub(unsigned num)
{ {
unsigned ret=GarbleAX(); unsigned ret=GarbleAX();
return CALC(num, &); return CALCLA(num, -);
} }
unsigned WrongOr(unsigned num) unsigned LhsAAnd(unsigned num)
{ {
unsigned ret=GarbleAX(); unsigned ret=GarbleAX();
return CALC(num, |); return CALCLA(num, &);
} }
unsigned WrongXor(unsigned num) unsigned LhsAOr(unsigned num)
{ {
unsigned ret=GarbleAX(); 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) 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) int main(void)
{ {
Test(WrongAdd, 0x4715, CALC(0x4715, +)); /* Test 1+ */
Test(WrongAnd, 0x4715, CALC(0x4715, &)); Test(LhsAAdd, 0x4715, CALCLA(0x4715, +));
Test(WrongOr, 0x4715, CALC(0x4715, |)); Test(LhsASub, 0x4715, CALCLA(0x4715, -));
Test(WrongXor, 0x4715, CALC(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); printf("Failures: %u\n", Failures);
return Failures; return Failures;
} }

244
test/val/pr2951.c Normal file
View File

@@ -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 <stdio.h>
#include <stdlib.h>
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;
}