In
compiler optimization
An optimizing compiler is a compiler designed to generate code that is optimized in aspects such as minimizing program execution time, memory usage, storage size, and power consumption. Optimization is generally implemented as a sequence of op ...
, register allocation is the process of assigning local
automatic variable
In computer programming, an automatic variable is a local variable which is allocated and deallocated automatically when program flow enters and leaves the variable's scope. The scope is the lexical context, particularly the function or block in ...
s and
expression results to a limited number of
processor register
A processor register is a quickly accessible location available to a computer's processor. Registers usually consist of a small amount of fast storage, although some registers have specific hardware functions, and may be read-only or write-onl ...
s.
Register allocation can happen over a
basic block
In compiler construction, a basic block is a straight-line code sequence with no branches in except to the entry and no branches out except at the exit. This restricted form makes a basic block highly amenable to analysis. Compilers usually decom ...
(''local register allocation''), over a whole function/
procedure (''global register allocation''), or across function boundaries traversed via call-graph (''interprocedural register allocation''). When done per function/procedure the
calling convention
In computer science, a calling convention is an implementation-level (low-level) scheme for how subroutines or functions receive parameters from their caller and how they return a result. When some code calls a function, design choices have been ...
may require insertion of save/restore around each
call-site.
Context
Principle
{, class="wikitable floatright"
, + Different number of general-purpose registers in the most common architectures
, -
! Architecture
! scope="col" , 32 bit
! scope="col" , 64 bit
, -
! scope="row" , ARM
, 15
, 31
, -
! scope="row" , Intel x86
, 8
, 16
, -
! scope="row" , MIPS
, 32
, 32
, -
! scope="row" , POWER/PowerPC
, 32
, 32
, -
! scope="row" , RISC-V
, 16/32
, 32
, -
! scope="row" , SPARC
, 31
, 31
, -
In many
programming language
A programming language is a system of notation for writing computer programs.
Programming languages are described in terms of their Syntax (programming languages), syntax (form) and semantics (computer science), semantics (meaning), usually def ...
s, the programmer may use any number of
variables. The computer can quickly read and write
registers in the
CPU, so the
computer program
A computer program is a sequence or set of instructions in a programming language for a computer to Execution (computing), execute. It is one component of software, which also includes software documentation, documentation and other intangibl ...
runs faster when more variables can be in the CPU's registers. Also, sometimes code accessing registers is more compact, so the code is smaller, and can be fetched faster if it uses registers rather than memory. However, the number of registers is limited. Therefore, when the
compiler
In computing, a compiler is a computer program that Translator (computing), translates computer code written in one programming language (the ''source'' language) into another language (the ''target'' language). The name "compiler" is primaril ...
is translating code to machine-language, it must decide how to allocate variables to the limited number of registers in the CPU.
Not all variables are
in use (or "live") at the same time, so, over the lifetime of a program, a given register may be used to hold different variables. However, two variables in use at the ''same'' time cannot be assigned to the same register without corrupting one of the variables. If there are not enough registers to hold all the variables, some variables may be moved to and from
RAM. This process is called "spilling" the registers. Over the lifetime of a program, a variable can be both spilled and stored in registers: this variable is then considered as "split". Accessing RAM is significantly slower than accessing registers and so a compiled program runs slower. Therefore, an optimizing compiler aims to assign as many variables to registers as possible. A high "
Register pressure" is a technical term that means that more spills and reloads are needed; it is defined by Braun et al. as "the number of simultaneously live variables at an instruction".
In addition, some computer designs
cache frequently-accessed registers. So, programs can be further optimized by assigning the same register to a source and destination of a
move
instruction whenever possible. This is especially important if the compiler is using an
intermediate representation
An intermediate representation (IR) is the data structure or code used internally by a compiler or virtual machine to represent source code. An IR is designed to be conducive to further processing, such as optimization and translation. A "good" ...
such as
static single-assignment form
In compiler design, static single assignment form (often abbreviated as SSA form or simply SSA) is a type of intermediate representation (IR) where each variable is assigned exactly once. SSA is used in most high-quality optimizing compilers for ...
(SSA). In particular, when SSA is not fully optimized it can artificially generate additional
move
instructions.
Components of register allocation
Register allocation consists therefore of choosing where to store the variables at runtime, i.e. inside or outside registers. If the variable is to be stored in registers, then the allocator needs to determine in which register(s) this variable will be stored. Eventually, another challenge is to determine the duration for which a variable should stay at the same location.
A register allocator, disregarding the chosen allocation strategy, can rely on a set of core actions to address these challenges. These actions can be gathered in several different categories:
;Move insertion: This action consists of increasing the number of move instructions between registers, i.e. make a variable live in different registers during its lifetime, instead of one. This occurs in the split live range approach.
;Spilling: This action consists of storing a variable into memory instead of registers.
;Assignment: This action consists of assigning a register to a variable.
;Coalescing: This action consists of limiting the number of moves between registers, thus limiting the total number of instructions. For instance, by identifying a variable live across different methods, and storing it into one register during its whole lifetime.
Many register allocation approaches optimize for one or more specific categories of actions.
Common problems raised in register allocation
Register allocation raises several problems that can be tackled (or avoided) by different register allocation approaches. Three of the most common problems are identified as follows:
;Aliasing: In some architectures, assigning a value to one register can affect the value of another: this is called aliasing. For example, the
x86
x86 (also known as 80x86 or the 8086 family) is a family of complex instruction set computer (CISC) instruction set architectures initially developed by Intel, based on the 8086 microprocessor and its 8-bit-external-bus variant, the 8088. Th ...
architecture has four general purpose 32-bit registers that can also be used as 16-bit or 8-bit registers. In this case, assigning a 32-bit value to the eax register will affect the value of the al register.
;Pre-coloring: This problem is an act to force some variables to be assigned to particular registers. For example, in
PowerPC
PowerPC (with the backronym Performance Optimization With Enhanced RISC – Performance Computing, sometimes abbreviated as PPC) is a reduced instruction set computer (RISC) instruction set architecture (ISA) created by the 1991 Apple Inc., App ...
calling convention
In computer science, a calling convention is an implementation-level (low-level) scheme for how subroutines or functions receive parameters from their caller and how they return a result. When some code calls a function, design choices have been ...
s, parameters are commonly passed in R3-R10 and the return value is passed in R3.
;NP-Problem: Chaitin et al. showed that register allocation is an
NP-complete
In computational complexity theory, NP-complete problems are the hardest of the problems to which ''solutions'' can be verified ''quickly''.
Somewhat more precisely, a problem is NP-complete when:
# It is a decision problem, meaning that for any ...
problem. They reduce the
graph coloring
In graph theory, graph coloring is a methodic assignment of labels traditionally called "colors" to elements of a Graph (discrete mathematics), graph. The assignment is subject to certain constraints, such as that no two adjacent elements have th ...
problem to the register allocation problem by showing that for an arbitrary graph, a program can be constructed such that the register allocation for the program (with registers representing nodes and machine registers representing available colors) would be a coloring for the original graph. As Graph Coloring is an NP-Hard problem and Register Allocation is in NP, this proves the NP-completeness of the problem.
Register allocation techniques
Register allocation can happen over a
basic block
In compiler construction, a basic block is a straight-line code sequence with no branches in except to the entry and no branches out except at the exit. This restricted form makes a basic block highly amenable to analysis. Compilers usually decom ...
of code: it is said to be "local", and was first mentioned by Horwitz et al. As basic blocks do not contain branches, the allocation process is thought to be fast, because the management of
control-flow graph merge points in register allocation reveals itself a time-consuming operation. However, this approach is thought not to produce as optimized code as the "global" approach, which operates over the whole compilation unit (a method or procedure for instance).
Graph-coloring allocation
Graph-coloring allocation is the predominant approach to solve register allocation. It was first proposed by Chaitin et al.
In this approach, nodes in the
graph
Graph may refer to:
Mathematics
*Graph (discrete mathematics), a structure made of vertices and edges
**Graph theory, the study of such graphs and their properties
*Graph (topology), a topological space resembling a graph in the sense of discret ...
represent live ranges (
variables,
temporaries
''Richelieu'' ( (Québec), (France)), also released as ''Temporaries'' in some territories, is a Canadian drama film, directed by Pier-Philippe Chevigny and released in 2023. The film stars Ariane Castellanos as Ariane, a woman who is hired as ...
, virtual/symbolic registers) that are candidates for register allocation. Edges connect live ranges that interfere, i.e., live ranges that are simultaneously live at at least one program point. Register allocation then reduces to the
graph coloring
In graph theory, graph coloring is a methodic assignment of labels traditionally called "colors" to elements of a Graph (discrete mathematics), graph. The assignment is subject to certain constraints, such as that no two adjacent elements have th ...
problem in which colors (registers) are assigned to the nodes such that two nodes connected by an edge do not receive the same color.
Using
liveness analysis, an interference graph can be built. The interference graph, which is an
undirected graph
In discrete mathematics, particularly in graph theory, a graph is a structure consisting of a set of objects where some pairs of the objects are in some sense "related". The objects are represented by abstractions called '' vertices'' (also call ...
where the nodes are the program's variables, is used to model which variables cannot be allocated to the same register.
Principle
The main phases in a Chaitin-style graph-coloring register allocator are:
# Renumber: discover live range information in the source program.
# Build: build the interference graph.
# Coalesce: merge the live ranges of non-interfering variables related by copy instructions.
# Spill cost: compute the spill cost of each variable. This assesses the impact of mapping a variable to memory on the speed of the final program.
# Simplify: construct an ordering of the nodes in the inferences graph
# Spill Code: insert spill instructions, i.e. loads and stores to commute values between registers and memory.
# Select: assign a register to each variable.
Drawbacks and further improvements
The graph-coloring allocation has three major drawbacks. First, it relies on graph-coloring, which is an
NP-complete problem, to decide which variables are spilled. Finding a minimal coloring graph is indeed an NP-complete problem. Second, unless live-range splitting is used, evicted variables are spilled everywhere: store instructions are inserted as early as possible, i.e., just after variable definitions; load instructions are respectively inserted late, just before variable use. Third, a variable that is not spilled is kept in the same register throughout its whole lifetime.
On the other hand, a single register name may appear in multiple register classes, where a class is a set of register names that are interchangeable in a particular role. Then, multiple register names may be aliases for a single hardware register.
Finally, graph coloring is an aggressive technique for allocating registers, but is computationally expensive due to its use of the interference graph, which can have a worst-case size that is
quadratic in the number of live ranges.
The traditional formulation of graph-coloring register allocation implicitly assumes a single bank of non-overlapping general-purpose registers and does not handle irregular architectural features like overlapping registers pairs, special purpose registers and multiple register banks.
One later improvement of Chaitin-style graph-coloring approach was found by Briggs et al.: it is called conservative coalescing. This improvement adds a criterion to decide when two live ranges can be merged. Mainly, in addition to the non-interfering requirements, two variables can only be coalesced if their merging will not cause further spilling. Briggs et al. introduces a second improvement to Chaitin's works which is biased coloring. Biased coloring tries to assign the same color in the graph-coloring to live range that are copy related.
Linear scan
Linear scan is another global register allocation approach. It was first proposed by Poletto et al. in 1999. In this approach, the code is not turned into a graph. Instead, all the variables are linearly scanned to determine their live range, represented as an interval. Once the live ranges of all variables have been figured out, the intervals are traversed chronologically. Although this traversal could help identifying variables whose live ranges interfere, no interference graph is being built and the variables are allocated in a greedy way.
The motivation for this approach is speed; not in terms of execution time of the generated code, but in terms of time spent in code generation. Typically, the standard graph coloring approaches produce quality code, but have a significant
overhead, the used graph coloring algorithm having a quadratic cost. Owing to this feature, linear scan is the approach currently used in several JIT compilers, like the
Hotspot client compiler,
V8,
Jikes RVM, and the Android Runtime (ART).
The
Hotspot server compiler uses graph coloring for its superior code.
Pseudocode
This describes the algorithm as first proposed by Poletto et al., where:
* R is the number of available registers.
* active is the list, sorted in order of increasing end point, of live intervals overlapping the current point and placed in registers.
LinearScanRegisterAllocation
active ← {}
for each live interval ''i'', in order of increasing start point do
ExpireOldIntervals(i)
if length(active) = R then
SpillAtInterval(i)
else
register
← a register removed from pool of free registers
add ''i'' to active, sorted by increasing end point
ExpireOldIntervals(i)
for each interval ''j'' in active, in order of increasing end point do
if endpoint
≥ startpoint
then
return
remove ''j'' from active
add register
to pool of free registers
SpillAtInterval(i)
spill ← last interval in active
if endpoint
pill
Pill or The Pill may refer to:
Drugs
* Pill (pharmacy), referring to anything small for a specific dose of medicine
* "The Pill", a general nickname for the combined oral contraceptive pill
Film and television
* ''The Pill'' (film), a 2011 fil ...
> endpoint
then
register
← register
pill
Pill or The Pill may refer to:
Drugs
* Pill (pharmacy), referring to anything small for a specific dose of medicine
* "The Pill", a general nickname for the combined oral contraceptive pill
Film and television
* ''The Pill'' (film), a 2011 fil ...
location
pill
Pill or The Pill may refer to:
Drugs
* Pill (pharmacy), referring to anything small for a specific dose of medicine
* "The Pill", a general nickname for the combined oral contraceptive pill
Film and television
* ''The Pill'' (film), a 2011 fil ...
← new stack location
remove spill from active
add ''i'' to active, sorted by increasing end point
else
location
← new stack location
Drawbacks and further improvements
However, the linear scan presents two major drawbacks. First, due to its greedy aspect, it does not take lifetime holes into account, i.e. "ranges where the value of the variable is not needed". Besides, a spilled variable will stay spilled for its entire lifetime.

Many other research works followed up on the Poletto's linear scan algorithm. Traub et al., for instance, proposed an algorithm called second-chance binpacking aiming at generating code of better quality. In this approach, spilled variables get the opportunity to be stored later in a register by using a different
heuristic
A heuristic or heuristic technique (''problem solving'', '' mental shortcut'', ''rule of thumb'') is any approach to problem solving that employs a pragmatic method that is not fully optimized, perfected, or rationalized, but is nevertheless ...
from the one used in the standard linear scan algorithm. Instead of using live intervals, the algorithm relies on live ranges, meaning that if a range needs to be spilled, it is not necessary to spill all the other ranges corresponding to this variable.
Linear scan allocation was also adapted to take advantage from the
SSA form: the properties of this intermediate representation simplify the allocation algorithm and allow lifetime holes to be computed directly. First, the time spent in data-flow graph analysis, aimed at building the lifetime intervals, is reduced, namely because variables are unique. It consequently produces shorter live intervals, because each new assignment corresponds to a new live interval. To avoid modeling intervals and liveness holes, Rogers showed a simplification called future-active sets that successfully removed intervals for 80% of instructions.
Rematerialization
Coalescing
In the context of register allocation, coalescing is the act of merging variable-to-variable move operations by allocating those two variables to the same location. The coalescing operation takes place after the interference graph is built. Once two nodes have been coalesced, they must get the same color and be allocated to the same register, once the copy operation becomes unnecessary.
Doing coalescing might have both positive and negative impacts on the colorability of the interference graph. For example, one negative impact that coalescing could have on graph inference colorability is when two nodes are coalesced, as the result node will have a union of the edges of those being coalesced.
A positive impact of coalescing on inference graph colorability is, for example, when a node interferes with both nodes being coalesced, the degree of the node is reduced by one which leads to improving the overall colorability of the interference graph.
There are several coalescing heuristics available:
; Aggressive coalescing: It was first introduced in Chaitin's original register allocator. This heuristic aims at coalescing any non-interfering, copy-related nodes. From the perspective of copy elimination, this heuristic has the best results. On the other hand, aggressive coalescing could impact the colorability of the inference graph.
;Conservative Coalescing: It mainly uses the same heuristic as aggressive coalescing but it merges moves if, and only if, it does not compromise the colorability of the interference graph.
;Iterated coalescing: It removes one particular move at the time, while keeping the colorability of the graph.
;Optimistic coalescing: It is based on aggressive coalescing, but if the inference graph colorability is compromised, then it gives up as few moves as possible.
Mixed approaches
Hybrid allocation
Some other register allocation approaches do not limit to one technique to optimize register's use. Cavazos et al., for instance, proposed a solution where it is possible to use both the linear scan and the graph coloring algorithms. In this approach, the choice between one or the other solution is determined dynamically: first, a
machine learning
Machine learning (ML) is a field of study in artificial intelligence concerned with the development and study of Computational statistics, statistical algorithms that can learn from data and generalise to unseen data, and thus perform Task ( ...
algorithm is used "offline", that is to say not at runtime, to build a heuristic function that determines which allocation algorithm needs to be used. The heuristic function is then used at runtime; in light of the code behavior, the allocator can then choose between one of the two available algorithms.
Trace register allocation is a recent approach developed by Eisl et al. This technique handles the allocation locally: it relies on dynamic
profiling data to determine which branches will be the most frequently used in a given control flow graph. It then infers a set of "traces" (i.e. code segments) in which the merge point is ignored in favor of the most used branch. Each trace is then independently processed by the allocator. This approach can be considered as hybrid because it is possible to use different register allocation algorithms between the different traces.
Split allocation
Split allocation is another register allocation technique that combines different approaches, usually considered as opposite. For instance, the hybrid allocation technique can be considered as split because the first heuristic building stage is performed offline, and the heuristic use is performed online. In the same fashion, B. Diouf et al. proposed an allocation technique relying both on offline and online behaviors, namely static and dynamic compilation. During the offline stage, an optimal spill set is first gathered using
Integer Linear Programming
An integer programming problem is a mathematical optimization or feasibility program in which some or all of the variables are restricted to be integers. In many settings the term refers to integer linear programming (ILP), in which the objective ...
. Then, live ranges are annotated using the
compressAnnotation
algorithm which relies on the previously identified optimal spill set. Register allocation is performed afterwards during the online stage, based on the data collected in the offline phase.
In 2007, Bouchez et al. suggested as well to split the register allocation in different stages, having one stage dedicated to spilling, and one dedicated to coloring and coalescing.
Comparison between the different techniques
Several metrics have been used to assess the performance of one register allocation technique against the other. Register allocation has typically to deal with a trade-off between code quality, i.e. code that executes quickly, and analysis overhead, i.e. the time spent determining analyzing the source code to generate code with optimized register allocation. From this perspective, execution time of the generated code and time spent in liveness analysis are relevant metrics to compare the different techniques.
Once relevant metrics have been chosen, the code on which the metrics will be applied should be available and relevant to the problem, either by reflecting the behavior of real-world application, or by being relevant to the particular problem the algorithm wants to address. The more recent articles about register allocation uses especially the Dacapo benchmark suite.
See also
*
Strahler number
In mathematics, the Strahler number or Horton–Strahler number of a mathematical tree (graph theory), tree is a numerical measure of its branching complexity.
These numbers were first developed in hydrology, as a way of measuring the complexity ...
, the minimum number of registers needed to evaluate an expression tree.
*
Register (keyword), the hint in C and C++ for a variable to be placed in a register.
*
Sethi–Ullman algorithm, an algorithm to produce the most efficient register allocation for evaluating a single expression when the number of registers required to evaluate the expression exceeds the number of available registers.
References
Sources
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
External links
A Tutorial on Integer Programming* Conferenc
Integer Programming and Combinatorial Optimization, IPCOThe Aussois Combinatorial Optimization Workshop* Bosscher, Steven; and Novillo, Diego
GCC gets a new Optimizer Framework An article about GCC's use of SSA and how it improves over older IRs.
Extensive catalogue of SSA research papers.
* Zadeck, F. Kenneth
"The Development of Static Single Assignment Form" December 2007 talk on the origins of SSA.
* VV.AA
"SSA-based Compiler Design"(2014)
Citations from CiteSeerOptimization manualsby
Agner Fog - documentation about x86 processor architecture and low-level code optimization
{{DEFAULTSORT:Register Allocation
Compiler optimizations