SSA 的核心是每个值名字只有一个静态定义,使用明确关联到该定义。“静态”很重要:循环中的同一条指令可以动态执行很多次,每次产生不同结果,仍然符合 SSA。
SSA 改善数据依赖的表示,不会自动消除所有循环分析,也不要求物理寄存器无限或所有内存只能写一次。
分支里重新赋值,汇合处如何表示
普通程序先令 x=1,再根据条件可能令 x=2,最后使用 x。改成 SSA 后,两次赋值使用不同名字,汇合处增加一个 φ 定义,根据进入的前驱边选择输入。
define i32 @choose(i1 %cond) {
entry:
br i1 %cond, label %left, label %right
left:
br label %merge
right:
br label %merge
merge:
%x = phi i32 [ 2, %left ], [ 1, %right ]
%y = add i32 %x, 1
ret i32 %y
}
φ 不是同时求两个输入再判断条件,而是与实际控制流边关联的值选择。在此例里返回 3 或 2。它可以作为 IR 指令存在,但不要求目标 ISA 有同名机器指令。
支配:一个定义是否对使用必然可见
块 A 支配 B,表示从入口到 B 的每条路径都经过 A。对普通 SSA 使用,定义应支配使用;同块内还要先定义后使用。φ 的输入则按对应前驱边上的使用来检查。
立即支配关系在入口可达块上构成树,每个非入口节点只有一个立即支配者。CFG 中的汇合节点可以有多个前驱,却不会在支配树里有两个父节点。把菱形 CFG 直接标成支配树会混淆这两种结构。
循环中的 φ:把初值与回边值接起来
define i32 @count_to(i32 %n) {
entry:
br label %header
header:
%i = phi i32 [ 0, %entry ], [ %next, %body ]
%more = icmp slt i32 %i, %n
br i1 %more, label %body, label %exit
body:
%next = add i32 %i, 1
br label %header
exit:
ret i32 %i
}
第一次进入 header 选择 0,后续从 body 回来选择上一轮的 next。定义和使用在文本或依赖图中可能形成循环,这不违反 SSA;因此需要求不动点的分析不会仅因换成 SSA 就全部变成单遍。
n=2 时,同一个静态名字怎样产生多次动态结果
| 进入 header 的边 | 本次 %i | 比较结果 | 接下来发生什么 |
| entry → header | 0 | 0 < 2,为真 | body 计算 %next=1 |
| body → header | 1 | 1 < 2,为真 | body 计算 %next=2 |
| body → header | 2 | 2 < 2,为假 | exit 返回 2 |
文本里 %i 只有一条 φ 定义,%next 只有一条 add 定义;表格记录的是同一程序的三次动态到达,并没有引入三个冲突的静态定义。若 n=0 或负数,第一次比较就退出并返回 0,没有任何 body → header 回边执行。
把 choose 和 count_to 的两段 IR 放进同一文件 ssa.ll,可先运行 opt -passes=verify -disable-output ssa.ll。再配合下面的 C 驱动观察两条分支和零次/多次循环:
extern int choose(_Bool);
extern int count_to(int);
int main(void) {
return choose(1) != 3 || choose(0) != 2 ||
count_to(-1) != 0 || count_to(0) != 0 || count_to(2) != 2;
}
如果保留 CFG 的 body → header 回边,却从 %i 的 φ 中删掉 [ %next, %body ],verifier 应拒绝:φ 输入必须与前驱结构对应。若把 %next 当普通值直接用于 entry,则它的定义不支配该使用,同样应拒绝。验证器证明的是 IR 结构满足规则,不是前端已经正确表达了源程序意图。LLVM φ 规则
同一条 φ,怎样接住初值和每次回边?
将“是哪条静态指令”与“是第几次动态到达”分成两个坐标,就不会把循环误判为违反 SSA。下面始终只有一条 %i 定义;观察位置选择的是某次 header 到达,进入边决定 φ 取初值还是上一轮的结果。到 i=n 的那次只执行比较和退出,没有再计算一个并不存在的 next。
正在呈现知识画面
同一条 φ,怎样接住初值和每次回边?第一次从 entry 进入选择 0;以后从 body 返回选择上一轮 next。多次动态到达不会产生多条静态定义,退出时也不会再执行 body。
{"id":"phi-loop-edges","title":"同一条 φ,怎样接住初值和每次回边?","summary":"第一次从 entry 进入选择 0;以后从 body 返回选择上一轮 next。多次动态到达不会产生多条静态定义,退出时也不会再执行 body。","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=\"2\" step=\"1\"></div><div class=\"control\"><label for=\"visit\" data-i18n=\"visit\"></label><output for=\"visit\" id=\"visit-value\"></output><input id=\"visit\" type=\"range\" min=\"0\" max=\"2\" value=\"1\" step=\"1\"></div><svg id=\"cfg\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\"></svg><div id=\"visits\" class=\"track\"></div><div class=\"metrics\"><div><span data-i18n=\"incoming\"></span><strong id=\"incoming\"></strong></div><div><span data-i18n=\"value\"></span><strong id=\"value\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"body\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"exit\"></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:18px}#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 draw(){const n=+$('#n').value;$('#visit').max=n;const i=Math.min(n,+$('#visit').value);set('n-value',n);set('visit-value',i+' / '+n);graph('cfg',[{name:'entry',x:50,y:55},{name:'header',x:180,y:55,cls:'on'},{name:'body',x:305,y:55,cls:i<n?'gold':''},{name:'exit',x:180,y:160,cls:i===n?'gold':''}],[[0,1,i===0],[1,2,i<n],[2,1,i>0],[1,3,i===n]]);cells('visits',Array.from({length:6},(_,j)=>({text:j<=n?'i='+j:'·',cls:j===i?'gold':j<=n?'on':'empty'})));set('incoming',i===0?'entry → header':'body → header');set('value',i);set('equation','%i = φ ['+(i===0?'0 ✓':'0')+', entry], ['+(i>0?i+' ✓':'%next')+', body]\\n'+i+' < '+n+' = '+(i<n));set('explanation',t(i<n?'body':'exit'));$('#value').dataset.exited=String(i===n);}$('#n').oninput=draw;$('#visit').oninput=draw;draw();","audio":false,"strings":{"body":"条件为真:执行 body,计算 next=i+1,再返回 header。","exit":"条件为假:直接退出,返回 i;本次不计算 next。","heading":"同一条 φ,怎样接住初值和每次回边?","incoming":"进入 header 的边","intro":"选择循环上限和到达 header 的次数。控制流边、当前 i 与下一动作同步显示,静态定义始终只有一条。","n":"循环上限 n","next":"下一动作","reset":"重置","scope":"非负整数 n≤5,循环 i<n、每次加 1。观察位置是一次执行的切片;改 n 重新计算执行,不改写已执行历史。","scopeLabel":"模型范围与约定","value":"当前 %i","visit":"观察第几次 header 到达"}}
支配边界:定义的影响在哪些汇合处需要选择
DF(X) = {Y | X 支配 Y 的某个前驱,但 X 不严格支配 Y}。
“不严格支配”允许某些循环中 Y=X 的情况,所以不能把支配边界总解释为完全在 X 支配区域之外的第一个节点。
经典构造从变量的定义块开始,在迭代支配边界上插入 φ,新 φ 块也作为定义继续传播。Pruned SSA 再利用变量入口活跃性避免插入没有用途的 φ。这不是给所有变量在每个汇合点都插一次。
重命名:沿支配树维护当前版本
维护每个源变量的版本栈,进入块时为 φ 结果创建版本;扫描普通指令时先把使用替换为当前版本,再为新定义创建版本;离开该块去处理后继边时填入对应 φ 输入;递归支配树子节点后撤销本块压入的版本。
未初始化使用需要由源语言语义或前端错误处理决定,不能为了让 SSA 通过就随意填 0。不可达块也需要清理或按算法约定独立处理。
一般 CFG,包括不可规约控制流,也可以构建 SSA;不要求先把所有循环变成单入口自然循环。某些后续循环优化可能需要规约形态,那是另一个问题。
支配边界,是哪一条进入路径打破了“必经”?
“不严格支配”不能简写为“完全不支配”。下面给 M 加一条自回边:M 支配这条边的前驱 M,但不会严格支配自身,因此 M∈DF(M)。在普通菱形里选择 L 或 R,则可以看到另一条路径如何让 M 不再受该分支支配。图中颜色来自实际支配集合计算,CFG 的形状本身不是支配树。
DF(X)={Y∣∃P∈pred(Y):XdomP∧¬(XsdomY)}
正在呈现知识画面
支配边界,是哪一条进入路径打破了“必经”?X 支配 Y 的某个前驱,却不严格支配 Y,Y 就在 DF(X)。菱形两支的定义在 M 汇合;有自回边时 M 也可以属于自己的边界。
{"id":"dominance-frontier","title":"支配边界,是哪一条进入路径打破了“必经”?","summary":"X 支配 Y 的某个前驱,却不严格支配 Y,Y 就在 DF(X)。菱形两支的定义在 M 汇合;有自回边时 M 也可以属于自己的边界。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"choose\"><button data-mode=\"E\" data-i18n=\"E\"></button><button data-mode=\"L\" data-i18n=\"L\"></button><button data-mode=\"R\" data-i18n=\"R\"></button><button data-mode=\"M\" data-i18n=\"M\"></button><button data-mode=\"Z\" data-i18n=\"Z\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"loop\"><span data-i18n=\"loop\"></span></label><svg id=\"cfg\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\"></svg><div class=\"metrics\"><div><span data-i18n=\"dominated\"></span><strong id=\"dominated\"></strong></div><div><span data-i18n=\"frontier\"></span><strong id=\"frontier\"></strong></div></div><p id=\"reason\" class=\"log\"></p><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:17px;min-height:3.2em}#cfg{min-height:160px}","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 chosen='L';const names=['E','L','R','M','Z'];function draw(){let edges=[[0,1],[0,2],[1,3],[2,3],[3,4]];if($('#loop').checked)edges.push([3,3]);let dom=names.map((_,i)=>new Set(i?names:[names[0]])),changed=true;while(changed){changed=false;for(let i=1;i<5;i++){const pred=edges.filter(e=>e[1]===i).map(e=>e[0]),d=new Set([names[i],...names.filter(x=>pred.every(p=>dom[p].has(x)))]);if(fmt(d)!==fmt(dom[i])){dom[i]=d;changed=true}}}const dominated=names.filter((_,i)=>dom[i].has(chosen)),df=names.filter((y,i)=>edges.some(([p,q])=>q===i&&dom[p].has(chosen))&&!(y!==chosen&&dom[i].has(chosen)));const pos=[[180,28],[65,95],[295,95],[180,135],[180,250]];graph('cfg',names.map((name,i)=>({name,x:pos[i][0],y:pos[i][1],cls:df.includes(name)?'gold':dominated.includes(name)?'on':''})),edges.filter(e=>e[0]!==e[1]));$('#cfg').setAttribute('viewBox','0 0 360 285');if($('#loop').checked)$('#cfg').insertAdjacentHTML('beforeend','<path class=\"edge\" d=\"M205 135 C290 125 290 195 200 155\" marker-end=\"url(#arrow-cfg)\"/>');set('dominated',fmt(dominated));set('frontier',fmt(df));set('reason',df.map(y=>{const i=names.indexOf(y),p=edges.find(([p,q])=>q===i&&dom[p].has(chosen))[0];return chosen+' dom '+names[p]+' → '+y+'; '+chosen+' ≠ strict-dom('+y+')'}).join('\\n')||'DF('+chosen+') = ∅');$('#frontier').dataset.members=df.join(',');pressed(chosen);}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{chosen=b.dataset.mode;draw()});$('#loop').onchange=draw;draw();","audio":false,"strings":{"E":"E","L":"L","M":"M","R":"R","Z":"Z","choose":"选择 X","dominated":"X 支配的块","frontier":"DF(X)","heading":"支配边界,是哪一条进入路径打破了“必经”?","intro":"选择块 X,图中蓝色为 X 支配的块,金色为 DF(X)。给 M 加自回边,观察边界为何能包含 X 自己。","loop":"加入 M → M 回边","reason":"边界见证:前驱与非严格支配","reset":"重置","scope":"固定可达 CFG:E→L/R→M→Z,可选 M→M。实际迭代求支配集合,再按定义求边界。图不替代支配树,也不自动决定所有 φ 的插入。","scopeLabel":"模型范围与约定"}}
解构:φ 产生的是边上的并行复制
某条前驱边上有多个 φ 输入时,可把它们理解为同时赋值到结果位置。例如位置分配后需要 a←b 与 b←a,顺序执行会丢掉一个旧值,必须用临时位置或合适交换序列。
关键边的前驱有多个后继、后继有多个前驱,复制既不能无条件放在前驱末尾,也不能无条件放在后继开头;常通过拆边建立只属于该路径的执行位置。
φ 输入来自不同前驱,并不证明它们在整个程序中绝不同时活跃。来源值可能还在别处使用,因此共用物理位置必须经过干涉与 copy 合并检查,不能无条件把一个 φ 的所有值合成一个寄存器。
一组交换复制必须读到同一份旧状态
位置 a、b 的旧值分别为 10、20,目标并行复制是 a←b、b←a。第一条顺序复制后 a 已变成 20,第二条若再读 a 就会得到新值,最终两者都为 20。临时位置的作用是保留被提前覆盖的旧值。
old = {"a": 10, "b": 20}
expected = {"a": old["b"], "b": old["a"]}
wrong = old.copy()
wrong["a"] = wrong["b"]
wrong["b"] = wrong["a"]
assert wrong == {"a": 20, "b": 20} and wrong != expected
actual = old.copy()
temporary = actual["a"]
actual["a"] = actual["b"]
actual["b"] = temporary
assert actual == expected
这个问题发生在把 SSA 值映射到可复用的物理位置以后,不是在说 SSA 本身允许给同一个名字重新赋值。一般并行复制可能有无环链与多个环:先安排不会破坏其他旧输入的复制,遇环再引入临时位置;只有交换两项时才可以直接使用这里的三步模板。
复制不能简单搬到 P 的末尾,否则 P→X 也会执行它;也不能放到 M 的入口,否则 Q→M 也会执行来自 P 的复制。拆边后还必须将 M 的 φ 前驱标识从 P 更新为新块,而不是只改跳转目标。
旧值被覆盖以后,另一条复制还能读到它吗?
这里被覆盖的是复用的物理位置,不是重新定义 SSA 名字。目标右侧需要读同一份旧状态;选择复制动作后,画面保留每个位置的当前值和最近写入记录。先直接复制两次,再重置尝试临时位置方案,可以找到丢值发生的准确时刻。临时位置也不是魔法:若保存动作太晚,保存到的仍然是已经被覆盖的新值。
正在呈现知识画面
旧值被覆盖以后,另一条复制还能读到它吗?并行复制的所有右侧来自同一份旧状态。直接按顺序写会丢失值;临时位置保留旧 a 后,b 才能得到它。重置建立新的独立尝试。
{"id":"parallel-copy-cycle","title":"旧值被覆盖以后,另一条复制还能读到它吗?","summary":"并行复制的所有右侧来自同一份旧状态。直接按顺序写会丢失值;临时位置保留旧 a 后,b 才能得到它。重置建立新的独立尝试。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><p class=\"source\">old: a=10, b=20 → target: a=20, b=10</p><div class=\"actions\"><button id=\"save\" data-i18n=\"save\"></button><button id=\"ab\" data-i18n=\"ab\"></button><button id=\"ba\" data-i18n=\"ba\"></button><button id=\"bt\" data-i18n=\"bt\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div id=\"current\" class=\"track\"></div><section class=\"panel\"><h3 data-i18n=\"history\"></h3><div id=\"history\"></div></section><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=\"wait\"></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#history{font:13px/1.8 var(--viz-font-ui);min-height:7.2em;white-space:pre-line}","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 a=10,b=20,temp=null,history=[];function draw(){cells('current',[{text:'a = '+a,cls:a===20?'on':'gold'},{text:'b = '+b,cls:b===10?'on':'gold'},{text:'t = '+(temp??'∅'),cls:temp===10?'on':'empty'}]);$('#bt').disabled=temp===null;set('history',history.slice(-4).join('\\n')||'∅');set('explanation',t(a===20&&b===10?'ok':'wait'));$('#current').dataset.values=[a,b,temp].join(',');}for(const k of ['save','ab','ba','bt'])$('#'+k).onclick=()=>{if(k==='save')temp=a;if(k==='ab')a=b;if(k==='ba')b=a;if(k==='bt')b=temp;history.push(t(k)+' → ['+a+','+b+','+(temp??'∅')+']');draw()};$('#reset').onclick=()=>{a=10;b=20;temp=null;history=[];draw()};draw();","audio":false,"strings":{"ab":"a ← b","ba":"b ← a","bt":"b ← t","current":"当前位置","goal":"并行目标","heading":"旧值被覆盖以后,另一条复制还能读到它吗?","history":"已执行动作","intro":"目标是同时执行 a←b、b←a。选择具体写入动作,自行安排顺序;临时位置只能先保存旧 a 才能打破环。","ok":"当前结果满足并行目标。","reset":"重置","save":"t ← a","scope":"两个可复用位置,初值 a=10、b=20,一个临时位置。动作是真实顺序复制;不模拟一般 SSA 解构、关键边拆分或多环调度。","scopeLabel":"模型范围与约定","wait":"目标要求 a=20、b=10;检查哪个旧值仍被保留。"}}
SSA 值与可变内存并存
LLVM 的 %p = alloca i32 只定义一次指针值,之后可以多次 store 到其指向的内存;每个 load 又产生新的 SSA 值。SSA 约束的是命名值,不是把整个堆变成不可变数据。
mem2reg 将适合提升的局部槽位转为 SSA 值与 φ;别名、地址逃逸和不支持的访问会影响提升。前端可以先用 alloca 表达可变局部变量,再交给提升过程,避免自己立即实现完整 SSA 构造。LLVM 可变变量教程
验证速查
检查唯一定义、类型、支配关系、φ 与前驱边对应、终结指令和不可达块处理。修改 CFG 时也要更新 φ 输入;删除一条边却保留对应输入会破坏 IR。
相关章节:LLVM IR 实战、数据流分析、寄存器分配。
参考
Keywords: SSA, phi, dominator, dominance frontier, renaming, parallel copy, critical edge, mem2reg