mirror of
https://github.com/cc65/cc65.git
synced 2025-01-22 06:30:38 +00:00
Added/improved the optimizations:
* Added a new pass that optimizes PHA/PLA sequences * Added a new pass that optimizes AND/EOR/ORA when an operand is known * Added a run of an existing pass at later stages to remove code that otherwise goes unchanged. * Handle binary operations in OptUnusedLoads in addition to real loads.
This commit is contained in:
parent
7260c10062
commit
6e18e0880a
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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 && !CE_UseLoadFlags (N)) {
|
||||
|
||||
/* 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.
|
||||
|
@ -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.
|
||||
|
@ -733,3 +733,175 @@ 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.
|
||||
*/
|
||||
CodeEntry* N = CS_GetNextEntry (S, I);
|
||||
if (N && !CE_UseLoadFlags (N)) {
|
||||
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.
|
||||
*/
|
||||
CodeEntry* N = CS_GetNextEntry (S, I);
|
||||
if (N && !CE_UseLoadFlags (N)) {
|
||||
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.
|
||||
*/
|
||||
CodeEntry* N = CS_GetNextEntry (S, I);
|
||||
if (N && !CE_UseLoadFlags (N)) {
|
||||
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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user