From 99ccd7e1a31ef231f885f6484b39ead7fdacb915 Mon Sep 17 00:00:00 2001 From: Alex Volkov Date: Wed, 11 Mar 2026 17:44:33 -0400 Subject: [PATCH] Refactor: Replace OptStackOps() complex (and buggy) flag-based subopt precondition evaluation system with plain language predicate functions. Any custom preconditions can now be expressed without much trouble. Also, begin establishing parity between precondition logic and actual subopt branching logic. --- src/cc65/coptstop.c | 827 ++++++++++++++++++++++++-------------------- 1 file changed, 450 insertions(+), 377 deletions(-) diff --git a/src/cc65/coptstop.c b/src/cc65/coptstop.c index 5fa3f5dce..0cbb4beec 100644 --- a/src/cc65/coptstop.c +++ b/src/cc65/coptstop.c @@ -53,37 +53,15 @@ -/* Flags for the functions */ -typedef enum { - OP_NONE = 0x00, /* Nothing special */ - OP_A_KNOWN = 0x01, /* Value of A must be known */ - OP_X_ZERO = 0x02, /* X must be zero */ - OP_LHS_LOAD = 0x04, /* Must have load insns for LHS */ - OP_LHS_SAME_BY_OP = 0x08, /* Load result of LHS must be the same if relocated to op */ - OP_LHS_LOAD_DIRECT = 0x0C, /* Must have direct load insn for LHS */ - OP_RHS_LOAD = 0x10, /* Must have load insns for RHS */ - OP_RHS_SAME_BY_OP = 0x20, /* Load result of RHS must be the same if relocated to op */ - OP_RHS_LOAD_DIRECT = 0x30, /* Must have direct load insn for RHS */ - OP_AX_INTERCHANGE = 0x40, /* Preconditions of A/X may be interchanged */ - OP_LR_INTERCHANGE = 0x80, /* Preconditions of LHS/RHS may be interchanged */ - OP_LHS_SAME_BY_PUSH = 0x0100, /* LHS must load the same content if relocated to push */ - OP_RHS_SAME_BY_PUSH = 0x0200, /* RHS must load the same content if relocated to push */ - OP_LHS_SAME_BY_RHS = 0x0400, /* LHS must load the same content if relocated to RHS */ - OP_RHS_SAME_BY_LHS = 0x0800, /* RHS must load the same content if relocated to LHS */ - OP_LHS_REMOVE = 0x1000, /* LHS must be removable or RHS may use ZP store/load */ - OP_LHS_REMOVE_DIRECT = 0x3000, /* LHS must be directly removable */ - OP_RHS_REMOVE = 0x4000, /* RHS must be removable or LHS may use ZP store/load */ - OP_RHS_REMOVE_DIRECT = 0xC000, /* RHS must be directly removable */ -} OP_FLAGS; - /* Structure that describes an optimizer subfunction for a specific op */ typedef unsigned (*OptFunc) (StackOpData* D); +typedef int (*PreCondFunc) (const StackOpData* D); + typedef struct OptFuncDesc OptFuncDesc; struct OptFuncDesc { const char* Name; /* Name of the replaced runtime function */ OptFunc Func; /* Function pointer */ - unsigned UnusedRegs; /* Regs that must not be used later */ - OP_FLAGS Flags; /* Flags */ + PreCondFunc PreCond; /* Precondition predicate pointer */ }; @@ -118,6 +96,13 @@ static int SameRegXValueAtOp (const StackOpData* D, CodeEntry* OpEntry) CHECK (D->PushIndex >= 0); PushEntry = CS_GetEntry (D->Code, D->PushIndex); + /* ### This is a temporary workaround, removable in next phases. */ + if (OpEntry == 0) { + /* Not provided; then OpIndex must be present */ + CHECK (D->OpIndex >= 0); + OpEntry = CS_GetEntry (D->Code, D->OpIndex); + } + return RegValIsKnown (PushEntry->RI->In.RegX) && RegValIsKnown (OpEntry->RI->In.RegX) && @@ -126,6 +111,108 @@ static int SameRegXValueAtOp (const StackOpData* D, CodeEntry* OpEntry) +static inline int RegIsDirectLoaded (const LoadRegInfo* LRI) +/* Check if Reg specified by LoadRegInfo is direct-loaded. */ +{ + /* Must have load insns for Reg, and must be direct */ + return (LRI->Flags & LI_LOAD_INSN) != 0 && + (LRI->Flags & LI_DIRECT) != 0; +} + + + +static inline int RegIsDirectNonStackLoaded (const LoadRegInfo* LRI) +/* Check if Reg specified by LoadRegInfo is direct-loaded without +** stack-relative Y-indexing. +*/ +{ + /* Note: this is not a precise test. abs,y addressing would also + ** require reloading Y. + */ + return (LRI->Flags & (LI_LOAD_INSN | LI_DIRECT | LI_RELOAD_Y)) == + (LI_LOAD_INSN | LI_DIRECT /* no LI_RELOAD_Y */); +} + + + +static inline int RegLoadIsRemovable (const LoadRegInfo* LRI) +/* Check if Reg specified by LoadRegInfo is marked as non-removable. */ +{ + /* Must have load entry for Reg, which is not marked */ + return LRI->LoadEntry != 0 && + (LRI->LoadEntry->Flags & CEF_DONT_REMOVE) == 0; +} + + + +static inline int LhsIsDirectLoad (const StackOpData* D) +/* Check if Lhs A/X is direct-loaded. */ +{ + return RegIsDirectLoaded (&D->Lhs.A) && RegIsDirectLoaded (&D->Lhs.X); +} + + + +static inline int RhsIsDirectLoad (const StackOpData* D) +/* Check if Rhs A/X is direct-loaded. */ +{ + return RegIsDirectLoaded (&D->Rhs.A) && RegIsDirectLoaded (&D->Rhs.X); +} + + + +static inline int LhsIsDirectNonStackLoad (const StackOpData* D) +/* Check if Lhs A/X is direct-loaded and not stack-relative. */ +{ + /* Note: this is not a precise test. abs,y addressing would also + ** require reloading Y. + */ + return RegIsDirectNonStackLoaded (&D->Lhs.A) && + RegIsDirectNonStackLoaded (&D->Lhs.X); +} + + + +static inline int RhsIsDirectNonStackLoad (const StackOpData* D) +/* Check if Rhs A/X is direct-loaded and not stack-relative. */ +{ + /* Note: this is not a precise test. abs,y addressing would also + ** require reloading Y. + */ + return RegIsDirectNonStackLoaded (&D->Rhs.A) && + RegIsDirectNonStackLoaded (&D->Rhs.X); +} + + + +static int RhsIsRemovable (const StackOpData* D) +/* Check if Rhs A/X is direct-loaded and removable (because Lhs A/X must +** be in force at operation). +*/ +{ + /* Must have direct load insns for Rhs */ + if (!RhsIsDirectLoad (D)) { + return 0; /* Fail */ + } + + /* Rhs loads must be removable */ + if (!RegLoadIsRemovable (&D->Rhs.A) || !RegLoadIsRemovable (&D->Rhs.X)) { + return 0; /* Fail */ + } + + /* Special condition: for Rhs to be removable, it must be loaded + ** exactly *once*. When multiple loads exists, only the last one would + ** be removed, and the Lhs A/X will not be in force at op. + */ + if (D->RhsMultiChg) { + return 0; /* Fail */ + } + + return 1; /* Pass */ +} + + + static void UseLhsRegVarLoc (StackOpData* D) /* Prepare StackOpData to use the Lhs ZP location. */ { @@ -148,7 +235,7 @@ static int HaveUnusedTempZPLoc (const StackOpData* D) ** but it is a lesser evil than predicate side effects. */ return (D->ZPUsage & REG_PTR1) == 0 || (D->ZPUsage & REG_SREG) == 0 || - (D->ZPUsage & REG_PTR2) == 0; + (D->ZPUsage & REG_PTR2) == 0; } @@ -178,6 +265,259 @@ static void ChooseTempZPLoc (StackOpData* D) +/*****************************************************************************/ +/* Precondition Predicates */ +/*****************************************************************************/ + + + +static int CanUseAny (const StackOpData* D) +/* Precondition: any operand will do when temp ZP location is available +** as fallback. +*/ +{ + return HaveUnusedTempZPLoc (D); +} + + + +static int CanUseDirectLhsOrTempZP (const StackOpData* D) +/* Precondition: Either Lhs is direct-loaded, or temp ZP location +** must be available. Used by subopts falling back on temp ZP with +** AddStoreLhsX(), AddStoreLhsA(), ReplacePushByStore(). +*/ +{ + return LhsIsDirectLoad (D) || HaveUnusedTempZPLoc (D); +} + + + +static int CanUseNonStackLhsOrTempZP (const StackOpData* D) +/* Precondition: Either Lhs is direct-loaded and not stack-relative, +** or temp ZP location must be available. Used by subopts falling +** back on temp ZP after testing !LI_RELOAD_Y. +*/ +{ + return LhsIsDirectNonStackLoad (D) || HaveUnusedTempZPLoc (D); +} + + + +static int CanUseNonStackLhsOrAny (const StackOpData* D) +/* Precondition: Either Lhs is direct-loaded and not stack-relative, +** or Rhs is direct-loaded and removable (because Lhs A/X must be in force +** at operation), or temp ZP location must be available. +** Used by subopts falling back on temp ZP after testing !LI_RELOAD_Y. +*/ +{ + return LhsIsDirectNonStackLoad (D) || RhsIsRemovable (D) || + HaveUnusedTempZPLoc (D); +} + + + +static int CanUseRegVarOrTempZP (const StackOpData* D) +/* Precondition: either the operand is a register (ZP) var, or a temp +** ZP location is available to store its value. +*/ +{ + return IsRegVar (D) || HaveUnusedTempZPLoc (D); +} + + + +static int CanUseRemovableRhs (const StackOpData* D) +/* Precondition: Rhs is direct-loaded and removable (because Lhs A/X must +** be in force at operation). +*/ +{ + return RhsIsRemovable (D); +} + + + +/* ### Note: This is a temporary, artificially-limited precondition reproducing +** the original (OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT) evaluation, +** but not useful otherwise. +*/ +static int CanUseRemovableRhsWithTempZP (const StackOpData* D) +/* Precondition: Rhs is direct-loaded and removable (because Lhs A/X must +** be in force at operation), and a temp ZP location is available. +*/ +{ + return RhsIsDirectLoad (D) && RhsIsRemovable (D) && HaveUnusedTempZPLoc (D); +} + + + +static int CanUseDirectLhsOrRemovableRhs (const StackOpData* D) +/* Precondition: Either Lhs is direct-loaded, or Rhs is direct-loaded +** and removable (because Lhs A/X must be in force at operation). +*/ +{ + return LhsIsDirectLoad (D) || RhsIsRemovable (D); +} + + + +/* ### Note: This is a temporary, artificially-limited precondition reproducing +** the original (OP_LR_INTERCHANGE | OP_RHS_REMOVE_DIRECT) evaluation, +** but not useful otherwise. +*/ +static int CanUseDirectWithRemovableRhsAndTempZP (const StackOpData* D) +/* Precondition: Lhs is direct-loaded, or Rhs is direct-loaded, and +** Rhs is removable, and a temp ZP location is available. +*/ +{ + return (LhsIsDirectLoad (D) || RhsIsDirectLoad (D)) && + RhsIsRemovable (D) && HaveUnusedTempZPLoc (D); +} + + + +static int CanUseAnyRemovableOrTempZP (const StackOpData* D) +/* Precondition: Either Lhs is direct-loaded, or Rhs is direct-loaded +** and removable (because Lhs A/X must be in force at operation), or +** a temp ZP location is available to store the Lhs. +*/ +{ + return CanUseDirectLhsOrRemovableRhs (D) || HaveUnusedTempZPLoc (D); +} + + + +static int CanUseRegADirectLhsOrRemovableRhs (const StackOpData* D) +/* Precondition: Either Lhs reg A is direct-loaded, or entire Rhs is +** direct-loaded and removable (because Lhs A/X must be in force +** at operation). +*/ +{ + /* Note: the entire Rhs A/X must be removable, not just A. */ + return RegIsDirectLoaded (&D->Lhs.A) || RhsIsRemovable (D); +} + + + +static int CanUseRegANonStackLhsOrRemovableRhs (const StackOpData* D) +/* Precondition: Either Lhs reg A is direct-loaded and not stack-relative, +** or entire Rhs is direct-loaded removable (because Lhs A/X must be +** in force at operation). +*/ +{ + /* Note: the entire Rhs A/X must be removable, not just A. */ + return RegIsDirectNonStackLoaded (&D->Lhs.A) || RhsIsRemovable (D); +} + + + +static int MustHaveTempZP (const StackOpData* D) +/* Precondition: a temp ZP location must be available in all cases */ +{ + return HaveUnusedTempZPLoc (D); +} + + + +static int WithAXlt100CanUseRegVarOrTempZP (const StackOpData* D) +/* Precondition: When X is 0 and A is known at op, either the operand is a +** register (ZP) var, or a temp ZP location is available to store its value. +*/ +{ + CHECK (D->OpEntry != 0); + + return CanUseRegVarOrTempZP (D) && + /* X is 0 at op, and A is known at op */ + (D->OpEntry->RI->In.RegX == 0) && + RegValIsKnown (D->OpEntry->RI->In.RegA); +} + + + +static int WithUnusedACanUseRegVarOrTempZP (const StackOpData* D) +/* Precondition: either the operand is a register (ZP) var, or a temp ZP +** location is available to store its value. Reg A must not be used later +** (because it is changed). +*/ +{ + return (IsRegVar (D) || HaveUnusedTempZPLoc (D)) && + !RegAUsed (D->Code, D->OpIndex + 1); +} + + + +static int WithSameXMustHaveTempZP (const StackOpData* D) +/* Precondition: When Rhs X == Lhs X, any operand will do because a temp +** ZP location will be used. Used by subopts primarily targeting temp +** ZP with AddStoreLhsA() or similar. +*/ +{ + return SameRegXValueAtOp (D, 0) && HaveUnusedTempZPLoc (D); +} + + + +static int WithSameXCanUseRemovableRhs (const StackOpData* D) +/* Precondition: When Rhs X == Lhs X, Rhs must be direct-loaded and removable +** (because Lhs A/X must be in force at operation). +*/ +{ + /* Note: the entire Rhs A/X must be removable, not just A. */ + return SameRegXValueAtOp (D, 0) && RhsIsRemovable (D); +} + + + +/* ### Note: This is a temporary, artificially-limited precondition reproducing +** the original (OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT) evaluation +** for A-only subopts, but not useful otherwise. +*/ +static int WithSameXCanUseRemovableRhsWithTempZP (const StackOpData* D) +/* Precondition: When Rhs X == Lhs X, Rhs is direct-loaded and removable, +** and a temp ZP location is available. +*/ +{ + return SameRegXValueAtOp (D, 0) && RhsIsDirectLoad (D) && + RhsIsRemovable (D) && HaveUnusedTempZPLoc (D); +} + + + +static int WithSameXCanUseNonStackLhsOrRemovableRhs (const StackOpData* D) +/* Precondition: When Rhs X == Lhs X, either Lhs reg A is direct-loaded and not +** stack-relative, or Rhs must be direct-loaded and removable (because Lhs A/X +** must be in force at operation). +*/ +{ + return SameRegXValueAtOp (D, 0) && + CanUseRegANonStackLhsOrRemovableRhs (D); +} + + + +static int WithSameXCanUseNonStackLhsOrAny (const StackOpData* D) +/* Precondition: When Rhs X == Lhs X, either Lhs reg A is direct-loaded and not +** stack-relative, or Rhs must be direct-loaded and removable (because Lhs A/X +** must be in force at operation), or a temp ZP location is available. +*/ +{ + return SameRegXValueAtOp (D, 0) && + (CanUseRegANonStackLhsOrRemovableRhs (D) || HaveUnusedTempZPLoc (D)); +} + + + +static int WithSameXCanUseAny (const StackOpData* D) +/* Precondition: When Rhs X == Lhs X, either Lhs reg A is direct-loaded, or Rhs +** is direct-loaded and removable (because Lhs A/X must be in force at +** operation), or a temp ZP location is available to store the Lhs. +*/ +{ + return SameRegXValueAtOp (D, 0) && + (CanUseRegADirectLhsOrRemovableRhs (D) || HaveUnusedTempZPLoc (D)); +} + + + /*****************************************************************************/ /* Actual optimization functions */ /*****************************************************************************/ @@ -201,8 +541,7 @@ static unsigned Opt_toseqax_tosneax (StackOpData* D, const char* BoolTransformer /* If the lhs is direct (but not stack relative), encode compares with lhs ** effectively reverting the order (which doesn't matter for ==). */ - if ((D->Lhs.A.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT && - (D->Lhs.X.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT) { + if (LhsIsDirectNonStackLoad (D)) { CodeEntry* LoadX = D->Lhs.X.LoadEntry; CodeEntry* LoadA = D->Lhs.A.LoadEntry; @@ -225,9 +564,7 @@ static unsigned Opt_toseqax_tosneax (StackOpData* D, const char* BoolTransformer D->Lhs.X.Flags |= LI_REMOVE; D->Lhs.A.Flags |= LI_REMOVE; - } else if ((D->Rhs.A.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT && - (D->Rhs.X.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT && - D->RhsMultiChg == 0) { + } else if (RhsIsDirectNonStackLoad (D) && RhsIsRemovable (D)) { CodeEntry* LoadX = D->Rhs.X.LoadEntry; CodeEntry* LoadA = D->Rhs.A.LoadEntry; @@ -250,8 +587,7 @@ static unsigned Opt_toseqax_tosneax (StackOpData* D, const char* BoolTransformer D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_REMOVE; - } else if ((D->Rhs.A.Flags & LI_DIRECT) != 0 && - (D->Rhs.X.Flags & LI_DIRECT) != 0) { + } else if (RhsIsRemovable (D)) { D->IP = D->OpIndex+1; @@ -308,8 +644,7 @@ static unsigned Opt_tosshift (StackOpData* D, const char* Name) /* If the lhs is direct (but not stack relative), we can just reload the ** data later. */ - if ((D->Lhs.A.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT && - (D->Lhs.X.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT) { + if (LhsIsDirectNonStackLoad (D)) { CodeEntry* LoadX = D->Lhs.X.LoadEntry; CodeEntry* LoadA = D->Lhs.A.LoadEntry; @@ -497,6 +832,9 @@ static unsigned Opt_staspidx (StackOpData* D) X = NewCodeEntry (OP65_STA, AM65_ZP_INDY, D->ZPLo, 0, D->OpEntry->LI); InsertEntry (D, X, D->OpIndex+1); + /* Note: We have not flagged any loads for removal. If any are unnecessary, + ** other optimizers will remove them. + */ /* Remove the push and the call to the staspidx function */ RemoveRemainders (D); @@ -525,7 +863,7 @@ static unsigned Opt_staxspidx (StackOpData* D) AddStoreLhsA (D); } - /* Note: This subopt demands for REG_AX to not be used later. + /* Note: Earlier, this subopt demanded for REG_AX to not be used later. ** X is unchanged by the code here, so only REG_A must not be used. ** Note 2: Should be updated to common D->IP++ syntax. */ @@ -701,10 +1039,11 @@ static unsigned Opt_tosaddax (StackOpData* D) /* Value of first op high byte is unknown. Load from ZP or ** original storage. */ - if (D->Lhs.X.Flags & LI_DIRECT) { + if (RegIsDirectLoaded (&D->Lhs.X)) { CodeEntry* LoadX = D->Lhs.X.LoadEntry; X = NewCodeEntry (OP65_LDX, LoadX->AM, LoadX->Arg, 0, D->OpEntry->LI); } else { + CHECK (D->ZPHi != 0); X = NewCodeEntry (OP65_LDX, AM65_ZP, D->ZPHi, 0, D->OpEntry->LI); } } @@ -788,12 +1127,12 @@ static unsigned Opt_tosgeax (StackOpData* D) CodeEntry* X; CodeLabel* L; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); + /* Inline the sbc */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* Add code for low operand */ AddOpLow (D, OP65_CMP, &D->Rhs); @@ -845,13 +1184,12 @@ static unsigned Opt_tosltax (StackOpData* D) CodeEntry* X; CodeLabel* L; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the compare */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* Add code for low operand */ AddOpLow (D, OP65_CMP, &D->Rhs); @@ -951,6 +1289,8 @@ static unsigned Opt_tossubax (StackOpData* D) { CodeEntry* X; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the sbc */ D->IP = D->OpIndex+1; @@ -959,9 +1299,6 @@ static unsigned Opt_tossubax (StackOpData* D) X = NewCodeEntry (OP65_SEC, AM65_IMP, 0, 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* Add code for low operand */ AddOpLow (D, OP65_SBC, &D->Rhs); @@ -986,13 +1323,12 @@ static unsigned Opt_tosugeax (StackOpData* D) { CodeEntry* X; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the sbc */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* Add code for low operand */ AddOpLow (D, OP65_CMP, &D->Rhs); @@ -1029,13 +1365,12 @@ static unsigned Opt_tosugtax (StackOpData* D) { CodeEntry* X; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the sbc */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* sec */ X = NewCodeEntry (OP65_SEC, AM65_IMP, 0, 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); @@ -1076,13 +1411,12 @@ static unsigned Opt_tosuleax (StackOpData* D) { CodeEntry* X; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the sbc */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* sec */ X = NewCodeEntry (OP65_SEC, AM65_IMP, 0, 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); @@ -1123,13 +1457,12 @@ static unsigned Opt_tosultax (StackOpData* D) { CodeEntry* X; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the sbc */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* Add code for low operand */ AddOpLow (D, OP65_CMP, &D->Rhs); @@ -1204,7 +1537,7 @@ static unsigned Opt_a_tosbitwise (StackOpData* D, opc_t OPC) /* Inline the bitwise operation */ D->IP = D->OpIndex+1; - if ((D->Rhs.A.Flags & LI_DIRECT) != 0) { + if (RhsIsRemovable (D)) { /* Add code for low operand using direct Rhs */ X = NewCodeEntry (OPC, D->Rhs.A.LoadEntry->AM, D->Rhs.A.LoadEntry->Arg, 0, D->OpEntry->LI); @@ -1213,7 +1546,7 @@ static unsigned Opt_a_tosbitwise (StackOpData* D, opc_t OPC) /* Rhs load entries must be removed */ D->Rhs.A.Flags |= LI_REMOVE; - } else if ((D->Lhs.A.Flags & (LI_DIRECT | LI_RELOAD_Y)) == LI_DIRECT) { + } else if (RegIsDirectNonStackLoaded (&D->Lhs.A)) { /* Add code for low operand using direct Lhs */ X = NewCodeEntry (OPC, D->Lhs.A.LoadEntry->AM, D->Lhs.A.LoadEntry->Arg, 0, D->OpEntry->LI); @@ -1244,6 +1577,7 @@ static unsigned Opt_a_tosbitwise (StackOpData* D, opc_t OPC) if ((GetRegInfo (D->Code, D->IP, REG_X) & REG_X) != 0) { /* Replace the high-byte load with 0 for EOR, or just leave it alone */ if (OPC == OP65_EOR) { + /* 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++); D->Rhs.X.Flags |= LI_REMOVE; @@ -1272,9 +1606,7 @@ static unsigned Opt_a_toscmpbool (StackOpData* D, const char* BoolTransformer) D->IP = D->OpIndex + 1; - if (!D->RhsMultiChg && - (D->Rhs.A.Flags & LI_DIRECT) != 0 && - (D->Rhs.A.LoadEntry->Flags & CEF_DONT_REMOVE) == 0) { + if (RhsIsRemovable (D)) { /* cmp */ AddOpLow (D, OP65_CMP, &D->Rhs); @@ -1283,7 +1615,7 @@ static unsigned Opt_a_toscmpbool (StackOpData* D, const char* BoolTransformer) D->Rhs.X.Flags |= LI_REMOVE; D->Rhs.A.Flags |= LI_REMOVE; - } else if ((D->Lhs.A.Flags & LI_DIRECT) != 0) { + } else if (RegIsDirectLoaded (&D->Lhs.A)) { /* If the lhs is direct (but not stack relative), encode compares with lhs, ** effectively reversing the order (which doesn't matter for == and !=). */ @@ -1377,7 +1709,10 @@ static unsigned Opt_a_tosicmp (StackOpData* D) D->IP = D->OpIndex + 1; - if ((D->Rhs.A.Flags & LI_DIRECT) == 0) { + /* ### Note: This should be updated to follow the common + ** if (Non-Stack) { } else if (Direct) { } else { } pattern. + */ + if (!RegIsDirectLoaded (&D->Rhs.A)) { /* RHS src is not directly comparable */ X = NewCodeEntry (OP65_STA, AM65_ZP, D->ZPHi, 0, D->OpEntry->LI); InsertEntry (D, X, D->Rhs.A.ChgIndex + 1); @@ -1484,13 +1819,12 @@ static unsigned Opt_a_tossub (StackOpData* D) { CodeEntry* X; + /* Because of CanUseRemovableRhs */ + CHECK (RhsIsRemovable (D)); /* Inline the sbc */ D->IP = D->OpIndex+1; - /* Must be true because of OP_RHS_LOAD */ - CHECK ((D->Rhs.A.Flags & D->Rhs.X.Flags & LI_DIRECT) != 0); - /* sec */ X = NewCodeEntry (OP65_SEC, AM65_IMP, 0, 0, D->OpEntry->LI); InsertEntry (D, X, D->IP++); @@ -1586,47 +1920,47 @@ static unsigned Opt_a_tosxor (StackOpData* D) /* CAUTION: table must be sorted for bsearch */ static const OptFuncDesc FuncTable[] = { /* BEGIN SORTED.SH */ - { "___bzero", Opt___bzero, REG_NONE, OP_X_ZERO | OP_A_KNOWN }, - { "staspidx", Opt_staspidx, REG_NONE, OP_NONE }, - { "staxspidx", Opt_staxspidx, REG_AX, OP_NONE }, - { "tosaddax", Opt_tosaddax, REG_NONE, OP_NONE }, - { "tosandax", Opt_tosandax, REG_NONE, OP_NONE }, - { "tosaslax", Opt_tosaslax, REG_NONE, OP_NONE }, - { "tosasrax", Opt_tosasrax, REG_NONE, OP_NONE }, - { "toseqax", Opt_toseqax, REG_NONE, OP_LR_INTERCHANGE | OP_RHS_REMOVE_DIRECT }, - { "tosgeax", Opt_tosgeax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosltax", Opt_tosltax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosneax", Opt_tosneax, REG_NONE, OP_LR_INTERCHANGE | OP_RHS_REMOVE_DIRECT }, - { "tosorax", Opt_tosorax, REG_NONE, OP_NONE }, - { "tosshlax", Opt_tosshlax, REG_NONE, OP_NONE }, - { "tosshrax", Opt_tosshrax, REG_NONE, OP_NONE }, - { "tossubax", Opt_tossubax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosugeax", Opt_tosugeax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosugtax", Opt_tosugtax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosuleax", Opt_tosuleax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosultax", Opt_tosultax, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosxorax", Opt_tosxorax, REG_NONE, OP_NONE }, + { "___bzero", Opt___bzero, WithAXlt100CanUseRegVarOrTempZP }, + { "staspidx", Opt_staspidx, CanUseRegVarOrTempZP }, + { "staxspidx", Opt_staxspidx, WithUnusedACanUseRegVarOrTempZP }, + { "tosaddax", Opt_tosaddax, MustHaveTempZP }, + { "tosandax", Opt_tosandax, MustHaveTempZP }, + { "tosaslax", Opt_tosaslax, MustHaveTempZP }, + { "tosasrax", Opt_tosasrax, MustHaveTempZP }, + { "toseqax", Opt_toseqax, CanUseDirectWithRemovableRhsAndTempZP }, + { "tosgeax", Opt_tosgeax, CanUseRemovableRhsWithTempZP }, + { "tosltax", Opt_tosltax, CanUseRemovableRhsWithTempZP }, + { "tosneax", Opt_tosneax, CanUseDirectWithRemovableRhsAndTempZP }, + { "tosorax", Opt_tosorax, MustHaveTempZP }, + { "tosshlax", Opt_tosshlax, MustHaveTempZP }, + { "tosshrax", Opt_tosshrax, MustHaveTempZP }, + { "tossubax", Opt_tossubax, CanUseRemovableRhsWithTempZP }, + { "tosugeax", Opt_tosugeax, CanUseRemovableRhsWithTempZP }, + { "tosugtax", Opt_tosugtax, CanUseRemovableRhsWithTempZP }, + { "tosuleax", Opt_tosuleax, CanUseRemovableRhsWithTempZP }, + { "tosultax", Opt_tosultax, CanUseRemovableRhsWithTempZP }, + { "tosxorax", Opt_tosxorax, MustHaveTempZP }, /* END SORTED.SH */ }; /* CAUTION: table must be sorted for bsearch */ static const OptFuncDesc FuncRegATable[] = { /* BEGIN SORTED.SH */ - { "tosandax", Opt_a_tosand, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "toseqax", Opt_a_toseq, REG_NONE, OP_NONE }, - { "tosgeax", Opt_a_tosuge, REG_NONE, OP_NONE }, - { "tosgtax", Opt_a_tosugt, REG_NONE, OP_NONE }, - { "tosicmp", Opt_a_tosicmp, REG_NONE, OP_NONE }, - { "tosleax", Opt_a_tosule, REG_NONE, OP_NONE }, - { "tosltax", Opt_a_tosult, REG_NONE, OP_NONE }, - { "tosneax", Opt_a_tosne, REG_NONE, OP_NONE }, - { "tosorax", Opt_a_tosor, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tossubax", Opt_a_tossub, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, - { "tosugeax", Opt_a_tosuge, REG_NONE, OP_NONE }, - { "tosugtax", Opt_a_tosugt, REG_NONE, OP_NONE }, - { "tosuleax", Opt_a_tosule, REG_NONE, OP_NONE }, - { "tosultax", Opt_a_tosult, REG_NONE, OP_NONE }, - { "tosxorax", Opt_a_tosxor, REG_NONE, OP_RHS_REMOVE_DIRECT | OP_RHS_LOAD_DIRECT }, + { "tosandax", Opt_a_tosand, WithSameXCanUseRemovableRhsWithTempZP }, + { "toseqax", Opt_a_toseq, WithSameXMustHaveTempZP }, + { "tosgeax", Opt_a_tosuge, WithSameXMustHaveTempZP }, + { "tosgtax", Opt_a_tosugt, WithSameXMustHaveTempZP }, + { "tosicmp", Opt_a_tosicmp, WithSameXMustHaveTempZP }, + { "tosleax", Opt_a_tosule, WithSameXMustHaveTempZP }, + { "tosltax", Opt_a_tosult, WithSameXMustHaveTempZP }, + { "tosneax", Opt_a_tosne, WithSameXMustHaveTempZP }, + { "tosorax", Opt_a_tosor, WithSameXCanUseRemovableRhsWithTempZP }, + { "tossubax", Opt_a_tossub, WithSameXCanUseRemovableRhsWithTempZP }, + { "tosugeax", Opt_a_tosuge, WithSameXMustHaveTempZP }, + { "tosugtax", Opt_a_tosugt, WithSameXMustHaveTempZP }, + { "tosuleax", Opt_a_tosule, WithSameXMustHaveTempZP }, + { "tosultax", Opt_a_tosult, WithSameXMustHaveTempZP }, + { "tosxorax", Opt_a_tosxor, WithSameXCanUseRemovableRhsWithTempZP }, /* END SORTED.SH */ }; @@ -1654,283 +1988,23 @@ static const OptFuncDesc* FindFunc (const OptFuncDesc FuncTable[], size_t Count, static int PreCondOk (StackOpData* D) /* Check if the preconditions for a call to the optimizer subfunction are -** satisfied. As a side effect, this function will also choose the zero page -** register to use for temporary storage. +** satisfied. */ { - LoadInfo* Lhs; - LoadInfo* Rhs; - LoadRegInfo* LhsLo; - LoadRegInfo* LhsHi; - LoadRegInfo* RhsLo; - LoadRegInfo* RhsHi; - short LoVal; - short HiVal; - int I; - int Passed = 0; - - /* Check the flags */ const OptFuncDesc* Desc = D->OptFunc; - unsigned UnusedRegs = Desc->UnusedRegs; - if (UnusedRegs != REG_NONE && - (GetRegInfo (D->Code, D->OpIndex+1, UnusedRegs) & UnusedRegs) != 0) { - /* Cannot optimize */ - return 0; + + /* Common to all: must have a basic block */ + if (!CS_IsBasicBlock (D->Code, D->PushIndex, D->OpIndex)) { + return 0; /* Fail */ } - Passed = 0; - LoVal = D->OpEntry->RI->In.RegA; - HiVal = D->OpEntry->RI->In.RegX; - /* Check normally first, then interchange A/X and check again if necessary */ - for (I = (Desc->Flags & OP_AX_INTERCHANGE ? 0 : 1); !Passed && I < 2; ++I) { - - do { - if ((Desc->Flags & OP_A_KNOWN) != 0 && - RegValIsUnknown (LoVal)) { - /* Cannot optimize */ - break; - } - if ((Desc->Flags & OP_X_ZERO) != 0 && - HiVal != 0) { - /* Cannot optimize */ - break; - } - Passed = 1; - } while (0); - - /* Interchange A/X */ - LoVal = D->OpEntry->RI->In.RegX; - HiVal = D->OpEntry->RI->In.RegA; - } - if (!Passed) { - /* Cannot optimize */ - return 0; + /* Call the pre-cond predicate if one was provided. */ + if (Desc->PreCond == 0 || Desc->PreCond (D)) { + /* Preconditions passed (even if they were null). */ + return 1; } - Passed = 0; - Lhs = &D->Lhs; - Rhs = &D->Rhs; - /* Check normally first, then interchange LHS/RHS and check again if necessary */ - for (I = (Desc->Flags & OP_LR_INTERCHANGE ? 0 : 1); !Passed && I < 2; ++I) { - - do { - LhsLo = &Lhs->A; - LhsHi = &Lhs->X; - RhsLo = &Rhs->A; - RhsHi = &Rhs->X; - /* Currently we have only LHS/RHS checks with identical requirements for A/X, - ** so we don't need to check twice for now. - */ - - if ((Desc->Flags & OP_LHS_LOAD) != 0) { - if ((LhsLo->Flags & LhsHi->Flags & LI_LOAD_INSN) == 0) { - /* Cannot optimize */ - break; - } else if ((Desc->Flags & OP_LHS_LOAD_DIRECT) != 0) { - if ((LhsLo->Flags & LhsHi->Flags & LI_DIRECT) == 0) { - /* Cannot optimize */ - break; - } - } - } - if ((Desc->Flags & OP_RHS_LOAD) != 0) { - if ((RhsLo->Flags & RhsHi->Flags & LI_LOAD_INSN) == 0) { - /* Cannot optimize */ - break; - } else if ((Desc->Flags & OP_RHS_LOAD_DIRECT) != 0) { - if ((RhsLo->Flags & RhsHi->Flags & LI_DIRECT) == 0) { - /* Cannot optimize */ - break; - } - } - } - if ((Desc->Flags & OP_LHS_REMOVE) != 0) { - /* Check if the load entries cannot be removed */ - if ((LhsLo->LoadEntry != 0 && (LhsLo->LoadEntry->Flags & CEF_DONT_REMOVE) != 0) || - (LhsHi->LoadEntry != 0 && (LhsHi->LoadEntry->Flags & CEF_DONT_REMOVE) != 0)) { - if ((Desc->Flags & OP_LHS_REMOVE_DIRECT) != 0) { - /* Cannot optimize */ - break; - } - } - } - if ((Desc->Flags & OP_RHS_REMOVE) != 0) { - if ((RhsLo->LoadEntry != 0 && (RhsLo->LoadEntry->Flags & CEF_DONT_REMOVE) != 0) || - (RhsHi->LoadEntry != 0 && (RhsHi->LoadEntry->Flags & CEF_DONT_REMOVE) != 0)) { - if ((Desc->Flags & OP_RHS_REMOVE_DIRECT) != 0) { - /* Cannot optimize */ - break; - } - } - } - if (D->RhsMultiChg && (Desc->Flags & OP_RHS_REMOVE_DIRECT) != 0) { - /* Cannot optimize */ - break; - } - Passed = 1; - } while (0); - - /* Interchange LHS/RHS for next round */ - Lhs = &D->Rhs; - Rhs = &D->Lhs; - } - if (!Passed) { - /* Cannot optimize */ - return 0; - } - - /* Check if an unused temp ZP location is available */ - if (!HaveUnusedTempZPLoc (D)) { - /* No registers available */ - return 0; - } - - /* Determine if we have a basic block */ - return CS_IsBasicBlock (D->Code, D->PushIndex, D->OpIndex); -} - - - -static int RegAPreCondOk (StackOpData* D) -/* Check if the preconditions for a call to the RegA-only optimizer subfunction -** are satisfied. As a side effect, this function will also choose the zero page -** register to use for temporary storage. -*/ -{ - LoadInfo* Lhs; - LoadInfo* Rhs; - LoadRegInfo* LhsLo; - LoadRegInfo* RhsLo; - short LhsLoVal, LhsHiVal; - short RhsLoVal, RhsHiVal; - int I; - int Passed = 0; - - /* Check the flags */ - const OptFuncDesc* Desc = D->OptFunc; - unsigned UnusedRegs = Desc->UnusedRegs; - if (UnusedRegs != REG_NONE && - (GetRegInfo (D->Code, D->OpIndex+1, UnusedRegs) & UnusedRegs) != 0) { - /* Cannot optimize */ - return 0; - } - - Passed = 0; - LhsLoVal = D->PushEntry->RI->In.RegA; - LhsHiVal = D->PushEntry->RI->In.RegX; - RhsLoVal = D->OpEntry->RI->In.RegA; - RhsHiVal = D->OpEntry->RI->In.RegX; - /* Check normally first, then interchange A/X and check again if necessary */ - for (I = (Desc->Flags & OP_AX_INTERCHANGE ? 0 : 1); !Passed && I < 2; ++I) { - - do { - if (LhsHiVal != RhsHiVal) { - /* Cannot optimize */ - break; - } - if ((Desc->Flags & OP_A_KNOWN) != 0 && - RegValIsUnknown (LhsLoVal)) { - /* Cannot optimize */ - break; - } - if ((Desc->Flags & OP_X_ZERO) != 0 && - LhsHiVal != 0) { - /* Cannot optimize */ - break; - } - Passed = 1; - } while (0); - - /* Suppress warning about unused assignment in GCC */ - (void)RhsLoVal; - - /* Interchange A/X */ - LhsLoVal = D->PushEntry->RI->In.RegX; - LhsHiVal = D->PushEntry->RI->In.RegA; - RhsLoVal = D->OpEntry->RI->In.RegX; - RhsHiVal = D->OpEntry->RI->In.RegA; - } - if (!Passed) { - /* Cannot optimize */ - return 0; - } - - Passed = 0; - Lhs = &D->Lhs; - Rhs = &D->Rhs; - /* Check normally first, then interchange LHS/RHS and check again if necessary */ - for (I = (Desc->Flags & OP_LR_INTERCHANGE ? 0 : 1); !Passed && I < 2; ++I) { - - do { - LhsLo = &Lhs->A; - RhsLo = &Rhs->A; - /* Currently we have only LHS/RHS checks with identical requirements for A/X, - ** so we don't need to check twice for now. - */ - - if ((Desc->Flags & OP_LHS_LOAD) != 0) { - if ((LhsLo->Flags & LI_LOAD_INSN) == 0) { - /* Cannot optimize */ - break; - } else if ((Desc->Flags & OP_LHS_LOAD_DIRECT) != 0) { - if ((LhsLo->Flags & LI_DIRECT) == 0) { - /* Cannot optimize */ - break; - } - } - } - if ((Desc->Flags & OP_RHS_LOAD) != 0) { - if ((RhsLo->Flags & LI_LOAD_INSN) == 0) { - /* Cannot optimize */ - break; - } else if ((Desc->Flags & OP_RHS_LOAD_DIRECT) != 0) { - if ((RhsLo->Flags & LI_DIRECT) == 0) { - /* Cannot optimize */ - break; - } - } - } - if ((Desc->Flags & OP_LHS_REMOVE) != 0) { - /* Check if the load entries cannot be removed */ - if ((LhsLo->LoadEntry != 0 && (LhsLo->LoadEntry->Flags & CEF_DONT_REMOVE) != 0)) { - if ((Desc->Flags & OP_LHS_REMOVE_DIRECT) != 0) { - /* Cannot optimize */ - break; - } - } - } - if ((Desc->Flags & OP_RHS_REMOVE) != 0) { - if ((RhsLo->LoadEntry != 0 && (RhsLo->LoadEntry->Flags & CEF_DONT_REMOVE) != 0)) { - if ((Desc->Flags & OP_RHS_REMOVE_DIRECT) != 0) { - /* Cannot optimize */ - break; - } - } - } - if (D->RhsMultiChg && (Desc->Flags & OP_RHS_REMOVE_DIRECT) != 0) { - /* Cannot optimize */ - break; - } - Passed = 1; - } while (0); - - /* Interchange LHS/RHS for next round */ - Lhs = &D->Rhs; - Rhs = &D->Lhs; - } - if (!Passed) { - /* Cannot optimize */ - return 0; - } - - /* Check if an unused temp ZP location is available */ - if (!HaveUnusedTempZPLoc (D)) { - /* No registers available */ - return 0; - } - - /* Determine if we have a basic block */ - return CS_IsBasicBlock (D->Code, D->PushIndex, D->OpIndex); + return 0; /* Preconditions failed */ } @@ -2051,6 +2125,8 @@ unsigned OptStackOps (CodeSeg* S) ** checked. There is no fallback to the full A/X subopts. ** When the A-only preconditions fail, good A/X cases are ** left unoptimized. + ** The FuncTables should be merged into a single precondition + ** system. */ if (SameRegXValueAtOp (&Data, E)) { Data.OptFunc = FindFunc (FuncRegATable, FUNC_COUNT (FuncRegATable), E->Arg); @@ -2174,10 +2250,7 @@ unsigned OptStackOps (CodeSeg* S) ** load tracking but at least a/x has probably lost between ** pushax and here and will be tracked again when restarting. */ - /* ### Note: PreCondOk() and RegAPreCondOk() should be merged - ** into a single precondition system. - */ - if (IsRegAOptFunc ? !RegAPreCondOk (&Data) : !PreCondOk (&Data)) { + if (!PreCondOk (&Data)) { /* Unflag entries that can't be removed */ ResetDontRemoveEntryFlags (&Data); I = Data.PushIndex;