LLVM IR 是优化器与后端共同处理的有类型中间语言。它包含 SSA 值、显式控制流和显式内存操作,但并不表示所有值最后都放物理寄存器,也不等价于某种源语言的全部语义。
下面例子采用现代 ptr 不透明指针语法,可使用支持该语法的同版本 LLVM 工具验证。语言参考规定每条指令的准确语义,不能只凭名称推断。LLVM Language Reference
三种表示与工具分工
文本 .ll 便于阅读,bitcode .bc 用于序列化,内存 IR 由编译器 API 操作。llvm-as 把文本组装为 bitcode,llvm-dis 反向显示;opt 分析和变换 IR,llc 生成目标代码。并不是所有工具都在任意三种表示间互转。
可以先用 clang -S -emit-llvm 观察 C 代码的 IR,再用小型手写模块理解具体指令。不同优化级别会明显改变输出结构。
名字、类型与基本块
@name 常引用全局变量或函数,%name 可以引用局部值、参数及其他局部标识。整数类型 i32 表示 32 位,不自带“有符号”属性;icmp slt、icmp ult 和不同除法指令分别选择有符号或无符号解释。
基本块以终结指令结束,如 br、ret 或 switch。普通指令不能放到终结指令之后继续执行;φ 位于块开头,并为前驱提供输入。间接函数调用使用 call 的函数指针目标,不能把 indirectbr 当成普通虚函数调用指令。
一个可执行的模块
将下面保存为 example.ll:
%Pair = type { i32, double }
define i32 @choose_add(i1 %take_a, i32 %a, i32 %b) {
entry:
br i1 %take_a, label %left, label %right
left:
%a1 = add i32 %a, 1
br label %merge
right:
%b1 = add i32 %b, 1
br label %merge
merge:
%result = phi i32 [ %a1, %left ], [ %b1, %right ]
ret i32 %result
}
define double @read_weight(ptr %p) {
entry:
%field = getelementptr %Pair, ptr %p, i64 0, i32 1
%value = load double, ptr %field
ret double %value
}
define i32 @main() {
entry:
%pair = alloca %Pair
%weight = getelementptr %Pair, ptr %pair, i64 0, i32 1
store double 2.500000e+00, ptr %weight
%got = call double @read_weight(ptr %pair)
%a = call i32 @choose_add(i1 true, i32 40, i32 9)
%b = call i32 @choose_add(i1 false, i32 40, i32 9)
%ok_a = icmp eq i32 %a, 41
%ok_b = icmp eq i32 %b, 10
%ok_w = fcmp oeq double %got, 2.500000e+00
%ok_ab = and i1 %ok_a, %ok_b
%ok = and i1 %ok_ab, %ok_w
%status = select i1 %ok, i32 0, i32 1
ret i32 %status
}
llvm-as example.ll - o example.bc
opt - passes= verify - disable-output example.bc
lli example.bc
opt - passes= ' default<O2>' - S example.bc - o optimized.ll
main 用两条分支的实际结果和结构体字段读取结果构造退出状态,成功为 0。验证器检查 IR 结构与部分语义条件,但不会证明任意指针在运行时都有效。
Opaque pointer:指针类型不携带 pointee type
现代 ptr 不编码指向 i32 还是 double,读写宽度由 load/store 指令给出,地址计算布局由 GEP 的源元素类型给出。地址空间仍然是指针类型的重要部分,不应把所有 ptr 当作相同物理表示。Opaque Pointers
因此 load i32, ptr %p 不是运行时检查 p 真的指向 i32,而是前端声明这次读取采用 i32 语义。有效对象、对齐、生命周期和别名等要求仍需满足。
GEP:第一个索引不等于“解引用”
GEP 计算位置,load 才读取该位置的内容
ptr %base
指向 i32 元素序列
GEP i32,索引 3
按布局前进 3 个元素
ptr %element
仍然是地址
随后 load i32, ptr %element 才产生读取的 i32 值。
对 %Pair,getelementptr %Pair, ptr %p, i64 0, i32 1 先选择第 0 个 Pair,再选择字段 1。字段偏移包含目标 DataLayout 的对齐与填充,不是简单把前面字段大小相加。
第一个索引可以非零:把 %p 当作连续 Pair 序列的基址时,索引 2 表示按 Pair 的分配大小前进两个元素。普通数组元素访问也经常使用非零索引,opaque pointer 没有把它变成 no-op。
GEP 本身不访存,不意味着任意计算出的地址都能读取。inbounds 还添加更强的对象与计算约束,违反时可能产生 poison,不能为了“让优化更好”无依据地添加。
Pair 数组:元素步长与字段偏移要一起计算
在常见 x86-64 System V 布局下,{ i32, double } 的字段 0 占字节 [0,4),接着有 4 字节填充,字段 1 占 [8,16),整个对象大小为 16。若访问 Pair 数组的第 2 个元素的 weight,偏移为 2*16+8=40,不是 2*(4+8)+4=28。
计算部分 该目标上的字节数 来源
一个 Pair 的分配大小 16 字段布局与整体对齐
第 2 个元素起点 32 按分配大小跳过两个元素
weight 在元素内偏移 8 double 字段对齐产生填充
最终相对地址 40 元素起点加字段偏移
可用相同目标的 C 布局作交叉核对。这里静态断言明确固定预期,不声称这些数值适用于任意 DataLayout:
#include < stddef.h>
struct Pair { int tag; double weight; } ;
_Static_assert ( sizeof ( int ) == 4 , " requires 32-bit int" ) ;
_Static_assert ( sizeof ( struct Pair) == 16 , " different target layout" ) ;
_Static_assert ( offsetof ( struct Pair, weight) == 8 , " different field layout" ) ;
int main ( void ) {
struct Pair pairs[ 3 ] = { { 0 , 0.0 } , { 0 , 0.0 } , { 7 , 2.5 } } ;
return pairs[ 2 ] . weight != 2.5 ;
}
不要用“指针是 64 位”单独推导整个聚合布局。DataLayout 还规定不同类型的大小与对齐,packed 结构也会改变字段间填充;即使算出地址,load 的对齐承诺也必须符合实际对象。GEP 布局说明
第几个对象、哪个字段,各自贡献多少偏移?
地址式的乘法与加法分别对应两层选择:跨过 i 个完整对象,再进入当前对象的字段。下面保持各行字节标尺,显式画出普通布局的填充与 packed 的短步长。变化的是示例 DataLayout,并没有执行 load;某字段地址不是自然对齐时,也不能无依据地给后续访问添加更强对齐承诺。
addr = base + i ⋅ allocSize ( T ) + fieldOffset ( T , j )
正在呈现知识画面 重新加载
第几个对象、哪个字段,各自贡献多少偏移? GEP 按对象分配大小跨元素,再加字段偏移。普通示例布局含对齐填充;packed 改变布局但不保证访问自然对齐。地址计算不读取内存。
{"id":"gep-layout-offset","title":"第几个对象、哪个字段,各自贡献多少偏移?","summary":"GEP 按对象分配大小跨元素,再加字段偏移。普通示例布局含对齐填充;packed 改变布局但不保证访问自然对齐。地址计算不读取内存。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"index\" data-i18n=\"index\"></label><output for=\"index\" id=\"index-value\"></output><input id=\"index\" type=\"range\" min=\"0\" max=\"2\" value=\"2\" step=\"1\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"field\"><button data-mode=\"0\" data-i18n=\"first\"></button><button data-mode=\"1\" data-i18n=\"second\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"packed\"><span data-i18n=\"packed\"></span></label><div id=\"layout\"></div><div class=\"metrics\"><div><span data-i18n=\"stride\"></span><strong id=\"stride\"></strong></div><div><span data-i18n=\"offset\"></span><strong id=\"offset\"></strong></div><div><span data-i18n=\"address\"></span><strong id=\"address\"></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#layout{display:grid;gap:8px}#layout .bytes{display:grid;grid-template-columns:repeat(16,minmax(0,1fr));gap:2px}.bytes i{height:24px;background:var(--surface);border:1px dashed var(--rule);border-radius:2px}.bytes i.first{background:var(--accent-soft);border-style:solid}.bytes i.second{background:var(--second-soft);border-style:solid}.bytes i.selected{border:2px solid var(--ink)}.metrics{grid-template-columns:repeat(3,minmax(0,1fr))}.metrics strong{font-size:18px}#equation{white-space:pre-line;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;}));}let field=1;function draw(){const i=+$('#index').value,packed=$('#packed').checked,stride=packed?12:16,off=field?(packed?4:8):0,size=field?8:4;set('index-value',i);$('#layout').replaceChildren(...Array.from({length:3},(_,row)=>{const e=document.createElement('div');e.className='bytes';for(let b=0;b<16;b++){const c=document.createElement('i');c.className=b<4?'first':b>=(packed?4:8)&&b<stride?'second':'';if(b>=stride)c.style.visibility='hidden';if(row===i&&b>=off&&b<off+size)c.classList.add('selected');c.title='+'+(row*stride+b);e.append(c)}return e}));set('stride',stride);set('offset',off);set('address',4096+i*stride+off);set('equation','4096 + '+i+' × '+stride+' + '+off+' = '+(4096+i*stride+off)+'\\n'+(i*stride+off)+' mod '+size+' = '+((i*stride+off)%size));pressed(String(field));}$('#index').oninput=draw;$('#packed').onchange=draw;document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{field=+b.dataset.mode;draw()});draw();","audio":false,"strings":{"address":"结果地址","field":"字段","first":"字段 0:i32","heading":"第几个对象、哪个字段,各自贡献多少偏移?","hint":"每行一个对象;蓝色字段 0,金色字段 1,虚线为填充。粗边框是当前字段。","index":"对象索引 i","intro":"固定数组 Pair[3],Pair={i32,double}。选择对象和字段,或比较 packed 布局,字节图与地址式同步变化。","offset":"字段偏移 / byte","packed":"使用 packed 布局","reset":"重置","scope":"固定示例 DataLayout:i32 大小/对齐 4,double 大小/对齐 8;普通 Pair 大小16、packed大小12。基址4096,索引0..2,不涵盖所有目标或 inbounds 规则。","scopeLabel":"模型范围与约定","second":"字段 1:double","stride":"对象步长 / byte"}}
算术标志与源语言语义
普通整数 add 按位宽处理;nsw、nuw 表达相应无溢出的承诺,并非自动检查溢出抛出源语言异常。若源语言需要有检查加法,前端要生成检测、分支或合适 intrinsic。
同理,不能用无条件求值的 select 简单替代所有短路 &&。若右侧会调用函数、访存或抛错,必须保留源语言的条件执行。选择两份已算好的值与控制是否求值不同。
有检查加法:把溢出标志变成控制流
设接口约定:两个有符号 i32 相加,成功时写入输出槽并返回 1,溢出时不修改输出槽并返回 0。普通 add 没有这个检查行为,add nsw 也不会自动替我们跳到失败分支。保存为 checked.ll:
declare { i32, i1 } @llvm.sadd.with.overflow.i32(i32, i32)
define i32 @checked_add(i32 %a, i32 %b, ptr %out) {
entry:
%pair = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 %a, i32 %b)
%sum = extractvalue { i32, i1 } %pair, 0
%overflow = extractvalue { i32, i1 } %pair, 1
br i1 %overflow, label %failure, label %success
success:
store i32 %sum, ptr %out, align 4
ret i32 1
failure:
ret i32 0
}
这个 intrinsic 同时产生截断后的位模式和有符号溢出标志。即使失败分支不使用 %sum,计算标志也没有消失;成功分支才把结果暴露给调用方。这里的接口要求 out 指向有效、至少 4 字节对齐的 i32 输出对象,C 驱动使用普通 int 对象满足本目标要求。有溢出检测的加法
#include < limits.h>
_Static_assert ( INT_MAX == 2147483647 , " requires 32-bit int" ) ;
extern int checked_add ( int , int , int * ) ;
int main ( void ) {
int out = 99 ;
if ( checked_add ( 20 , 22 , & out) != 1 || out != 42 ) return 1 ;
if ( checked_add ( INT_MAX, 0 , & out ) != 1 || out != INT_MAX) return 2 ;
out = 99 ;
if ( checked_add ( INT_MAX, 1 , & out ) != 0 || out != 99 ) return 3 ;
if ( checked_add ( INT_MIN, - 1 , & out ) != 0 || out != 99 ) return 4 ;
if ( checked_add ( INT_MIN, 0 , & out ) != 1 || out != INT_MIN) return 5 ;
return 0 ;
}
对输入 INT_MAX 与 1,数学和超出有符号范围,返回 0 且保留原 out=99;对 INT_MAX 与 0,边界值本身合法,返回 1。两个分支的差异由显式 IR 定义,优化器可以更换机器实现,但必须保留此接口行为。
溢出检测产生事实,分支决定如何兑现接口
sadd.with.overflow
同时产生 sum 与 overflow
检查 overflow
按 i1 选择后继块
未溢出
store sum;返回 1
溢出
不写 out;返回 0
nsw 是无有符号溢出的承诺;它不是上述检查和错误返回流程的替代品。
同一份位模式,为什么不能代表同一种承诺?
把位模式和接口承诺并排放置,才能避免用“溢出了”概括所有行为。下面缩成 i8 以便看清符号位,但三种语义仍分开:模加法得到截断位模式,nsw 对超出承诺的结果给出 poison,检查接口则通过显式分支决定是否写 out。poison 标签只是结果类别,不模拟后续任意使用的行为。
正在呈现知识画面 重新加载
同一份位模式,为什么不能代表同一种承诺? 普通 add 保留模 256 位模式;nsw 在有符号溢出时产生 poison,不是抛异常;检查接口显式返回失败,并保留原输出槽 99。
{"id":"integer-overflow-contracts","title":"同一份位模式,为什么不能代表同一种承诺?","summary":"普通 add 保留模 256 位模式;nsw 在有符号溢出时产生 poison,不是抛异常;检查接口显式返回失败,并保留原输出槽 99。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"a\" data-i18n=\"a\"></label><output for=\"a\" id=\"a-value\"></output><input id=\"a\" type=\"range\" min=\"-128\" max=\"127\" value=\"127\" step=\"1\"></div><div class=\"control\"><label for=\"b\" data-i18n=\"b\"></label><output for=\"b\" id=\"b-value\"></output><input id=\"b\" type=\"range\" min=\"-128\" max=\"127\" value=\"1\" step=\"1\"></div><div id=\"bits\" class=\"track\"></div><div class=\"metrics\"><div><span data-i18n=\"math\"></span><strong id=\"math\"></strong></div><div><span data-i18n=\"wrap\"></span><strong id=\"wrap\"></strong></div><div><span data-i18n=\"nsw\"></span><strong id=\"nsw\"></strong></div><div><span data-i18n=\"checked\"></span><strong id=\"checked\"></strong></div></div><p id=\"equation\" class=\"formula\"></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}#equation{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;}));}function draw(){const a=+$('#a').value,b=+$('#b').value,sum=a+b,bits=(sum%256+256)%256,signed=bits>=128?bits-256:bits,overflow=sum< -128||sum>127;set('a-value',a);set('b-value',b);cells('bits',bits.toString(2).padStart(8,'0').split('').map((x,i)=>({text:x,cls:i===0?'gold':'on'})));set('math',sum);set('wrap',signed);set('nsw',overflow?'poison':sum);set('checked',overflow?'false / 99':'true / '+sum);set('equation',sum+' mod 256 = '+bits+'; −128 ≤ '+sum+' ≤ 127 '+(overflow?'✗':'✓'));$('#checked').dataset.overflow=String(overflow);}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"a":"操作数 a","b":"操作数 b","checked":"检查接口:成功标志 / out","heading":"同一份位模式,为什么不能代表同一种承诺?","intro":"用有符号 i8 看清边界。改变两个操作数,同时比较普通 add、add nsw 和显式溢出检查。","math":"数学和","nsw":"add nsw","reset":"重置","scope":"教学 i8 范围 −128..127,位模式按二补码显示。只对比加法结果契约,不演示 poison 传播、freeze 或所有 UB 规则。","scopeLabel":"模型范围与约定","wrap":"普通 add 的有符号读法"}}
可变局部变量与地址身份
初学前端可以为可变局部变量建立合适的 alloca,使用 load/store,再让 mem2reg 或 SROA 提升可优化部分。并非“对 alloca 取地址”本身就阻止提升,alloca 本来就返回地址;关键是地址的具体使用是否符合提升条件。
若源程序允许对象地址逃逸或可观察身份,前端应从一开始正确建模。不能在优化后看到源代码的 &x 就随意新建一个 alloca 并声称恢复了原对象身份。
模块与 ABI 边界
目标 triple 和 DataLayout 影响布局与生成目标。调用外部函数还需要正确的签名、调用约定、参数属性和 ABI lowering。IR 中一个聚合参数的写法不保证天然符合所有平台 C ABI。
musttail、异常调用、原子操作、volatile 与各种属性都有独立约束;先用最小可信语义生成合法 IR,再根据证明添加优化信息。错误属性可能使优化器合法地产生不符合原意的程序。
参考
Keywords: LLVM IR, bitcode, ptr, opaque pointer, phi, GEP, load, store, DataLayout, poison, mem2reg, verifier