In systems programming, the most practical entry point to understanding compiler internals is to trace the artifacts: where source code becomes IR, when cross-file information becomes visible, and which runtime libraries are finally linked. Only then can you determine whether an option affects semantics, optimization, target instructions, or the linking process.
Use the Same Program Across Stages
First, save prog.c in a separate directory. It selects one of two datasets based on the number of arguments, and all calculations use unsigned values to avoid undefined behavior from signed overflow. The results are used for the exit status and output, making it easy to check whether different builds maintain consistent behavior.
#include<stdint.h>#include<stddef.h>#include<stdio.h>uint32_tsum(constuint32_t*values,size_tcount){uint32_t total =0;for(size_t i =0; i < count;++i) total += values[i];return total;}intmain(intargc,char**argv){(void)argv;constuint32_t values[]={1,2,3,4};uint32_t result = argc >1?sum(values,4):sum(values,2);printf("%lu\n",(unsignedlong)result);return result ==(argc >1?10u:3u)?0:1;}
Running with no arguments should print 3, and running with any argument should print 10. This tiny workload is useful for observing the compilation chain but is insufficient to prove that a specific optimization option is faster; the compiler might even fold the known data calculations in main directly, which is exactly why you need to inspect the final artifacts.
The Driver Orchestrates Multiple Stages
gcc and clang are typically user-facing driver entry points; they select the frontend, assembler, linker, startup files, and runtime libraries. Final linking for C++ should use the corresponding g++ or clang++ drivers to bring in matching C++ runtime support; you cannot assume that directly invoking ld is fully equivalent just because .o files already contain machine code.
The first command displays the commands that would be invoked without executing them; the subsequent commands stop at the preprocessing, LLVM IR, assembly, and object file stages, respectively. A full build can fuse multiple stages and does not necessarily write every intermediate file to disk. Clang can use an integrated assembler and different linkers, and LLVM IR does not require that the final output be handed off to LLD. Clang Toolchain
GIMPLE and LLVM IR Are Not Simply "High vs. Low Level"
The GCC frontend reduces language structures to a generic representation. GIMPLE provides a simplified statement form, and many optimizations work on its SSA representation before moving to RTL and other target-machine-close processing. LLVM IR also uses SSA values but includes target data layout, calling conventions, memory semantics, and attributes.
Reuse at the intermediate layer does not mean the architecture is completely irrelevant: vector widths, legal instructions, and cost models can all affect optimization. The backend is also not a fixed single run of "select instructions, allocate registers, schedule"; different targets and optimization pipelines combine these processes at different points. GCC passes
Dumps are used to observe results, and optimization remarks are used to understand decisions. A loop might not be vectorized due to aliasing, safety, benefit estimation, or target capabilities; you cannot simply conclude that SIMD should be generated just because "O3 is enabled." For conceptual derivation, see Classic Optimizations.
When Reading IR, Look for Data Flow and Control Flow First
In the source code, sum repeatedly modifies total, but in the SSA representation, each result name has only one definition; loops typically use phi nodes to select different previous values from the entry and back edge. Phi nodes are not evaluated on all predecessors, nor do they necessarily correspond to a single machine instruction. Optimizations may unroll, vectorize, or completely replace the loop, so specific outputs should be read in the context of the current parameters. LLVM phi
Observation Point
Questions to Ask
Conclusions to Avoid
Preprocessed output prog.i
What do macros and headers expand to?
Business logic has completed type checking.
AST / IR
How are types, branches, calls, and memory accesses expressed?
Every IR instruction has a corresponding machine instruction.
Optimization remarks
Why did a specific pass accept or reject a transformation?
No message means no optimization occurred.
prog.s
Which instructions does the target actually select?
Layout and addresses are determined after linking.
Final executable
Are calls preserved? How are symbols resolved?
Disappearing symbols necessarily mean faster execution.
For example, adding restrict for element-wise transformations may help the compiler exclude pointer aliasing, but it simultaneously requires callers to adhere to stricter aliasing contracts. If the caller still overlaps input and output in disallowed ways, you cannot call subsequent result changes "optimization instability." You must first prove the premises, then optimize based on them.
LTO: Deferring Some Optimizations to Link-Time Visibility
Prepare a.c and b.c for the following two-file command. a.c only knows the function declaration, while b.c contains the implementation:
/* a.c */#include<stdio.h>intdoubled(intvalue);intmain(void){int result =doubled(21);printf("%d\n", result);return result ==42?0:1;}
/* b.c: In this example, called only with 21, so multiplication will not overflow */intdoubled(intvalue){return value *2;}
Ordinary separate compilation mainly looks at the current translation unit; LTO saves information in object files or other intermediate artifacts that can be processed during the linking stage, enabling cross-file inlining, constant propagation, and dead code elimination.
Pass the required options in both stages and use the compiler driver to select the matching toolchain. Object files participating in LTO, archive tool plugins, compiler versions, and linker support need to be compatible; not all files named .o contain representations consumable by the same LTO implementation.
ThinLTO uses summaries to complete cross-module analysis and allows backend processing to be parallelizable and cacheable. It is not "doing slightly less optimization," nor does it eliminate the need for a linking stage. Version matching and caching strategies affect build effectiveness. ThinLTO Documentation
LTO does not automatically see all code loaded at runtime; shared library boundaries, symbol visibility, dynamic replacement semantics, and external calls all limit the assumptions that can be made. It may reduce or increase code size, shorten or lengthen execution time; you must measure the target workload rather than promising a fixed benefit range.
Both programs should print 42. First, look for the call to doubled in the disassembly of main, then see if the LTO version changes to directly use the constant; whether external symbols still exist and whether calls are inlined are related but distinct issues. nm is suitable for checking symbols but cannot alone prove how each call site is executed.
Tool versions must also correspond: clang, ld.lld, and llvm-profdata in the PATH may come from different installations. Record their --version outputs; when bitcode or profile version incompatibilities appear, first verify the actual tools being invoked; do not assume they can be arbitrarily mixed just because they all bear the LLVM name.
What information blocks cross-file folding?
With separate compilation, a.c has only a declaration: constant 21 does not prove 42. Link-time body visibility still requires valid target-binding assumptions. LTO supplies cross-file information; symbol semantics constrain its use. Inspect the final binary for the actual decision.
Preparing the visual
What information blocks cross-file folding?
A name and arguments are insufficient. Cross-file propagation needs the body and valid assumptions about the bound target; an opportunity is not a guaranteed compiler decision.
{"id":"lto-visibility-boundary","title":"What information blocks cross-file folding?","summary":"A name and arguments are insufficient. Cross-file propagation needs the body and valid assumptions about the bound target; an opportunity is not a guaranteed compiler decision.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"arg\" data-i18n=\"arg\"></label><output for=\"arg\" id=\"arg-value\"></output><input id=\"arg\" type=\"range\" min=\"0\" max=\"100\" value=\"21\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"visible\"><span data-i18n=\"visible\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"replace\"><span data-i18n=\"replace\"></span></label><div class=\"two\"><section class=\"panel\"><h3 data-i18n=\"caller\"></h3><div id=\"caller\"></div></section><section class=\"panel\"><h3 data-i18n=\"callee\"></h3><div id=\"callee\"></div></section></div><div class=\"metrics\"><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div></div><p id=\"equation\" class=\"source\"></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.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#caller,#callee{font:12px/1.9 var(--viz-font-mono);white-space:pre-line;min-height:7.6em;overflow-wrap:anywhere}.two .panel{margin:0}.metrics{grid-template-columns:1fr}.metrics strong{font-size:17px}#equation{min-height:5.7em}@media(max-width:420px){.two{grid-template-columns:1fr}#caller,#callee{min-height:5.7em}}","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).classList.add('table');$('#'+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 band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const x=+$('#arg').value,v=$('#visible').checked,r=$('#replace').checked,k=v&&!r;set('arg-value',x);set('caller','extern int doubled(int);\\nreturn doubled('+x+');');set('callee','int doubled(int x) {\\n return 2*x;\\n}');$('#callee').style.opacity=v?'1':'.5';set('result',t(k?'known':'unknown'));set('equation',k?'doubled('+x+')\\n→ 2 × '+x+'\\n→ '+2*x:'doubled('+x+')\\n→ ?\\n→ call');$('#result').dataset.fold=k;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"arg":"Argument x","callee":"b.c: implementation","caller":"a.c: caller","heading":"What information blocks cross-file folding?","intro":"Change the argument, body visibility and interposition boundary; inspect the provable expression.","known":"May fold to a constant","replace":"Call target may be interposed","reset":"Reset","result":"What is provable here","scope":"Fixed pure doubled(x)=2x, inputs 0–100. Shows a provable transformation, not browser execution of GCC/Clang or real performance prediction.","scopeLabel":"Model scope and assumptions","unknown":"Keep call: target behavior unknown","visible":"Function body visible at link time"}}
PGO: Using Training Execution to Guide Optimization Decisions
PGO feeds actual path frequency feedback to the compiler, helping with inlining, layout, and hot/cold separation. It does not modify the static configuration of the CPU branch predictor, nor does it guarantee that inputs outside the training set will still be fastest.
Below is the Clang instrumentation-based workflow; prog.c should be directly executable; for programs requiring arguments, pass representative arguments during the training step:
Raw data is merged first, then used for the feedback build; training filenames containing the process ID help avoid concurrent processes writing to the same file. Practical applications must also consider dynamic libraries, weights for multiple training datasets, short-circuit exits that do not write to disk, and matching issues caused by source code changes. Clang PGO User Guide
Arrange Training Inputs and Independent Verification Inputs Separately
For prog.c on this page, the training phase should execute at least the no-argument and with-argument paths separately, then merge the profile generated in this round. Do not let train-*.profraw files left over from old source code in the directory automatically mix into the current experiment; keep independent directories or explicit input lists for each training run.
After the feedback build is complete, run ./prog and ./prog sample again to verify that the output remains 3 and 10. You can also use llvm-profdata show --all-functions --counts prog.profdata to check if function counts were indeed captured; having a non-empty file does not represent that critical business paths have been trained, nor does it mean version matching warnings can be ignored.
The training set should cover important paths and input scales in production. Using only startup self-checks to train a throughput service will likely yield unrepresentative hotspots. Verify the final build with independent evaluation workloads, and record the compiler version, source code, and training data sources; LTO and PGO can be combined, but the build chain and data validity should be confirmed separately.
Optimization Levels Are Sets of Strategies
Option
Primary Intent
Common Misconception
-O0
Retain low-optimization builds suitable for debugging
Does not mean every line of source code is translated literally.
-O1 / -O2 / -O3
Sets of different cost and optimization strategies
O3 does not guarantee being faster than O2; O1 does not guarantee smaller size.
-Os / -Oz
Tendency to reduce code size
Does not mean all speed optimizations are disabled.
-march=native
Allows target-architecture-specific instructions and tuning
Artifacts may not run on older or different CPUs.
-fno-omit-frame-pointer
Request to retain frame chains suitable for unwinding
The effect on specific functions and targets still depends on the implementation.
Which passes are included in each level changes with the compiler, version, and target; you cannot statically write "vectorization is only enabled at O3." GCC can query relevant states with gcc -Q -O2 --help=optimizers. GCC Optimization Options
-fno-semantic-interposition changes optimization assumptions about whether symbols can be replaced externally; it is not a simple "do not generate PLT" switch. Options like -ffast-math change the floating-point semantics that can be relied upon and should not be confused with performance switches that do not alter language contracts.
Separate Correctness and Performance Verification
Sanitizer instrumentation is suitable for discovering runtime issues; see Valgrind and Sanitizers in Practice. They change layout, execution time, and runtime libraries, so their measurement values cannot be directly used as baselines for release builds.
When comparing compilation options, fix the completed work, target platform, and inputs; first check behavior, then look at time, code size, and build cost. If an error appears only after optimization at O2, prioritize checking UB, data races, and ABI mismatches, then use a minimal test case to determine if it is a compiler issue; permanently disabling optimization should not be treated as having explained the cause.