Optimization involves two independent questions: whether a transformation is legal, and whether legal transformations are worthwhile. Fewer instructions, fewer calls, or less source code do not automatically answer these questions. Correctness depends on the semantics of the source language and IR, while benefits depend on the target and workload.
These optimizations are not limited to SSA form, nor do they all have uniform linear complexity. SSA provides conveniences such as explicit def-use chains, but practical algorithms also involve control flow, aliasing, cost models, and compilation time budgets.
Constant Folding and Propagation
Folding computes the value of known expressions at compile time, e.g., integer 2+3 yields 5; propagation carries known constants to their use sites, thereby triggering further folding. SCCP also tracks executable control-flow edges simultaneously, using constant conditions to exclude impossible branches.
x*0→0 is an algebraic simplification, not a fold where all operands are constants. Its legality depends on semantics: in IEEE floating-point, NaNs, infinities, and signed zeros can break certain real-number algebraic identities; if x comes from an expression with side effects, deleting its evaluation also requires proof. LLVM IR's poison, undef, and related flags have independent rules. LLVM UB Manual
A unique static definition in SSA does not guarantee the value is a constant; φ-nodes and loops still require analysis. Conditional propagation does not mean that eliminated branches never require syntax or type checking at the language frontend.
CSE and GVN: Finding Reusable Values
If two instances of b+c have consistent operation semantics, operands, and visible state, the second may reuse the result of the first. The existing value must be available on the path where it is needed; a value computed only in the left branch of an if cannot directly replace a computation in the right branch.
GVN uses value numbering to identify equivalences, which can be stronger than literal expression matching. When handling loads, one must also prove that no potentially aliasing stores or calls during the interval have modified memory. Two instructions with identical text are not replaceable values if they read different states.
Reuse may also extend the live range of certain results, increasing register pressure; therefore, legal CSE does not guarantee speedup in all cases.
Identical loads separated by a potentially aliasing store
The source code for both reads below is *p, but q may point to the same object. If p=q and the initial value is 3, the first read gets 3, and after writing 7, the second read gets 7, resulting in 10; directly reusing the first read would incorrectly calculate 6.
/* alias.c */staticintoriginal(int*p,int*q){int before =*p;*q =7;return before +*p;}staticintwrong_reuse(int*p,int*q){int before =*p;*q =7;return before + before;}intmain(void){int a =3, b =3;if(original(&a,&a)!=10)return1;if(wrong_reuse(&b,&b)!=6)return2;
a =3; b =0;if(original(&a,&b)!=6|| b !=7)return3;return0;}
This counterexample does not violate aliasing conventions; both pointer relationships are valid inputs for the function. If the interface additionally uses promises like restrict, the optimizer may leverage stronger facts, but callers must also satisfy the corresponding rules; one cannot arbitrarily add promises to a function and then continue treating previously allowed aliasing calls as equivalent.
Do identical loads observe the same memory state?
The transformation needs evidence that the store cannot affect the load, not accidental numeric agreement. With q=A and stored value3, both results are6, yet aliasing remains. Change it to7 and the counterexample appears. With q pointing to independent B, the store cannot change A in this model.
Preparing the visual
Do identical loads observe the same memory state?
Identical instruction text does not prove equal values. If q=p, the store changes the second load; separate objects provide reuse evidence in this model.
{"id":"load-alias-evidence","title":"Do identical loads observe the same memory state?","summary":"Identical instruction text does not prove equal values. If q=p, the store changes the second load; separate objects provide reuse evidence in this model.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"target\"><button data-mode=\"A\" data-i18n=\"same\"></button><button data-mode=\"B\" data-i18n=\"separate\"></button></div><div class=\"control\"><label for=\"write\" data-i18n=\"write\"></label><output for=\"write\" id=\"write-value\"></output><input id=\"write\" type=\"range\" min=\"0\" max=\"9\" value=\"7\" step=\"1\"></div><p class=\"source\">a = *p; *q = v; b = *p; return a+b;</p><div id=\"memory\" class=\"track\"></div><div id=\"loads\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"real\"></span><strong id=\"real\"></strong></div><div><span data-i18n=\"reuse\"></span><strong id=\"reuse\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.metrics strong{font-size:20px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let target='A';function draw(){const v=+$('#write').value,a=3,b=target==='A'?v:3;set('write-value',v);cells('memory',[{text:'A: 3 → '+(target==='A'?v:3),cls:target==='A'?'gold':'on'},{text:'B: 3 → '+(target==='B'?v:3),cls:target==='B'?'gold':'on'}]);table('loads',[['first *p','store *q','second *p'],['3',target+' ← '+v,{text:String(b),cls:b!==3?'gold':'on'}]]);set('real',a+b);set('reuse',6);set('explanation',t(a+b===6?'ok':'bad'));$('#real').dataset.safe=String(target==='B');pressed(target);}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{target=b.dataset.mode;draw()});$('#write').oninput=draw;draw();","audio":false,"strings":{"bad":"The object changed; reusing the old value changes the result.","heading":"Do identical loads observe the same memory state?","intro":"p points to A; choose q=A or B. Inspect the second load after the store and compare reusing the old value.","ok":"This input agrees; only the separate-object branch gives structural evidence of no effect from this store.","real":"Keep both loads","reset":"Reset","reuse":"Reuse first load","same":"A: aliases p","scope":"Two independent integer objects initially A=B=3; sequential execution, no threads, volatile, atomics, restrict or calls. Tests this load elimination only.","scopeLabel":"Model scope and assumptions","separate":"B: separate object","target":"q target","write":"Stored value"}}
DCE and DSE: Unused Results Do Not Mean Behaviors Are Deletable
When pure computation results have no users, they can be recursively deleted; their operand definitions may also become dead code. Side effects, exceptions, synchronization, and control-flow semantics must be preserved or separately proven.
Stores cannot be deleted simply because they have no SSA users; however, if they are completely overwritten later with no observations in between, DSE can remove the dead store. Function calls are not always undeletable: if known effects, termination, and exception properties allow it, calls with unused results may be eliminated. Conversely, "looks like just a memory read" does not automatically imply deletability.
Inlining: Exposing Context, Not Just Removing Calls
Introducing the called function body at the call site allows constant parameters, aliasing relationships, and control-flow conditions to participate in subsequent optimizations. Costs include code bloat, compilation effort, instruction cache pressure, and register pressure.
Functions called only once may be good candidates for inlining, but whether the original definition can be deleted depends on exports, address escaping, etc. Recursion also requires limits on expansion. Practical decisions are made per call site combined with cost models, rather than "always inline all small functions."
LICM: Separating Proof of Invariance from Safe Early Execution
The operands of a loop invariant do not change across loop iterations, and the relevant memory cannot be modified by writes within the loop. However, moving it to the preheader also changes the execution condition: the loop might execute zero times, and the original instruction might be located within a conditional branch.
Legal hoisting requires proving that the instruction can be safely speculatively executed, or proving via necessary execution conditions and other constraints that early execution does not change behavior. Loads must consider address validity and aliasing, division must consider exception semantics, and calls must consider effects. "No side effects plus invariant operands" does not fully cover all constraints.
After hoisting, the values needed by the original users must still be preserved. Deleting the original assignment directly without connecting subsequent uses is an incorrect teaching transformation.
Verifying Zero-Trip Loops with Explicit Exception Semantics, Then Adding Execution Guards
To avoid relying on C's undefined behavior for division by zero, the following constructs a teaching model using Python's integer division and exception semantics. We only consider non-negative loop counts; the original and hoisted-with-guard versions should produce identical results and exceptions. The unguarded version raises an extra error when n=0 and d=0, constituting a counterexample.
deforiginal(n,d):total=0for_inrange(n):total+=10//dreturntotaldefhoisted(n,d):ifn==0:return0value=10//dtotal=0for_inrange(n):total+=valuereturntotaldefwrong(n,d):value=10//dreturnsum(valuefor_inrange(n))defoutcome(fn,n,d):try:return("value",fn(n,d))exceptZeroDivisionError:return("error","division by zero")forninrange(5):fordin(-2,0,2,3):assertoutcome(original,n,d)==outcome(hoisted,n,d)assertoutcome(original,0,0)==("value",0)assertoutcome(wrong,0,0)==("error","division by zero")
The guard's effectiveness also depends on the specific structure of this example: division is a pure computation that must occur every time the loop body is entered, d remains unchanged, and there are no prior effects that must happen first. If the division were hidden in another conditional branch inside the loop, proving only n>0 would still be insufficient for hoisting; if other operations might raise exceptions first, the language-required order of exceptions must still be preserved. This fix cannot be generalized into a universal template for arbitrary LICM.
Why are invariant operands not enough for hoisting?
Separate results, exceptions and division attempts to judge legality before savings. n=0,d=0 is the key counterexample: the original never divides, while unguarded hoisting throws early. The guarded form restores the condition in this restricted loop. Counts are division attempts, not total instructions or measured time.
Preparing the visual
Why are invariant operands not enough for hoisting?
A zero-trip loop performs no division. Unguarded hoisting can introduce division by zero; n>0 restores the condition in this example.
{"id":"licm-execution-condition","title":"Why are invariant operands not enough for hoisting?","summary":"A zero-trip loop performs no division. Unguarded hoisting can introduce division by zero; n>0 restores the condition in this example.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"n\" data-i18n=\"n\"></label><output for=\"n\" id=\"n-value\"></output><input id=\"n\" type=\"range\" min=\"0\" max=\"5\" value=\"0\" step=\"1\"></div><div class=\"control\"><label for=\"d\" data-i18n=\"d\"></label><output for=\"d\" id=\"d-value\"></output><input id=\"d\" type=\"range\" min=\"0\" max=\"4\" value=\"0\" step=\"1\"></div><div id=\"results\" class=\"table\"></div><p id=\"equation\" class=\"formula\"></p><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#results .tr{grid-template-columns:1.25fr 1fr .7fr}#results .tr>div{min-height:4.8em}#equation{white-space:pre-line;min-height:7.2em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function run(n,d,mode){let count=0,total=0;function div(){count++;if(d===0)throw Error('DivZero');return Math.floor(12/d)}try{if(mode==='original'){for(let i=0;i<n;i++)total+=div()}else if(mode==='unguarded'){const x=div();for(let i=0;i<n;i++)total+=x}else if(n>0){const x=div();for(let i=0;i<n;i++)total+=x}return{result:String(total),count}}catch(e){return{result:e.message,count}}}function draw(){const n=+$('#n').value,d=+$('#d').value,modes=['original','unguarded','guarded'],r=modes.map(x=>run(n,d,x));set('n-value',n);set('d-value',d);table('results',[['',t('outcome'),t('count')],...modes.map((m,i)=>[t(m),{text:r[i].result,cls:r[i].result===r[0].result?'on':'gold'},r[i].count])]);set('equation','original: n > 0 → divide\\nunguarded: always divide\\nguarded: n > 0 → divide');set('explanation',t(r[0].result===r[1].result?'ok':'bad'));$('#results').dataset.results=JSON.stringify(r);}$('#n').oninput=draw;$('#d').oninput=draw;draw();","audio":false,"strings":{"bad":"The unguarded version raises an error absent from the original.","count":"Division count","d":"Divisor d","guarded":"Hoist guarded by n>0","heading":"Why are invariant operands not enough for hoisting?","intro":"Vary loop count n and divisor d. Three programs share explicit exception semantics; compare results, errors and division counts.","n":"Iteration count n","ok":"These inputs agree; n=0,d=0 exposes the key boundary.","original":"Original loop","outcome":"Result / error","reset":"Reset","scope":"Teaching semantics: nonnegative floor(12/d), d=0 raises DivZero. Division is unconditional within each iteration, with no earlier effects or errors. Not a universal LICM guard.","scopeLabel":"Model scope and assumptions","unguarded":"Unguarded hoist"}}
Loop Unrolling: Trading Code Size for Control and Parallelism Opportunities
Partial unrolling copies several iteration bodies and handles the remaining iterations; complete unrolling requires determinable finite iterations or corresponding specialization conditions. Unknown iteration counts can also undergo partial unrolling with tail handling.
Unrolling may reduce back-edge overhead, expose instruction-level parallelism, or help vectorization, but it may also increase code volume and spills. There is no rule that all LLVM targets unroll by two to four times; consecutive scalar loads do not automatically become legal SIMD loads: bounds, alignment, dependencies, and target capabilities still need checking.
SROA and mem2reg: Making Local Memory States Easier to Analyze
If a local aggregate object does not escape in address and its accesses are understandable, it can be split according to usage intervals, reducing unnecessary aggregate memory operations. Suitable local slots can also be promoted to SSA values, with φ-nodes expressing merges at control-flow join points.
SROA does not mechanically have to be "one field corresponds to one variable"; some usages hinder splitting, while some implementations can perform promotion directly. mem2reg can also handle alloca that are already scalars, without requiring all scenarios to pass through SROA first. LLVM Pass Documentation
Pass Order: Opportunities and Invalidations Arise Together
Inlining exposes constants, constant propagation eliminates branches, and branch elimination renders more code useless; unrolling changes loop structure, and subsequent cleanup simplifies expressions. Repeating certain transformations can be valuable, but compilers do not infinitely pursue saturation; they must also control time, size, and analysis invalidation.
After modifying the CFG, the dominator tree, loop information, or memory analysis may no longer be valid. Transformations must correctly maintain or declare invalidation of related analyses; they cannot just make the output IR look superficially legal.
Verification and Quick Reference
Transformation
Priority Checks
Folding, Algebraic Simplification
Bit-width, overflow, floating-point, poison, and side effects
CSE / GVN
Availability, dominance, memory modifications
DCE / DSE
Observable effects, exceptions, synchronization, and coverage scope
LICM
Zero-trip loop execution, conditional paths, and safe speculation
Inlining, Unrolling
Code volume and final machine code benefits
SROA / mem2reg
Address escaping, analyzable accesses, and φ-nodes
You can combine IR verification, counterexample testing, diffs before and after transformation, and equivalence verification within specific ranges. Tests should include boundary paths, not just ensure identical output for one normal input. To measure performance, observe the target code after the complete optimization pipeline.