From 7a706901a425d58824dea535bec2a22275f00d4a Mon Sep 17 00:00:00 2001 From: Jan Mercl <0xjnml@gmail.com> Date: Sun, 25 Oct 2026 18:20:48 +0100 Subject: [PATCH 1/3] Let a register local of several longs give its registers back A local held in registers that is larger than a long -- a struct, or a 64 bit value -- is a base register with a subregister for each further long, and RenameSubregs keeps those in consecutive registers. ReuseLocalRegisters leaves subregisters alone, so such a local keeps all its registers to the end of the function. A function passing many struct values to calls, each a compound literal or a temporary of its own, runs out of registers ("fit 480 failed"), or past the limit crashes. SplitLocalStructs runs before a function's first optimization and splits each such local into one local per long, unless something needs the longs together: the register's address taken for an indexed access (an IMM_COG_LABEL of its name), inline assembly, or an instruction after a SETQ. The optimizer then reuses those registers as it does any other local's. Test/stest322.c passes a struct literal to four calls: the literals took 12 registers and take none, their values moved straight into the argument registers. 15 asm test outputs change: 9 only drop registers reserved for locals the optimizer had already removed, 6 have shorter code (in two of them functions become identical and are merged). --- Test/Expect/stest121.pasm | 6 -- Test/Expect/stest155.pasm | 4 -- Test/Expect/stest191.pasm | 4 -- Test/Expect/stest229.pasm | 6 -- Test/Expect/stest230.pasm | 22 +------- Test/Expect/stest233.pasm | 4 -- Test/Expect/stest249.pasm | 26 ++------- Test/Expect/stest258.pasm | 4 -- Test/Expect/stest275.pasm | 24 +++----- Test/Expect/stest278.pasm | 29 +++------- Test/Expect/stest286.pasm | 4 -- Test/Expect/stest315.pasm | 40 +++---------- Test/Expect/stest317.p2asm | 12 +--- Test/Expect/stest318.p2asm | 8 --- Test/Expect/stest319.p2asm | 4 -- Test/Expect/stest322.pasm | 55 ++++++++++++++++++ Test/stest322.c | 23 ++++++++ backends/asm/outasm.c | 111 +++++++++++++++++++++++++++++++++++++ 18 files changed, 223 insertions(+), 163 deletions(-) create mode 100644 Test/Expect/stest322.pasm create mode 100644 Test/stest322.c diff --git a/Test/Expect/stest121.pasm b/Test/Expect/stest121.pasm index c23eec73c..3b470b76e 100644 --- a/Test/Expect/stest121.pasm +++ b/Test/Expect/stest121.pasm @@ -14,12 +14,6 @@ result1 COG_BSS_START fit 496 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 -_var03 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest155.pasm b/Test/Expect/stest155.pasm index f0de34c9c..f9a0b8d3a 100644 --- a/Test/Expect/stest155.pasm +++ b/Test/Expect/stest155.pasm @@ -32,10 +32,6 @@ COG_BSS_START _dat_ byte $00, $00, $00, $00, $00, $00, $00, $00 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest191.pasm b/Test/Expect/stest191.pasm index 324117b0b..b6a88ab1e 100644 --- a/Test/Expect/stest191.pasm +++ b/Test/Expect/stest191.pasm @@ -48,10 +48,6 @@ _var01 res 1 _var02 res 1 -_var03 - res 1 -_var04 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest229.pasm b/Test/Expect/stest229.pasm index 9d7736543..59a722a88 100644 --- a/Test/Expect/stest229.pasm +++ b/Test/Expect/stest229.pasm @@ -13,12 +13,6 @@ _show_ret COG_BSS_START fit 496 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 -_var03 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest230.pasm b/Test/Expect/stest230.pasm index f15021e80..da182fdb1 100644 --- a/Test/Expect/stest230.pasm +++ b/Test/Expect/stest230.pasm @@ -12,13 +12,9 @@ _show_ret ret _main - mov _main_s__0001, #1 - mov _main_s__0001_01, #2 - mov _main_s__0001_02, #3 - mov arg01, _main_s__0001 - mov arg02, _main_s__0001_01 - mov arg03, _main_s__0001_02 - call #_show + mov outa, #1 + mov outb, #2 + mov ina, #3 mov result1, #0 _main_ret ret @@ -28,18 +24,6 @@ result1 COG_BSS_START fit 496 org COG_BSS_START -_main_s__0001 - res 1 -_main_s__0001_01 - res 1 -_main_s__0001_02 - res 1 -_var01 - res 1 -_var02 - res 1 -_var03 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest233.pasm b/Test/Expect/stest233.pasm index 5e082b43b..30f292fbb 100644 --- a/Test/Expect/stest233.pasm +++ b/Test/Expect/stest233.pasm @@ -14,10 +14,6 @@ result1 COG_BSS_START fit 496 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest249.pasm b/Test/Expect/stest249.pasm index 63defe364..7bcb5838b 100644 --- a/Test/Expect/stest249.pasm +++ b/Test/Expect/stest249.pasm @@ -5,23 +5,19 @@ dat entry _insertstr + add arg01, arg03 mov result1, arg01 - add result1, arg03 mov result2, arg02 _insertstr_ret ret _main - mov _main_p_0007, #1 - mov _main_p_0007_01, #2 - mov arg02, _main_p_0007_01 - mov arg01, _main_p_0007 + mov arg01, #1 + mov arg02, #2 mov arg03, #99 call #_insertstr - mov _main_q_0008, result1 - mov _main_q_0008_01, result2 - mov outa, _main_p_0007 - mov outb, _main_q_0008 + mov outa, #1 + mov outb, result1 _main_ret ret @@ -32,18 +28,6 @@ result2 COG_BSS_START fit 496 org COG_BSS_START -_main_p_0007 - res 1 -_main_p_0007_01 - res 1 -_main_q_0008 - res 1 -_main_q_0008_01 - res 1 -_var01 - res 1 -_var02 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest258.pasm b/Test/Expect/stest258.pasm index a69872bb6..d4e3e9ea9 100644 --- a/Test/Expect/stest258.pasm +++ b/Test/Expect/stest258.pasm @@ -27,10 +27,6 @@ result2 COG_BSS_START fit 496 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest275.pasm b/Test/Expect/stest275.pasm index 10ca82389..c5d79cc9b 100644 --- a/Test/Expect/stest275.pasm +++ b/Test/Expect/stest275.pasm @@ -21,13 +21,13 @@ _getit2_ret _getitA - rdlong _var05, arg01 - rdlong result1, _var05 - add _var05, #4 - rdlong result2, _var05 - rdlong _var06, arg01 - add _var06, #8 - wrlong _var06, arg01 + rdlong result2, arg01 + rdlong result1, result2 + add result2, #4 + rdlong result2, result2 + rdlong _var01, arg01 + add _var01, #8 + wrlong _var01, arg01 _getitA_ret ret @@ -40,16 +40,6 @@ COG_BSS_START org COG_BSS_START _var01 res 1 -_var02 - res 1 -_var03 - res 1 -_var04 - res 1 -_var05 - res 1 -_var06 - res 1 arg01 res 1 fit 496 diff --git a/Test/Expect/stest278.pasm b/Test/Expect/stest278.pasm index b4db2bba8..a8604693f 100644 --- a/Test/Expect/stest278.pasm +++ b/Test/Expect/stest278.pasm @@ -5,25 +5,11 @@ dat entry _bump1 - rdlong arg04, ptr__dat__ - add ptr__dat__, #4 - rdlong arg03, ptr__dat__ - mov result1, arg04 - mov result2, arg03 - sub ptr__dat__, #4 - add arg04, #1 wc - addx arg03, #0 - wrlong arg04, ptr__dat__ - add ptr__dat__, #4 - wrlong arg03, ptr__dat__ - sub ptr__dat__, #4 -_bump1_ret - ret - -_bump2 rdlong result1, ptr__dat__ add ptr__dat__, #4 rdlong result2, ptr__dat__ + mov _var01, result1 + mov _var02, result2 sub ptr__dat__, #4 add result1, #1 wc addx result2, #0 @@ -31,10 +17,13 @@ _bump2 add ptr__dat__, #4 wrlong result2, ptr__dat__ sub ptr__dat__, #4 -_bump2_ret + mov result1, _var01 + mov result2, _var02 +_bump1_ret ret _bump3 +_bump2 rdlong result1, ptr__dat__ add ptr__dat__, #4 rdlong result2, ptr__dat__ @@ -45,9 +34,11 @@ _bump3 add ptr__dat__, #4 wrlong result2, ptr__dat__ sub ptr__dat__, #4 +_bump2_ret _bump3_ret ret + ptr__dat__ long @@@_dat_ result1 @@ -64,10 +55,6 @@ _var01 res 1 _var02 res 1 -_var03 - res 1 -_var04 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest286.pasm b/Test/Expect/stest286.pasm index 46c9b4fa8..bd076b73e 100644 --- a/Test/Expect/stest286.pasm +++ b/Test/Expect/stest286.pasm @@ -22,10 +22,6 @@ result2 COG_BSS_START fit 496 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest315.pasm b/Test/Expect/stest315.pasm index f6eb3aa6d..e41339671 100644 --- a/Test/Expect/stest315.pasm +++ b/Test/Expect/stest315.pasm @@ -4,43 +4,33 @@ dat org 0 entry +_zcheck2 _zcheck1 cmp arg01, #0 wz if_ne mov result1, arg02 - if_ne mov _var06, arg03 + if_ne mov result2, arg03 if_e mov result1, arg04 - if_e mov _var06, arg05 - mov result2, _var06 + if_e mov result2, arg05 _zcheck1_ret - ret - -_zcheck2 - cmp arg01, #0 wz - if_ne mov result1, arg02 - if_ne mov _var06, arg03 - if_e mov result1, arg04 - if_e mov _var06, arg05 - mov result2, _var06 _zcheck2_ret ret + _zcheck3 cmp arg01, #0 wz if_ne mov result1, #0 - if_ne mov _var04, #0 + if_ne mov result2, #0 if_e mov result1, arg04 - if_e mov _var04, arg05 - mov result2, _var04 + if_e mov result2, arg05 _zcheck3_ret ret _zcheck4 cmp arg01, #0 wz if_ne mov result1, arg02 - if_ne mov _var04, arg03 + if_ne mov result2, arg03 if_e mov result1, #0 - if_e mov _var04, #0 - mov result2, _var04 + if_e mov result2, #0 _zcheck4_ret ret @@ -51,20 +41,6 @@ result2 COG_BSS_START fit 496 org COG_BSS_START -_var01 - res 1 -_var02 - res 1 -_var03 - res 1 -_var04 - res 1 -_var05 - res 1 -_var06 - res 1 -_var07 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest317.p2asm b/Test/Expect/stest317.p2asm index 98007b71c..84c382076 100644 --- a/Test/Expect/stest317.p2asm +++ b/Test/Expect/stest317.p2asm @@ -30,12 +30,10 @@ _foo mov objptr, foo_tmp002_ call foo_tmp003_ mov objptr, foo_tmp005_ - mov _foo_y, result1 - mov _foo_y_01, result2 - mov result1, _foo_y_01 add _foo_x, #4 - rdlong result2, _foo_x - add result1, result2 + rdlong foo_tmp002_, _foo_x + add result2, foo_tmp002_ + mov result1, result2 _foo_ret ret builtin_bytefill_ @@ -72,10 +70,6 @@ objmem org COG_BSS_START _foo_x res 1 -_foo_y - res 1 -_foo_y_01 - res 1 arg01 res 1 foo_tmp002_ diff --git a/Test/Expect/stest318.p2asm b/Test/Expect/stest318.p2asm index a3eb904ab..eb3bc1e84 100644 --- a/Test/Expect/stest318.p2asm +++ b/Test/Expect/stest318.p2asm @@ -44,14 +44,6 @@ COG_BSS_START fit 480 orgh org COG_BSS_START -_var01 - res 1 -_var02 - res 1 -_var03 - res 1 -_var04 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest319.p2asm b/Test/Expect/stest319.p2asm index e5637b89e..8f3e79b1d 100644 --- a/Test/Expect/stest319.p2asm +++ b/Test/Expect/stest319.p2asm @@ -64,10 +64,6 @@ _dat_ objmem long 0[0] org COG_BSS_START -_var01 - res 1 -_var02 - res 1 arg01 res 1 arg02 diff --git a/Test/Expect/stest322.pasm b/Test/Expect/stest322.pasm new file mode 100644 index 000000000..99aa66f54 --- /dev/null +++ b/Test/Expect/stest322.pasm @@ -0,0 +1,55 @@ +pub main + coginit(0, @entry, 0) +dat + org 0 +entry + +_use + add arg01, arg02 + add arg01, arg03 + mov result1, arg01 +_use_ret + ret + +_sum + mov sum_tmp002_, arg01 + mov _sum_s_0001, #0 + mov arg01, sum_tmp002_ + mov arg02, #1 + mov arg03, #2 + call #_use + add _sum_s_0001, result1 + mov arg01, sum_tmp002_ + mov arg02, #3 + mov arg03, #4 + call #_use + add _sum_s_0001, result1 + mov arg01, sum_tmp002_ + mov arg02, #5 + mov arg03, #6 + call #_use + add _sum_s_0001, result1 + mov arg01, sum_tmp002_ + mov arg02, #7 + mov arg03, #8 + call #_use + add result1, _sum_s_0001 +_sum_ret + ret + +result1 + long 0 +COG_BSS_START + fit 496 + org COG_BSS_START +_sum_s_0001 + res 1 +arg01 + res 1 +arg02 + res 1 +arg03 + res 1 +sum_tmp002_ + res 1 + fit 496 diff --git a/Test/stest322.c b/Test/stest322.c new file mode 100644 index 000000000..02f36f766 --- /dev/null +++ b/Test/stest322.c @@ -0,0 +1,23 @@ +// +// a struct value passed to a call is built in registers of its own; nothing +// reads them as a block, so they need not stay together, and each call's +// registers may be reused by the next +// +typedef struct triple { + int a, b, c; +} T; + +int use(T t) __attribute__(noinline) +{ + return t.a + t.b + t.c; +} + +int sum(int k) +{ + int s = 0; + s += use((T){k, 1, 2}); + s += use((T){k, 3, 4}); + s += use((T){k, 5, 6}); + s += use((T){k, 7, 8}); + return s; +} diff --git a/backends/asm/outasm.c b/backends/asm/outasm.c index ce167fdda..1441f492b 100644 --- a/backends/asm/outasm.c +++ b/backends/asm/outasm.c @@ -1703,6 +1703,116 @@ static void EmitFunctionEpilog(IRList *irl, Function *func) FreeTempRegisters(irl, 0); } +/* + * A local struct (or 64 bit value) held in registers is a base register with a + * subregister for each long past the first, and RenameSubregs keeps its longs in + * consecutive registers; ReuseLocalRegisters gives no subregister back, so such a + * local holds all its registers for the whole function. A function passing many + * struct values to calls, each a temporary of its own, runs out of them. Where + * nothing reads the longs as a block -- the register's address taken for an + * indexed access, inline assembly, a SETQ burst -- the local is split into one + * local a long, which the optimizer treats as it treats any other. + */ +static bool +RefersToBase(Operand *op, Operand *base) +{ + return op && (op == base || (op->kind == REG_SUBREG && (Operand *)op->name == base)); +} + +static bool +AddressesBase(Operand *op, Operand *base) +{ + return op && op->kind == IMM_COG_LABEL && op->name && !strcmp(op->name, base->name); +} + +static bool +CanSplitLocal(IRList *irl, Operand *base) +{ + IR *ir, *prev = NULL; + + for (ir = irl->head; ir; prev = ir, ir = ir->next) { + if (AddressesBase(ir->dst, base) || AddressesBase(ir->src, base)) { + return false; + } + if (RefersToBase(ir->dst, base) || RefersToBase(ir->src, base)) { + if (ir->flags & FLAG_USER_INSTR) { + return false; + } + if (prev && (prev->opc == OPC_SETQ || prev->opc == OPC_SETQ2)) { + return false; + } + } + } + return true; +} + +static void +SplitLocalStructs(IRList *irl) +{ + struct flexbuf bases; + Operand **list; + size_t nbases, i; + IR *ir; + + flexbuf_init(&bases, 64); + for (ir = irl->head; ir; ir = ir->next) { + Operand *ops[2] = { ir->dst, ir->src }; + for (int k = 0; k < 2; k++) { + Operand *op = ops[k]; + Operand *base; + bool seen = false; + if (!op || op->kind != REG_SUBREG || !IsLocal(op)) { + continue; + } + base = (Operand *)op->name; + list = (Operand **)flexbuf_peek(&bases); + nbases = flexbuf_curlen(&bases) / sizeof(Operand *); + for (i = 0; i < nbases; i++) { + if (list[i] == base) seen = true; + } + if (!seen) { + flexbuf_addmem(&bases, (const char *)&base, sizeof(base)); + } + } + } + list = (Operand **)flexbuf_peek(&bases); + nbases = flexbuf_curlen(&bases) / sizeof(Operand *); + for (i = 0; i < nbases; i++) { + Operand *base = list[i]; + Operand **parts; + int nparts = 1; + if (!CanSplitLocal(irl, base)) { + continue; + } + for (ir = irl->head; ir; ir = ir->next) { + if (ir->dst && ir->dst->kind == REG_SUBREG && (Operand *)ir->dst->name == base && ir->dst->val >= nparts) { + nparts = ir->dst->val + 1; + } + if (ir->src && ir->src->kind == REG_SUBREG && (Operand *)ir->src->name == base && ir->src->val >= nparts) { + nparts = ir->src->val + 1; + } + } + parts = (Operand **)calloc(nparts, sizeof(*parts)); + parts[0] = base; + for (int k = 1; k < nparts; k++) { + char *name = (char *)calloc(1, strlen(base->name) + 16); + sprintf(name, "%s__long%d", base->name, k); + parts[k] = GetOneGlobal(base->kind, name, 0); + parts[k]->used = 1; + } + for (ir = irl->head; ir; ir = ir->next) { + if (ir->dst && ir->dst->kind == REG_SUBREG && (Operand *)ir->dst->name == base) { + ir->dst = parts[ir->dst->val]; + } + if (ir->src && ir->src->kind == REG_SUBREG && (Operand *)ir->src->name == base) { + ir->src = parts[ir->src->val]; + } + } + free(parts); + } + flexbuf_delete(&bases); +} + /* * rename one operand locally */ @@ -5665,6 +5775,7 @@ CompileFunctionBody(Function *f) EmitLabel(irl,jumpover); } EmitFunctionEpilog(irl, f); + SplitLocalStructs(irl); OptimizeIRLocal(irl, f); HashFuncIRL(f); } From 8c0858400d638dd61521d8f6c3b2a21f781e33e9 Mon Sep 17 00:00:00 2001 From: Jan Mercl <0xjnml@gmail.com> Date: Wed, 28 Oct 2026 15:15:45 +0100 Subject: [PATCH 2/3] Keep a local whole where the address of one of its longs is taken GetLea of a subregister names that long's address as OffsetName does, base_01, and SplitLocalStructs compared an address with the base register's name only. No frontend was found to reach this with a local held in registers, since a local whose address is taken goes to the stack; but where one did, the split would leave the label naming a register the long is no longer in. An address of the base register or of any of its longs now keeps the local whole. No test output changes. --- backends/asm/outasm.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/backends/asm/outasm.c b/backends/asm/outasm.c index 1441f492b..4a7ae1cca 100644 --- a/backends/asm/outasm.c +++ b/backends/asm/outasm.c @@ -1709,9 +1709,9 @@ static void EmitFunctionEpilog(IRList *irl, Function *func) * consecutive registers; ReuseLocalRegisters gives no subregister back, so such a * local holds all its registers for the whole function. A function passing many * struct values to calls, each a temporary of its own, runs out of them. Where - * nothing reads the longs as a block -- the register's address taken for an - * indexed access, inline assembly, a SETQ burst -- the local is split into one - * local a long, which the optimizer treats as it treats any other. + * nothing reads the longs as a block -- the address of the register or of one + * of its longs taken, inline assembly, a SETQ burst -- the local is split into + * one local a long, which the optimizer treats as it treats any other. */ static bool RefersToBase(Operand *op, Operand *base) @@ -1719,10 +1719,35 @@ RefersToBase(Operand *op, Operand *base) return op && (op == base || (op->kind == REG_SUBREG && (Operand *)op->name == base)); } +/* + * the address of the base register, or of one of its longs, which GetLea names + * as OffsetName does ("base_01") + */ static bool AddressesBase(Operand *op, Operand *base) { - return op && op->kind == IMM_COG_LABEL && op->name && !strcmp(op->name, base->name); + size_t n; + const char *s; + + if (!op || op->kind != IMM_COG_LABEL || !op->name) { + return false; + } + n = strlen(base->name); + if (strncmp(op->name, base->name, n) != 0) { + return false; + } + if (op->name[n] == 0) { + return true; + } + if (op->name[n] != '_' || !op->name[n+1]) { + return false; + } + for (s = op->name + n + 1; *s; s++) { + if (!isdigit((unsigned char)*s)) { + return false; + } + } + return true; } static bool From 01c3f516399265e7998569c0759cb351f134db41 Mon Sep 17 00:00:00 2001 From: Jan Mercl <0xjnml@gmail.com> Date: Wed, 28 Oct 2026 16:18:21 +0100 Subject: [PATCH 3/3] Run SplitLocalStructs only with experimental optimizations Subregisters are used in ways that are easy to miss, so the pass now runs only where OPT_EXPERIMENTAL is set (-O2, -Oexperimental). At the default -O1 the output is master's. The comments now sit next to the code they describe. The asm tests are compiled with all optimizations, so their expected output does not change. --- backends/asm/outasm.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/backends/asm/outasm.c b/backends/asm/outasm.c index 4a7ae1cca..121d5b8bd 100644 --- a/backends/asm/outasm.c +++ b/backends/asm/outasm.c @@ -1703,26 +1703,16 @@ static void EmitFunctionEpilog(IRList *irl, Function *func) FreeTempRegisters(irl, 0); } -/* - * A local struct (or 64 bit value) held in registers is a base register with a - * subregister for each long past the first, and RenameSubregs keeps its longs in - * consecutive registers; ReuseLocalRegisters gives no subregister back, so such a - * local holds all its registers for the whole function. A function passing many - * struct values to calls, each a temporary of its own, runs out of them. Where - * nothing reads the longs as a block -- the address of the register or of one - * of its longs taken, inline assembly, a SETQ burst -- the local is split into - * one local a long, which the optimizer treats as it treats any other. - */ +// is op the base register of a local larger than a long, or one of its +// subregisters? static bool RefersToBase(Operand *op, Operand *base) { return op && (op == base || (op->kind == REG_SUBREG && (Operand *)op->name == base)); } -/* - * the address of the base register, or of one of its longs, which GetLea names - * as OffsetName does ("base_01") - */ +// is op the address of the base register, or of one of its longs? GetLea +// names the address of a subregister as OffsetName does ("base_01") static bool AddressesBase(Operand *op, Operand *base) { @@ -1750,19 +1740,24 @@ AddressesBase(Operand *op, Operand *base) return true; } +// may the longs of the local "base" be kept in registers that are not +// consecutive? not if anything uses them as a block static bool CanSplitLocal(IRList *irl, Operand *base) { IR *ir, *prev = NULL; for (ir = irl->head; ir; prev = ir, ir = ir->next) { + // its address is taken, as for an index that is not a constant if (AddressesBase(ir->dst, base) || AddressesBase(ir->src, base)) { return false; } if (RefersToBase(ir->dst, base) || RefersToBase(ir->src, base)) { + // inline assembly may use the longs as a block (s+1, #s) if (ir->flags & FLAG_USER_INSTR) { return false; } + // a SETQ burst reads or writes consecutive registers if (prev && (prev->opc == OPC_SETQ || prev->opc == OPC_SETQ2)) { return false; } @@ -1771,6 +1766,16 @@ CanSplitLocal(IRList *irl, Operand *base) return true; } +// +// A local larger than a long (a struct, or a 64 bit value) held in registers +// is a base register with a subregister for each further long, and +// RenameSubregs keeps its longs in consecutive registers. ReuseLocalRegisters +// does not reuse subregisters, so such a local holds all of its registers +// until the end of the function, and a function passing many struct values to +// calls runs out of registers. Each such local that CanSplitLocal allows is +// split here into one local per long, which the optimizer then treats like any +// other local. +// static void SplitLocalStructs(IRList *irl) { @@ -1779,6 +1784,7 @@ SplitLocalStructs(IRList *irl) size_t nbases, i; IR *ir; + // collect the base registers of the locals that have subregisters flexbuf_init(&bases, 64); for (ir = irl->head; ir; ir = ir->next) { Operand *ops[2] = { ir->dst, ir->src }; @@ -1809,6 +1815,7 @@ SplitLocalStructs(IRList *irl) if (!CanSplitLocal(irl, base)) { continue; } + // count the longs in use for (ir = irl->head; ir; ir = ir->next) { if (ir->dst && ir->dst->kind == REG_SUBREG && (Operand *)ir->dst->name == base && ir->dst->val >= nparts) { nparts = ir->dst->val + 1; @@ -1817,6 +1824,8 @@ SplitLocalStructs(IRList *irl) nparts = ir->src->val + 1; } } + // the base register keeps the first long, and each further long gets + // a local of its own parts = (Operand **)calloc(nparts, sizeof(*parts)); parts[0] = base; for (int k = 1; k < nparts; k++) { @@ -1825,6 +1834,7 @@ SplitLocalStructs(IRList *irl) parts[k] = GetOneGlobal(base->kind, name, 0); parts[k]->used = 1; } + // replace each subregister by the local of its long for (ir = irl->head; ir; ir = ir->next) { if (ir->dst && ir->dst->kind == REG_SUBREG && (Operand *)ir->dst->name == base) { ir->dst = parts[ir->dst->val]; @@ -5800,7 +5810,9 @@ CompileFunctionBody(Function *f) EmitLabel(irl,jumpover); } EmitFunctionEpilog(irl, f); - SplitLocalStructs(irl); + if (f->optimize_flags & OPT_EXPERIMENTAL) { + SplitLocalStructs(irl); + } OptimizeIRLocal(irl, f); HashFuncIRL(f); }