Stop chasing the optimizer; reduce the repro instead
When to use
You have a hot function that passes on one toolchain (typically MSVC)
but miscompiles or crashes under GCC / Clang at -O2 (or higher), and
you're on attempt three of sprinkling anti-optimization decorations:
volatileon a local / a parameter__attribute__((noinline))on the callee__attribute__((optimize("O0")))on the callee-fno-strict-aliasingon the TUasm volatile("" ::: "memory")barriers
Each one "almost works" — the symptom moves by a few bytes or into a different test case — but never fully goes away. That's the signal to stop.
Problem
Adding anti-optimization pragmas is a local fix applied to a global belief: "the optimizer is wrong." Three things go wrong in practice:
- The decorations change the bug, not the cause.
noinlinein one spot shifts the inlining decision elsewhere; the miscompile now lives in a different frame. You spend another iteration finding it. - Some decorations are themselves traps.
__attribute__((optimize("O0")))on a single function inside an-O2TU is known to produce ABI mismatches between the-O0callee and the-O2caller on GCC (frame pointer, red-zone, stack alignment). The "fix" introduces a new SIGSEGV on the first call. - You never learn whether it's a compiler bug, your UB, or your
aliasing. All three present identically — "works on MSVC, breaks
on GCC
-O2" — and have very different fixes. Patching blindly leaves the root cause unknown, so the same bug returns the next time someone touches the file.
The heuristic: after two anti-optimization patches in a row have failed to fully fix the symptom, the next step is not a third patch. It's reduction.
Solution
Switch from "patch" mode to "reduce" mode:
- Extract a standalone repro. Rip the suspect function out of the
project into a single
.cppof ≤ 200 lines that links with nothing but libc. Must reproduce the divergence between MSVC and GCC/Clang-O2. If it doesn't reproduce standalone, the bug is in how the function is called, not the function itself — go up a frame. - Diff the codegen.
g++ -O2 -S -masm=intel repro.cppvsclang++ -O2 -S -masm=intel repro.cppvs MSVC/FAs. Look for loads from offsets you never wrote, or stores that the compiler elided. This usually tells you within minutes whether it's UB (compiler is within its rights) or a real miscompile. - Shrink with
creduce/cvise. Feed the standalone repro plus a predicate script (g++ -O2 x.cpp && ./a.out; [ $? -ne 0 ]) tocvise. 200 lines typically collapses to 20. - Decide once, fix once.
- UB in your code → fix the UB (e.g. replace
x >> 64with a branch, usememcpyinstead of pointer-punning, add an explicit bounds check). Novolatileneeded. - Real compiler bug → file upstream with the reduced repro, then put one narrow workaround (guarded by compiler-version macros) with a link to the bug report.
- Aliasing assumption → use
memcpyor__attribute__((may_alias))at the type, not-fno-strict-aliasingon the whole TU.
- UB in your code → fix the UB (e.g. replace
The key is that step 4 is a single, documented change, not another round of sprinkling.
Example
Real case (the one this skill came from): a bit-blit routine
appBitsCpyFast passed all MSVC tests but produced zeros on Linux GCC
-O2. Sequence that didn't work, in order:
// Attempt 1: mark the output volatile at call sites. Symptom moves.
// Attempt 2: __attribute__((noinline)) on the inner helper. Passes
// aligned cases, still fails unaligned.
// Attempt 3: __attribute__((optimize("O0"))) on the helper.
// Now SIGSEGVs on the first call (ABI mismatch). Worse.
At this point the right move is not attempt 4 (yet another decoration). It is to switch modes: stop patching, start reducing. The concrete recipe:
# 1. Rip the function into a standalone repro.cpp that links only
# against libc and still reproduces the MSVC-vs-GCC divergence.
# 2. Write a predicate script that exits 0 iff the bug reproduces.
cat > check.sh <<'EOF'
#!/bin/sh
g++ -std=c++17 -O2 -o /tmp/a repro.cpp || exit 1
/tmp/a | grep -q 'FAIL'
EOF
chmod +x check.sh
# 3. Let cvise shrink it.
cvise check.sh repro.cpp # typically 180 lines → ~20
The point of this skill is the mode switch, not a specific root cause — so this Example deliberately stops at "run the reducer." Whatever the 20-line output turns out to be (UB you wrote, a real miscompile, or an aliasing assumption), the next step is one documented fix per the four bullets in the Solution section, not a fourth anti-optimization decoration.
Status note: on the project that inspired this skill, the reduction step above has not yet been performed at the time of writing. The skill is deliberately published before the fix lands, because the lesson ("after two failed decorations, reduce") is independent of which root cause reduction eventually uncovers. If you want the specific root cause, check the project's issue tracker rather than trusting a plausible-sounding example in a skill file.
Pitfalls
__attribute__((optimize("O0")))on a single function inside an-O2TU on GCC can crash. The-O0callee and-O2caller disagree on frame-pointer / red-zone / alignment convention. If you really need a function at lower optimization, put it in its own TU and compile that whole TU at-O1(not-O0) via the build system.- "Works on MSVC" is weak portability evidence. MSVC tends to fold undefined shifts, undefined unions, and out-of-range enum values into "the obvious answer". GCC and Clang exploit them. Do not use MSVC green as a justification to ship.
-fno-strict-aliasingis a TU-wide sledgehammer. If you reach for it as a fix, first check whether a targetedmemcpy(orstd::bit_castin C++20) expresses what you meant. The sledgehammer also disables legitimate optimizations for every other function in the file.- The optimizer is almost never "wrong." In ~20 years of shipping C++, roughly 1 in 50 "GCC miscompile" reports survives reduction. Assume UB until reduction says otherwise.
- If
cviseis unavailable, a manual binary-chop of the function body (delete the second half, does it still fail?) gets you 80% of the way in 15 minutes.
See also
- detect-tool-vendor-by-query — another "don't guess the compiler, query it" lesson.
- doc-code-consistency-check — the mirror-image pitfall: patching the doc instead of the code.
cviseproject: https://github.com/marxin/cvisecreduceproject: https://github.com/csmith-project/creduce