diff --git a/src/cc65/codeent.c b/src/cc65/codeent.c index 928815934d..a870ee981d 100644 --- a/src/cc65/codeent.c +++ b/src/cc65/codeent.c @@ -2445,6 +2445,24 @@ static char* RegContentDesc (const RegContents* RC, char* Buf) sprintf (B, "Y:%02X ", RC->RegY); } B += 5; + if (RegValIsUnknown (RC->Ptr1Lo)) { + strcpy (B, "P1L:XX "); + } else { + sprintf (B, "P1L:%02X ", RC->Ptr1Lo); + } + B += 7; + if (RegValIsUnknown (RC->Ptr1Hi)) { + strcpy (B, "P1H:XX "); + } else { + sprintf (B, "P1H:%02X ", RC->Ptr1Hi); + } + B += 7; + if (RegValIsUnknown (RC->Tmp1)) { + strcpy (B, "T1:XX "); + } else { + sprintf (B, "T1:%02X ", RC->Tmp1); + } + B += 6; if (PStatesAreUnknown (RC->PFlags, PSTATE_C)) { strcpy (B, "~"); } else { diff --git a/src/cc65/codeinfo.c b/src/cc65/codeinfo.c index 427bfc52b2..435794613c 100644 --- a/src/cc65/codeinfo.c +++ b/src/cc65/codeinfo.c @@ -560,11 +560,8 @@ fncls_t GetFuncInfo (const char* Name, unsigned int* Use, unsigned int* Chg) *Use = REG_NONE; } - /* Will destroy all registers */ - *Chg = REG_ALL; - - /* and will destroy all processor flags */ - *Chg |= PSTATE_ALL; + /* Will destroy all registers and processor flags */ + *Chg = (REG_ALL | PSTATE_ALL); /* Done */ return FNCLS_GLOBAL; @@ -577,8 +574,7 @@ fncls_t GetFuncInfo (const char* Name, unsigned int* Use, unsigned int* Chg) ** are used mostly in inline assembly anyway. */ *Use = REG_ALL; - *Chg = REG_ALL; - *Chg |= PSTATE_ALL; + *Chg = (REG_ALL | PSTATE_ALL); return FNCLS_NUMERIC; } else { @@ -605,8 +601,7 @@ fncls_t GetFuncInfo (const char* Name, unsigned int* Use, unsigned int* Chg) fprintf (stderr, "No info about internal function '%s'\n", Name); } *Use = REG_ALL; - *Chg = REG_ALL; - *Chg |= PSTATE_ALL; + *Chg = (REG_ALL | PSTATE_ALL); } return FNCLS_BUILTIN; } @@ -615,8 +610,7 @@ fncls_t GetFuncInfo (const char* Name, unsigned int* Use, unsigned int* Chg) ** registers and processor flags are changed */ *Use = REG_EAXY; - *Chg = REG_ALL; - *Chg |= PSTATE_ALL; + *Chg = (REG_ALL | PSTATE_ALL); return FNCLS_UNKNOWN; } @@ -899,6 +893,14 @@ int RegEAXUsed (struct CodeSeg* S, unsigned Index) +int LoadFlagsUsed (struct CodeSeg* S, unsigned Index) +/* Check if one of the flags set by a register load (Z and N) are used. */ +{ + return (GetRegInfo (S, Index, PSTATE_ZN) & PSTATE_ZN) != 0; +} + + + unsigned GetKnownReg (unsigned Use, const RegContents* RC) /* Return the register or zero page location from the set in Use, thats ** contents are known. If Use does not contain any register, or if the diff --git a/src/cc65/codeinfo.h b/src/cc65/codeinfo.h index 14ef54d8f3..c57908dad4 100644 --- a/src/cc65/codeinfo.h +++ b/src/cc65/codeinfo.h @@ -201,6 +201,9 @@ int RegAXUsed (struct CodeSeg* S, unsigned Index); int RegEAXUsed (struct CodeSeg* S, unsigned Index); /* Check if any of the four bytes in EAX are used. */ +int LoadFlagsUsed (struct CodeSeg* S, unsigned Index); +/* Check if one of the flags set by a register load (Z and N) are used. */ + unsigned GetKnownReg (unsigned Use, const struct RegContents* RC); /* Return the register or zero page location from the set in Use, thats ** contents are known. If Use does not contain any register, or if the diff --git a/src/cc65/codeopt.c b/src/cc65/codeopt.c index c9c1592bc0..a716ad4311 100644 --- a/src/cc65/codeopt.c +++ b/src/cc65/codeopt.c @@ -117,6 +117,7 @@ static OptFunc DOptBNegAX1 = { OptBNegAX1, "OptBNegAX1", 100, 0, static OptFunc DOptBNegAX2 = { OptBNegAX2, "OptBNegAX2", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptBNegAX3 = { OptBNegAX3, "OptBNegAX3", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptBNegAX4 = { OptBNegAX4, "OptBNegAX4", 100, 0, 0, 0, 0, 0 }; +static OptFunc DOptBinOps = { OptBinOps, "OptBinOps", 0, 0, 0, 0, 0, 0 }; static OptFunc DOptBoolCmp = { OptBoolCmp, "OptBoolCmp", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptBoolTrans = { OptBoolTrans, "OptBoolTrans", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptBoolUnary1 = { OptBoolUnary1, "OptBoolUnary1", 40, 0, 0, 0, 0, 0 }; @@ -179,6 +180,7 @@ static OptFunc DOptPush1 = { OptPush1, "OptPush1", 65, 0, static OptFunc DOptPush2 = { OptPush2, "OptPush2", 50, 0, 0, 0, 0, 0 }; static OptFunc DOptPushPop1 = { OptPushPop1, "OptPushPop1", 0, 0, 0, 0, 0, 0 }; static OptFunc DOptPushPop2 = { OptPushPop2, "OptPushPop2", 0, 0, 0, 0, 0, 0 }; +static OptFunc DOptPushPop3 = { OptPushPop3, "OptPushPop3", 0, 0, 0, 0, 0, 0 }; static OptFunc DOptRTS = { OptRTS, "OptRTS", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptRTSJumps1 = { OptRTSJumps1, "OptRTSJumps1", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptRTSJumps2 = { OptRTSJumps2, "OptRTSJumps2", 100, 0, 0, 0, 0, 0 }; @@ -231,6 +233,7 @@ static OptFunc* OptFuncs[] = { &DOptBNegAX2, &DOptBNegAX3, &DOptBNegAX4, + &DOptBinOps, &DOptBoolCmp, &DOptBoolTrans, &DOptBoolUnary1, @@ -292,6 +295,8 @@ static OptFunc* OptFuncs[] = { &DOptPush1, &DOptPush2, &DOptPushPop1, + &DOptPushPop2, + &DOptPushPop3, &DOptRTS, &DOptRTSJumps1, &DOptRTSJumps2, @@ -740,9 +745,11 @@ static unsigned RunOptGroup3 (CodeSeg* S) C += RunOptFunc (S, &DOptStore5, 1); C += RunOptFunc (S, &DOptPushPop1, 1); C += RunOptFunc (S, &DOptPushPop2, 1); + C += RunOptFunc (S, &DOptPushPop3, 1); C += RunOptFunc (S, &DOptPrecalc, 1); C += RunOptFunc (S, &DOptShiftBack, 1); C += RunOptFunc (S, &DOptSignExtended, 1); + C += RunOptFunc (S, &DOptBinOps, 1); Changes += C; @@ -849,6 +856,7 @@ static unsigned RunOptGroup7 (CodeSeg* S) Changes += RunOptFunc (S, &DOptUnusedStores, 1); Changes += RunOptFunc (S, &DOptJumpTarget1, 5); Changes += RunOptFunc (S, &DOptStore5, 1); + Changes += RunOptFunc (S, &DOptTransfers1, 1); } C = RunOptFunc (S, &DOptSize2, 1); diff --git a/src/cc65/coptind.c b/src/cc65/coptind.c index 9e9985c100..52c47481e6 100644 --- a/src/cc65/coptind.c +++ b/src/cc65/coptind.c @@ -151,7 +151,9 @@ static short ZPRegVal (unsigned short Use, const RegContents* RC) unsigned OptUnusedLoads (CodeSeg* S) -/* Remove loads of registers where the value loaded is not used later. */ +/* Remove loads of or operations with registers where the value loaded or +** produced is not used later. +*/ { unsigned Changes = 0; @@ -164,17 +166,24 @@ unsigned OptUnusedLoads (CodeSeg* S) /* Get next entry */ CodeEntry* E = CS_GetEntry (S, I); - /* Check if it's a register load or transfer insn */ - if ((E->Info & (OF_LOAD | OF_XFR | OF_REG_INCDEC)) != 0 && - (N = CS_GetNextEntry (S, I)) != 0 && - !CE_UseLoadFlags (N)) { + /* Check if this is one of the instruction we can operate on */ + int IsOp = (E->Info & (OF_LOAD | OF_XFR | OF_REG_INCDEC)) != 0 || + E->OPC == OP65_AND || + E->OPC == OP65_EOR || + E->OPC == OP65_ORA; + + /* Check for the necessary preconditions */ + if (IsOp && (N = CS_GetNextEntry (S, I)) != 0 && !LoadFlagsUsed (S, I+1)) { /* Check which sort of load or transfer it is */ unsigned R; switch (E->OPC) { + case OP65_AND: case OP65_DEA: + case OP65_EOR: case OP65_INA: case OP65_LDA: + case OP65_ORA: case OP65_TXA: case OP65_TYA: R = REG_A; break; case OP65_DEX: @@ -1342,6 +1351,97 @@ unsigned OptPushPop2 (CodeSeg* S) +unsigned OptPushPop3 (CodeSeg* S) +/* Remove a pha/pla sequence where the contents of A are known */ +{ + unsigned Changes = 0; + unsigned Pha = 0; /* Index of PHA insn */ + unsigned Pla = 0; /* Index of PLA insn */ + CodeEntry* PhaEntry = 0; /* Pointer to PHA */ + + enum { + Searching, + FoundPha, + FoundPla + } State = Searching; + + /* Walk over the entries. Look for a PHA instruction where the contents + ** of A is known followed by a PLA later. + */ + unsigned I = 0; + while (I < CS_GetEntryCount (S)) { + + /* Get next entry */ + CodeEntry* E = CS_GetEntry (S, I); + + /* Get the input registers */ + const RegInfo* RI = E->RI; + + + const char* Arg; + CodeEntry* X; + switch (State) { + + case Searching: + if (E->OPC == OP65_PHA && RegValIsKnown (RI->In.RegA)) { + /* Found start of sequence */ + Pha = I; + PhaEntry = E; + State = FoundPha; + } + break; + + case FoundPha: + /* Check for several things that abort the sequence: + ** - End of the basic block + ** - Another PHA or any other stack manipulating instruction + ** If we find something that aborts the sequence, start over + ** searching for the next PHA. + */ + if (CE_HasLabel (E)) { + /* Switch back to searching at this instruction */ + State = Searching; + continue; + } + if (E->OPC == OP65_PHA) { + /* Start over at this instruction */ + State = Searching; + continue; + } + if (E->OPC == OP65_PHP || E->OPC == OP65_PLP || E->OPC == OP65_TXS) { + /* Start over at the next instruction */ + State = Searching; + } else if (E->OPC == OP65_PLA) { + /* Switch state. This will also switch to the next insn + ** which is ok. + */ + Pla = I; + State = FoundPla; + } + break; + + case FoundPla: + /* We found the sequence we were looking for. Replace it. */ + Arg = MakeHexArg (PhaEntry->RI->In.RegA); + X = NewCodeEntry (OP65_LDA, AM65_IMM, Arg, 0, E->LI); + CS_InsertEntry (S, X, Pla + 1); + CS_DelEntry (S, Pla); + CS_DelEntry (S, Pha); + ++Changes; + State = Searching; + break; + } + + /* Next entry */ + ++I; + } + + /* Return the number of changes made */ + return Changes; +} + + + unsigned OptPrecalc (CodeSeg* S) /* Replace immediate operations with the accu where the current contents are ** known by a load of the final value. diff --git a/src/cc65/coptind.h b/src/cc65/coptind.h index 6d39ee1f8d..3493543a46 100644 --- a/src/cc65/coptind.h +++ b/src/cc65/coptind.h @@ -50,7 +50,9 @@ unsigned OptUnusedLoads (CodeSeg* S); -/* Remove loads of registers where the value loaded is not used later. */ +/* Remove loads of or operations with registers where the value loaded or +** produced is not used later. +*/ unsigned OptUnusedStores (CodeSeg* S); /* Remove stores into zero page registers that aren't used later */ @@ -91,6 +93,9 @@ unsigned OptPushPop1 (CodeSeg* S); unsigned OptPushPop2 (CodeSeg* S); /* Remove a PHP/PLP sequence were no processor flags changed inside */ +unsigned OptPushPop3 (CodeSeg* S); +/* Remove a pha/pla sequence where the contents of A are known */ + unsigned OptPrecalc (CodeSeg* S); /* Replace immediate operations with the accu where the current contents are ** known by a load of the final value. diff --git a/src/cc65/coptmisc.c b/src/cc65/coptmisc.c index 523fbf17cb..e48d469a1c 100644 --- a/src/cc65/coptmisc.c +++ b/src/cc65/coptmisc.c @@ -733,3 +733,172 @@ unsigned OptLoad2 (CodeSeg* S) /* Return the number of changes made */ return Changes; } + + + +unsigned OptBinOps (CodeSeg* S) +/* Search for an AND/EOR/ORA where the value of A or the operand is known and +** replace it by something simpler. +*/ +{ + unsigned Changes = 0; + + /* Walk over the entries */ + unsigned I = 0; + while (I < CS_GetEntryCount (S)) { + + /* Get next entry */ + CodeEntry* E = CS_GetEntry (S, I); + + /* Get a pointer to the input registers of the insn */ + const RegContents* In = &E->RI->In; + + /* Check for AND/EOR/ORA and a known value in A */ + int Delete = 0; + CodeEntry* X = 0; + switch (E->OPC) { + + case OP65_AND: + if (In->RegA == 0x00) { + /* Zero AND anything gives zero. The instruction can be + ** replaced by an immediate load of zero. + */ + X = NewCodeEntry (OP65_LDA, AM65_IMM, "$00", 0, E->LI); + } else if (In->RegA == 0xFF) { + /* 0xFF AND anything equals the operand. The instruction + ** can be replaced by a simple load of the operand. + */ + X = NewCodeEntry (OP65_LDA, E->AM, E->Arg, 0, E->LI); + } else if (E->AM == AM65_ZP) { + short Operand = -1; + switch (GetKnownReg (E->Use & REG_ZP, In)) { + case REG_TMP1: Operand = In->Tmp1; break; + case REG_PTR1_LO: Operand = In->Ptr1Lo; break; + case REG_PTR1_HI: Operand = In->Ptr1Hi; break; + case REG_SREG_LO: Operand = In->SRegLo; break; + case REG_SREG_HI: Operand = In->SRegHi; break; + } + if (Operand == 0x00) { + /* AND with zero gives zero. The instruction can be + ** replaced by an immediate load of zero. + */ + X = NewCodeEntry (OP65_LDA, AM65_IMM, "$00", 0, E->LI); + } else if (Operand == 0xFF) { + /* AND with 0xFF is a no-op besides setting the flags. + ** The instruction can be removed if the flags aren't + ** used later. + */ + if (!LoadFlagsUsed (S, I+1)) { + Delete = 1; + } + } else if (Operand >= 0) { + /* The instruction can be replaced by an immediate + ** AND. + */ + const char* Arg = MakeHexArg (Operand); + X = NewCodeEntry (OP65_AND, AM65_IMM, Arg, 0, E->LI); + } + } + break; + + case OP65_EOR: + if (In->RegA == 0x00) { + /* Zero EOR anything equals the operand. The instruction + ** can be replaced by a simple load. + */ + X = NewCodeEntry (OP65_LDA, E->AM, E->Arg, 0, E->LI); + } else if (E->AM == AM65_ZP) { + short Operand = -1; + switch (GetKnownReg (E->Use & REG_ZP, In)) { + case REG_TMP1: Operand = In->Tmp1; break; + case REG_PTR1_LO: Operand = In->Ptr1Lo; break; + case REG_PTR1_HI: Operand = In->Ptr1Hi; break; + case REG_SREG_LO: Operand = In->SRegLo; break; + case REG_SREG_HI: Operand = In->SRegHi; break; + } + if (Operand == 0x00) { + /* EOR with 0x00 is a no-op besides setting the flags. + ** The instruction can be removed if the flags aren't + ** used later. + */ + if (!LoadFlagsUsed (S, I+1)) { + Delete = 1; + } + } else if (Operand >= 0) { + /* The instruction can be replaced by an immediate + ** EOR. + */ + const char* Arg = MakeHexArg (Operand); + X = NewCodeEntry (OP65_EOR, AM65_IMM, Arg, 0, E->LI); + } + } + break; + + case OP65_ORA: + if (In->RegA == 0x00) { + /* ORA with 0x00 is a no-op. The instruction can be + ** replaced by a simple load. + */ + X = NewCodeEntry (OP65_LDA, E->AM, E->Arg, 0, E->LI); + } else if (In->RegA == 0xFF) { + /* ORA with 0xFF gives 0xFF. The instruction can be replaced + ** by an immediate load of 0xFF. + */ + X = NewCodeEntry (OP65_LDA, AM65_IMM, "$FF", 0, E->LI); + } else if (E->AM == AM65_ZP) { + short Operand = -1; + switch (GetKnownReg (E->Use & REG_ZP, In)) { + case REG_TMP1: Operand = In->Tmp1; break; + case REG_PTR1_LO: Operand = In->Ptr1Lo; break; + case REG_PTR1_HI: Operand = In->Ptr1Hi; break; + case REG_SREG_LO: Operand = In->SRegLo; break; + case REG_SREG_HI: Operand = In->SRegHi; break; + } + if (Operand == 0x00) { + /* ORA with 0x00 is a no-op besides setting the flags. + ** The instruction can be removed if the flags aren't + ** used later. + */ + if (!LoadFlagsUsed (S, I+1)) { + Delete = 1; + } + } else if (Operand == 0xFF) { + /* ORA with 0xFF results in 0xFF. The instruction can + ** be replaced by a simple load. + */ + X = NewCodeEntry (OP65_LDA, AM65_IMM, "$FF", 0, E->LI); + } else if (Operand >= 0) { + /* The instruction can be replaced by an immediate + ** ORA. + */ + const char* Arg = MakeHexArg (Operand); + X = NewCodeEntry (OP65_ORA, AM65_IMM, Arg, 0, E->LI); + } + } + break; + + default: + break; + + } + + /* If we must delete the instruction, do that. If we have a replacement + ** entry, place it and remove the old one. + */ + if (X) { + CS_InsertEntry (S, X, I+1); + Delete = 1; + } + if (Delete) { + CS_DelEntry (S, I); + ++Changes; + } + + /* Next entry */ + ++I; + + } + + /* Return the number of changes made */ + return Changes; +} diff --git a/src/cc65/coptmisc.h b/src/cc65/coptmisc.h index 89242351ce..418b61e943 100644 --- a/src/cc65/coptmisc.h +++ b/src/cc65/coptmisc.h @@ -107,6 +107,12 @@ unsigned OptLoad1 (CodeSeg* S); unsigned OptLoad2 (CodeSeg* S); /* Replace calls to ldaxysp by inline code */ +unsigned OptBinOps (CodeSeg* S); +/* Search for an AND/EOR/ORA where the value of A or the operand is known and +** replace it by something simpler. +*/ + + /* End of coptmisc.h */ diff --git a/test/val/and-eor-ora-optimizations.c b/test/val/and-eor-ora-optimizations.c new file mode 100644 index 0000000000..6b5aba68c5 --- /dev/null +++ b/test/val/and-eor-ora-optimizations.c @@ -0,0 +1,86 @@ +/* Test some new optimization passes */ + +#include + +static unsigned failures = 0; + +int func0() +{ + unsigned a = 0x1234, b = 0x55AA; + return (a & 0x00FF) & (b & 0xFF00); +} + +int func1() +{ + unsigned a = 0x1234, b = 0x55AA; + return (0x00FF & a) & (0xFF00 & b); +} + +int func2() +{ + unsigned a = 0x1234, b = 0x55AA; + return (a | 0x00FF) & (b | 0xFF00); +} + +int func3() +{ + unsigned a = 0x1234, b = 0x55AA; + return (0x00FF | a) & (0xFF00 | b); +} + +int func4() +{ + unsigned a = 0x1234, b = 0x55AA; + return (a | 0x00FF) | (b | 0xFF00); +} + +int func5() +{ + unsigned a = 0x1234, b = 0x55AA; + return (0x00FF | a) | (0xFF00 | b); +} + +int func6() +{ + unsigned a = 0x1234, b = 0x55AA; + return (a ^ 0x00FF) & (b & 0xFF00); +} + +int func7() +{ + unsigned a = 0x1234, b = 0x55AA; + return (0x00FF ^ a) & (0xFF00 & b); +} + +int func8() +{ + unsigned a = 0x1234, b = 0x55AA; + return (a | 0x00FF) | (b ^ 0xFF00); +} + +int func9() +{ + unsigned a = 0x1234, b = 0x55AA; + return (0x00FF | a) | (0xFF00 ^ b); +} + +void onetest(unsigned count, int (*f1)(void), int (*f2)(void), int result) +{ + int r1 = f1(); + int r2 = f2(); + if (r1 != result || r2 != result) { + printf("Test %u failed! Expected 0x%04X but got 0x%04X/0x%04X\n", + count, result, r1, r2); + ++failures; + } +} + +int main() +{ + onetest(1, func0, func1, 0x0000); + onetest(2, func2, func3, 0x12AA); + onetest(3, func4, func5, 0xFFFF); + onetest(4, func6, func7, 0x1000); + onetest(5, func8, func9, 0xBAFF); + return failures; +}