mirror of
https://github.com/cc65/cc65.git
synced 2024-11-19 06:31:31 +00:00
More stack op optimizations
git-svn-id: svn://svn.cc65.org/cc65/trunk@991 b7a2c559-68d2-44c3-8de9-860c34a00d81
This commit is contained in:
parent
ab692b8de8
commit
b08adf831d
@ -33,6 +33,11 @@
|
||||
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* common */
|
||||
#include "xsprintf.h"
|
||||
|
||||
/* cc65 */
|
||||
#include "codeent.h"
|
||||
#include "codeinfo.h"
|
||||
@ -48,52 +53,55 @@
|
||||
|
||||
|
||||
|
||||
static unsigned Opt_tosaddax (CodeSeg* S, unsigned Push, unsigned Add)
|
||||
static unsigned Opt_staspidx (CodeSeg* S, unsigned Push, unsigned Store,
|
||||
const char* ZPLo, const char* ZPHi)
|
||||
/* Optimize the staspidx sequence if possible */
|
||||
{
|
||||
CodeEntry* X;
|
||||
CodeEntry* PushEntry;
|
||||
CodeEntry* StoreEntry;
|
||||
|
||||
/* Generate register info */
|
||||
CS_GenRegInfo (S);
|
||||
|
||||
/* Get the push entry */
|
||||
PushEntry = CS_GetEntry (S, Push);
|
||||
|
||||
/* Store the value into the zeropage instead of pushing it */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP, ZPLo, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+1);
|
||||
X = NewCodeEntry (OP65_STX, AM65_ZP, ZPHi, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+2);
|
||||
|
||||
/* Correct the index of the store and get a pointer to the entry */
|
||||
Store += 2;
|
||||
StoreEntry = CS_GetEntry (S, Store);
|
||||
|
||||
/* Inline the store */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP_INDY, ZPLo, 0, StoreEntry->LI);
|
||||
CS_InsertEntry (S, X, Store+1);
|
||||
|
||||
/* Remove the push and the call to the staspidx function */
|
||||
CS_DelEntry (S, Store);
|
||||
CS_DelEntry (S, Push);
|
||||
|
||||
/* Free the register info */
|
||||
CS_FreeRegInfo (S);
|
||||
|
||||
/* We changed the sequence */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static unsigned Opt_tosaddax (CodeSeg* S, unsigned Push, unsigned Add,
|
||||
const char* ZPLo, const char* ZPHi)
|
||||
/* Optimize the tosaddax sequence if possible */
|
||||
{
|
||||
unsigned I;
|
||||
CodeEntry* N;
|
||||
CodeEntry* X;
|
||||
CodeEntry* PushEntry;
|
||||
CodeEntry* AddEntry;
|
||||
const char* ZPLo;
|
||||
const char* ZPHi;
|
||||
|
||||
/* Check if the sequence is safe. This means that there may not be any
|
||||
* jumps between the two data points, and no usage of the stack. Handling
|
||||
* these conditions is possible and may be done later.
|
||||
*/
|
||||
unsigned UsedRegs = REG_NONE;
|
||||
for (I = Push + 1; I < Add; ++I) {
|
||||
CodeEntry* E = CS_GetEntry (S, I);
|
||||
if ((E->Info & OF_BRA) != 0 ||
|
||||
E->OPC == OP65_JSR ||
|
||||
(E->Use & REG_SP) != 0 ||
|
||||
CE_HasLabel (E)) {
|
||||
/* A jump or stack pointer usage - bail out */
|
||||
return 0;
|
||||
}
|
||||
UsedRegs |= (E->Use | E->Chg);
|
||||
}
|
||||
|
||||
/* We prefer usage of sreg for the intermediate value, since sreg is
|
||||
* tracked and optimized.
|
||||
*/
|
||||
UsedRegs |= GetRegInfo (S, Push+1, REG_ALL);
|
||||
if ((UsedRegs & REG_SREG) == REG_NONE) {
|
||||
/* SREG is available */
|
||||
ZPLo = "sreg";
|
||||
ZPHi = "sreg+1";
|
||||
} else if ((UsedRegs & REG_PTR1) == REG_NONE) {
|
||||
ZPLo = "ptr1";
|
||||
ZPHi = "ptr1+1";
|
||||
} else if ((UsedRegs & REG_PTR2) == REG_NONE) {
|
||||
ZPLo = "ptr2";
|
||||
ZPHi = "ptr2+1";
|
||||
} else {
|
||||
/* No registers available */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need the entry behind the add */
|
||||
if ((N = CS_GetNextEntry (S, Add)) == 0) {
|
||||
@ -107,7 +115,7 @@ static unsigned Opt_tosaddax (CodeSeg* S, unsigned Push, unsigned Add)
|
||||
/* Get the push entry */
|
||||
PushEntry = CS_GetEntry (S, Push);
|
||||
|
||||
/* Store the value into sreg instead of pushing it */
|
||||
/* Store the value into the zeropage instead of pushing it */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP, ZPLo, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+1);
|
||||
X = NewCodeEntry (OP65_STX, AM65_ZP, ZPHi, 0, PushEntry->LI);
|
||||
@ -156,58 +164,13 @@ static unsigned Opt_tosaddax (CodeSeg* S, unsigned Push, unsigned Add)
|
||||
|
||||
|
||||
|
||||
static unsigned Opt_staspidx (CodeSeg* S, unsigned Push, unsigned Store)
|
||||
/* Optimize the staspidx sequence if possible */
|
||||
static unsigned Opt_tosandax (CodeSeg* S, unsigned Push, unsigned And,
|
||||
const char* ZPLo, const char* ZPHi)
|
||||
/* Optimize the tosandax sequence if possible */
|
||||
{
|
||||
unsigned I;
|
||||
CodeEntry* N;
|
||||
CodeEntry* X;
|
||||
CodeEntry* PushEntry;
|
||||
CodeEntry* StoreEntry;
|
||||
const char* ZPLo;
|
||||
const char* ZPHi;
|
||||
|
||||
/* Check if the sequence is safe. This means that there may not be any
|
||||
* jumps between the two data points, and no usage of the stack. Handling
|
||||
* these conditions is possible and may be done later.
|
||||
*/
|
||||
unsigned UsedRegs = REG_NONE;
|
||||
for (I = Push + 1; I < Store; ++I) {
|
||||
CodeEntry* E = CS_GetEntry (S, I);
|
||||
if ((E->Info & OF_BRA) != 0 ||
|
||||
E->OPC == OP65_JSR ||
|
||||
(E->Use & REG_SP) != 0 ||
|
||||
CE_HasLabel (E)) {
|
||||
/* A jump or stack pointer usage - bail out */
|
||||
return 0;
|
||||
}
|
||||
UsedRegs |= (E->Use | E->Chg);
|
||||
}
|
||||
|
||||
/* We prefer usage of sreg for the intermediate value, since sreg is
|
||||
* tracked and optimized.
|
||||
*/
|
||||
UsedRegs |= GetRegInfo (S, Push+1, REG_SREG | REG_PTR1 | REG_PTR2);
|
||||
if ((UsedRegs & REG_SREG) == REG_NONE) {
|
||||
/* SREG is available */
|
||||
ZPLo = "sreg";
|
||||
ZPHi = "sreg+1";
|
||||
} else if ((UsedRegs & REG_PTR1) == REG_NONE) {
|
||||
ZPLo = "ptr1";
|
||||
ZPHi = "ptr1+1";
|
||||
} else if ((UsedRegs & REG_PTR2) == REG_NONE) {
|
||||
ZPLo = "ptr2";
|
||||
ZPHi = "ptr2+1";
|
||||
} else {
|
||||
/* No registers available */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need the entry behind the store */
|
||||
if ((N = CS_GetNextEntry (S, Store)) == 0) {
|
||||
/* Unavailable */
|
||||
return 0;
|
||||
}
|
||||
CodeEntry* AndEntry;
|
||||
|
||||
/* Generate register info */
|
||||
CS_GenRegInfo (S);
|
||||
@ -215,22 +178,99 @@ static unsigned Opt_staspidx (CodeSeg* S, unsigned Push, unsigned Store)
|
||||
/* Get the push entry */
|
||||
PushEntry = CS_GetEntry (S, Push);
|
||||
|
||||
/* Store the value into sreg instead of pushing it */
|
||||
/* Store the value into the zeropage instead of pushing it */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP, ZPLo, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+1);
|
||||
X = NewCodeEntry (OP65_STX, AM65_ZP, ZPHi, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+2);
|
||||
|
||||
/* Correct the index of the store and get a pointer to the entry */
|
||||
Store += 2;
|
||||
StoreEntry = CS_GetEntry (S, Store);
|
||||
/* Correct the index of the add and get a pointer to the entry */
|
||||
And += 2;
|
||||
AndEntry = CS_GetEntry (S, And);
|
||||
|
||||
/* Inline the store */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP_INDY, ZPLo, 0, StoreEntry->LI);
|
||||
CS_InsertEntry (S, X, Store+1);
|
||||
/* Inline the and */
|
||||
X = NewCodeEntry (OP65_AND, AM65_ZP, ZPLo, 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+1);
|
||||
if (PushEntry->RI->In.RegX == 0 || AndEntry->RI->In.RegX == 0) {
|
||||
/* The high byte is zero */
|
||||
X = NewCodeEntry (OP65_LDX, AM65_IMM, "$00", 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+2);
|
||||
} else {
|
||||
/* High byte is unknown */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP, ZPLo, 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+2);
|
||||
X = NewCodeEntry (OP65_TXA, AM65_IMP, 0, 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+3);
|
||||
X = NewCodeEntry (OP65_AND, AM65_ZP, ZPHi, 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+4);
|
||||
X = NewCodeEntry (OP65_TAX, AM65_IMP, 0, 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+5);
|
||||
X = NewCodeEntry (OP65_LDA, AM65_ZP, ZPLo, 0, AndEntry->LI);
|
||||
CS_InsertEntry (S, X, And+6);
|
||||
}
|
||||
|
||||
/* Remove the push and the call to the staspidx function */
|
||||
CS_DelEntry (S, Store);
|
||||
/* Remove the push and the call to the tosandax function */
|
||||
CS_DelEntry (S, And);
|
||||
CS_DelEntry (S, Push);
|
||||
|
||||
/* Free the register info */
|
||||
CS_FreeRegInfo (S);
|
||||
|
||||
/* We changed the sequence */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static unsigned Opt_tosorax (CodeSeg* S, unsigned Push, unsigned Or,
|
||||
const char* ZPLo, const char* ZPHi)
|
||||
/* Optimize the tosorax sequence if possible */
|
||||
{
|
||||
CodeEntry* X;
|
||||
CodeEntry* PushEntry;
|
||||
CodeEntry* OrEntry;
|
||||
|
||||
/* Generate register info */
|
||||
CS_GenRegInfo (S);
|
||||
|
||||
/* Get the push entry */
|
||||
PushEntry = CS_GetEntry (S, Push);
|
||||
|
||||
/* Store the value into the zeropage instead of pushing it */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP, ZPLo, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+1);
|
||||
X = NewCodeEntry (OP65_STX, AM65_ZP, ZPHi, 0, PushEntry->LI);
|
||||
CS_InsertEntry (S, X, Push+2);
|
||||
|
||||
/* Correct the index of the add and get a pointer to the entry */
|
||||
Or += 2;
|
||||
OrEntry = CS_GetEntry (S, Or);
|
||||
|
||||
/* Inline the or */
|
||||
X = NewCodeEntry (OP65_ORA, AM65_ZP, ZPLo, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+1);
|
||||
if (PushEntry->RI->In.RegX >= 0 && OrEntry->RI->In.RegX == 0) {
|
||||
/* Value of X will be that of the first operand */
|
||||
char Buf [16];
|
||||
xsprintf (Buf, sizeof (Buf), "$%02X", PushEntry->RI->In.RegX);
|
||||
X = NewCodeEntry (OP65_LDX, AM65_IMM, Buf, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+2);
|
||||
} else if (PushEntry->RI->In.RegX != 0) {
|
||||
/* High byte is unknown */
|
||||
X = NewCodeEntry (OP65_STA, AM65_ZP, ZPLo, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+2);
|
||||
X = NewCodeEntry (OP65_TXA, AM65_IMP, 0, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+3);
|
||||
X = NewCodeEntry (OP65_ORA, AM65_ZP, ZPHi, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+4);
|
||||
X = NewCodeEntry (OP65_TAX, AM65_IMP, 0, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+5);
|
||||
X = NewCodeEntry (OP65_LDA, AM65_ZP, ZPLo, 0, OrEntry->LI);
|
||||
CS_InsertEntry (S, X, Or+6);
|
||||
}
|
||||
|
||||
/* Remove the push and the call to the tosandax function */
|
||||
CS_DelEntry (S, Or);
|
||||
CS_DelEntry (S, Push);
|
||||
|
||||
/* Free the register info */
|
||||
@ -248,42 +288,147 @@ static unsigned Opt_staspidx (CodeSeg* S, unsigned Push, unsigned Store)
|
||||
|
||||
|
||||
|
||||
typedef unsigned (*OptFunc) (CodeSeg* S, unsigned Push, unsigned Store,
|
||||
const char* ZPLo, const char* ZPHi);
|
||||
typedef struct OptFuncDesc OptFuncDesc;
|
||||
struct OptFuncDesc {
|
||||
const char* Name; /* Name of the replaced runtime function */
|
||||
OptFunc Func; /* Function pointer */
|
||||
};
|
||||
|
||||
static const OptFuncDesc FuncTable[] = {
|
||||
{ "staspidx", Opt_staspidx },
|
||||
{ "tosaddax", Opt_tosaddax },
|
||||
{ "tosandax", Opt_tosandax },
|
||||
{ "tosorax", Opt_tosorax },
|
||||
};
|
||||
#define FUNC_COUNT (sizeof(FuncTable) / sizeof(FuncTable[0]))
|
||||
|
||||
|
||||
|
||||
static int CmpFunc (const void* Key, const void* Func)
|
||||
/* Compare function for bsearch */
|
||||
{
|
||||
return strcmp (Key, ((const OptFuncDesc*) Func)->Name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const OptFuncDesc* FindFunc (const char* Name)
|
||||
/* Find the function with the given name. Return a pointer to the table entry
|
||||
* or NULL if the function was not found.
|
||||
*/
|
||||
{
|
||||
return bsearch (Name, FuncTable, FUNC_COUNT, sizeof(OptFuncDesc), CmpFunc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Code */
|
||||
/*****************************************************************************/
|
||||
|
||||
|
||||
|
||||
unsigned OptStackOps (CodeSeg* S)
|
||||
/* Optimize operations that take operands via the stack */
|
||||
{
|
||||
unsigned Changes = 0; /* Number of changes in one run */
|
||||
int LastPush = -1; /* Last call to pushax */
|
||||
unsigned Changes = 0; /* Number of changes in one run */
|
||||
int InSeq = 0; /* Inside a sequence */
|
||||
unsigned Push = 0; /* Index of pushax */
|
||||
unsigned UsedRegs = 0; /* Zeropage registers used in sequence */
|
||||
|
||||
/* Walk over all entries */
|
||||
|
||||
/* Look for a call to pushax followed by a call to some other function
|
||||
* that takes it's first argument on the stack, and the second argument
|
||||
* in the primary register.
|
||||
* It depends on the code between the two if we can handle/transform the
|
||||
* sequence, so check this code for the following list of things:
|
||||
*
|
||||
* - there must not be a jump or conditional branch (this may
|
||||
* get relaxed later).
|
||||
* - there may not be accesses to local variables (may also be
|
||||
* relaxed later)
|
||||
* - no subroutine calls
|
||||
* - no jump labels
|
||||
*
|
||||
* Since we need a zero page register later, do also check the
|
||||
* intermediate code for zero page use.
|
||||
*/
|
||||
unsigned I = 0;
|
||||
while (I < CS_GetEntryCount (S)) {
|
||||
|
||||
/* Get the next entry */
|
||||
CodeEntry* E = CS_GetEntry (S, I);
|
||||
|
||||
/* Check for a subroutine call */
|
||||
if (E->OPC == OP65_JSR) {
|
||||
/* Handling depends if we're inside a sequence or not */
|
||||
if (InSeq) {
|
||||
|
||||
/* We look for two things: A call to pushax, and a call to one
|
||||
* of the known functions we're going to replace. We're only
|
||||
* interested in the latter ones, if we had a push before.
|
||||
*/
|
||||
if (strcmp (E->Arg, "pushax") == 0) {
|
||||
/* Subroutine call? */
|
||||
if (E->OPC == OP65_JSR) {
|
||||
|
||||
/* Just remember it */
|
||||
LastPush = I;
|
||||
/* Check if this is one of our functions */
|
||||
const OptFuncDesc* F = FindFunc (E->Arg);
|
||||
if (F) {
|
||||
|
||||
} else if (LastPush >= 0) {
|
||||
/* Determine the register to use */
|
||||
const char* ZPLo;
|
||||
const char* ZPHi;
|
||||
UsedRegs |= GetRegInfo (S, I+1, REG_SREG | REG_PTR1 | REG_PTR2);
|
||||
if ((UsedRegs & REG_SREG) == REG_NONE) {
|
||||
/* SREG is available */
|
||||
ZPLo = "sreg";
|
||||
ZPHi = "sreg+1";
|
||||
} else if ((UsedRegs & REG_PTR1) == REG_NONE) {
|
||||
ZPLo = "ptr1";
|
||||
ZPHi = "ptr1+1";
|
||||
} else if ((UsedRegs & REG_PTR2) == REG_NONE) {
|
||||
ZPLo = "ptr2";
|
||||
ZPHi = "ptr2+1";
|
||||
} else {
|
||||
/* No registers available */
|
||||
ZPLo = 0;
|
||||
ZPHi = 0;
|
||||
}
|
||||
|
||||
if (strcmp (E->Arg, "tosaddax") == 0) {
|
||||
Changes += Opt_tosaddax (S, LastPush, I);
|
||||
LastPush = -1;
|
||||
} else if (strcmp (E->Arg, "staspidx") == 0) {
|
||||
Changes += Opt_staspidx (S, LastPush, I);
|
||||
LastPush = -1;
|
||||
/* If we have a register, call the optimizer function */
|
||||
if (ZPLo && ZPHi) {
|
||||
Changes += F->Func (S, Push, I, ZPLo, ZPHi);
|
||||
}
|
||||
|
||||
/* End of sequence */
|
||||
InSeq = 0;
|
||||
|
||||
} else if (strcmp (E->Arg, "pushax") == 0) {
|
||||
/* Restart the sequence */
|
||||
Push = I;
|
||||
UsedRegs = REG_NONE;
|
||||
} else {
|
||||
/* A call to an unkown subroutine ends the sequence */
|
||||
InSeq = 0;
|
||||
}
|
||||
|
||||
} else if ((E->Info & OF_BRA) != 0 ||
|
||||
(E->Use & REG_SP) != 0 ||
|
||||
CE_HasLabel (E)) {
|
||||
|
||||
/* All this stuff is not allowed in a sequence */
|
||||
InSeq = 0;
|
||||
|
||||
} else {
|
||||
|
||||
/* Other stuff: Track zeropage register usage */
|
||||
UsedRegs |= (E->Use | E->Chg);
|
||||
|
||||
}
|
||||
|
||||
} else if (CE_IsCall (E, "pushax")) {
|
||||
|
||||
/* This starts a sequence */
|
||||
Push = I;
|
||||
UsedRegs = REG_NONE;
|
||||
InSeq = 1;
|
||||
|
||||
}
|
||||
|
||||
/* Next entry */
|
||||
|
Loading…
Reference in New Issue
Block a user