LLVM IR is a typed intermediate language processed by both the optimizer and the backend. It contains SSA values, explicit control flow, and explicit memory operations, but it does not imply that all values are eventually placed in physical registers, nor is it equivalent to the full semantics of any source language.
The following examples use modern ptr opaque pointer syntax and can be verified using LLVM tools of the same version that support this syntax. The language reference specifies the exact semantics of each instruction; do not infer semantics solely from names. LLVM Language Reference
Three Representations and Tool Responsibilities
Textual .ll files are convenient for reading, bitcode .bc is used for serialization, and memory IR is manipulated via the compiler API. llvm-as assembles text into bitcode, while llvm-dis displays it in reverse; opt analyzes and transforms IR, and llc generates target code. Not all tools can convert between all three representations.
You can first observe the IR of C code using clang -S -emit-llvm, and then use small, hand-written modules to understand specific instructions. Different optimization levels will significantly change the output structure.
Names, Types, and Basic Blocks
@name typically refers to global variables or functions, while %name can refer to local values, arguments, and other local identifiers. The integer type i32 represents 32 bits and does not inherently carry a "signed" attribute; icmp slt, icmp ult, and different division instructions select signed or unsigned interpretations respectively.
Basic blocks end with a terminator instruction, such as br, ret, or switch. Ordinary instructions cannot be placed after a terminator to continue execution; phi nodes are located at the beginning of the block and provide inputs from predecessors. Indirect function calls use the function pointer target of the call instruction; do not treat indirectbr as a standard virtual function call instruction.
main constructs the exit status using the actual results of two branches and the result of reading a struct field; success is indicated by 0. The verifier checks IR structure and some semantic conditions, but it does not prove that any pointer is valid at runtime.
Opaque Pointers: Pointer Types Do Not Carry Pointee Type
Modern ptr does not encode whether it points to an i32 or a double; the read/write width is provided by the load/store instructions, and the address calculation layout is provided by the source element type of the GEP. Address space remains an important part of the pointer type; do not treat all ptrs as having the same physical representation. Opaque Pointers
Therefore, load i32, ptr %p is not a runtime check that p actually points to an i32, but rather a frontend declaration that this read uses i32 semantics. Requirements such as valid objects, alignment, lifetime, and aliasing still must be satisfied.
GEP: The First Index Is Not "Dereference"
For %Pair, getelementptr %Pair, ptr %p, i64 0, i32 1 first selects the 0th Pair, then selects field 1. Field offsets include the alignment and padding of the target DataLayout; they are not simply the sum of the sizes of preceding fields.
The first index can be non-zero: when treating %p as the base address of a continuous sequence of Pairs, index 2 means advancing by two elements according to the Pair's allocation size. Ordinary array element accesses also frequently use non-zero indices; opaque pointers do not turn this into a no-op.
GEP itself does not perform memory access; this does not mean that any calculated address can be read. inbounds adds stronger object and calculation constraints; violating them may produce poison, so do not add it without justification "to make optimization better."
Pair Array: Element Stride and Field Offsets Must Be Calculated Together
Under the common x86-64 System V layout, field 0 of { i32, double } occupies bytes [0,4), followed by 4 bytes of padding, and field 1 occupies [8,16), with the entire object size being 16. If accessing the weight of the 2nd element in a Pair array, the offset is 2*16+8=40, not 2*(4+8)+4=28.
Calculation Part
Bytes on this Target
Source
Allocation size of one Pair
16
Field layout and overall alignment
Start of 2nd element
32
Skip two elements by allocation size
Offset of weight within element
8
Padding generated by double field alignment
Final relative address
40
Element start plus field offset
You can cross-check with the C layout of the same target. The static assertions here explicitly fix expected values and do not claim these numbers apply to any DataLayout:
Do not derive the entire aggregate layout solely from "pointers are 64-bit." DataLayout also specifies the size and alignment of different types, and packed structures change padding between fields; even if you calculate the address, the alignment promise of the load must match the actual object. GEP Layout Explanation
How do element stride and field offset compose?
The address multiplication skips i whole objects; the addition enters a field in the selected object. Byte rows expose padding and packed stride. The control changes an example DataLayout and performs no load. An unaligned field address does not justify a stronger alignment promise on a later access.
addr=base+i⋅allocSize(T)+fieldOffset(T,j)
Preparing the visual
How do element stride and field offset compose?
GEP advances by allocation size, then field offset. Padding affects ordinary layout; packed layout can yield unaligned fields. Address calculation does not load memory.
Ordinary integer add operates on bit width; nsw (no signed wrap) and nuw (no unsigned wrap) express corresponding no-overflow promises, they do not automatically check for overflow and throw source language exceptions. If the source language requires checked addition, the frontend must generate checks, branches, or appropriate intrinsics.
Similarly, you cannot simply replace all short-circuit && operations with unconditionally evaluated select. If the right-hand side calls functions, accesses memory, or throws errors, you must preserve the source language's conditional execution. Choosing between two pre-calculated values is different from controlling whether evaluation occurs.
Checked Addition: Turning Overflow Flags into Control Flow
Assume the interface convention: adding two signed i32s, writing the output slot and returning 1 on success, not modifying the output slot and returning 0 on overflow. Ordinary add does not have this checking behavior, and add nsw will not automatically jump to the failure branch for us. Save as checked.ll:
This intrinsic simultaneously produces the truncated bit pattern and the signed overflow flag. Even if the failure branch does not use %sum, the flag calculation does not disappear; only the success branch exposes the result to the caller. The interface here requires out to point to a valid, at least 4-byte aligned i32 output object; C drivers use ordinary int objects to satisfy this target requirement. Addition with Overflow Detection
/* checked-main.c: clang checked.ll checked-main.c -o checked-demo */#include<limits.h>_Static_assert(INT_MAX ==2147483647,"requires 32-bit int");externintchecked_add(int,int,int*);intmain(void){int out =99;if(checked_add(20,22,&out)!=1|| out !=42)return1;if(checked_add(INT_MAX,0,&out)!=1|| out != INT_MAX)return2;
out =99;if(checked_add(INT_MAX,1,&out)!=0|| out !=99)return3;if(checked_add(INT_MIN,-1,&out)!=0|| out !=99)return4;if(checked_add(INT_MIN,0,&out)!=1|| out != INT_MIN)return5;return0;}
For inputs INT_MAX and 1, the mathematical sum exceeds the signed range, returning 0 and preserving the original out=99; for INT_MAX and 0, the boundary value itself is valid, returning 1. The difference between the two branches is defined explicitly in IR; the optimizer may replace it with a machine implementation, but must preserve this interface behavior.
Why does the same bit pattern not imply the same contract?
Separate the bit pattern from the interface contract. i8 makes the sign bit visible: modular addition truncates, nsw produces poison when its signed-overflow promise fails, and the checked API explicitly decides whether to write out. The poison label denotes a result category, not a simulation of every later use.
Preparing the visual
Why does the same bit pattern not imply the same contract?
Plain add keeps modulo-256 bits. Signed overflow under nsw produces poison, not an exception. The checked interface reports failure and preserves output99.
{"id":"integer-overflow-contracts","title":"Why does the same bit pattern not imply the same contract?","summary":"Plain add keeps modulo-256 bits. Signed overflow under nsw produces poison, not an exception. The checked interface reports failure and preserves output99.","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":"Operand a","b":"Operand b","checked":"Checked API: success / out","heading":"Why does the same bit pattern not imply the same contract?","intro":"Use signed i8 to expose the boundary. Vary operands and compare plain add, add nsw and checked addition.","math":"Mathematical sum","nsw":"add nsw","reset":"Reset","scope":"Teaching i8 range −128..127, two’s-complement display. Compares addition contracts only; no poison propagation, freeze or complete UB model.","scopeLabel":"Model scope and assumptions","wrap":"Plain add, signed reading"}}
Mutable Local Variables and Address Identity
Frontends new to this can create appropriate allocas for mutable local variables, use load/store, and then let mem2reg or SROA promote optimizable parts. It is not that "taking the address of an alloca" itself prevents promotion; alloca inherently returns an address; the key is whether the specific use of the address meets the promotion conditions.
If the source program allows object address escaping or observable identity, the frontend should model this correctly from the start. You cannot see &x in the source code after optimization and arbitrarily create a new alloca, claiming to have restored the original object identity.
Module and ABI Boundaries
Target triple and DataLayout affect layout and generated targets. Calling external functions also requires correct signatures, calling conventions, argument attributes, and ABI lowering. The way an aggregate parameter is written in IR does not guarantee it naturally conforms to all platform C ABIs.
musttail, exception calls, atomic operations, volatile, and various attributes have independent constraints; first generate valid IR with minimal trusted semantics, then add optimization information based on proofs. Incorrect attributes may cause the optimizer to legally produce programs that do not match the original intent.