数据流分析为程序点建立事实,例如某次赋值可能到达哪里、一个值之后是否还会使用、某表达式是否在所有路径上已算过。它把执行路径压缩为可计算的抽象信息,并在控制流图 CFG 上传播。
结果应当保守地服务用途:用于删除代码的证明不能漏掉潜在行为,用于报告可能错误的分析则可以接受一定误报。保守不等于随便猜测,而是明确抽象结果与真实执行的包含关系。
从分支汇合理解 may 与 must
若问 x 可能来自哪次定义,就合并两条路径的候选;若问表达式 a+b 是否必然已计算,则两条路径都要提供该事实。对经典集合分析,前者常用并集,后者常用交集。
但 may/must 并不要求所有抽象域都简单用集合并交。格的偏序、top/bottom 与 join/meet 命名也会随约定变化,应先给出含义,再写公式。
统一框架需要五项定义
| 项目 | 要明确什么 |
| 抽象域 | 一个程序点保存哪些事实 |
| 传播方向 | 从前驱向后,还是从后继向前 |
| 汇合算子 | 多条路径的信息如何合并 |
| 传递函数 | 一个基本块怎样变换信息 |
| 边界与初始值 | 入口、出口及其他节点如何开始 |
Gen/Kill 是许多位向量分析的便利形式,不是所有传递函数的唯一形式。即使用交集汇合的 must 分析,也不意味着要把传递函数内部的 gen ∪ ... 改成交集。
到达定义:前向 may 分析
定义 d 到达点 p,表示存在一条从 d 到 p 的路径,其间没有重新定义同一个变量。给每条赋值独立编号,而不是只记录变量名。
IN[B] = ∪ OUT[P],P 遍历 B 的前驱。
OUT[B] = GEN[B] ∪ (IN[B] − KILL[B])。
GEN 包含块内能存活到出口的定义;KILL 包含被本块赋值覆盖的同变量其他定义。若块内依次 d1: x=1; d2: x=2,d1 不在出口 GEN 中,d2 在其中。
入口通常还要建模参数或外部初值的定义;教学模型若以空集开始,应说明这些不在分析范围。SSA 的标量使用直接关联唯一静态定义,但内存中的别名写入仍然需要单独分析。
活跃变量:后向 may 分析
一个值在点 p 活跃,表示存在后续路径先使用该值、再重新定义它。因为问题看未来,所以从后继向前传播。
OUT[B] = ∪ IN[S],S 遍历 B 的后继。
IN[B] = USE[B] ∪ (OUT[B] − DEF[B])。
USE 是块内在首次定义前使用的变量;DEF 是块内定义的变量,不要求该定义之前从未使用过。
例如块 x=x+1; y=x; return y,USE={x}、DEF={x,y}。出口 OUT=∅ 时,入口 IN={x}。x 同时存在于 USE 与 DEF 完全合理:进入块的旧 x 需要用于第一条指令,块内也产生了一个新 x。
循环会让活跃信息跨回边传播。一个值的活跃范围是相关 CFG 路径上的程序点集合,不是把源码中的定义行到最后使用行简单连成直线。
可用表达式:前向 must 分析
表达式 e 在 p 可用,表示每条进入 p 的相关路径都计算过 e,并且之后没有修改其操作数。纯标量模型下:
IN[B] = ∩ OUT[P]。
OUT[B] = GEN[B] ∪ (IN[B] − KILL[B])。
注意 x=x+y 并不会让基于新 x 的 x+y 在出口可用:右侧计算使用旧 x,而赋值改变了 x。构造 GEN 时必须考虑块内后续赋值及该语句自身定义。
入口通常为空,其他可达块可从表达式全集开始向下收缩。不可达块和无前驱节点需要明确处理,不能随便对空集合族取交集后把假事实传播出去。
Worklist:只重新计算受到影响的邻居
以前向分析为例,初始化边界和各块状态,把待处理块入队;取出 B,重新合并前驱输出并应用传递函数;若 OUT 改变,将后继入队。后向分析则在 IN 改变时通知前驱。
在有限高度的格上,以正确初值进行单调迭代可终止。仅“有最大元素”不意味着高度有限;无限高度的区间域等可能需要 widening。轮数受 CFG、循环和域影响,没有固定三到五轮保证。Clang 数据流分析介绍
循环中的到达定义:第一次经过头部还没有全部答案
只分析变量 x 的两个定义:入口 E 执行 d0:x=0,循环体 B 执行 d1:x=x+1。循环头 H 可以进入 B,也可以到出口 X;B 再回到 H。条件和其他变量不在本模型的定义集合内。
第一次从 E 到 H 时,只知道 d0。扫描 B 后发现 d1;把它沿回边送到 H,H 才知道 x 也可能来自上一轮的 d1。这里 d1 是一条静态赋值语句的身份,并不为每次动态循环执行生成无限个定义编号。
下面实现 worklist。状态采用定义集合的包含序,所有块从空集开始;入口没有前驱,由 GEN 产生 d0。所有节点都可达,因此这里不额外处理不可达输入。
from collections import deque
succ = {"E": ["H"], "H": ["B", "X"], "B": ["H"], "X": []}
pred = {b: [p for p in succ if b in succ[p]] for b in succ}
gen = {"E": {"d0"}, "H": set(), "B": {"d1"}, "X": set()}
kill = {"E": {"d1"}, "H": set(), "B": {"d0"}, "X": set()}
def solve(order):
out = {b: set() for b in succ}
work = deque(order)
queued = set(order)
history = []
while work:
b = work.popleft()
queued.remove(b)
incoming = set().union(*(out[p] for p in pred[b]))
new = gen[b] | (incoming - kill[b])
if new != out[b]:
out[b] = new
history.append((b, tuple(sorted(new))))
for nxt in succ[b]:
if nxt not in queued:
work.append(nxt)
queued.add(nxt)
return out, history
expected = {"E": {"d0"}, "H": {"d0", "d1"}, "B": {"d1"}, "X": {"d0", "d1"}}
out, history = solve(["E", "H", "B", "X"])
assert out == expected
assert [v for b, v in history if b == "H"] == [("d0",), ("d0", "d1")]
assert solve(["X", "B", "H", "E"])[0] == expected
for b in succ:
incoming = set().union(*(out[p] for p in pred[b]))
assert out[b] == gen[b] | (incoming - kill[b])
| 节点 | 最终 IN | 最终 OUT | 解释 |
| E | ∅ | {d0} | 建立初值 |
| H | {d0,d1} | {d0,d1} | 初次进入和回边汇合 |
| B | {d0,d1} | {d1} | 本次赋值覆盖之前的 x |
| X | {d0,d1} | {d0,d1} | 循环可能零次或多次执行 |
工作队列顺序改变中间过程,但在这个有限、单调的框架与相同边界条件下,最终解相同。若只遍历 CFG 一次,H 可能永远看不到回边;若发现任何节点未改变就停止,也可能遗漏队列中其他节点带来的新信息。
回边上的事实,什么时候才能传回出口?
下面的按钮不是规定顺序的课程步骤,而是待处理集合里的真实分析任务。可以先处理出口,让它暂时得到空集;入口或回边随后产生新事实时,出口仍会经由后继重新入队。一次不变只说明该次计算没有新增信息,全局停止条件是队列为空。模型中的 d1 始终是一条静态赋值身份。
IN[B]=P∈pred(B)⋃OUT[P],OUT[B]=GEN[B]∪(IN[B]∖KILL[B])
正在呈现知识画面
回边上的事实,什么时候才能传回出口?处理一个块没有变化,不代表全局结束。只有所有待处理块都完成,回边带来的 d1 才已传播到 H 与 X;不同处理顺序给出同一个最终解。
{"id":"reaching-definitions-worklist","title":"回边上的事实,什么时候才能传回出口?","summary":"处理一个块没有变化,不代表全局结束。只有所有待处理块都完成,回边带来的 d1 才已传播到 H 与 X;不同处理顺序给出同一个最终解。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"loop\" checked><span data-i18n=\"loop\"></span></label><div class=\"actions\"><button id=\"E\" data-i18n=\"E\"></button><button id=\"H\" data-i18n=\"H\"></button><button id=\"B\" data-i18n=\"B\"></button><button id=\"X\" data-i18n=\"X\"></button><button id=\"solve\" data-i18n=\"solve\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><svg id=\"cfg\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\"></svg><div id=\"facts\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"pending\"></span><strong id=\"pending\"></strong></div></div><p id=\"formula\" class=\"log\"></p><p class=\"hint\" data-i18n=\"hint\"></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#facts .tr{grid-template-columns:40px 1fr 1fr}.metrics strong{font-size:18px}#formula{min-height:5.4em}","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;}));}const names=['E','H','B','X'];let out,ins,pending,last='',formula='';function edges(){return [[0,1],[1,2],[1,3],...($('#loop').checked?[[2,1]]:[])]}function reset(){out=[0,0,0,0];ins=[0,0,0,0];pending=new Set([0,1,2,3]);last='';formula='∅';draw()}function f(mask){return fmt(['d0','d1'].filter((_,i)=>mask&(1<<i)))}function process(i){if(!pending.has(i))return;pending.delete(i);const input=edges().filter(e=>e[1]===i).reduce((a,[p])=>a|out[p],0),gen=i===0?1:i===2?2:0,kill=i===0?2:i===2?1:0,result=gen|(input&~kill),changed=out[i]!==result;ins[i]=input;out[i]=result;if(changed)edges().filter(e=>e[0]===i).forEach(e=>pending.add(e[1]));last=names[i];formula='IN['+last+'] = '+f(input)+'\\nOUT = '+f(gen)+' ∪ ('+f(input)+' − '+f(kill)+') = '+f(result);draw()}function draw(){const pos=[[45,80],[165,80],[290,35],[290,155]];graph('cfg',names.map((name,i)=>({name,x:pos[i][0],y:pos[i][1],cls:pending.has(i)?'gold':out[i]?'on':''})),edges());table('facts',[['block','IN','OUT'],...names.map((n,i)=>[n,f(ins[i]),f(out[i])])]);names.forEach((n,i)=>$('#'+n).disabled=!pending.has(i));$('#solve').disabled=pending.size===0;set('pending',fmt([...pending].map(i=>names[i])));set('formula',formula);$('#facts').dataset.out=out.join(',');$('#pending').dataset.count=pending.size;}names.forEach((n,i)=>$('#'+n).onclick=()=>process(i));$('#solve').onclick=()=>{let guard=0;while(pending.size&&guard++<100)process(pending.values().next().value);if(pending.size)throw Error('nonconvergence')};$('#reset').onclick=reset;$('#loop').onchange=reset;reset();","audio":false,"strings":{"B":"B","E":"E","H":"H","X":"X","formula":"本次传递函数","heading":"回边上的事实,什么时候才能传回出口?","hint":"按钮只处理对应块;OUT 变化时才把后继重新加入待处理集合。","intro":"任意选择待处理块,或直接求至不动点。蓝色显示定义事实,金色标记仍需重新计算的块。","loop":"保留 B → H 回边","pending":"待处理块","reset":"重置","scope":"前向 may 到达定义,集合域 {d0,d1},从空集开始。E 定义 d0,B 定义 d1;固定全部可达 CFG,条件与其他变量不在模型中。","scopeLabel":"模型范围与约定","solve":"求至不动点"}}
不动点与按路径求解:哪里损失精度
MOP 先沿各条 CFG 路径变换再汇合;迭代方程则会在中途汇合路径,再继续变换。在传递函数对汇合可分配等条件下,两者一致;否则提前合并可能丢掉关联信息。
例如两条路径分别得到 (x=1,y=2) 和 (x=2,y=1),随后计算 z=x+y。逐路径计算都得 3;非关系常量传播先汇合时把 x、y 都标成非常量,就可能推不出 z=3。保留变量间关系的抽象域可以改善精度,代价更高。
MOP 也通常遍历 CFG 中的语法路径,其中可能包含实际上无法同时满足条件的路径,所以不能直接称作真实程序语义的完美解。MFP 与 MOP 的大小方向依赖采用的格序,不应脱离约定记一个不等号。
丢掉相关性以后,不能再把所有组合都当作真实路径
把前面的两条路径直接写成集合,精确路径状态为 {(1,2),(2,1)};分别保留 x、y 的可能值后,两者都变成 {1,2}。之后再做笛卡尔积,会额外引入 (1,1) 与 (2,2),从而得到并不真实存在的和 2、4。
paths = {(1, 2), (2, 1)}
xs, ys = {x for x, y in paths}, {y for x, y in paths}
precise = {x + y for x, y in paths}
nonrelational = {x + y for x in xs for y in ys}
assert precise == {3}
assert nonrelational == {2, 3, 4}
assert precise <= nonrelational
非关系结果仍包含真实结果,因此可以保守地说“z 可能属于 {2,3,4}”,却不能证明 z 恒为 3。这个精度损失来自抽象状态没有保存配对关系;再增加几轮 worklist 也恢复不了已经丢弃的信息。要改善,需改变抽象域、延迟汇合或利用路径条件,而不只是反复运行同一方程。
分别记住 x、y,丢掉了什么?
将具体路径画成二维坐标中的点,可以准确定位“相关性丢失”。分别投影到 x、y 轴后再组合,真实的两点可能扩成四点;多出来的点不是新发现的执行路径,而是抽象允许的额外状态。修改第二条路径,还能看到什么时候不会产生额外组合。重复相同迭代不能重新找回已经丢失的配对关系。
R⊆πx(R)×πy(R)
正在呈现知识画面
分别记住 x、y,丢掉了什么?独立集合只记住 x 出现过什么、y 出现过什么,不能记住它们如何配对。金色格是抽象额外允许的状态,可能让本来恒定的 x+y 变得不可证明。
{"id":"lost-path-correlation","title":"分别记住 x、y,丢掉了什么?","summary":"独立集合只记住 x 出现过什么、y 出现过什么,不能记住它们如何配对。金色格是抽象额外允许的状态,可能让本来恒定的 x+y 变得不可证明。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"x\" data-i18n=\"x\"></label><output for=\"x\" id=\"x-value\"></output><input id=\"x\" type=\"range\" min=\"0\" max=\"3\" value=\"2\" step=\"1\"></div><div class=\"control\"><label for=\"y\" data-i18n=\"y\"></label><output for=\"y\" id=\"y-value\"></output><input id=\"y\" type=\"range\" min=\"0\" max=\"3\" value=\"1\" step=\"1\"></div><div id=\"pairs\" class=\"tile-grid\"></div><div class=\"metrics\"><div><span data-i18n=\"exact\"></span><strong id=\"exact\"></strong></div><div><span data-i18n=\"abstract\"></span><strong id=\"abstract\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><p class=\"hint\" data-i18n=\"hint\"></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:18px}","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 x=+$('#x').value,y=+$('#y').value,pairs=[[1,2],[x,y]],xs=new Set([1,x]),ys=new Set([2,y]),exact=new Set(pairs.map(([a,b])=>a+b)),abstract=new Set;set('x-value',x);set('y-value',y);cells('pairs',Array.from({length:16},(_,i)=>{const a=i%4,b=Math.floor(i/4),real=pairs.some(p=>p[0]===a&&p[1]===b),possible=xs.has(a)&&ys.has(b);if(possible)abstract.add(a+b);return{text:a+','+b,cls:real?'on':possible?'gold':'empty'}}));set('exact',fmt(exact));set('abstract',fmt(abstract));set('equation',fmt(xs)+' × '+fmt(ys)+' → '+abstract.size+' sum(s)');$('#pairs').dataset.extra=xs.size*ys.size-new Set(pairs.map(p=>p.join(','))).size;$('#abstract').dataset.sums=[...abstract].sort((a,b)=>a-b).join(',');}$('#x').oninput=draw;$('#y').oninput=draw;draw();","audio":false,"strings":{"abstract":"独立集合的 x+y","exact":"精确路径的 x+y","heading":"分别记住 x、y,丢掉了什么?","hint":"路径 A 固定 (1,2)。格子标签为 (x,y),蓝色是真实配对,金色仅由独立组合产生。","intro":"两条路径分别产生一对 (x,y)。修改第二条路径,观察精确配对和笛卡尔积多出了哪些组合。","reset":"重置","scope":"两条可行路径、整数0..3、精确有限集合;这不是完整常量传播格,也不把所有 CFG 语法路径都当作真实可行。","scopeLabel":"模型范围与约定","x":"路径 B 的 x","y":"路径 B 的 y"}}
SSA 与稀疏分析
SSA 使标量 def-use 显式化,很多事实可以沿相关值传播,减少在无关 CFG 边上的工作。但 φ 输入与前驱边有关,循环仍可能需要不动点,内存仍有别名和副作用。SSA 不是“所有分析退化为支配树单遍遍历”的保证。
速查
| 分析 | 方向 | 典型汇合 | 用途 |
| 到达定义 | 前向 | 并集 | 非 SSA def-use、可能来源 |
| 活跃变量 | 后向 | 并集 | 寄存器分配、值生命周期 |
| 可用表达式 | 前向 | 交集 | 冗余计算消除 |
相关章节:经典优化、寄存器分配、SSA。
参考
Keywords: dataflow, CFG, may, must, gen, kill, liveness, reaching definitions, available expressions, fixed point, MOP