diff --git a/src/cc65/codeent.c b/src/cc65/codeent.c index f2661e1e5..2a0c6164f 100644 --- a/src/cc65/codeent.c +++ b/src/cc65/codeent.c @@ -33,9 +33,11 @@ +#include #include /* common */ +#include "chartype.h" #include "check.h" #include "xmalloc.h" @@ -90,6 +92,47 @@ static char* GetArgCopy (const char* Arg) +static int NumArg (const char* Arg, unsigned long* Num) +/* If the given argument is numerical, convert it and return true. Otherwise + * set Num to zero and return false. + */ +{ + char* End; + unsigned long Val; + + /* Determine the base */ + int Base = 10; + if (*Arg == '$') { + ++Arg; + Base = 16; + } else if (*Arg == '%') { + ++Arg; + Base = 2; + } + + /* Convert the value. strtol is not exactly what we want here, but it's + * cheap and may be replaced by something fancier later. + */ + Val = strtoul (Arg, &End, Base); + + /* Check if the conversion was successful */ + if (*End != '\0') { + + /* Could not convert */ + *Num = 0; + return 0; + + } else { + + /* Conversion ok */ + *Num = Val; + return 1; + + } +} + + + /*****************************************************************************/ /* Code */ /*****************************************************************************/ @@ -108,8 +151,11 @@ CodeEntry* NewCodeEntry (const OPCDesc* D, am_t AM, const char* Arg, CodeLabel* E->Size = GetInsnSize (E->OPC, E->AM); E->Hints = 0; E->Arg = GetArgCopy (Arg); - E->Num = 0; - E->Flags = 0; + if (NumArg (E->Arg, &E->Num)) { + E-> Flags = CEF_NUMARG; + } else { + E->Flags = 0; + } E->Info = D->Info; E->Use = D->Use; E->Chg = D->Chg; @@ -118,7 +164,7 @@ CodeEntry* NewCodeEntry (const OPCDesc* D, am_t AM, const char* Arg, CodeLabel* GetFuncInfo (E->Arg, &E->Use, &E->Chg); } else { /* Some other instruction */ - E->Use |= GetAMUseInfo (AM); + E->Use |= GetAMUseInfo (E->AM); } E->JumpTo = JumpTo; InitCollection (&E->Labels); @@ -149,6 +195,31 @@ void FreeCodeEntry (CodeEntry* E) +void ReplaceOPC (CodeEntry* E, opc_t OPC) +/* Replace the opcode of the instruction. This will also replace related info, + * Size, Use and Chg, but it will NOT update any arguments or labels. + */ +{ + /* Get the opcode descriptor */ + const OPCDesc* D = GetOPCDesc (OPC); + + /* Replace the opcode */ + E->OPC = OPC; + E->Size = GetInsnSize (E->OPC, E->AM); + E->Info = D->Info; + E->Use = D->Use; + E->Chg = D->Chg; + if (E->OPC == OPC_JSR) { + /* A subroutine call */ + GetFuncInfo (E->Arg, &E->Use, &E->Chg); + } else { + /* Some other instruction */ + E->Use |= GetAMUseInfo (E->AM); + } +} + + + int CodeEntriesAreEqual (const CodeEntry* E1, const CodeEntry* E2) /* Check if both code entries are equal */ { diff --git a/src/cc65/codeent.h b/src/cc65/codeent.h index ba704eb28..e862a197a 100644 --- a/src/cc65/codeent.h +++ b/src/cc65/codeent.h @@ -67,7 +67,7 @@ struct CodeEntry { unsigned char Size; /* Estimated size */ unsigned char Hints; /* Hints for this entry */ char* Arg; /* Argument as string */ - unsigned Num; /* Numeric argument */ + unsigned long Num; /* Numeric argument */ unsigned short Flags; /* Flags */ unsigned char Info; /* Additional code info */ unsigned char Use; /* Registers used */ @@ -90,6 +90,11 @@ CodeEntry* NewCodeEntry (const OPCDesc* D, am_t AM, const char* Arg, CodeLabel* void FreeCodeEntry (CodeEntry* E); /* Free the given code entry */ +void ReplaceOPC (CodeEntry* E, opc_t OPC); +/* Replace the opcode of the instruction. This will also replace related info, + * Size, Use and Chg, but it will NOT update any arguments or labels. + */ + int CodeEntriesAreEqual (const CodeEntry* E1, const CodeEntry* E2); /* Check if both code entries are equal */ diff --git a/src/cc65/codeopt.c b/src/cc65/codeopt.c index 90417a537..b52212dbb 100644 --- a/src/cc65/codeopt.c +++ b/src/cc65/codeopt.c @@ -33,13 +33,17 @@ +#include + /* common */ +#include "abend.h" #include "print.h" /* cc65 */ #include "asmlabel.h" #include "codeent.h" #include "codeinfo.h" +#include "error.h" #include "global.h" #include "codeopt.h" @@ -56,10 +60,67 @@ */ static unsigned OptChanges; +/* 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 +}; + /*****************************************************************************/ -/* Remove dead jumps */ +/* Helper functions */ +/*****************************************************************************/ + + + +static cmp_t FindCmpCond (const char* Suffix) +/* Map a condition suffix to a code. Return the code or CMP_INV on failure */ +{ + int I; + + /* Linear search */ + for (I = 0; I < sizeof (CmpSuffixTab) / sizeof (CmpSuffixTab [0]); ++I) { + if (strcmp (Suffix, CmpSuffixTab [I]) == 0) { + /* Found */ + return I; + } + } + + /* Not found */ + return CMP_INV; +} + + + +/*****************************************************************************/ +/* Remove dead jumps */ /*****************************************************************************/ @@ -109,7 +170,7 @@ static void OptDeadJumps (CodeSeg* S) /*****************************************************************************/ -/* Remove dead code */ +/* Remove dead code */ /*****************************************************************************/ @@ -237,7 +298,7 @@ static void OptJumpCascades (CodeSeg* S) ++OptChanges; /* Done */ - goto NextEntry; + continue; } @@ -260,7 +321,7 @@ NextEntry: /*****************************************************************************/ -/* Optimize jsr/rts */ +/* Optimize jsr/rts */ /*****************************************************************************/ @@ -377,7 +438,7 @@ static void OptJumpTarget (CodeSeg* S) * move references to this label to the new label. */ if (CodeEntryHasLabel (E1)) { - MoveCodeLabels (S, E1, T1); + MoveCodeLabels (S, E1, T1); } /* Remove the entry preceeding the jump */ @@ -398,44 +459,315 @@ NextEntry: +/*****************************************************************************/ +/* Remove conditional jumps never taken */ +/*****************************************************************************/ + + + +static void OptDeadCondBranches (CodeSeg* S) +/* If an immidiate load of a register is followed by a conditional jump that + * is never taken because the load of the register sets the flags in such a + * manner, remove the conditional branch. + */ +{ + unsigned I; + + /* Get the number of entries, bail out if we have not enough */ + unsigned Count = GetCodeEntryCount (S); + if (Count < 2) { + return; + } + + /* Walk over the entries */ + I = 0; + while (I < Count-1) { + + /* Get next entry */ + CodeEntry* E = GetCodeEntry (S, I); + + /* Check if it's a register load */ + if ((E->Info & OF_LOAD) != 0 && E->AM == AM_IMM && (E->Flags & CEF_NUMARG) != 0) { + + bc_t BC; + + /* Immidiate register load, get next instruction */ + CodeEntry* N = GetCodeEntry (S, I+1); + + /* Check if the following insn is a conditional branch or if it + * has a label attached. + */ + if ((N->Info & OF_CBRA) == 0 || CodeEntryHasLabel (E)) { + /* No conditional jump or label attached, bail out */ + goto NextEntry; + } + + /* Get the branch condition */ + BC = GetBranchCond (N->OPC); + + /* Check the argument against the branch condition */ + if ((BC == BC_EQ && E->Num != 0) || + (BC == BC_NE && E->Num == 0) || + (BC == BC_PL && (E->Num & 0x80) != 0) || + (BC == BC_MI && (E->Num & 0x80) == 0)) { + + /* Remove the conditional branch */ + DelCodeEntry (S, I+1); + --Count; + + /* Remember, we had changes */ + ++OptChanges; + + } + } + +NextEntry: + /* Next entry */ + ++I; + + } +} + + + +/*****************************************************************************/ +/* Remove calls to the bool transformer subroutines */ +/*****************************************************************************/ + + + +static void OptBoolTransforms (CodeSeg* S) +/* Try to remove the call to boolean transformer routines where the call is + * not really needed. + */ +{ + unsigned I; + + /* Get the number of entries, bail out if we have not enough */ + unsigned Count = GetCodeEntryCount (S); + if (Count < 2) { + return; + } + + /* Walk over the entries */ + I = 0; + while (I < Count-1) { + + /* Get next entry */ + CodeEntry* E = GetCodeEntry (S, I); + + /* Check for a boolean transformer */ + if (E->OPC == OPC_JSR && strncmp (E->Arg, "bool", 4) == 0) { + + cmp_t Cond; + + /* Get the next entry */ + CodeEntry* N = GetCodeEntry (S, I+1); + + /* Check if this is a conditional branch */ + if ((N->Info & OF_CBRA) == 0) { + /* No conditional branch, bail out */ + goto NextEntry; + } + + /* 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. + */ + Cond = FindCmpCond (E->Arg + 4); + if (Cond == CMP_INV) { + /* Unknown function */ + goto NextEntry; + } + + /* 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 */ + switch (Cond) { + + case CMP_EQ: + ReplaceOPC (N, OPC_JEQ); + break; + + case CMP_NE: + ReplaceOPC (N, OPC_JNE); + break; + + case CMP_GT: + /* Not now ### */ + goto NextEntry; + + case CMP_GE: + ReplaceOPC (N, OPC_JPL); + break; + + case CMP_LT: + ReplaceOPC (N, OPC_JMI); + break; + + case CMP_LE: + /* Not now ### */ + goto NextEntry; + + case CMP_UGT: + /* Not now ### */ + goto NextEntry; + + case CMP_UGE: + ReplaceOPC (N, OPC_JCS); + break; + + case CMP_ULT: + ReplaceOPC (N, OPC_JCC); + break; + + case CMP_ULE: + /* Not now ### */ + goto NextEntry; + + default: + Internal ("Unknown jump condition: %d", Cond); + + } + + /* Remove the call to the bool transformer */ + DelCodeEntry (S, I); + --Count; + + /* Remember, we had changes */ + ++OptChanges; + + } + +NextEntry: + /* Next entry */ + ++I; + + } +} + + + /*****************************************************************************/ /* Code */ /*****************************************************************************/ +/* Table with all the optimization functions */ +typedef struct OptFunc OptFunc; +struct OptFunc { + void (*Func) (CodeSeg*); /* Optimizer function */ + const char* Name; /* Name of optimizer step */ + char Disabled; /* True if pass disabled */ +}; + + + +/* Table with optimizer steps - are called in this order */ +static OptFunc OptFuncs [] = { + /* Optimize jump cascades */ + { OptJumpCascades, "OptJumpCascades", 0 }, + /* Remove dead jumps */ + { OptDeadJumps, "OptDeadJumps", 0 }, + /* Change jsr/rts to jmp */ + { OptRTS, "OptRTS", 0 }, + /* Remove dead code */ + { OptDeadCode, "OptDeadCode", 0 }, + /* Optimize jump targets */ + { OptJumpTarget, "OptJumpTarget", 0 }, + /* Remove dead conditional branches */ + { OptDeadCondBranches, "OptDeadCondBranches", 0 }, + /* Remove calls to the bool transformer subroutines */ + { OptBoolTransforms, "OptBoolTransforms", 0 }, +}; + + + +static OptFunc* FindOptStep (const char* Name) +/* Find an optimizer step by name in the table and return a pointer. Print an + * error and cann 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 */ +{ + OptFunc* F = FindOptStep (Name); + F->Disabled = 1; +} + + + +void EnableOpt (const char* Name) +/* Enable the optimization with the given name */ +{ + OptFunc* F = FindOptStep (Name); + F->Disabled = 0; +} + + + void RunOpt (CodeSeg* S) /* Run the optimizer */ { - typedef void (*OptFunc) (CodeSeg*); + unsigned Pass = 0; - /* Table with optimizer steps - are called in this order */ - static const OptFunc OptFuncs [] = { - OptJumpCascades, /* Optimize jump cascades */ - OptDeadJumps, /* Remove dead jumps */ - OptDeadCode, /* Remove dead code */ - OptRTS, /* Change jsr/rts to jmp */ - OptJumpTarget, /* Optimize jump targets */ - }; + /* 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 */ do { - unsigned long Flags; - unsigned I; + unsigned I; /* Reset the number of changes */ OptChanges = 0; + /* Keep the user hapy */ + Print (stdout, 1, " Optimizer pass %u:\n", ++Pass); + /* Run all optimization steps */ - Flags = 1UL; for (I = 0; I < sizeof(OptFuncs)/sizeof(OptFuncs[0]); ++I) { - if ((OptDisable & Flags) == 0) { - OptFuncs[I] (S); - } else if (Verbosity > 0 || Debug) { - printf ("Optimizer pass %u skipped\n", I); + + /* Print the name of the following optimizer step */ + Print (stdout, 1, " %s:%*s", OptFuncs[I].Name, + (int) (30-strlen(OptFuncs[I].Name)), ""); + + /* Check if the step is disabled */ + if (OptFuncs[I].Disabled) { + Print (stdout, 1, "Disabled\n"); + } else { + unsigned Changes = OptChanges; + OptFuncs[I].Func (S); + Changes = OptChanges - Changes; + Print (stdout, 1, "%u Changes\n", Changes); } - Flags <<= 1; } } while (OptChanges > 0); diff --git a/src/cc65/codeopt.h b/src/cc65/codeopt.h index 15960a99f..72f9c44bb 100644 --- a/src/cc65/codeopt.h +++ b/src/cc65/codeopt.h @@ -55,6 +55,12 @@ +void DisableOpt (const char* Name); +/* Disable the optimization with the given name */ + +void EnableOpt (const char* Name); +/* Enable the optimization with the given name */ + void RunOpt (CodeSeg* S); /* Run the optimizer */ diff --git a/src/cc65/main.c b/src/cc65/main.c index 65346db94..874e9b4b1 100644 --- a/src/cc65/main.c +++ b/src/cc65/main.c @@ -52,6 +52,7 @@ /* cc65 */ #include "asmcode.h" #include "compile.h" +#include "codeopt.h" #include "cpu.h" #include "error.h" #include "global.h" @@ -103,6 +104,8 @@ static void Usage (void) " --data-name seg\tSet the name of the DATA segment\n" " --debug\t\tDebug mode\n" " --debug-info\t\tAdd debug info to object file\n" + " --disable-opt name\tDisable an optimization step\n" + " --enable-opt name\tEnable an optimization step\n" " --help\t\tHelp (this text)\n" " --include-dir dir\tSet an include directory search path\n" " --rodata-name seg\tSet the name of the RODATA segment\n" @@ -377,6 +380,22 @@ static void OptDebugInfo (const char* Opt, const char* Arg) +static void OptDisableOpt (const char* Opt, const char* Arg) +/* Disable an optimization step */ +{ + DisableOpt (Arg); +} + + + +static void OptEnableOpt (const char* Opt, const char* Arg) +/* Enable an optimization step */ +{ + EnableOpt (Arg); +} + + + static void OptHelp (const char* Opt, const char* Arg) /* Print usage information and exit */ { @@ -463,6 +482,8 @@ int main (int argc, char* argv[]) { "--data-name", 1, OptDataName }, { "--debug", 0, OptDebug }, { "--debug-info", 0, OptDebugInfo }, + { "--disable-opt", 1, OptDisableOpt }, + { "--enable-opt", 1, OptEnableOpt, }, { "--help", 0, OptHelp }, { "--include-dir", 1, OptIncludeDir }, { "--rodata-name", 1, OptRodataName }, diff --git a/src/cc65/opcodes.c b/src/cc65/opcodes.c index 79f6658f5..fb765c296 100644 --- a/src/cc65/opcodes.c +++ b/src/cc65/opcodes.c @@ -96,9 +96,9 @@ static const OPCDesc OPCTable[OPC_COUNT] = { { OPC_JSR, "jsr", 3, REG_NONE, REG_NONE, OF_NONE }, { OPC_JVC, "jvc", 5, REG_NONE, REG_NONE, OF_CBRA }, { OPC_JVS, "jvs", 5, REG_NONE, REG_NONE, OF_CBRA }, - { OPC_LDA, "lda", 0, REG_NONE, REG_A, OF_NONE }, - { OPC_LDX, "ldx", 0, REG_NONE, REG_X, OF_NONE }, - { OPC_LDY, "ldy", 0, REG_NONE, REG_Y, OF_NONE }, + { OPC_LDA, "lda", 0, REG_NONE, REG_A, OF_LOAD }, + { OPC_LDX, "ldx", 0, REG_NONE, REG_X, OF_LOAD }, + { OPC_LDY, "ldy", 0, REG_NONE, REG_Y, OF_LOAD }, { OPC_LSR, "lsr", 0, REG_A, REG_A, OF_NONE }, { OPC_NOP, "nop", 1, REG_NONE, REG_NONE, OF_NONE }, { OPC_ORA, "ora", 0, REG_A, REG_A, OF_NONE }, diff --git a/src/cc65/opcodes.h b/src/cc65/opcodes.h index df590fd14..fcd4aa444 100644 --- a/src/cc65/opcodes.h +++ b/src/cc65/opcodes.h @@ -156,6 +156,7 @@ typedef enum { #define OF_UBRA 0x0001U /* Unconditional branch */ #define OF_CBRA 0x0002U /* Conditional branch */ #define OF_RET 0x0004U /* Return from function */ +#define OF_LOAD 0x0008U /* Register load */ #define OF_BRA (OF_UBRA|OF_CBRA) /* Operation is a jump/branch */ #define OF_DEAD (OF_UBRA|OF_RET) /* Dead end - no exec behind this point */