# 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.