概述
干涉图着色是寄存器分配的核心算法。它的目标是:把程序中的虚拟寄存器(无限多个)映射到物理寄存器(数量有限,如 x86-64 只有 16 个通用寄存器)。
如果映射不了,就把一些变量溢出到栈上。
什么是干涉图?
核心概念:活跃区间(Live Range)
一个变量的活跃区间是从它被定义(def)到它最后一次被使用(use)之间的程序点。
c
a = 1; // a 被定义
b = 2; // b 被定义
c = a + b; // a 和 b 被使用,c 被定义
d = a + c; // a 和 c 被使用,d 被定义
return d; // d 被使用
活跃区间:
| 变量 | 定义点 | 最后使用点 | 活跃区间 |
|---|---|---|---|
| a | 第1行 | 第4行 | 1~4 |
| b | 第2行 | 第3行 | 2~3 |
| c | 第3行 | 第4行 | 3~4 |
| d | 第4行 | 第5行 | 4~5 |
干涉的定义
两个变量干涉(interfere):如果它们的活跃区间有重叠,就不能共用同一个物理寄存器。
从上面的例子:
- a 和 b 干涉:a 活跃到第4行,b 活跃到第3行,重叠区间 2~3。
- a 和 c 干涉:a 活跃到第4行,c 从第3行开始活跃,重叠区间 3~4。
- b 和 c 干涉:b 活跃到第3行,c 从第3行开始活跃,重叠区间 3。
- c 和 d 干涉:c 活跃到第4行,d 从第4行开始活跃,重叠区间 4。
- a 和 d 不干涉 :a 在第4行之后不再活跃,d 从第4行开始活跃,没有重叠(严格来说,如果 a 在第4行被使用后死亡,d 在第4行被定义,它们可以共用寄存器)。
干涉图
节点:每个变量(虚拟寄存器)一个节点。
边 :如果两个变量干涉,就在它们之间连一条边

(实际图中 a-c, a-b, b-c, c-d 都有边;a-d 无边。)
图着色
问题转化
给干涉图的每个节点分配一种颜色(物理寄存器),使得相邻节点颜色不同。
如果 K = 可用物理寄存器数量:
- 如果图能用 K 种颜色着色 → 所有变量都能分配到寄存器。
- 如果不能用 K 种颜色着色 → 需要 溢出(spill) 一些变量到栈上。
例子:K = 3
假设有 3 个物理寄存器(r0, r1, r2)。
干涉图:

着色过程:
text
步骤 1:选一个度数 < K 的节点。
这里 d 的度数是 1(只连 c),1 < 3,可以简化。
把 d 从图中移除,压入栈。
栈:[d]
步骤 2:剩余图中,c 的度数是 2(连 a, b, d 已移除),2 < 3,可以简化。
把 c 移除,压入栈。
栈:[d, c]
步骤 3:剩余图中,a 的度数是 1(连 b),1 < 3,可以简化。
把 a 移除,压入栈。
栈:[d, c, a]
步骤 4:剩余图中,b 的度数是 0,可以简化。
把 b 移除,压入栈。
栈:[d, c, a, b]
步骤 5:栈非空,开始分配颜色(从栈顶弹出):
b 弹出:分配 r0
a 弹出:邻居 b 用了 r0,a 可以用 r1
c 弹出:邻居 a 用了 r1,b 用了 r0,c 可以用 r2
d 弹出:邻居 c 用了 r2,d 可以用 r0 或 r1
最终分配:
a → r1
b → r0
c → r2
d → r0
验证:
- a 和 b 颜色不同(r1 vs r0)✅;
- a 和 c 不同(r1 vs r2)✅;
- b 和 c 不同(r0 vs r2)✅;
- c 和 d 不同(r2 vs r0)✅。
如果 K = 2 呢?
只有 2 个物理寄存器,干涉图需要 3 种颜色(因为 a, b, c 两两干涉,形成三角形)。

三角形需要 3 种颜色。K=2 时无法着色,必须溢出一个变量。
溢出选择:选度数最高的节点(c 的度数是 2),把它溢出到栈上。
溢出后,c 不再占寄存器,图变成:
text
a ─── b
这是二分图,可以用 2 种颜色着色。
从 IR 到寄存器分配
原始 IR
assembly
mov $1, a ; a = 1
mov $2, b ; b = 2
add a, b ; c = a + b
mov b, c ; 保存 c
add a, c ; d = a + c
mov c, d ; 保存 d
ret d ; return d
虚拟寄存器:a, b, c, d。
活跃区间
| 变量 | 定义 | 最后使用 | 活跃区间 |
|---|---|---|---|
| a | 第1行 | 第5行 | 1~5 |
| b | 第2行 | 第3行 | 2~3 |
| c | 第3行 | 第6行 | 3~6 |
| d | 第5行 | 第7行 | 5~7 |
干涉图
a 和 b 干涉(2~3 重叠)
a 和 c 干涉(3~5 重叠)
b 和 c 干涉(3 重叠)
c 和 d 干涉(5~6 重叠)
a 和 d 不干涉(a 在第5行之后死亡,d 从第5行开始)

用 3 个寄存器着色
如上所述:
text
a → r1
b → r0
c → r2
d → r0
最终汇编
assembly
mov $1, r1 ; a = 1
mov $2, r0 ; b = 2
add r1, r0 ; c = a + b(结果在 r0)
mov r0, r2 ; 保存 c 到 r2
add r1, r2 ; d = a + c(结果在 r2)
mov r2, r0 ; 保存 d 到 r0
ret r0 ; return d
注意:d 分配到了 r0,而 b 之前也用了 r0。因为 b 在第3行之后就不再活跃,r0 可以复用给 d。
代码
python
def release_pressure(self, node, reg_class):
"""Remove some register pressure from the given node."""
if node in self._num_blocked:
self._num_blocked[node] -= self.q(node.reg_class, reg_class)
# ------ #
def enable_moves(self, nodes):
for node in nodes:
for move in self.NodeMoves(node):
if move in self.activeMoves:
self.activeMoves.remove(move)
self.worklistMoves.add(move)
# ------ #
def decrement_degree(self, m):
"""If a node was lowered in degree, check if there are nodes that
can be moved from the spill list to the freeze of simplify
list
"""
# This check was m.degree == self.K - 1
if m in self.spill_worklist and self.is_colorable(m):
self.enable_moves({m} | m.adjecent)
self.spill_worklist.remove(m)
if self.is_move_related(m):
self.freeze_worklist.add(m)
else:
self.simplify_worklist.add(m)
# ------ #
def simplify(self):
"""Remove nodes from the graph"""
n = self.simplify_worklist.pop()
self.select_stack.append(n)
if self.verbose:
self.logger.debug("Simplify node %s", n)
# Pop out of graph, we place it back later:
self.frame.ig.mask_node(n)
for m in n.adjecent:
self.release_pressure(m, n.reg_class)
self.decrement_degree(m)
# ------ #
def unlink_move(self, move):
src = self.node(move.used_registers[0])
dst = self.node(move.defined_registers[0])
if move in src.moves:
src.moves.remove(move)
if move in dst.moves:
dst.moves.remove(move)
# ------ #
def is_move_related(self, n):
"""Check if a node is used by move instructions"""
return bool(self.NodeMoves(n))
# ------ #
def calc_num_blocked(self, node):
"""Calculate for the given node how many registers are blocked
by it's adjecent nodes.
This is an advanced form of a nodes degree, but then for
register of different classes.
"""
B = node.reg_class
num_blocked = sum(self.q(B, j.reg_class) for j in node.adjecent)
return num_blocked
# ------ #
def is_colorable(self, node) -> bool:
"""
Helper function to determine whether a node is trivially
colorable. This means: no matter the colors of the nodes neighbours,
the node can be given a color.
In case of one register class, this is: n.degree < self.K
In case of more than one register class, somehow the worst case
damage by all neighbours must be determined.
We do this now with the pq-test.
"""
if node.is_colored:
return True
if node in self._num_blocked:
num_blocked = self._num_blocked[node]
else:
num_blocked = self.calc_num_blocked(node)
self._num_blocked[node] = num_blocked
# This invariant should hold:
# assert num_blocked == self.calc_num_blocked(node)
return num_blocked < self.K[node.reg_class]
# ------ #
def add_worklist(self, u):
if (
(u not in self.precolored)
and (not self.is_move_related(u))
and self.is_colorable(u)
):
self.freeze_worklist.remove(u)
self.simplify_worklist.add(u)
# ------ #
def coalesc(self):
"""Coalesc moves conservative.
This means, merge the variables of a move into
one variable, and delete the move. But do this
only when no spill will occur.
"""
# Remove the move from the worklist:
m = self.worklistMoves.pop()
x = self.node(m.defined_registers[0])
y = self.node(m.used_registers[0])
u, v = (y, x) if y in self.precolored else (x, y)
if self.verbose:
self.logger.debug("Coalescing %s which couples %s and %s", m, u, v)
if u is v:
# u is v, so we do 'mov x, x', which is redundant
self.coalescedMoves.add(m)
self.unlink_move(m)
self.add_worklist(u)
if self.verbose:
self.logger.debug("Move was an identity move")
elif (v in self.precolored) or self.has_edge(u, v):
# Both u and v are precolored
# or there is an interfering edge
# between the two nodes:
self.constrainedMoves.add(m)
self.unlink_move(m)
self.add_worklist(u)
self.add_worklist(v)
if self.verbose:
self.logger.debug("Move is constrained!")
elif (
u.is_colored
and issubclass(u.reg_class, v.reg_class)
and all(self.ok(t, u) for t in v.adjecent)
) or ((not u.is_colored) and self.conservative(u, v)):
# Check if v can be given the class of u, in other words:
# is u a subclass of v?
self.coalescedMoves.add(m)
self.unlink_move(m)
self.combine(u, v)
self.add_worklist(u)
else:
self.logger.debug("Active move!")
self.activeMoves.add(m)
# ------ #
def freeze_moves(self, u):
"""Freeze moves for node u"""
for m in list(self.NodeMoves(u)):
if m in self.activeMoves:
self.activeMoves.remove(m)
else:
self.worklistMoves.remove(m)
self.unlink_move(m)
self.frozenMoves.add(m)
# Check other part of the move for still being move related:
src = self.node(m.used_registers[0])
dst = self.node(m.defined_registers[0])
v = src if u is dst else dst
if (
v not in self.precolored
and not self.is_move_related(v)
and self.is_colorable(v)
):
assert v in self.freeze_worklist
self.freeze_worklist.remove(v)
self.simplify_worklist.add(v)
# ------ #
def select_spill(self):
"""Select potential spill node.
Select a node to be spilled. This is optimistic,
since this might be turned into a real spill.
Continue nevertheless, to discover more potential
spills, or we might be lucky and able to color the
graph any ways.
"""
# TODO: select a node which is certainly not a node that was
# introduced during spilling?
# Select to be spilled variable:
# Select node with the lowest priority:
p = []
for n in self.spill_worklist:
assert not n.is_colored
d = sum(len(self.frame.ig.defs(t)) for t in n.temps)
u = sum(len(self.frame.ig.uses(t)) for t in n.temps)
priority = (u + d) / n.degree
self.logger.debug("%s has spill priority=%s", n, priority)
p.append((n, priority))
node = min(p, key=lambda x: x[1])[0]
# Potential spill node, place in simplify worklist:
self.spill_worklist.remove(node)
self.simplify_worklist.add(node)
self.freeze_moves(node)
# ------ #
def rewrite_program(self, node):
"""Rewrite program by creating a load and a store for each use"""
# Generate spill code:
self.logger.debug(f"Placing {node} on stack")
if self.verbose:
self.reporter.message(f"Placing {node} on stack")
size = node.reg_class.bitsize // 8
alignment = size
slot = self.frame.alloc(size, alignment)
self.logger.debug("Allocating stack slot %s", slot)
# TODO: maybe break-up coalesced node before doing this?
for tmp in node.temps:
instructions = OrderedSet(
self.frame.ig.uses(tmp) + self.frame.ig.defs(tmp)
)
for instruction in instructions:
if self.verbose:
self.reporter.message(
f"Updating instruction: {instruction}"
)
vreg2 = self.frame.new_reg(type(tmp))
self.logger.debug("tmp: %s, new: %s", tmp, vreg2)
if self.verbose:
self.reporter.message(f"Replace {tmp} by {vreg2}")
instruction.replace_register(tmp, vreg2)
if instruction.reads_register(vreg2):
code = self.spill_gen.gen_load(self.frame, vreg2, slot)
if self.verbose:
self.reporter.message(
f"Load code before: {list(map(str, code))}"
)
self.frame.insert_code_before(instruction, code)
if instruction.writes_register(vreg2):
code = self.spill_gen.gen_store(self.frame, vreg2, slot)
if self.verbose:
self.reporter.message(
f"Store code after: {list(map(str, code))}"
)
self.frame.insert_code_after(instruction, code)
if self.verbose:
self.reporter.dump_frame(self.frame)
# ------ #
def remove_redundant_moves(self):
"""Remove coalesced moves"""
for move in self.coalescedMoves:
self.frame.instructions.remove(move)
# ------ #
def apply_colors(self):
"""Assign colors to registers"""
# Apply all colors:
for node in self.frame.ig:
assert node.reg is not None
for reg in node.temps:
if reg.is_colored:
assert reg.color == node.reg.color
else:
reg.set_color(node.reg.color)
# Mark the register as used in this frame:
self.frame.used_regs.add(node.reg.get_real())
# TODO:
# if self.frame.debug_db:
# self.frame.debug_db.map(
# reg, self.arch.get_register(node.reg))
# ------ #
def alloc_frame(self, frame: Frame):
"""Do iterated register allocation for a single frame.
This is the entry function for the register allocator and drives
through all stages of the algorithm.
Args:
frame: The frame to perform register allocation on.
"""
spill_rounds = 0
self.logger.debug("Starting iterative coloring")
while True:
self.init_data(frame)
# Process all work lists:
while True:
# self.check_invariants()
# Run one of the possible steps:
if self.simplify_worklist:
self.simplify()
elif self.worklistMoves:
self.coalesc()
elif self.freeze_worklist:
self.freeze()
elif self.spill_worklist:
self.select_spill()
else:
break
self.logger.debug("Now assinging colors")
spilled_nodes = self.assign_colors()
if spilled_nodes:
spill_rounds += 1
self.logger.debug("Spilling round %s", spill_rounds)
max_spill_rounds = 30
if spill_rounds > max_spill_rounds:
raise RuntimeError(
f"Give up after {max_spill_rounds} spill rounds!"
)
# Rewrite program now.
for node in spilled_nodes:
self.rewrite_program(node)
if self.verbose:
self.reporter.message("Rewrote program with spilling")
self.reporter.dump_frame(self.frame)
else:
# Done!
break
self.remove_redundant_moves()
self.apply_colors()
release_pressure
python
def release_pressure(self, node, reg_class):
"""Remove some register pressure from the given node."""
if node in self._num_blocked:
self._num_blocked[node] -= self.q(node.reg_class, reg_class)
作用:
- 当某个节点被简化(从图中移除)时,减少它邻居的"阻塞计数"。
- _num_blocked:记录每个节点被多少寄存器阻塞(用于多寄存器类的情况)。q(B, j):计算寄存器类 B 和 j 之间的阻塞数量。
enable_moves
python
def enable_moves(self, nodes):
for node in nodes:
for move in self.NodeMoves(node):
if move in self.activeMoves:
self.activeMoves.remove(move)
self.worklistMoves.add(move)
作用:把与给定节点相关的、之前被"冻结"的 move 指令重新激活,放回工作队列。
背景:在寄存器分配中,move 指令(如 mov a, b)如果两个操作数能分配到同一寄存器,就可以删除。但有时因为干涉关系,暂时不能合并,就把 move 冻结。当图变化后,可能又可以合并了,就重新激活。
decrement_degree
python
def decrement_degree(self, m):
if m in self.spill_worklist and self.is_colorable(m):
self.enable_moves({m} | m.adjecent)
self.spill_worklist.remove(m)
if self.is_move_related(m):
self.freeze_worklist.add(m)
else:
self.simplify_worklist.add(m)
作用:当一个节点的度数降低后,检查它是否从"可能溢出"变成"可着色"。
逻辑:
- 如果 m 在 spill 列表中,且现在可着色了 → 从 spill 列表移到 freeze 或 simplify 列表。
- 如果 m 与 move 相关 → 移到 freeze 列表(先尝试合并 move)。
- 否则 → 移到 simplify 列表(直接简化)。
simplify
python
def simplify(self):
"""Remove nodes from the graph"""
n = self.simplify_worklist.pop()
self.select_stack.append(n)
self.frame.ig.mask_node(n)
for m in n.adjecent:
self.release_pressure(m, n.reg_class)
self.decrement_degree(m)
作用:从图中移除一个度数 < K 的节点,压入栈。
步骤:
- 从 simplify 工作队列弹出一个节点 n。
- 把 n 压入 select_stack(后面着色时用)。
- 在干涉图中"遮蔽"n(暂时移除)。
- 对每个邻居 m:减少阻塞计数,降低度数。
unlink_move
python
def unlink_move(self, move):
src = self.node(move.used_registers[0])
dst = self.node(move.defined_registers[0])
if move in src.moves:
src.moves.remove(move)
if move in dst.moves:
dst.moves.remove(move)
作用:从源节点和目标节点的 move 列表中移除这条 move 指令。
is_move_related
python
def is_move_related(self, n):
"""Check if a node is used by move instructions"""
return bool(self.NodeMoves(n))
作用:检查节点 n 是否与任何 move 指令相关。
calc_num_blocked
python
def calc_num_blocked(self, node):
B = node.reg_class
num_blocked = sum(self.q(B, j.reg_class) for j in node.adjecent)
return num_blocked
作用:计算节点 node 被多少寄存器阻塞。
公式:对所有邻居 j,累加 q(B, j.reg_class)。q(B, j):寄存器类 B 被寄存器类 j 阻塞的数量。
在单寄存器类情况下,q(B, j) = 1(每个邻居阻塞一个寄存器)。
is_colorable
python
def is_colorable(self, node) -> bool:
if node.is_colored:
return True
if node in self._num_blocked:
num_blocked = self._num_blocked[node]
else:
num_blocked = self.calc_num_blocked(node)
self._num_blocked[node] = num_blocked
return num_blocked < self.K[node.reg_class]
作用:判断节点是否"可着色"。
条件:
- 如果已经着色 → 可着色。
- 否则,计算 num_blocked,如果 < K(可用寄存器数量)→ 可着色。
直觉:如果邻居占用的寄存器数量小于 K,那么至少还有一个寄存器可用。
add_worklist
python
def add_worklist(self, u):
if (
(u not in self.precolored)
and (not self.is_move_related(u))
and self.is_colorable(u)
):
self.freeze_worklist.remove(u)
self.simplify_worklist.add(u)
作用:如果节点 u 不是预着色的、不与 move 相关、且可着色,就从 freeze 列表移到 simplify 列表。
coalesc
python
def coalesc(self):
"""Coalesc moves conservative."""
m = self.worklistMoves.pop()
x = self.node(m.defined_registers[0])
y = self.node(m.used_registers[0])
u, v = (y, x) if y in self.precolored else (x, y)
...
作用:尝试合并 move 指令的两个操作数(mov x, y 合并为同一个寄存器)。
逻辑:
- 如果 u is v → 冗余 move,删除。
- 如果 v 是预着色的,或 u 和 v 之间有干涉边 → 不能合并,标记为 constrained。
- 如果 u 已着色且 v 的邻居都能接受 u 的寄存器类,或保守合并成功 → 合并。
- 否则 → 标记为 active move。
freeze_moves
python
def freeze_moves(self, u):
"""Freeze moves for node u"""
for m in list(self.NodeMoves(u)):
if m in self.activeMoves:
self.activeMoves.remove(m)
else:
self.worklistMoves.remove(m)
self.unlink_move(m)
self.frozenMoves.add(m)
...
作用:冻结与节点 u 相关的所有 move 指令。
逻辑:
- 从 active 或 worklist 中移除 move。
- 从源/目标节点的 move 列表中移除。
- 加入 frozen 集合。
- 检查 move 另一端的节点是否还能简化。
select_spill
python
def select_spill(self):
"""Select potential spill node."""
p = []
for n in self.spill_worklist:
d = sum(len(self.frame.ig.defs(t)) for t in n.temps)
u = sum(len(self.frame.ig.uses(t)) for t in n.temps)
priority = (u + d) / n.degree
p.append((n, priority))
node = min(p, key=lambda x: x[1])[0]
self.spill_worklist.remove(node)
self.simplify_worklist.add(node)
self.freeze_moves(node)
作用:从 spill 列表中选择一个节点溢出。
优先级公式:
priority=use 次数+def 次数度数 priority = \frac{use\ 次数 + def\ 次数}{度数} priority=度数use 次数+def 次数
- 度数高 → 优先级低(更倾向于溢出)。
- 使用次数多 → 优先级高(更倾向于保留)。
选择优先级最低的节点溢出。
注意:这是"乐观"溢出------先标记为可能溢出,继续着色,可能最后不需要真正溢出。
rewrite_program
python
def rewrite_program(self, node):
"""Rewrite program by creating a load and a store for each use"""
size = node.reg_class.bitsize // 8
alignment = size
slot = self.frame.alloc(size, alignment)
for tmp in node.temps:
instructions = OrderedSet(
self.frame.ig.uses(tmp) + self.frame.ig.defs(tmp)
)
for instruction in instructions:
vreg2 = self.frame.new_reg(type(tmp))
instruction.replace_register(tmp, vreg2)
if instruction.reads_register(vreg2):
code = self.spill_gen.gen_load(self.frame, vreg2, slot)
self.frame.insert_code_before(instruction, code)
if instruction.writes_register(vreg2):
code = self.spill_gen.gen_store(self.frame, vreg2, slot)
self.frame.insert_code_after(instruction, code)
作用:真正执行溢出------为每个被溢出的变量分配栈槽,在每次使用前插入 load,每次定义后插入 store。
步骤:
- 分配栈槽(大小 = 寄存器位宽 / 8)。
- 对每个临时变量:
- 创建新的虚拟寄存器 vreg2。
- 替换指令中的 tmp 为 vreg2。
- 如果指令读 vreg2 → 在指令前插入 load。
- 如果指令写 vreg2 → 在指令后插入 store。
remove_redundant_moves
python
def remove_redundant_moves(self):
"""Remove coalesced moves"""
for move in self.coalescedMoves:
self.frame.instructions.remove(move)
作用:删除所有已合并的 move 指令(它们变成冗余的)。
apply_colors
python
def apply_colors(self):
"""Assign colors to registers"""
for node in self.frame.ig:
assert node.reg is not None
for reg in node.temps:
if reg.is_colored:
assert reg.color == node.reg.color
else:
reg.set_color(node.reg.color)
self.frame.used_regs.add(node.reg.get_real())
作用:把干涉图节点的颜色应用到实际的虚拟寄存器上。
alloc_frame(主循环)
python
def alloc_frame(self, frame: Frame):
spill_rounds = 0
while True:
self.init_data(frame)
while True:
if self.simplify_worklist:
self.simplify()
elif self.worklistMoves:
self.coalesc()
elif self.freeze_worklist:
self.freeze()
elif self.spill_worklist:
self.select_spill()
else:
break
spilled_nodes = self.assign_colors()
if spilled_nodes:
spill_rounds += 1
if spill_rounds > 30:
raise RuntimeError(...)
for node in spilled_nodes:
self.rewrite_program(node)
else:
break
self.remove_redundant_moves()
self.apply_colors()
作用:寄存器分配的主循环。
流程:
- 初始化数据(构建干涉图)。
- 反复执行:
- 如果 simplify 队列非空 → simplify(移除低度数节点)。
- 否则如果 move 队列非空 → coalesc(尝试合并 move)。
- 否则如果 freeze 队列非空 → freeze(冻结 move)。
- 否则如果 spill 队列非空 → select_spill(选择溢出)。
- 否则 → 跳出,开始着色。
- 着色(assign_colors)。
- 如果有溢出节点 → 重写程序(插入 load/store),增加溢出轮次,重新开始。
- 如果无溢出 → 完成。
- 删除冗余 move,应用颜色。
总结
| 概念 | 含义 |
|---|---|
| 干涉图 | 节点 = 变量,边 = 活跃区间重叠 |
| 图着色 | 给节点分配颜色(物理寄存器),相邻节点颜色不同 |
| K 种颜色 | K = 可用物理寄存器数量 |
| 可着色 | 度数 < K 的节点一定可着色 |
| 溢出 | 无法着色时,把变量放到栈上 |
| 简化 | 移除度数 < K 的节点,压入栈 |
| 合并(coalesc) | 尝试把 move 的两个操作数合并到同一寄存器 |
| 冻结(freeze) | 暂时放弃合并 move |
| 乐观溢出 | 先标记为可能溢出,继续着色,可能最后不需要 |
一句话:干涉图着色就是把变量分配问题转化为图着色问题。节点是变量,边是活跃区间重叠。
用 K 种颜色着色,K 是物理寄存器数量。如果着不了色,就溢出到栈上。
代码实现了一个完整的迭代寄存器分配器,包括简化、合并、冻结、溢出、重写程序等步骤。