From fe86149203d171dd8746b365a52ef2097897f5df Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Sun, 6 Jul 2025 13:28:53 +0200 Subject: [PATCH 1/2] Further optimize inc/decsp The optimizer can now merge incsp+decsp together, and merge jsr incsp/decsp + jmp incsp/decsp together. Drop the code altogether if both instructions cancel each other out. Fixes #2748 --- src/cc65/codeopt.c | 5 +++ src/cc65/coptmisc.c | 76 +++++++++++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/cc65/codeopt.c b/src/cc65/codeopt.c index 988e2d770..d1e92b049 100644 --- a/src/cc65/codeopt.c +++ b/src/cc65/codeopt.c @@ -907,6 +907,11 @@ static unsigned RunOptGroup7 (CodeSeg* S) between branches */ C += RunOptFunc (S, &DOptBranchDist, 3); + /* Re-optimize inc/decsp that may now be grouped */ + C += RunOptFunc (S, &DOptStackPtrOps, 5); + /* Re-optimize JSR/RTS that may now be grouped */ + C += RunOptFunc (S, &DOptRTS, 1); + Changes += C; /* If we had changes, we must run dead code elimination again, ** since the changes may have introduced dead code. diff --git a/src/cc65/coptmisc.c b/src/cc65/coptmisc.c index a2cda7495..eaa73871b 100644 --- a/src/cc65/coptmisc.c +++ b/src/cc65/coptmisc.c @@ -380,28 +380,37 @@ unsigned OptIndLoads2 (CodeSeg* S) -static unsigned IsDecSP (const CodeEntry* E) -/* Check if this is an insn that decrements the stack pointer. If so, return -** the decrement. If not, return zero. -** The function expects E to be a subroutine call. +static signed IsSPShift (const CodeEntry* E) +/* Check if this is an insn that increments/decrements the stack pointer. +** If so, return the value (negative for dec, positive for inc). If not, return zero. */ { + if (E->OPC != OP65_JSR && E->OPC != OP65_JMP) { + return 0; + } + if (strncmp (E->Arg, "decsp", 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)) { + return -(E->RI->In.RegY); + } else if (strncmp (E->Arg, "incsp", 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)) { - return E->RI->In.RegY; + } else if (strcmp (E->Arg, "addysp") == 0 && RegValIsKnown (E->RI->In.RegY)) { + return (E->RI->In.RegY); } - /* If we come here, it's not a decsp op */ + /* If we come here, it's not a dec/incsp op */ return 0; } unsigned OptStackPtrOps (CodeSeg* S) -/* Merge adjacent calls to decsp into one. NOTE: This function won't merge all +/* Merge adjacent calls to inc/decsp into one. NOTE: This function won't merge all ** known cases! */ { @@ -412,38 +421,45 @@ unsigned OptStackPtrOps (CodeSeg* S) I = 0; while (I < CS_GetEntryCount (S)) { - unsigned Dec1; - unsigned Dec2; + signed Val1; + signed Val2; const CodeEntry* N; /* Get the next entry */ const CodeEntry* E = CS_GetEntry (S, I); - /* Check for decspn or subysp */ - if (E->OPC == OP65_JSR && - (Dec1 = IsDecSP (E)) > 0 && - (N = CS_GetNextEntry (S, I)) != 0 && - (Dec2 = IsDecSP (N)) > 0 && - (Dec1 += Dec2) <= 255 && + /* Check for decspn, incspn, subysp or addysp */ + if (E->OPC == OP65_JSR && + (Val1 = IsSPShift (E)) != 0 && + (N = CS_GetNextEntry (S, I)) != 0 && + (N->OPC == OP65_JSR || N->OPC == OP65_JMP) && + (Val2 = IsSPShift (N)) != 0 && + abs(Val1 += Val2) <= 255 && !CE_HasLabel (N)) { CodeEntry* X; char Buf[20]; - /* We can combine the two */ - if (Dec1 <= 8) { - /* Insert a call to decsp */ - xsprintf (Buf, sizeof (Buf), "decsp%u", Dec1); - X = NewCodeEntry (OP65_JSR, AM65_ABS, Buf, 0, N->LI); - CS_InsertEntry (S, X, I+2); - } else { - /* Insert a call to subysp */ - const char* Arg = MakeHexArg (Dec1); - X = NewCodeEntry (OP65_LDY, AM65_IMM, Arg, 0, N->LI); - CS_InsertEntry (S, X, I+2); - X = NewCodeEntry (OP65_JSR, AM65_ABS, "subysp", 0, N->LI); - CS_InsertEntry (S, X, I+3); - } + 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)); + X = NewCodeEntry (N->OPC, AM65_ABS, Buf, 0, N->LI); + CS_InsertEntry (S, X, I+2); + } else { + /* Insert a call to subysp */ + 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); + } else { + X = NewCodeEntry (N->OPC, AM65_ABS, "addysp", 0, N->LI); + } + CS_InsertEntry (S, X, I+3); + } + } /* If total shift == 0, just drop the old code. */ /* Delete the old code */ CS_DelEntries (S, I, 2); From facf7b2a0c7c81e65275685641742e0a91426090 Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Sun, 6 Jul 2025 18:35:21 +0200 Subject: [PATCH 2/2] Add basic test for incsp optimisation --- test/val/bug2748.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/val/bug2748.c diff --git a/test/val/bug2748.c b/test/val/bug2748.c new file mode 100644 index 000000000..c1bee3257 --- /dev/null +++ b/test/val/bug2748.c @@ -0,0 +1,32 @@ +#include "unittest.h" + +int func(int expr) +{ + { + int i = 5; + return i; + } +} + +static size_t c_sp_before, c_sp_after; + +TEST +{ + int a = 11; + int b; + + __asm__("lda c_sp"); + __asm__("ldx c_sp+1"); + c_sp_before = __AX__; + + b = func(a); + + __asm__("lda c_sp"); + __asm__("ldx c_sp+1"); + c_sp_after = __AX__; + + ASSERT_IsTrue(c_sp_before == c_sp_after, "Unexpected stack pointer"); + ASSERT_IsTrue(b == 5, "Wrong value for b"); + ASSERT_IsTrue(a == 11, "Wrong value for a"); +} +ENDTEST