Computer Architecture · Instruction Sets
Register Windows and Why Explicit Operand Registers Won
How register window architectures eliminate spill code on call, what the window management traps cost, and why load-store ISAs settled on flat register files with fixed fields.
A procedure call passes arguments, saves the caller’s live registers, and provides the callee with registers of its own. On most architectures the compiler implements all three with instructions: store the caller’s registers to the stack, move arguments into the callee’s parameter registers, and on return reload the caller’s state. A register window architecture removes the instructions and performs the same work by reinterpreting register numbers, so that the same five-bit field names different physical registers before and after a call.
The design was pursued seriously in the 1980s, most fully in SPARC, and it lost. The reasons it lost are more instructive than the design itself, because they show which costs the ISA can hide and which it cannot.
Renaming by rotation instead of by instruction
A windowed register file is a circular buffer of physical registers with a moving base pointer called the current window pointer. An instruction’s register field is interpreted relative to that base. Each window covers twenty-four windowed architectural registers — eight incoming, eight local and eight outgoing — of which the incoming eight are shared with the previous window’s outgoing registers and the outgoing eight with the next window’s incoming registers; only the eight locals are private to the window. Eight global registers sit outside the window and are visible in every window. A call advances the pointer by sixteen registers, so the callee’s incoming registers are the caller’s outgoing registers, with no data movement.
The saving is exact and easy to state. A conventional call that saves eight callee-saved registers and restores them on return executes sixteen memory operations, plus the argument moves. A windowed call executes one instruction, save, which decrements the pointer and traps if the window buffer is exhausted. The register file itself is larger — SPARC implementations commonly provided roughly 120 to 136 physical registers for seven or eight windows of sixteen registers each plus eight globals — and the pointer arithmetic is a few gates.
Nothing in the encoding changes. The register fields are still five bits, and the opcodes are the same ones a flat architecture would use. The window is a property of the register file’s addressing, not of the instruction format, which is why windowed and flat ISAs can share an encoding style.
The overflow trap and its cost
The circular buffer has a finite depth. When a call would advance the pointer past the last window, the hardware raises a window overflow trap, and a trap handler writes the oldest window’s registers to the stack. On return, a matching underflow trap reloads them. The architecture therefore has two extra trap types that a flat ISA does not need, and they fire on a pattern that is determined by call depth.
The handler is not free. It must run in a window of its own, which means the trap itself consumes window space, and the handler must avoid triggering the same trap recursively. SPARC implementations reserve a window for the handler and the operating system specifies the convention. The cost per overflow is a trap entry, a set of stores, and a trap return: on the order of tens to low hundreds of cycles depending on the implementation, against the sixteen memory operations a flat call would have performed.
The critical observation is that the overflow cost is paid on the call depth boundary rather than per call. A deeply recursive function overflows repeatedly and pays the trap every few frames, while a long chain of shallow calls may never overflow at all. The average depends on the program’s call-depth distribution, which is not something the ISA designer can control. A flat architecture’s spill cost is proportional to the number of live registers at each call, which the compiler can also control but which is at least bounded by the code it generates.
/* Flat-ISA call sequence: the compiler emits these, and the count is
proportional to live registers, not to call depth. */
extern int g(int, int, int);
int f(int a, int b, int c)
{
/* prologue: adjust sp, store callee-saved registers actually used */
int r = g(a, b, c);
/* epilogue: reload them, adjust sp, return */
return r + 1;
}
On a windowed architecture the same function compiles to a save, a call, a ret/restore, and the arithmetic, with no memory traffic at all in the common case. The difference is not a constant factor in instruction count; it is a change in what the compiler must reason about. A windowed compiler does not allocate callee-saved registers, because the hardware saves them.
Why the window lost
The design’s defeat was not a single decisive measurement but the accumulation of four costs, each of which became worse as processors changed.
The first is register file size and port pressure. A windowed file with eight windows has 128+ registers where a flat file has 32. That is four times the area and four times the state to save and restore on a context switch. The context-switch cost is the sharpest: a flat 32-register file costs 128 bytes of state per thread, while an eight-window file costs about 512 bytes, and an operating system that switches threads frequently pays it every time. Modern cores with 200 or more physical registers hide this through renaming, but renaming is a different mechanism: it maps a small architectural file onto a large physical one, and the architectural file stays small so that context switches and exceptions stay cheap.
The second is that the window does not eliminate the need for a stack. Locals that are address-taken, arrays, and anything whose lifetime crosses a call still live in memory. A windowed function with a local array needs a frame pointer and a stack frame just as a flat one does, so the register window saves the register spills and nothing else. As compilers became better at keeping values in registers across calls, the spill cost the window was designed to remove shrank.
The third is variable argument counts and indirect calls. A window’s argument registers are fixed at eight in and eight out. A variadic function, or a call through a function pointer whose signature is not known at the call site, cannot rely on the window’s overlap. The calling convention must then fall back to memory for arguments beyond the fixed count, and the compiler must generate both paths or restrict the language. Flat architectures have the same problem, but they do not have a hardware mechanism whose value depends on the common case holding.
The fourth is that the trap handler’s existence leaks into the programming model. Code that must run at a privilege level where window traps cannot be taken, or in a context where the trap handler’s own window requirements are not met, needs a separate convention. The complexity lands on the operating system and on any runtime that manages its own stacks, and it never goes away.
The encoding question that remains
A windowed architecture still has to answer the same encoding questions as a flat one: how many register bits, where they sit, and how the immediate shares the word. SPARC’s answer was a 32-bit instruction with five-bit register fields and a thirteen-bit immediate for the register-immediate formats, plus a format for the sethi instruction that loads the top twenty-two bits of a constant. The field layout is conventional; the window is orthogonal to it.
That orthogonality is the reason register windows are worth studying even though they lost. They demonstrate that a mechanism can be implemented without spending encoding space, and that the decision to include it is therefore purely a microarchitectural and system-software decision rather than an encoding one. The same is true of the flat register file: nothing in a five-bit register field forces 32 architectural registers, and architectures have used the same field width with 16, 31 and 32 names.
The comparison also clarifies what a calling convention is. On a flat ISA the convention is a software contract — which registers are caller-saved, which are callee-saved, where arguments go — and the hardware enforces none of it. On a windowed ISA part of the contract is in the hardware, which makes calls cheaper and makes every other use of the register file more constrained. The trade is between a fast common case and a uniform mechanism, and the uniform mechanism won because the common case stopped being common enough.
Spill code and what it actually costs
It is worth quantifying what the window was meant to save, because the number is smaller than the design’s motivation suggested.
A call that saves eight registers performs eight stores and, after the call, eight loads. On a processor with a store buffer and a load-to-use latency of a few cycles, the stores retire asynchronously and the loads hit in L1 because the frame was just written. The marginal cost over the call itself is a handful of cycles, and it overlaps with the callee’s work. The window saves those cycles and replaces them with a pointer update that has its own dependency: every register access in the callee depends on the window pointer, which is fine because the pointer changes only at calls.
The cost that does not overlap is the register file’s size, which affects context switch, exception entry, and the physical design’s port count. Those costs are paid on every thread switch and every interrupt, not just on calls, and they are the ones that ended the design’s viability as register files grew to support out-of-order execution. A core that renames 32 architectural registers onto 180 physical ones needs the architectural file to be small precisely so that the rename map and the exception state are manageable; a windowed architecture with 128 architectural registers multiplies the state that must be tracked, saved and restored, as covered in Two-Operand Encodings and the Destructive Write Problem.
Residual uses
Window-like mechanisms survive in narrower forms. Some digital signal processors use circular register buffers for filter state, where the rotation is over data rather than over call frames. Some architectures provide a rotating set of registers for software-pipelined loops, which removes the register copy at the loop boundary without changing the call convention. Both uses share the window’s property of making a rotation free and its property of requiring a trap or a check when the rotation wraps.
The general lesson is that hardware mechanisms which save instructions in the common case are worth their cost only if the common case dominates and the mechanism’s state is small. Register windows satisfied the first condition and failed the second, and the second condition became more important as processors grew more stateful for unrelated reasons.
References
- The SPARC Architecture Manual, Version 9 — SPARC International
- The RISC-V Instruction Set Manual, Volume I: Unprivileged ISA — RISC-V International
- RISC I: A Reduced Instruction Set VLSI Computer — Patterson & Séquin, ISCA 1981