Skip to content

Let a register local of several longs give its registers back - #521

Open
j-modernc-org wants to merge 3 commits into
totalspectrum:masterfrom
j-modernc-org:split-local-structs
Open

j-modernc-org wants to merge 3 commits into
totalspectrum:masterfrom
j-modernc-org:split-local-structs

Conversation

@j-modernc-org

@j-modernc-org j-modernc-org commented Oct 27, 2026 •

Copy link
Copy Markdown

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 its longs in consecutive registers. ReuseLocalRegisters
does not touch subregisters, so such a local holds all its registers until
the end of the function. A function that passes many struct values to calls,
each a compound literal or a temporary of its own, runs out of cog registers
("fit 480 failed") or, past the local register limit, crashes.

This adds SplitLocalStructs, run before a function's first OptimizeIRLocal
when experimental optimizations are enabled (-O2, -Oexperimental).
It 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 is a new asm test: four calls passing a struct literal. The
literals took 12 registers and now take none; their values are moved
straight into the argument registers. 15 existing asm test outputs change,
and I read each diff: 9 only drop registers reserved for locals the
optimizer had already removed, 6 have shorter code, and in two of those
(stest278, stest315) functions become identical and are merged.

Testing:

  • make test_offline: 553 of 553 (master's 552 and stest322).
  • Test/runtests_p2.sh on a P2-EDGE: 31 of 31, as on master.
  • Test/runtests_p1.sh and Test/runtests_bc.sh under spinsim: the same 55
    pass as on master.
  • Builds with OPT="-O2 -Werror".
  • Before rebasing onto master: 1503 C programs (the test corpus and fuzzer
    output of a compiler that emits C for flexcc, and its reproducers),
    compiled with -2 at b5993a8 with Fix two miscompiles of values of two to four longs passed and returned in registers #519's two commits and our other
    patches applied, with and without this change: 1165 build differently,
    44 that did not fit before now build, and on a P2-EDGE every one prints
    what it printed before (the ones that did not build before print what
    gcc's build prints).

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).
@Wuerfel21

Copy link
Copy Markdown
Contributor

Subregister is the Chesterton's Fence of flexpsin.
They're built on ancient native american burial grounds and anything that touches them is forever cursed to pain and suffering, which is why most passes avoid touching them.

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.
@j-modernc-org

Copy link
Copy Markdown
Author

Point taken, and that is why this pass stays away from them instead of
teaching the other passes about them. It runs once, before the function's
first OptimizeIRLocal, and where it splits a local, that local's subregisters
are gone and the optimizer sees plain locals. Where anything might need the
longs side by side, the local is left exactly as it was, subregisters and all.
The reasons I found to keep them together:

  • an index that is not a constant, which goes through the base register's
    address (OffsetMemory and ApplyArrayIndex make an IMM_COG_LABEL of its name
    for the indexed read or write)
  • inline assembly naming the local (s+1, #s)
  • an instruction after a SETQ or SETQ2

Your comment sent me back to look for more, and there was one I had missed:
GetLea of a subregister names that long's address base_01, as OffsetName
does, and the check compared an address with the base's name only. I could
not get any frontend to reach it with a local held in registers, since a local
whose address is taken goes to the stack, but I have pushed a commit that
keeps the local whole there too. It changes no test output, and none of the
1547 C programs I build it against.

If you know of anything else that relies on the longs being consecutive, I
would like to hear it.

Thank you.

@Wuerfel21

Copy link
Copy Markdown
Contributor

If I were to make an additional suggestion, instead of having a big comment at some random position in the file and a bunch of uncommented code below, the comments should be next to the code it actually applies to.
(also can we like, not have -- pseudo em dashes in there - don't debase us further than you have to, thanks(?))

@Wuerfel21

Copy link
Copy Markdown
Contributor

(and given aformentioned subregister cursedness, this should be OPT_EXPERIMENTAL gated)

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.
@j-modernc-org

Copy link
Copy Markdown
Author

Thank you, all three are done in 01c3f51:

  • SplitLocalStructs runs only when OPT_EXPERIMENTAL is set (-O2 or
    -Oexperimental). At the default -O1 the output is the same as master's.
  • Each comment now sits next to the code it describes.
  • There are no pseudo em dashes in the comments any more.

The asm tests are compiled with all optimizations, so their expected output
does not change, and make test_offline passes as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants