From 20a9db757d1b1661660c60deb30511082c3dccae Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Sun, 6 Jul 2025 18:40:04 +0200 Subject: [PATCH] Optimize multiple incax* and incax*/ldaxi - Group multiple calls to incax* and decax* into a single one - Replace incaxN/jsr ldaxi with ldy #N+1/jsr ldaxidx Fixes #2055 --- src/cc65/codeopt.c | 5 +++ src/cc65/coptmisc.c | 103 +++++++++++++++++++++++++++++++++++++------- src/cc65/coptmisc.h | 10 ++++- test/val/bug2205.c | 20 +++++++++ 4 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 test/val/bug2205.c diff --git a/src/cc65/codeopt.c b/src/cc65/codeopt.c index d1e92b049..eccceccb3 100644 --- a/src/cc65/codeopt.c +++ b/src/cc65/codeopt.c @@ -113,6 +113,8 @@ static OptFunc DOptAdd3 = { OptAdd3, "OptAdd3", 65, 0, static OptFunc DOptAdd4 = { OptAdd4, "OptAdd4", 90, 0, 0, 0, 0, 0 }; static OptFunc DOptAdd5 = { OptAdd5, "OptAdd5", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptAdd6 = { OptAdd6, "OptAdd6", 40, 0, 0, 0, 0, 0 }; +static OptFunc DOptAXLoad = { OptAXLoad, "OptAXLoad", 50, 0, 0, 0, 0, 0 }; +static OptFunc DOptAXOps = { OptAXOps, "OptAXOps", 50, 0, 0, 0, 0, 0 }; static OptFunc DOptBNegA1 = { OptBNegA1, "OptBNegA1", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptBNegA2 = { OptBNegA2, "OptBNegA2", 100, 0, 0, 0, 0, 0 }; static OptFunc DOptBNegAX1 = { OptBNegAX1, "OptBNegAX1", 100, 0, 0, 0, 0, 0 }; @@ -236,6 +238,7 @@ static OptFunc* OptFuncs[] = { &DOptAdd4, &DOptAdd5, &DOptAdd6, + &DOptAXOps, &DOptBNegA1, &DOptBNegA2, &DOptBNegAX1, @@ -629,6 +632,7 @@ static unsigned RunOptGroup1 (CodeSeg* S) Changes += RunOptFunc (S, &DOptGotoSPAdj, 1); Changes += RunOptFunc (S, &DOptStackPtrOps, 5); + Changes += RunOptFunc (S, &DOptAXOps, 5); Changes += RunOptFunc (S, &DOptAdd3, 1); /* Before OptPtrLoad5! */ Changes += RunOptFunc (S, &DOptPtrStore1, 1); Changes += RunOptFunc (S, &DOptPtrStore2, 1); @@ -871,6 +875,7 @@ static unsigned RunOptGroup7 (CodeSeg* S) ** may have opened new oportunities. */ Changes += RunOptFunc (S, &DOptUnusedLoads, 1); + Changes += RunOptFunc (S, &DOptAXLoad, 5); Changes += RunOptFunc (S, &DOptUnusedStores, 1); Changes += RunOptFunc (S, &DOptJumpTarget1, 5); Changes += RunOptFunc (S, &DOptStore5, 1); diff --git a/src/cc65/coptmisc.c b/src/cc65/coptmisc.c index eaa73871b..0ba447562 100644 --- a/src/cc65/coptmisc.c +++ b/src/cc65/coptmisc.c @@ -380,8 +380,8 @@ unsigned OptIndLoads2 (CodeSeg* S) -static signed IsSPShift (const CodeEntry* E) -/* Check if this is an insn that increments/decrements the stack pointer. +static signed IsShift (const CodeEntry* E, const char* dec, const char* inc, const char* sub, const char* add) +/* Check if this is an insn that increments/decrements the stack pointer or AX. ** If so, return the value (negative for dec, positive for inc). If not, return zero. */ { @@ -389,28 +389,28 @@ static signed IsSPShift (const CodeEntry* E) return 0; } - if (strncmp (E->Arg, "decsp", 5) == 0) { + if (strncmp (E->Arg, dec, 5) == 0) { if (E->Arg[5] >= '1' && E->Arg[5] <= '8') { return -(E->Arg[5] - '0'); } - } else if (strcmp (E->Arg, "subysp") == 0 && RegValIsKnown (E->RI->In.RegY)) { + } else if (strcmp (E->Arg, sub) == 0 && RegValIsKnown (E->RI->In.RegY)) { return -(E->RI->In.RegY); - } else if (strncmp (E->Arg, "incsp", 5) == 0) { + } else if (strncmp (E->Arg, inc, 5) == 0) { if (E->Arg[5] >= '1' && E->Arg[5] <= '8') { return (E->Arg[5] - '0'); } - } else if (strcmp (E->Arg, "addysp") == 0 && RegValIsKnown (E->RI->In.RegY)) { + } else if (strcmp (E->Arg, add) == 0 && RegValIsKnown (E->RI->In.RegY)) { return (E->RI->In.RegY); } - /* If we come here, it's not a dec/incsp op */ + /* If we come here, it's not a dec/inc op */ return 0; } -unsigned OptStackPtrOps (CodeSeg* S) -/* Merge adjacent calls to inc/decsp into one. NOTE: This function won't merge all +static unsigned OptIncDecOps (CodeSeg* S, const char* dec, const char* inc, const char* sub, const char* add) +/* Merge adjacent calls to inc/dec/add/sub into one. NOTE: This function won't merge all ** known cases! */ { @@ -430,10 +430,10 @@ unsigned OptStackPtrOps (CodeSeg* S) /* Check for decspn, incspn, subysp or addysp */ if (E->OPC == OP65_JSR && - (Val1 = IsSPShift (E)) != 0 && + (Val1 = IsShift (E, dec, inc, sub, add)) != 0 && (N = CS_GetNextEntry (S, I)) != 0 && (N->OPC == OP65_JSR || N->OPC == OP65_JMP) && - (Val2 = IsSPShift (N)) != 0 && + (Val2 = IsShift (N, dec, inc, sub, add)) != 0 && abs(Val1 += Val2) <= 255 && !CE_HasLabel (N)) { @@ -443,19 +443,19 @@ unsigned OptStackPtrOps (CodeSeg* S) if (Val1 != 0) { /* We can combine the two */ if (abs(Val1) <= 8) { - /* Insert a call to inc/decsp using the last OPC */ - xsprintf (Buf, sizeof (Buf), "%ssp%u", Val1 < 0 ? "dec":"inc", abs(Val1)); + /* Insert a call to inc/dec using the last OPC */ + xsprintf (Buf, sizeof (Buf), "%s%u", Val1 < 0 ? dec:inc, abs(Val1)); X = NewCodeEntry (N->OPC, AM65_ABS, Buf, 0, N->LI); CS_InsertEntry (S, X, I+2); } else { - /* Insert a call to subysp */ + /* Insert a call to add/sub */ const char* Arg = MakeHexArg (abs(Val1)); X = NewCodeEntry (OP65_LDY, AM65_IMM, Arg, 0, N->LI); CS_InsertEntry (S, X, I+2); if (Val1 < 0) { - X = NewCodeEntry (N->OPC, AM65_ABS, "subysp", 0, N->LI); + X = NewCodeEntry (N->OPC, AM65_ABS, sub, 0, N->LI); } else { - X = NewCodeEntry (N->OPC, AM65_ABS, "addysp", 0, N->LI); + X = NewCodeEntry (N->OPC, AM65_ABS, add, 0, N->LI); } CS_InsertEntry (S, X, I+3); } @@ -484,6 +484,77 @@ unsigned OptStackPtrOps (CodeSeg* S) +unsigned OptStackPtrOps (CodeSeg* S) +{ + return OptIncDecOps(S, "decsp", "incsp", "subysp", "addysp"); +} + + + +unsigned OptAXOps (CodeSeg* S) +{ + return OptIncDecOps(S, "decax", "incax", "decaxy", "incaxy"); +} + + + +unsigned OptAXLoad (CodeSeg* S) +/* Merge jsr incax/jsr ldaxi into ldy/jsr ldaxidx */ +{ + unsigned Changes = 0; + unsigned I; + + /* Walk over the entries */ + I = 0; + while (I < CS_GetEntryCount (S)) { + + signed Val; + const CodeEntry* N; + + /* Get the next entry */ + const CodeEntry* E = CS_GetEntry (S, I); + + /* Check for incax followed by jsr/jmp ldaxi */ + if (E->OPC == OP65_JSR && + strncmp (E->Arg, "incax", 5) == 0 && + (N = CS_GetNextEntry (S, I)) != 0 && + (N->OPC == OP65_JSR || N->OPC == OP65_JMP) && + strcmp (N->Arg, "ldaxi") == 0 && + !CE_HasLabel (N)) { + + CodeEntry* X; + const char* Arg; + + Val = E->Arg[5] - '0'; + Arg = MakeHexArg (Val + 1); + X = NewCodeEntry (OP65_LDY, AM65_IMM, Arg, 0, N->LI); + CS_InsertEntry (S, X, I+2); + X = NewCodeEntry (N->OPC, AM65_ABS, "ldaxidx", 0, N->LI); + CS_InsertEntry (S, X, I+3); + + /* Delete the old code */ + CS_DelEntries (S, I, 2); + + /* Regenerate register info */ + CS_GenRegInfo (S); + + /* Remember we had changes */ + ++Changes; + + } else { + + /* Next entry */ + ++I; + } + + } + + /* Return the number of changes made */ + return Changes; +} + + + unsigned OptGotoSPAdj (CodeSeg* S) /* Optimize SP adjustment for forward 'goto' */ { diff --git a/src/cc65/coptmisc.h b/src/cc65/coptmisc.h index aceaa55d3..cc6d1c519 100644 --- a/src/cc65/coptmisc.h +++ b/src/cc65/coptmisc.h @@ -92,10 +92,18 @@ unsigned OptIndLoads2 (CodeSeg* S); */ unsigned OptStackPtrOps (CodeSeg* S); -/* Merge adjacent calls to decsp into one. NOTE: This function won't merge all +/* Merge adjacent calls to decsp/incax into one. NOTE: This function won't merge all ** known cases! */ +unsigned OptAXOps (CodeSeg* S); +/* Merge adjacent calls to decax/incax into one. NOTE: This function won't merge all +** known cases! +*/ + +unsigned OptAXLoad (CodeSeg* S); +/* Merge adjacent calls to incax/ldaxi into ldy/ldaxidx */ + unsigned OptGotoSPAdj (CodeSeg* S); /* Optimize SP adjustment for forward 'goto' */ diff --git a/test/val/bug2205.c b/test/val/bug2205.c new file mode 100644 index 000000000..1b5458249 --- /dev/null +++ b/test/val/bug2205.c @@ -0,0 +1,20 @@ +#include "unittest.h" + +struct Object +{ + int a; + int data[10]; +}; + +struct Object object_data = { 0x0102, {0x0304, 0x0506, + 0x0708, 0x090A, 0x0B0C, + 0x0D0E, 0x0F10, 0x1112, + 0x1314, 0x1516}}; + +TEST +{ + struct Object *o = &object_data; + ASSERT_IsTrue(o->a == 0x0102, "Wrong value for a"); + ASSERT_IsTrue(o->data[2] == 0x0708, "Wrong value for data[2]"); +} +ENDTEST