Files
cc65/src/cc65/codeopt.c
cuz b4010bae8b Fixed several bugs
git-svn-id: svn://svn.cc65.org/cc65/trunk@832 b7a2c559-68d2-44c3-8de9-860c34a00d81
2001-07-30 19:33:52 +00:00

1883 lines
45 KiB
C

/*****************************************************************************/
/* */
/* codeopt.c */
/* */
/* Optimizer subroutines */
/* */
/* */
/* */
/* (C) 2001 Ullrich von Bassewitz */
/* Wacholderweg 14 */
/* D-70597 Stuttgart */
/* EMail: uz@cc65.org */
/* */
/* */
/* This software is provided 'as-is', without any expressed or implied */
/* warranty. In no event will the authors be held liable for any damages */
/* arising from the use of this software. */
/* */
/* Permission is granted to anyone to use this software for any purpose, */
/* including commercial applications, and to alter it and redistribute it */
/* freely, subject to the following restrictions: */
/* */
/* 1. The origin of this software must not be misrepresented; you must not */
/* claim that you wrote the original software. If you use this software */
/* in a product, an acknowledgment in the product documentation would be */
/* appreciated but is not required. */
/* 2. Altered source versions must be plainly marked as such, and must not */
/* be misrepresented as being the original software. */
/* 3. This notice may not be removed or altered from any source */
/* distribution. */
/* */
/*****************************************************************************/
#include <string.h>
/* common */
#include "abend.h"
#include "print.h"
/* cc65 */
#include "asmlabel.h"
#include "codeent.h"
#include "codeinfo.h"
#include "coptind.h"
#include "error.h"
#include "global.h"
#include "codeopt.h"
/*****************************************************************************/
/* Data */
/*****************************************************************************/
/* Defines for the conditions in a compare */
typedef enum {
CMP_INV = -1,
CMP_EQ,
CMP_NE,
CMP_GT,
CMP_GE,
CMP_LT,
CMP_LE,
CMP_UGT,
CMP_UGE,
CMP_ULT,
CMP_ULE
} cmp_t;
/* Table with the compare suffixes */
static const char CmpSuffixTab [][4] = {
"eq", "ne", "gt", "ge", "lt", "le", "ugt", "uge", "ult", "ule"
};
/* Table used to invert a condition, indexed by condition */
static const unsigned char CmpInvertTab [] = {
CMP_NE, CMP_EQ,
CMP_LE, CMP_LT, CMP_GE, CMP_GT,
CMP_ULE, CMP_ULT, CMP_UGE, CMP_UGT
};
/* Table to show which compares are signed (use the N flag) */
static const char CmpSignedTab [] = {
0, 0, 1, 1, 1, 1, 0, 0, 0, 0
};
/*****************************************************************************/
/* Helper functions */
/*****************************************************************************/
static cmp_t FindCmpCond (const char* Code, unsigned CodeLen)
/* Search for a compare condition by the given code using the given length */
{
unsigned I;
/* Linear search */
for (I = 0; I < sizeof (CmpSuffixTab) / sizeof (CmpSuffixTab [0]); ++I) {
if (strncmp (Code, CmpSuffixTab [I], CodeLen) == 0) {
/* Found */
return I;
}
}
/* Not found */
return CMP_INV;
}
static cmp_t FindBoolCmpCond (const char* Name)
/* Map a condition suffix to a code. Return the code or CMP_INV on failure */
{
/* Check for the correct subroutine name */
if (strncmp (Name, "bool", 4) == 0) {
/* Name is ok, search for the code in the table */
return FindCmpCond (Name+4, strlen(Name)-4);
} else {
/* Not found */
return CMP_INV;
}
}
static cmp_t FindTosCmpCond (const char* Name)
/* Check if this is a call to one of the TOS compare functions (tosgtax).
* Return the condition code or CMP_INV on failure.
*/
{
unsigned Len = strlen (Name);
/* Check for the correct subroutine name */
if (strncmp (Name, "tos", 3) == 0 && strcmp (Name+Len-2, "ax") == 0) {
/* Name is ok, search for the code in the table */
return FindCmpCond (Name+3, Len-3-2);
} else {
/* Not found */
return CMP_INV;
}
}
static void ReplaceCmp (CodeSeg* S, unsigned I, cmp_t Cond)
/* Helper function for the replacement of routines that return a boolean
* followed by a conditional jump. Instead of the boolean value, the condition
* codes are evaluated directly.
* I is the index of the conditional branch, the sequence is already checked
* to be correct.
*/
{
CodeEntry* N;
CodeLabel* L;
/* Get the entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Replace the conditional branch */
switch (Cond) {
case CMP_EQ:
CE_ReplaceOPC (E, OP65_JEQ);
break;
case CMP_NE:
CE_ReplaceOPC (E, OP65_JNE);
break;
case CMP_GT:
/* Replace by
* beq @L
* jpl Target
* @L: ...
*/
if ((N = CS_GetNextEntry (S, I)) == 0) {
/* No such entry */
Internal ("Invalid program flow");
}
L = CS_GenLabel (S, N);
N = NewCodeEntry (OP65_BEQ, AM65_BRA, L->Name, L, E->LI);
CS_InsertEntry (S, N, I);
CE_ReplaceOPC (E, OP65_JPL);
break;
case CMP_GE:
CE_ReplaceOPC (E, OP65_JPL);
break;
case CMP_LT:
CE_ReplaceOPC (E, OP65_JMI);
break;
case CMP_LE:
/* Replace by
* jmi Target
* jeq Target
*/
CE_ReplaceOPC (E, OP65_JMI);
L = E->JumpTo;
N = NewCodeEntry (OP65_JEQ, AM65_BRA, L->Name, L, E->LI);
CS_InsertEntry (S, N, I+1);
break;
case CMP_UGT:
/* Replace by
* beq @L
* jcs Target
* @L: ...
*/
if ((N = CS_GetNextEntry (S, I)) == 0) {
/* No such entry */
Internal ("Invalid program flow");
}
L = CS_GenLabel (S, N);
N = NewCodeEntry (OP65_BEQ, AM65_BRA, L->Name, L, E->LI);
CS_InsertEntry (S, N, I);
CE_ReplaceOPC (E, OP65_JCS);
break;
case CMP_UGE:
CE_ReplaceOPC (E, OP65_JCS);
break;
case CMP_ULT:
CE_ReplaceOPC (E, OP65_JCC);
break;
case CMP_ULE:
/* Replace by
* jcc Target
* jeq Target
*/
CE_ReplaceOPC (E, OP65_JCC);
L = E->JumpTo;
N = NewCodeEntry (OP65_JEQ, AM65_BRA, L->Name, L, E->LI);
CS_InsertEntry (S, N, I+1);
break;
default:
Internal ("Unknown jump condition: %d", Cond);
}
}
static int IsCmpToZero (const CodeEntry* E)
/* Check if the given instrcuction is a compare to zero instruction */
{
return (E->OPC == OP65_CMP &&
E->AM == AM65_IMM &&
(E->Flags & CEF_NUMARG) != 0 &&
E->Num == 0);
}
static int IsSpLoad (const CodeEntry* E)
/* Return true if this is the load of A from the stack */
{
return E->OPC == OP65_LDA && E->AM == AM65_ZP_INDY && strcmp (E->Arg, "sp") == 0;
}
static int IsLocalLoad16 (CodeSeg* S, unsigned Index,
CodeEntry** L, unsigned Count)
/* Check if a 16 bit load of a local variable follows:
*
* ldy #$xx
* lda (sp),y
* tax
* dey
* lda (sp),y
*
* If so, read Count entries following the first ldy into L and return true
* if this is possible. Otherwise return false.
*/
{
/* Be sure we read enough entries for the check */
CHECK (Count >= 5);
/* Read the first entry */
L[0] = CS_GetEntry (S, Index);
/* Check for the sequence */
return (L[0]->OPC == OP65_LDY &&
L[0]->AM == AM65_IMM &&
(L[0]->Flags & CEF_NUMARG) != 0 &&
CS_GetEntries (S, L+1, Index+1, Count-1) &&
IsSpLoad (L[1]) &&
!CE_HasLabel (L[1]) &&
L[2]->OPC == OP65_TAX &&
!CE_HasLabel (L[2]) &&
L[3]->OPC == OP65_DEY &&
!CE_HasLabel (L[3]) &&
IsSpLoad (L[4]) &&
!CE_HasLabel (L[4]));
}
static int IsImmCmp16 (CodeSeg* S, CodeEntry** L)
/* Check if the instructions at L are an immidiate compare of a/x:
*
*
*/
{
return (L[0]->OPC == OP65_CPX &&
L[0]->AM == AM65_IMM &&
(L[0]->Flags & CEF_NUMARG) != 0 &&
!CE_HasLabel (L[0]) &&
(L[1]->OPC == OP65_JNE || L[1]->OPC == OP65_BNE) &&
L[1]->JumpTo != 0 &&
!CE_HasLabel (L[1]) &&
L[2]->OPC == OP65_CMP &&
L[2]->AM == AM65_IMM &&
(L[2]->Flags & CEF_NUMARG) != 0 &&
(L[3]->Info & OF_ZBRA) != 0 &&
L[3]->JumpTo != 0 &&
(L[1]->JumpTo->Owner == L[3] || L[1]->JumpTo == L[3]->JumpTo));
}
/*****************************************************************************/
/* Remove calls to the bool transformer subroutines */
/*****************************************************************************/
static unsigned OptBoolTransforms (CodeSeg* S)
/* Try to remove the call to boolean transformer routines where the call is
* not really needed.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* N;
cmp_t Cond;
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for a boolean transformer */
if (E->OPC == OP65_JSR &&
(Cond = FindBoolCmpCond (E->Arg)) != CMP_INV &&
(N = CS_GetNextEntry (S, I)) != 0 &&
(N->Info & OF_ZBRA) != 0) {
/* Make the boolean transformer unnecessary by changing the
* the conditional jump to evaluate the condition flags that
* are set after the compare directly. Note: jeq jumps if
* the condition is not met, jne jumps if the condition is met.
* Invert the code if we jump on condition not met.
*/
if (GetBranchCond (N->OPC) == BC_EQ) {
/* Jumps if condition false, invert condition */
Cond = CmpInvertTab [Cond];
}
/* Check if we can replace the code by something better */
ReplaceCmp (S, I+1, Cond);
/* Remove the call to the bool transformer */
CS_DelEntry (S, I);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Optimize subtractions */
/*****************************************************************************/
static unsigned OptSub1 (CodeSeg* S)
/* Search for the sequence
*
* sbc ...
* bcs L
* dex
* L:
*
* and remove the handling of the high byte if X is not used later.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[3];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_SBC &&
CS_GetEntries (S, L, I+1, 3) &&
(L[0]->OPC == OP65_BCS || L[0]->OPC == OP65_JCS) &&
L[0]->JumpTo != 0 &&
!CE_HasLabel (L[0]) &&
L[1]->OPC == OP65_DEX &&
!CE_HasLabel (L[1]) &&
L[0]->JumpTo->Owner == L[2] &&
!RegXUsed (S, I+3)) {
/* Remove the bcs/dex */
CS_DelEntries (S, I+1, 2);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptSub2 (CodeSeg* S)
/* Search for the sequence
*
* lda xx
* sec
* sta tmp1
* lda yy
* sbc tmp1
* sta yy
*
* and replace it by
*
* sec
* lda yy
* sbc xx
* sta yy
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[5];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_LDA &&
CS_GetEntries (S, L, I+1, 5) &&
L[0]->OPC == OP65_SEC &&
!CE_HasLabel (L[0]) &&
L[1]->OPC == OP65_STA &&
strcmp (L[1]->Arg, "tmp1") == 0 &&
!CE_HasLabel (L[1]) &&
L[2]->OPC == OP65_LDA &&
!CE_HasLabel (L[2]) &&
L[3]->OPC == OP65_SBC &&
strcmp (L[3]->Arg, "tmp1") == 0 &&
!CE_HasLabel (L[3]) &&
L[4]->OPC == OP65_STA &&
strcmp (L[4]->Arg, L[2]->Arg) == 0 &&
!CE_HasLabel (L[4])) {
/* Remove the store to tmp1 */
CS_DelEntry (S, I+2);
/* Remove the subtraction */
CS_DelEntry (S, I+3);
/* Move the lda to the position of the subtraction and change the
* op to SBC.
*/
CS_MoveEntry (S, I, I+3);
CE_ReplaceOPC (E, OP65_SBC);
/* If the sequence head had a label, move this label back to the
* head.
*/
if (CE_HasLabel (E)) {
CS_MoveLabels (S, E, L[0]);
}
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Optimize additions */
/*****************************************************************************/
static unsigned OptAdd1 (CodeSeg* S)
/* Search for the sequence
*
* adc ...
* bcc L
* inx
* L:
*
* and remove the handling of the high byte if X is not used later.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[3];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_ADC &&
CS_GetEntries (S, L, I+1, 3) &&
(L[0]->OPC == OP65_BCC || L[0]->OPC == OP65_JCC) &&
L[0]->JumpTo != 0 &&
!CE_HasLabel (L[0]) &&
L[1]->OPC == OP65_INX &&
!CE_HasLabel (L[1]) &&
L[0]->JumpTo->Owner == L[2] &&
!RegXUsed (S, I+3)) {
/* Remove the bcs/dex */
CS_DelEntries (S, I+1, 2);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Optimizations for compares */
/*****************************************************************************/
static unsigned OptCmp1 (CodeSeg* S)
/* Search for the sequence
*
* stx xx
* stx tmp1
* ora tmp1
*
* and replace it by
*
* stx xx
* ora xx
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_STX &&
CS_GetEntries (S, L, I+1, 2) &&
L[0]->OPC == OP65_STX &&
strcmp (L[0]->Arg, "tmp1") == 0 &&
!CE_HasLabel (L[0]) &&
L[1]->OPC == OP65_ORA &&
strcmp (L[1]->Arg, "tmp1") == 0 &&
!CE_HasLabel (L[1])) {
/* Remove the remaining instructions */
CS_DelEntries (S, I+1, 2);
/* Insert the ora instead */
CS_InsertEntry (S, NewCodeEntry (OP65_ORA, E->AM, E->Arg, 0, E->LI), I+1);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptCmp2 (CodeSeg* S)
/* Search for
*
* lda/and/ora/eor ...
* cmp #$00
* jeq/jne
*
* and remove the cmp.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if ((E->OPC == OP65_ADC ||
E->OPC == OP65_AND ||
E->OPC == OP65_DEA ||
E->OPC == OP65_EOR ||
E->OPC == OP65_INA ||
E->OPC == OP65_LDA ||
E->OPC == OP65_ORA ||
E->OPC == OP65_PLA ||
E->OPC == OP65_SBC ||
E->OPC == OP65_TXA ||
E->OPC == OP65_TYA) &&
CS_GetEntries (S, L, I+1, 2) &&
IsCmpToZero (L[0]) &&
!CE_HasLabel (L[0]) &&
(L[1]->Info & OF_FBRA) != 0 &&
!CE_HasLabel (L[1])) {
/* Remove the compare */
CS_DelEntry (S, I+1);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptCmp3 (CodeSeg* S)
/* Search for
*
* lda x
* ldx y
* cpx #a
* bne L1
* cmp #b
* jne/jeq L2
*
* If a is zero, we may remove the compare. If a and b are both zero, we may
* replace it by the sequence
*
* lda x
* ora x+1
* jne/jeq ...
*
* L1 may be either the label at the branch instruction, or the target label
* of this instruction.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[5];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_LDA &&
CS_GetEntries (S, L, I+1, 5) &&
L[0]->OPC == OP65_LDX &&
!CE_HasLabel (L[0]) &&
IsImmCmp16 (S, L+1)) {
if (L[1]->Num == 0 && L[3]->Num == 0) {
/* The value is zero, we may use the simple code version. */
CE_ReplaceOPC (L[0], OP65_ORA);
CS_DelEntries (S, I+2, 3);
} else {
/* Move the lda instruction after the first branch. This will
* improve speed, since the load is delayed after the first
* test.
*/
CS_MoveEntry (S, I, I+4);
/* We will replace the ldx/cpx by lda/cmp */
CE_ReplaceOPC (L[0], OP65_LDA);
CE_ReplaceOPC (L[1], OP65_CMP);
/* Beware: If the first LDA instruction had a label, we have
* to move this label to the top of the sequence again.
*/
if (CE_HasLabel (E)) {
CS_MoveLabels (S, E, L[0]);
}
}
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptCmp4 (CodeSeg* S)
/* Optimize compares of local variables:
*
* ldy #o
* lda (sp),y
* tax
* dey
* lda (sp),y
* cpx #a
* bne L1
* cmp #b
* jne/jeq L2
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[9];
/* Check for the sequence */
if (IsLocalLoad16 (S, I, L, 9) && IsImmCmp16 (S, L+5)) {
if (L[5]->Num == 0 && L[7]->Num == 0) {
/* The value is zero, we may use the simple code version:
* ldy #o
* lda (sp),y
* dey
* ora (sp),y
* jne/jeq ...
*/
CE_ReplaceOPC (L[4], OP65_ORA);
CS_DelEntries (S, I+5, 3); /* cpx/bne/cmp */
CS_DelEntry (S, I+2); /* tax */
} else {
/* Change the code to just use the A register. Move the load
* of the low byte after the first branch if possible:
*
* ldy #o
* lda (sp),y
* cmp #a
* bne L1
* dey
* lda (sp),y
* cmp #b
* jne/jeq ...
*/
CS_DelEntry (S, I+2); /* tax */
CE_ReplaceOPC (L[5], OP65_CMP); /* cpx -> cmp */
CS_MoveEntry (S, I+4, I+2); /* cmp */
CS_MoveEntry (S, I+5, I+3); /* bne */
}
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptCmp5 (CodeSeg* S)
/* Search for calls to compare subroutines followed by a conditional branch
* and replace them by cheaper versions, since the branch means that the
* boolean value returned by these routines is not needed (we may also check
* that explicitly, but for the current code generator it is always true).
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* N;
cmp_t Cond;
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_JSR &&
(Cond = FindTosCmpCond (E->Arg)) != CMP_INV &&
(N = CS_GetNextEntry (S, I)) != 0 &&
(N->Info & OF_ZBRA) != 0 &&
!CE_HasLabel (N)) {
/* The tos... functions will return a boolean value in a/x and
* the Z flag says if this value is zero or not. We will call
* a cheaper subroutine instead, one that does not return a
* boolean value but only valid flags. Note: jeq jumps if
* the condition is not met, jne jumps if the condition is met.
* Invert the code if we jump on condition not met.
*/
if (GetBranchCond (N->OPC) == BC_EQ) {
/* Jumps if condition false, invert condition */
Cond = CmpInvertTab [Cond];
}
/* Replace the subroutine call. */
E = NewCodeEntry (OP65_JSR, AM65_ABS, "tosicmp", 0, E->LI);
CS_InsertEntry (S, E, I+1);
CS_DelEntry (S, I);
/* Replace the conditional branch */
ReplaceCmp (S, I+1, Cond);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptCmp6 (CodeSeg* S)
/* Search for a sequence ldx/txa/branch and remove the txa if A is not
* used later.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if ((E->OPC == OP65_LDX || E->OPC == OP65_TAX) &&
CS_GetEntries (S, L, I+1, 2) &&
L[0]->OPC == OP65_TXA &&
!CE_HasLabel (L[0]) &&
(L[1]->Info & OF_FBRA) != 0 &&
!CE_HasLabel (L[1]) &&
!RegAUsed (S, I+3)) {
/* Remove the txa */
CS_DelEntry (S, I+1);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Optimize tests */
/*****************************************************************************/
static unsigned OptTest1 (CodeSeg* S)
/* On a sequence
*
* stx xxx
* ora xxx
* beq/bne ...
*
* if X is zero, the sequence may be changed
*
* cmp #$00
* beq/bne ...
*
* which may be optimized further by another step.
*/
{
unsigned Changes = 0;
unsigned I;
/* Generate register info for this step */
CS_GenRegInfo (S);
/* Walk over the entries */
I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[3];
/* Get next entry */
L[0] = CS_GetEntry (S, I);
/* Check if it's the sequence we're searching for */
if (L[0]->OPC == OP65_STX &&
L[0]->RI->In.RegX == 0 &&
CS_GetEntries (S, L+1, I+1, 2) &&
!CE_HasLabel (L[1]) &&
L[1]->OPC == OP65_ORA &&
strcmp (L[0]->Arg, L[1]->Arg) == 0 &&
!CE_HasLabel (L[2]) &&
(L[2]->Info & OF_ZBRA) != 0) {
/* Insert the compare */
CodeEntry* N = NewCodeEntry (OP65_CMP, AM65_IMM, "$00", 0, L[0]->LI);
CS_InsertEntry (S, N, I+2);
/* Remove the two other insns */
CS_DelEntry (S, I+1);
CS_DelEntry (S, I);
/* We had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Free register info */
CS_FreeRegInfo (S);
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* nega optimizations */
/*****************************************************************************/
static unsigned OptNegA1 (CodeSeg* S)
/* Check for
*
* ldx #$00
* lda ..
* jsr bnega
*
* Remove the ldx if the lda does not use it.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for a ldx */
if (E->OPC == OP65_LDX &&
E->AM == AM65_IMM &&
(E->Flags & CEF_NUMARG) != 0 &&
E->Num == 0 &&
CS_GetEntries (S, L, I+1, 2) &&
L[0]->OPC == OP65_LDA &&
(L[0]->Use & REG_X) == 0 &&
!CE_HasLabel (L[0]) &&
L[1]->OPC == OP65_JSR &&
strcmp (L[1]->Arg, "bnega") == 0 &&
!CE_HasLabel (L[1])) {
/* Remove the ldx instruction */
CS_DelEntry (S, I);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptNegA2 (CodeSeg* S)
/* Check for
*
* lda ..
* jsr bnega
* jeq/jne ..
*
* Adjust the conditional branch and remove the call to the subroutine.
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if ((E->OPC == OP65_ADC ||
E->OPC == OP65_AND ||
E->OPC == OP65_DEA ||
E->OPC == OP65_EOR ||
E->OPC == OP65_INA ||
E->OPC == OP65_LDA ||
E->OPC == OP65_ORA ||
E->OPC == OP65_PLA ||
E->OPC == OP65_SBC ||
E->OPC == OP65_TXA ||
E->OPC == OP65_TYA) &&
CS_GetEntries (S, L, I+1, 2) &&
L[0]->OPC == OP65_JSR &&
strcmp (L[0]->Arg, "bnega") == 0 &&
!CE_HasLabel (L[0]) &&
(L[1]->Info & OF_ZBRA) != 0 &&
!CE_HasLabel (L[1])) {
/* Invert the branch */
CE_ReplaceOPC (L[1], GetInverseBranch (L[1]->OPC));
/* Delete the subroutine call */
CS_DelEntry (S, I+1);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* negax optimizations */
/*****************************************************************************/
static unsigned OptNegAX1 (CodeSeg* S)
/* On a call to bnegax, if X is zero, the result depends only on the value in
* A, so change the call to a call to bnega. This will get further optimized
* later if possible.
*/
{
unsigned Changes = 0;
unsigned I;
/* Generate register info for this step */
CS_GenRegInfo (S);
/* Walk over the entries */
I = 0;
while (I < CS_GetEntryCount (S)) {
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check if this is a call to bnegax, and if X is known and zero */
if (E->OPC == OP65_JSR &&
E->RI->In.RegX == 0 &&
strcmp (E->Arg, "bnegax") == 0) {
/* We're cheating somewhat here ... */
E->Arg[5] = '\0';
E->Use &= ~REG_X;
/* We had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Free register info */
CS_FreeRegInfo (S);
/* Return the number of changes made */
return Changes;
}
static unsigned OptNegAX2 (CodeSeg* S)
/* Search for the sequence:
*
* lda (xx),y
* tax
* dey
* lda (xx),y
* jsr bnegax
* jne/jeq ...
*
* and replace it by
*
* lda (xx),y
* dey
* ora (xx),y
* jeq/jne ...
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[5];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_LDA &&
E->AM == AM65_ZP_INDY &&
CS_GetEntries (S, L, I+1, 5) &&
L[0]->OPC == OP65_TAX &&
L[1]->OPC == OP65_DEY &&
L[2]->OPC == OP65_LDA &&
L[2]->AM == AM65_ZP_INDY &&
strcmp (L[2]->Arg, E->Arg) == 0 &&
!CE_HasLabel (L[2]) &&
L[3]->OPC == OP65_JSR &&
strcmp (L[3]->Arg, "bnegax") == 0 &&
!CE_HasLabel (L[3]) &&
(L[4]->Info & OF_ZBRA) != 0 &&
!CE_HasLabel (L[4])) {
/* lda --> ora */
CE_ReplaceOPC (L[2], OP65_ORA);
/* Invert the branch */
CE_ReplaceOPC (L[4], GetInverseBranch (L[4]->OPC));
/* Delete the entries no longer needed. Beware: Deleting entries
* will change the indices.
*/
CS_DelEntry (S, I+4); /* jsr bnegax */
CS_DelEntry (S, I+1); /* tax */
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptNegAX3 (CodeSeg* S)
/* Search for the sequence:
*
* lda xx
* ldx yy
* jsr bnegax
* jne/jeq ...
*
* and replace it by
*
* lda xx
* ora xx+1
* jeq/jne ...
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[3];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_LDA &&
CS_GetEntries (S, L, I+1, 3) &&
L[0]->OPC == OP65_LDX &&
!CE_HasLabel (L[0]) &&
L[1]->OPC == OP65_JSR &&
strcmp (L[1]->Arg, "bnegax") == 0 &&
!CE_HasLabel (L[1]) &&
(L[2]->Info & OF_ZBRA) != 0 &&
!CE_HasLabel (L[2])) {
/* ldx --> ora */
CE_ReplaceOPC (L[0], OP65_ORA);
/* Invert the branch */
CE_ReplaceOPC (L[2], GetInverseBranch (L[2]->OPC));
/* Delete the subroutine call */
CS_DelEntry (S, I+2);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptNegAX4 (CodeSeg* S)
/* Search for the sequence:
*
* jsr xxx
* jsr bnega(x)
* jeq/jne ...
*
* and replace it by:
*
* jsr xxx
* <boolean test>
* jne/jeq ...
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
CodeEntry* E = CS_GetEntry (S, I);
/* Check for the sequence */
if (E->OPC == OP65_JSR &&
CS_GetEntries (S, L, I+1, 2) &&
L[0]->OPC == OP65_JSR &&
strncmp (L[0]->Arg,"bnega",5) == 0 &&
!CE_HasLabel (L[0]) &&
(L[1]->Info & OF_ZBRA) != 0 &&
!CE_HasLabel (L[1])) {
CodeEntry* X;
/* Check if we're calling bnega or bnegax */
int ByteSized = (strcmp (L[0]->Arg, "bnega") == 0);
/* Insert apropriate test code */
if (ByteSized) {
/* Test bytes */
X = NewCodeEntry (OP65_TAX, AM65_IMP, 0, 0, L[0]->LI);
CS_InsertEntry (S, X, I+2);
} else {
/* Test words */
X = NewCodeEntry (OP65_STX, AM65_ZP, "tmp1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+2);
X = NewCodeEntry (OP65_ORA, AM65_ZP, "tmp1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+3);
}
/* Delete the subroutine call */
CS_DelEntry (S, I+1);
/* Invert the branch */
CE_ReplaceOPC (L[1], GetInverseBranch (L[1]->OPC));
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Optimize stores through pointers */
/*****************************************************************************/
static unsigned OptPtrStore1 (CodeSeg* S)
/* Search for the sequence:
*
* jsr pushax
* lda xxx
* ldy yyy
* jsr staspidx
*
* and replace it by:
*
* sta ptr1
* stx ptr1+1
* lda xxx
* ldy yyy
* sta (ptr1),y
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[4];
/* Get next entry */
L[0] = CS_GetEntry (S, I);
/* Check for the sequence */
if (L[0]->OPC == OP65_JSR &&
strcmp (L[0]->Arg, "pushax") == 0 &&
CS_GetEntries (S, L+1, I+1, 3) &&
L[1]->OPC == OP65_LDA &&
!CE_HasLabel (L[1]) &&
L[2]->OPC == OP65_LDY &&
!CE_HasLabel (L[2]) &&
L[3]->OPC == OP65_JSR &&
strcmp (L[3]->Arg, "staspidx") == 0 &&
!CE_HasLabel (L[3])) {
CodeEntry* X;
/* Create and insert the stores */
X = NewCodeEntry (OP65_STA, AM65_ZP, "ptr1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+1);
X = NewCodeEntry (OP65_STX, AM65_ZP, "ptr1+1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+2);
/* Delete the call to pushax */
CS_DelEntry (S, I);
/* Insert the store through ptr1 */
X = NewCodeEntry (OP65_STA, AM65_ZP_INDY, "ptr1", 0, L[3]->LI);
CS_InsertEntry (S, X, I+4);
/* Delete the call to staspidx */
CS_DelEntry (S, I+5);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Optimize loads through pointers */
/*****************************************************************************/
static unsigned OptPtrLoad1 (CodeSeg* S)
/* Search for the sequence:
*
* tax
* dey
* lda (sp),y # May be any destination
* ldy ...
* jsr ldauidx
*
* and replace it by:
*
* sta ptr1+1
* dey
* lda (sp),y
* sta ptr1
* ldy ...
* ldx #$00
* lda (ptr1),y
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[5];
/* Get next entry */
L[0] = CS_GetEntry (S, I);
/* Check for the sequence */
if (L[0]->OPC == OP65_TAX &&
CS_GetEntries (S, L+1, I+1, 4) &&
L[1]->OPC == OP65_DEY &&
!CE_HasLabel (L[1]) &&
L[2]->OPC == OP65_LDA &&
!CE_HasLabel (L[2]) &&
L[3]->OPC == OP65_LDY &&
!CE_HasLabel (L[3]) &&
L[4]->OPC == OP65_JSR &&
strcmp (L[4]->Arg, "ldauidx") == 0 &&
!CE_HasLabel (L[4])) {
CodeEntry* X;
/* Store the high byte and remove the TAX instead */
X = NewCodeEntry (OP65_STA, AM65_ZP, "ptr1+1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+1);
CS_DelEntry (S, I);
/* Store the low byte */
X = NewCodeEntry (OP65_STA, AM65_ZP, "ptr1", 0, L[2]->LI);
CS_InsertEntry (S, X, I+3);
/* Delete the call to ldauidx */
CS_DelEntry (S, I+5);
/* Load high and low byte */
X = NewCodeEntry (OP65_LDX, AM65_IMM, "$00", 0, L[3]->LI);
CS_InsertEntry (S, X, I+5);
X = NewCodeEntry (OP65_LDA, AM65_ZP_INDY, "ptr1", 0, L[3]->LI);
CS_InsertEntry (S, X, I+6);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
static unsigned OptPtrLoad2 (CodeSeg* S)
/* Search for the sequence
*
* ldy ...
* jsr ldauidx
*
* and replace it by:
*
* ldy ...
* stx ptr1+1
* sta ptr1
* ldx #$00
* lda (ptr1),y
*
* This step must be execute *after* OptPtrLoad1!
*/
{
unsigned Changes = 0;
/* Walk over the entries */
unsigned I = 0;
while (I < CS_GetEntryCount (S)) {
CodeEntry* L[2];
/* Get next entry */
L[0] = CS_GetEntry (S, I);
/* Check for the sequence */
if (L[0]->OPC == OP65_LDY &&
CS_GetEntries (S, L+1, I+1, 1) &&
L[1]->OPC == OP65_JSR &&
strcmp (L[1]->Arg, "ldauidx") == 0 &&
!CE_HasLabel (L[1])) {
CodeEntry* X;
/* Store the high byte */
X = NewCodeEntry (OP65_STA, AM65_ZP, "ptr1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+1);
/* Store the low byte */
X = NewCodeEntry (OP65_STX, AM65_ZP, "ptr1+1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+2);
/* Delete the call to ldauidx */
CS_DelEntry (S, I+3);
/* Load the high and low byte */
X = NewCodeEntry (OP65_LDX, AM65_IMM, "$00", 0, L[0]->LI);
CS_InsertEntry (S, X, I+3);
X = NewCodeEntry (OP65_LDA, AM65_ZP_INDY, "ptr1", 0, L[0]->LI);
CS_InsertEntry (S, X, I+4);
/* Remember, we had changes */
++Changes;
}
/* Next entry */
++I;
}
/* Return the number of changes made */
return Changes;
}
/*****************************************************************************/
/* Code */
/*****************************************************************************/
/* Types of optimization steps */
enum {
optPre, /* Repeated once */
optPreMain, /* Repeated more than once */
optMain, /* dito */
optPostMain, /* dito */
optPost /* Repeated once */
};
/* Table with all the optimization functions */
typedef struct OptFunc OptFunc;
struct OptFunc {
unsigned (*Func) (CodeSeg*); /* Optimizer function */
const char* Name; /* Name of optimizer step */
unsigned char Type; /* Type of this step */
char Disabled; /* True if pass disabled */
};
/* Macro that builds a table entry */
#define OptEntry(func,type) { func, #func, type, 0 }
/* Table with optimizer steps */
static OptFunc OptFuncs [] = {
/* Optimizes stores through pointers */
OptEntry (OptPtrStore1, optPre),
/* Optimize loads through pointers */
OptEntry (OptPtrLoad1, optMain),
OptEntry (OptPtrLoad2, optMain),
/* Optimize calls to nega */
OptEntry (OptNegA1, optPre),
OptEntry (OptNegA2, optPre),
/* Optimize calls to negax */
OptEntry (OptNegAX1, optPre),
OptEntry (OptNegAX2, optPre),
OptEntry (OptNegAX3, optPre),
OptEntry (OptNegAX4, optPre),
/* Optimize subtractions */
OptEntry (OptSub1, optMain),
OptEntry (OptSub2, optMain),
/* Optimize additions */
OptEntry (OptAdd1, optMain),
/* Optimize jump cascades */
OptEntry (OptJumpCascades, optMain),
/* Remove dead jumps */
OptEntry (OptDeadJumps, optMain),
/* Change jsr/rts to jmp */
OptEntry (OptRTS, optMain),
/* Remove dead code */
OptEntry (OptDeadCode, optMain),
/* Optimize jump targets */
OptEntry (OptJumpTarget, optMain),
/* Optimize conditional branches */
OptEntry (OptCondBranches, optMain),
/* Replace jumps to RTS by RTS */
OptEntry (OptRTSJumps, optMain),
/* Remove calls to the bool transformer subroutines */
OptEntry (OptBoolTransforms, optMain),
/* Optimize compares */
OptEntry (OptCmp1, optMain),
OptEntry (OptCmp2, optMain),
OptEntry (OptCmp3, optMain),
OptEntry (OptCmp4, optMain),
OptEntry (OptCmp5, optMain),
OptEntry (OptCmp6, optMain),
/* Optimize tests */
OptEntry (OptTest1, optMain),
/* Remove unused loads */
OptEntry (OptUnusedLoads, optMain),
OptEntry (OptDuplicateLoads, optMain),
OptEntry (OptStoreLoad, optMain),
/* Optimize branch distance */
OptEntry (OptBranchDist, optMain),
};
static OptFunc* FindOptStep (const char* Name)
/* Find an optimizer step by name in the table and return a pointer. Print an
* error and call AbEnd if not found.
*/
{
unsigned I;
/* Run all optimization steps */
for (I = 0; I < sizeof(OptFuncs)/sizeof(OptFuncs[0]); ++I) {
if (strcmp (OptFuncs[I].Name, Name) == 0) {
/* Found */
return OptFuncs+I;
}
}
/* Not found */
AbEnd ("Optimization step `%s' not found", Name);
return 0;
}
void DisableOpt (const char* Name)
/* Disable the optimization with the given name */
{
if (strcmp (Name, "any") == 0) {
unsigned I;
for (I = 0; I < sizeof(OptFuncs)/sizeof(OptFuncs[0]); ++I) {
OptFuncs[I].Disabled = 1;
}
} else {
OptFunc* F = FindOptStep (Name);
F->Disabled = 1;
}
}
void EnableOpt (const char* Name)
/* Enable the optimization with the given name */
{
if (strcmp (Name, "any") == 0) {
unsigned I;
for (I = 0; I < sizeof(OptFuncs)/sizeof(OptFuncs[0]); ++I) {
OptFuncs[I].Disabled = 0;
}
} else {
OptFunc* F = FindOptStep (Name);
F->Disabled = 0;
}
}
void ListOptSteps (FILE* F)
/* List all optimization steps */
{
unsigned I;
for (I = 0; I < sizeof(OptFuncs)/sizeof(OptFuncs[0]); ++I) {
fprintf (F, "%s\n", OptFuncs[I].Name);
}
}
static void RepeatOptStep (CodeSeg* S, unsigned char Type, unsigned Max)
/* Repeat the optimizer step of type Type at may Max times */
{
unsigned I;
unsigned Pass = 0;
unsigned OptChanges;
/* Repeat max times of until there are no more changes */
do {
/* Reset the number of changes */
OptChanges = 0;
/* Keep the user hapy */
Print (stdout, 1, " Optimizer pass %u:\n", ++Pass);
/* Run all optimization steps */
for (I = 0; I < sizeof(OptFuncs)/sizeof(OptFuncs[0]); ++I) {
/* Get the table entry */
const OptFunc* F = OptFuncs + I;
/* Check if the type matches */
if (F->Type != Type) {
/* Skip it */
continue;
}
/* Print the name of the following optimizer step */
Print (stdout, 1, " %s:%*s", F->Name, (int) (30-strlen(F->Name)), "");
/* Check if the step is disabled */
if (F->Disabled) {
Print (stdout, 1, "Disabled\n");
} else {
unsigned Changes = F->Func (S);
OptChanges += Changes;
Print (stdout, 1, "%u Changes\n", Changes);
}
}
} while (--Max > 0 && OptChanges > 0);
}
void RunOpt (CodeSeg* S)
/* Run the optimizer */
{
/* If we shouldn't run the optimizer, bail out */
if (!Optimize) {
return;
}
/* Print the name of the function we are working on */
if (S->Func) {
Print (stdout, 1, "Running optimizer for function `%s'\n", S->Func->Name);
} else {
Print (stdout, 1, "Running optimizer for global code segment\n");
}
/* Repeat all steps until there are no more changes */
RepeatOptStep (S, optPre, 1);
RepeatOptStep (S, optPreMain, 0xFFFF);
RepeatOptStep (S, optMain, 0xFFFF);
RepeatOptStep (S, optPostMain, 0xFFFF);
RepeatOptStep (S, optPost, 1);
}