# Compile-time register management

This unit helps compilers manage available registers at compile time.
Theoretically, this could be used outside compiler contexts, but I don't see how
exactly it could be done. For accessing more registers in HAL code, see
[hal/vreg] instead.

The goal of this unit is to optimize register usage when generating code for
arbitrary deep expressions.

This unit is global, that is, it can only be used on one compilation operation
at once. You need to be finished using all registers before you can begin
another operation.

## Register usage

We try to make the list as large as possible, but W is present in none of those
lists. The idea is that W is always available as a work register.

A and S are present, but because it's possible that some code generation use
them for special purposes, they're at the end of the list, A first, then S, and
this for all CPUs.

In x86, we don't include ECX and EDI because of their weird limitations (see
[hal/vreg]). We use [hal/vreg] registers, but not registers used as temporary
values in HAL codegen.

So, here's the list, leftmost is allocated first:

i386    EBX(A) EDX(S)
amd64   R8 R9 R10 R11 R12 R13 EBX(A) EDX(S)
arm     r3 r4 r6 r12 r11(A) r8(S)
riscv   x8 x9 x13-to-x29 x5(A) x6(S)
m68k    D1 D2 D3 D4 D6(A) D5(S)

amd64 TODO: as of now, amd64 regs are the same as i386. The HAL needs a bit of
work before it can be as documented above.

## Usage

Usage sessions both begin and end with "regs$". This ensures that there is no
dangling register usage and abort if there is.

Then, you get a register with "newreg". This allocates a register from the
stack, which if you're lucky is a physical register and otherwise a memory slot.

At that point, you can use that register with words such as "reg&)". Remember,
it's not because it's called a register that it can be used like any register.
If it's a memory slot, it can't be used as a destination register, hence the
special conditional API provided here.

Once you're done, you must free that register with "freereg". Registers have to
be freed in the reverse order they've been allocated.

It's **OK** to use a HAL operand generated by a register after it's been freed
as long as it's used before another register is allocated again.

## Saving and restoring registers

As long as no function call is made, this register stack is pretty much all we
have to care about. When a function call is made, it's something else: we need
to save those registers.

You do so with "pushregs, ( -- )". This generates code to push all used
registers up to whatever storage mechanism is appropriate by whatever means is
the best (this is CPU-specific). "popregs, ( -- )" do the opposite.

"pushregs," and "popregs," have no effect on current register allocation. This
unit is not designed to allow you to start a new compilation operation in
between. The idea is that you push your registers, compile your function call
and pop them right back.

## Speed consideration

When function calls are involved, you can have an effect on the cost of regs
push/popping by carefully choosing which "side" or an expression you generate
first. By generating the side with a function call first, you avoid having to
hold onto a temporary value because you haven't computed it yet.

Otherwise, when no function calls are involved, it's better to choose the
deepest side first.

## API

### Constants

PHYSREGS  Number of physical registers available on this CPU for this unit.
MAXREGS   64. The maximum number of registers that can be allocated.

### Words

regs$ ( -- )
  Ensure that no register is allocated. Abort with error otherwise.

newreg ( -- )
  Allocate a new register. Aborts if out of registers.

freereg ( -- )
  Free the last register to be allocated. Aborts if no register is allocated.

reg) ( -- halop f )
  Yield that HAL operand with a source that points to the allocated register.
  f=1 if it's a physical register and f=0 if it's a memory register.

  Careful, these two cases don't refer to the same level of indirection in the
  two cases! In most cases (but not all), you'll want to apply &) to halop when
  f=1.

reg&) ( -- halop )
  Calls reg) and applies &) if f=1.

?regid ( -- ?regid f )
  If allocated register corresponds to a physical register, yield f=1 and a
  register ID that can be used in the same way that REGW, REGA, REGS, etc. can
  be used.

  Otherwise, yield f=0 and "regid" is absent.

?reg>) ( halop -- halop f )
  If allocated register corresponds to a physical register, set "halop"
  destination to it and f=1. Otherwise, leave halop unchanged and f=0.

pushregs, ( -- )
  Generate code that pushes all used registers (including memory slots if any)
  up to whatever stack is best, most probably RSP. This is CPU-specific to allow
  for the fastest possible code.

popregs, ( -- )
  Generate code that pops back those registers we've pushed earlier.

usesA? ( -- f )
usesS? ( -- f )
  Yields whether the A/S register is currently used by the comp stack.

?pushA, ( -- )
?pushS, ( -- )
  If the A/S register is used, generate code that pushes it.

?popA, ( -- )
?popS, ( -- )
  If the A/S register is used, generate code that pops it.
