Skip to content

算法Algorithm

寄存器溢出与重物化

Register spilling · Rematerialization · 溢出重写

把栈位置真正改写为显式LOAD/STORE,核算暂存器和调用破坏,再以可证明的常量配方消除不必要访存。

形式陈述 ​

位置表不是机器会执行的指令 ​

本页输入是纯整数IR和已经通过分配验证合同的位置候选。输出是只含物理寄存器、私有槽和立即数的目标IR。源名字无论被标作R0还是slot0,都必须通过目标指令真的取得其值;解释器不会在后台替虚拟变量保存副本。

资源固定为四个物理寄存器:R0/R1可分配,S0/S1保留作重载和本条源指令内的中间结果。二元ADD/SUB先读取两输入再写结果;目标指令允许整数立即数。LD把私有槽读到寄存器,ST把寄存器或立即数写到私有槽。input从指定外部输入键取数学整数;分支只观察零/非零。没有可变堆、外部I/O、异常、溢出或有限栈耗尽。

每条源指令最多两个普通读取操作数。先将第一个溢出操作数装到S0、第二个装到S1;寄存器操作数直接读取。若结果在寄存器,就写那个寄存器;若在槽中,就先写S0再ST。由于所有输入先读完,S0也可作为第一个输入和结果,例如ADD S0,S0,S1。S0/S1中的残值不作为下一条源指令的保值承诺。

调用不能沿用普通加法的保存假设 ​

double先读取R0并在R0返回两倍值,破坏R0/S0/S1,保留R1和调用者私有槽。lowering先把参数送到R0,执行CALL2,再把R0中的结果送到目标位置。跨调用的旧值必须早已位于R1或槽中;不能等CALL2之后才想起保存原R0。

本执行器运行顶层过程。如果把这段代码嵌入真实函数,还需按具体ABI设置帧、对齐、保存它改写的callee-saved寄存器等。教学CALL2表达的是上述明确的外部状态变换。

重物化需要一份值不变的配方 ​

溢出保存“以后从哪里取值”;重物化(rematerialization)保存“以后怎样重新产生同一个值”。最小安全例是常量c=7:不必在定义处ST,使用处可用IMM暂存器,7。它改变实际指令和内存流量,不只是给栈槽换个名字。[1, §3]

一般表达式配方必须纯、全定义,且重新计算时各输入仍对应原来那份值。没有别名写入、异常和外部状态还不够:v:=x+1之后若x改了,照文本再算就不再是原v。下载版只接受恰有一个静态const定义的名字,并要求该名字的home不与其他名字共享;合并spill族需要额外族级证明,所以这里保守保留其store。

直觉

存一张结果,还是带一张能重做的纸条 ​

内存可以保存已经算好的数,之后读取即可。若结果是一个固定的小常量,现场重新装入可能更便宜;若它依赖一个已经被覆盖的输入,纸条只写“再加一”就不够了。

两者都要付实际费用:保存方案付store和load,重算方案付IMM或计算指令。重物化只在指定目标成本模型下有利;昂贵运算被反复重算,可能比一次保存更慢。

例子与边界

主例的完整物理代码 ​

图分配给a/flag两个槽,t2在R1,其余运算结果顺序复用R0。下面是实际可执行目标,不再含a、t2等虚拟操作数;IN的最后一个字段只是外部输入键。

text
E: IN S0,a;       ST slot0,S0
   IN S0,flag;    ST slot1,S0
   LD S0,slot0;   ADD R1,S0,1
   LD S0,slot0;   ADD R0,S0,2
   LD S0,slot1;   BR S0,T,F
T: ADD R0,R1,R0; JMP J
F: SUB R0,R1,R0; JMP J
J: CALL2
   LD S0,slot0;   ADD R0,S0,R0
   RET R0

t4→v的复制和把v送R0的参数复制都成为同位置动作,可以省略;它们的值没有凭空消失。CALL2返回后R1虽然保留,其旧t2已不再需要;真正要用的a从slot0重载。输入a=3、flag=0执行F,R0从5变成−1,再被CALL2变成−2,最后加a返回1;flag=1返回21。

每条实际分支路径都有2次ST和4次LD,总共6次私有槽访存。静态代码同时含T和F,不能把两侧都加进一次执行成本。槽中没有未初始化读取:两个IN之后立即ST,后面的每次LD都由这份存储或保持它的路径覆盖。

两次使用常量,少了哪三次访存 ​

text
c := 7; x := input(x)
u := x + c; y := u + c; return y

给c独立slot0,x/u/y按顺序共用R0。普通方案先ST slot0,7,两次加法前各LD S1,slot0。重物化方案删掉store,两次使用分别IMM S1,7。x=3时两者都返回17;普通路径7条目标指令、3次访存,重物化6条指令、0次访存。

这个账本采用一次ST/LD/IMM各计一条目标指令,不宣称真实处理器延迟相同。若常量编码需要多条机器指令,应换成真实展开成本;本文的立即数是数学模型的输入能力。

原输入已经改变的反例 ​

令x:=3; saved:=x+1; x:=9; return saved。正确返回4。若把saved当作“随时重新算x+1”而删除保存,最后会得到10。可用原x的不可变版本作为配方输入,但这份旧版本也需要自己的保值位置;重物化不能把它的成本隐藏起来。

另有c:=7; d:=c; return d,若分配器合并c/d到同一溢出槽,直接删c的store、再把c→d当物理自复制删掉,d就会读取未初始化槽。当前实现看到共享home会保留普通保存方案。更积极的实现可以给整个别名族证明相同常量配方,再统一改写所有使用。

推论与应用

用指令边界关系证明重写 ​

在每条源指令入口,要求所有当前活跃名字的值存在其分配位置;获准重物化的独立常量则由配方给出相同值。LD/IMM先建立两个操作数,ADD/SUB/MOV按照同一全定义运算产生结果,ST把需要保留的结果交回home。干涉检查保证目标写入不会覆盖另一个仍需的不同值;CALL2的破坏集合另由调用边保护。到下一条源指令边界,关系重新成立。

一条源指令被有限目标序列匹配,分支读取同一个整数,所以选择同一条源边,返回值也相同。循环中逐边归纳同样成立;有限额外动作没有新增独立循环。证明依赖明示的目标资源和无别名私有槽,不能由“随机输入都相等”代替。

成本与终点迁移 ​

固定元数普通指令至多插常数条重载/保存,故给定位置后普通lowering按源IR大小L花O(1+L)工作和输出。并行复制含k个动作时,另计本单元朴素断环排序O((k+1)2);每次原边仍只展开有限长度。动态成本要沿实际执行路径累加,循环次数、调用次数与每次是否访问溢出值都会影响总量。

先在主例目标上逐条解释得到21/1和6次访存,再把a误放R0而不保存,检查调用前后的值在哪一步丢掉。随后比较常量重物化与共享home反例;最后把double改为连R1也破坏,给分配器增加约束后重新生成目标,不能只修改目标CALL2的破坏行为却继续沿用旧候选。

参考资料

[1] Lal George、Andrew W. Appel,Iterated Register Coalescing,ACM TOPLAS18(3),1996,§2 p.303讨论spill引入的短活跃范围,§3 pp.306–307说明常量无需保存、可在使用处重建。

[2] Sandrine Blazy、Benoît Robillard、Andrew W. Appel,Formal Verification of Coalescing Graph-Coloring Register Allocation,ESOP2010作者稿,§§1–2:部分着色和spill代价的规格边界。本文保留两个暂存器、CALL2、配方限制与账本是独立教学机器,不是论文原编译器的目标ISA。

关系图谱7 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系