优化有两个独立问题:变换是否合法,合法变换是否值得。少一条指令、少一次调用或更少源码都不自动回答这两个问题。正确性依赖源语言与 IR 的语义,收益则依赖目标和工作负载。
这些优化并非只能在 SSA 上实现,也不都具有统一的线性复杂度。SSA 提供显式 def-use 等便利,实际算法还涉及控制流、别名、成本和编译时间预算。
常量折叠与传播
折叠在编译时求出已知表达式的值,例如整数 2+3 得 5;传播把已知常量带到使用位置,继而触发更多折叠。SCCP 还同时跟踪可执行控制流边,利用常量条件排除不可能分支。
x*0→0 是代数化简,不是所有操作数都为常量的折叠。其合法性取决于语义:IEEE 浮点里的 NaN、无穷和有符号零会破坏某些实数代数恒等式;如果 x 来自有副作用的表达式,删除其求值也需证明。LLVM IR 的 poison、undef 和相关标志也有独立规则。LLVM UB 手册
SSA 中唯一的静态定义不代表值一定是常量,φ 与循环仍然需要分析。条件传播也不意味着被删分支在语言前端永远无需语法或类型检查。
CSE 与 GVN:找到可复用的值
两次 b+c 若操作语义、操作数与可见状态一致,后一次可能复用前一次结果。已有值必须在需要它的路径上可用;一个只在 if 左分支算出的值,不能直接替换右分支里的计算。
GVN 用值编号识别等价关系,可能比逐字匹配表达式更强。处理 load 时还要证明期间没有可能别名的写入或调用改变内存。两条指令文本相同,但读到的状态不同,就不是可替换的值。
复用也可能延长某个结果的活跃范围,增加寄存器压力,因此合法的 CSE 不保证总能加速。
相同的 load,隔着一次可能别名的 store
下面两次读取的源码都是 *p,但 q 可能指向同一对象。若 p=q 且初值为 3,第一次读到 3,写入 7 后第二次读到 7,结果是 10;直接复用第一次读取会错误地算成 6。
static int original ( int * p , int * q ) {
int before = * p;
* q = 7 ;
return before + * p;
}
static int wrong_reuse ( int * p , int * q ) {
int before = * p;
* q = 7 ;
return before + before;
}
int main ( void ) {
int a = 3 , b = 3 ;
if ( original ( & a, & a) != 10 ) return 1 ;
if ( wrong_reuse ( & b, & b) != 6 ) return 2 ;
a = 3 ; b = 0 ;
if ( original ( & a, & b) != 6 || b != 7 ) return 3 ;
return 0 ;
}
这个反例没有违反别名约定,两种指针关系都属于函数允许的输入。若接口额外采用 restrict 等承诺,优化器可能利用更强事实,但调用方也必须满足相应规则;不能给函数随手加上承诺,再把原本允许的别名调用继续当作等价性测试。
两次相同的 load,读到的还是同一份状态吗?
这一变换需要的是不会被中间写入影响的证据,不是某一次数值碰巧相同。让 q 指向 A 并把写入值也设为 3,两种程序会同样得到 6,但别名风险并未消失。再改成 7,反例立即出现。若 q 指向独立 B,则该 store 不改变 A,本例中的后一次 load 才有可复用的依据。
正在呈现知识画面 重新加载
两次相同的 load,读到的还是同一份状态吗? 相同指令文本不足以证明值相同。q=p 时,store 改变了后一次 load 的来源;q 指向独立对象时,本例才有复用依据。
{"id":"load-alias-evidence","title":"两次相同的 load,读到的还是同一份状态吗?","summary":"相同指令文本不足以证明值相同。q=p 时,store 改变了后一次 load 的来源;q 指向独立对象时,本例才有复用依据。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"target\"><button data-mode=\"A\" data-i18n=\"same\"></button><button data-mode=\"B\" data-i18n=\"separate\"></button></div><div class=\"control\"><label for=\"write\" data-i18n=\"write\"></label><output for=\"write\" id=\"write-value\"></output><input id=\"write\" type=\"range\" min=\"0\" max=\"9\" value=\"7\" step=\"1\"></div><p class=\"source\">a = *p; *q = v; b = *p; return a+b;</p><div id=\"memory\" class=\"track\"></div><div id=\"loads\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"real\"></span><strong id=\"real\"></strong></div><div><span data-i18n=\"reuse\"></span><strong id=\"reuse\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.metrics strong{font-size:20px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let target='A';function draw(){const v=+$('#write').value,a=3,b=target==='A'?v:3;set('write-value',v);cells('memory',[{text:'A: 3 → '+(target==='A'?v:3),cls:target==='A'?'gold':'on'},{text:'B: 3 → '+(target==='B'?v:3),cls:target==='B'?'gold':'on'}]);table('loads',[['first *p','store *q','second *p'],['3',target+' ← '+v,{text:String(b),cls:b!==3?'gold':'on'}]]);set('real',a+b);set('reuse',6);set('explanation',t(a+b===6?'ok':'bad'));$('#real').dataset.safe=String(target==='B');pressed(target);}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{target=b.dataset.mode;draw()});$('#write').oninput=draw;draw();","audio":false,"strings":{"bad":"同一个对象已经改变,复用旧值改变了结果。","heading":"两次相同的 load,读到的还是同一份状态吗?","intro":"p 固定指向 A,选择 q 指向 A 或 B。观察中间 store 后的第二次读取,再比较错误复用旧值的结果。","ok":"这组输入结果相同;只有独立对象分支提供不受该 store 影响的结构性证据。","real":"保留两次 load","reset":"重置","reuse":"复用第一次 load","same":"A:与 p 别名","scope":"两个独立整数对象,初值 A=B=3,顺序执行,无线程、volatile、atomic、restrict 或外部调用。仅比较这一条 load 消除。","scopeLabel":"模型范围与约定","separate":"B:独立对象","target":"q 指向哪里","write":"store 写入值"}}
DCE 与 DSE:结果不用,不等于行为可删
纯计算结果没有使用者时,可以递归删除;其操作数定义也可能随之变成死代码。副作用、异常、同步和控制流语义则需要保留或单独证明。
Store 不能仅因为没有 SSA 使用者就删除,但若后续完全覆盖且中间无观察,DSE 可以移除死写。函数调用也并非永远不可删:若已知相关效果、终止及异常属性允许,未使用结果的调用可能被消除。反之,“看起来只是读内存”也不自动表示可删除。
内联:暴露上下文,而不只是去掉 call
把被调函数体引入调用点,可让常量参数、别名关系和控制流条件参与后续优化。成本包括代码膨胀、编译工作、指令缓存与寄存器压力。
只调用一次的函数可能很适合内联,但原定义是否能删除还受导出、地址逃逸等影响。递归也需要限制展开。实际决策以调用点为单位结合成本模型,而不是“一定内联所有小函数”。
LICM:值不变与提前执行安全分开证明
循环不变量的操作数在循环迭代中不变,相关内存也不能被循环写入改变。但移到 preheader 还改变了执行条件:循环可能一次都不执行,原指令也可能位于条件分支。
循环不变量不一定可以无条件移到循环前
原程序:n > 0 才执行
循环内计算 10 / d
错误外提:先计算 10 / d
再判断 n > 0
需要证明
当 n=0、d=0 时,原程序不除零;无条件外提可能引入原本不存在的错误行为。
合法外提需要证明指令可安全推测执行,或通过必经执行条件及其他约束证明提前不会改变行为。Load 要考虑地址有效性和别名,除法要考虑异常语义,调用要考虑效果。不是“无副作用加操作数不变”就完整覆盖所有限制。
外提后还要保留原来使用者所需的值。把原来的赋值直接删掉却不连接后续 use,是错误的教学变换。
用明确异常语义验证零次循环,再补上执行守卫
为避免依赖 C 除零的未定义行为,下面用 Python 的整数除法与异常语义构造教学模型。只考虑非负循环次数;原版与带守卫的外提版应在结果和异常上相同。无守卫版在 n=0、d=0 时额外抛错,构成反例。
def original ( n , d ) :
total = 0
for _ in range ( n ) :
total += 10 / / d
return total
def hoisted ( n , d ) :
if n == 0 :
return 0
value = 10 / / d
total = 0
for _ in range ( n ) :
total += value
return total
def wrong ( n , d ) :
value = 10 / / d
return sum ( value for _ in range ( n ) )
def outcome ( fn , n , d ) :
try :
return ( " value" , fn ( n , d ) )
except ZeroDivisionError :
return ( " error" , " division by zero" )
for n in range ( 5 ) :
for d in ( - 2 , 0 , 2 , 3 ) :
assert outcome ( original , n , d ) == outcome ( hoisted , n , d )
assert outcome ( original , 0 , 0 ) == ( " value" , 0 )
assert outcome ( wrong , 0 , 0 ) == ( " error" , " division by zero" )
守卫有效还依赖本例的具体结构:除法是每次进入循环后必经的纯计算,d 不变,之前没有必须先发生的效果。若除法藏在循环内的另一个条件分支,只证明 n>0 仍不足以外提;若其他操作可能先抛异常,还需保留语言要求的异常先后关系。不能把这段修复推广成任意 LICM 的统一模板。
操作数不变,为什么还不能直接移到循环外?
将结果、异常和除法次数分别列出,是为了先判断合法性,再讨论节省了多少工作。n=0、d=0 是关键反例:原版不执行除法,无守卫版却提前抛错。带守卫版在这个受限循环中恢复了原执行条件。表中的次数只数除法尝试,不是总指令数或实际耗时。
正在呈现知识画面 重新加载
操作数不变,为什么还不能直接移到循环外? 零次循环本来不执行除法。无守卫外提可能额外触发除零;本例用 n>0 守卫恢复执行条件,减少计算仍须先保持行为。
{"id":"licm-execution-condition","title":"操作数不变,为什么还不能直接移到循环外?","summary":"零次循环本来不执行除法。无守卫外提可能额外触发除零;本例用 n>0 守卫恢复执行条件,减少计算仍须先保持行为。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"n\" data-i18n=\"n\"></label><output for=\"n\" id=\"n-value\"></output><input id=\"n\" type=\"range\" min=\"0\" max=\"5\" value=\"0\" step=\"1\"></div><div class=\"control\"><label for=\"d\" data-i18n=\"d\"></label><output for=\"d\" id=\"d-value\"></output><input id=\"d\" type=\"range\" min=\"0\" max=\"4\" value=\"0\" step=\"1\"></div><div id=\"results\" class=\"table\"></div><p id=\"equation\" class=\"formula\"></p><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#results .tr{grid-template-columns:1.25fr 1fr .7fr}#results .tr>div{min-height:4.8em}#equation{white-space:pre-line;min-height:7.2em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function run(n,d,mode){let count=0,total=0;function div(){count++;if(d===0)throw Error('DivZero');return Math.floor(12/d)}try{if(mode==='original'){for(let i=0;i<n;i++)total+=div()}else if(mode==='unguarded'){const x=div();for(let i=0;i<n;i++)total+=x}else if(n>0){const x=div();for(let i=0;i<n;i++)total+=x}return{result:String(total),count}}catch(e){return{result:e.message,count}}}function draw(){const n=+$('#n').value,d=+$('#d').value,modes=['original','unguarded','guarded'],r=modes.map(x=>run(n,d,x));set('n-value',n);set('d-value',d);table('results',[['',t('outcome'),t('count')],...modes.map((m,i)=>[t(m),{text:r[i].result,cls:r[i].result===r[0].result?'on':'gold'},r[i].count])]);set('equation','original: n > 0 → divide\\nunguarded: always divide\\nguarded: n > 0 → divide');set('explanation',t(r[0].result===r[1].result?'ok':'bad'));$('#results').dataset.results=JSON.stringify(r);}$('#n').oninput=draw;$('#d').oninput=draw;draw();","audio":false,"strings":{"bad":"无守卫版本产生了原程序没有的异常。","count":"除法次数","d":"除数 d","guarded":"n>0 守卫外提","heading":"操作数不变,为什么还不能直接移到循环外?","intro":"改变循环次数 n 和除数 d。三种程序采用相同的明确异常语义,比较结果、异常与除法执行次数。","n":"循环次数 n","ok":"本组输入行为一致;把 n=0、d=0 放在一起检查关键边界。","original":"原始循环","outcome":"结果 / 异常","reset":"重置","scope":"教学语义为非负整数 floor(12/d),d=0 抛 DivZero;循环每次必经除法,没有更早的副作用或异常。这不是所有 LICM 的通用守卫。","scopeLabel":"模型范围与约定","unguarded":"无守卫外提"}}
循环展开:用代码大小换控制与并行机会
部分展开复制若干迭代体,并处理剩余迭代;完全展开需要可确定的有限迭代或相应专门化条件。未知次数也可以做带尾部处理的部分展开。
展开可能减少回边开销、暴露指令级并行或帮助向量化,但也可能增加代码体积和 spill。没有所有 LLVM 目标都固定展开两到四次的规则,连续标量 load 也不自动变成合法 SIMD load:边界、对齐、依赖与目标能力仍需检查。
SROA 与 mem2reg:让局部内存状态更容易分析
一个局部聚合对象若地址不逃逸、访问可理解,可以按使用区间等拆解,减少不必要的聚合内存操作。适合的局部槽位还可以提升为 SSA 值,控制流汇合处用 φ 表达。
SROA 不必机械地“一字段对应一个变量”,有些使用会妨碍拆分,有些实现还能直接完成提升。mem2reg 也可处理本来就是标量的合适 alloca,不需要所有场景先经过 SROA。LLVM Pass 说明
Pass 顺序:机会与失效一起产生
内联暴露常量,常量传播删分支,删分支使更多代码无用;展开改变循环结构,后续清理又简化表达式。重复某些变换可以有价值,但编译器不会无限追求饱和,还需控制时间、大小和分析失效。
修改 CFG 后,支配树、循环信息或内存分析可能不再有效。变换必须正确维护或宣布失效相关分析,不能只让输出 IR 表面上看起来合法。
证明合法与估计收益,使用的是不同证据
候选变换
复用 load / 外提计算
语义检查
别名、路径、异常与位宽
收益判断
最终代码、压力与负载
反例能推翻合法性;单个正常样本相等不能证明全部输入等价。
合法的变换仍可能变慢;快的错误变换仍然不可接受。
验证与速查
变换 优先检查
折叠、代数化简 位宽、溢出、浮点、poison 与副作用
CSE / GVN 可用性、支配、内存改变
DCE / DSE 可观察效果、异常、同步与覆盖范围
LICM 循环零次执行、条件路径与安全推测
内联、展开 代码体积与最终机器码收益
SROA / mem2reg 地址逃逸、可分析访问与 φ
可以结合 IR verifier、反例测试、变换前后差分以及特定范围内的等价性验证。测试应包含边界路径,而不是仅让一个正常输入输出相同。测性能则应观察完整优化流水线后的目标代码。
参考
Keywords: optimization, constant propagation, SCCP, CSE, GVN, DCE, DSE, inlining, LICM, unrolling, SROA, mem2reg