Serial Studio -- C++ compiler flags
Serial Studio's release builds are aggressively optimized and hardened across seven
toolchains. The flag surface lives in four cmake modules, not scattered through
CMakeLists.txt or per-target calls, and those modules are the single source of truth --
read them before answering anything flag-related:
| Module |
Owns |
cmake/Optimization.cmake |
-O3//O2, -march, SIMD, LTO, and the full PGO (GENERATE/USE) flow, per toolchain. |
cmake/Hardening.cmake |
Stack protectors, FORTIFY, CFI/CET, RELRO/DEP, ASLR, serial_studio_harden() per-target deltas. |
cmake/Sanitizers.cmake |
ASan+UBSan (DEBUG_SANITIZER) and the separate TSan build (ENABLE_TSAN). |
cmake/MiMalloc.cmake |
The process-wide mimalloc allocator override (Windows/MSVC + Linux shared; macOS static interpose via -Wl,-force_load). |
Wiring: the four user-facing options (DEBUG_SANITIZER, PRODUCTION_OPTIMIZATION,
ENABLE_HARDENING, ENABLE_PGO) are declared in CMakeLists.txt (around lines 79-87);
PGO_STAGE and the internal DISABLE_LTO live in Optimization.cmake, ENABLE_TSAN in
Sanitizers.cmake. The modules are include()d in CMakeLists.txt (MiMalloc ->
Optimization -> Sanitizers, then Hardening, then Signing). The PGO two-stage flow is driven
by .github/workflows/ci.yml (the only workflow file).
This skill does NOT build
Per CLAUDE.md, the developer runs cmake, the compiler, and the benchmark themselves. Never
invoke cmake/jom/clang/cl/the compiler to "test" a flag. Reason from the modules and
the references; propose the edit; hand off. Flag changes that touch the hotpath are gated by
--benchmark-hotpath (256 kHz) -- flag it for the developer to re-run; see [[ss-hotpath]].
Build-option matrix (all default OFF except where noted)
| Option |
Effect |
Set by |
PRODUCTION_OPTIMIZATION |
The aggressive per-toolchain release branch (-O3//O2, -march=x86-64-v2, LTO). |
Release CI; manual. |
ENABLE_PGO + PGO_STAGE |
Profile-guided opt; GENERATE then USE. Needs PRODUCTION_OPTIMIZATION. |
CI two-pass; manual. |
ENABLE_HARDENING |
Defense-in-depth (canaries, FORTIFY, CFI, RELRO, CFG). |
CI; auto-on for Release/RelWithDebInfo and sandboxed builds. |
DEBUG_SANITIZER |
ASan + UBSan (Debug only, GCC/Clang). |
Manual. |
ENABLE_TSAN |
ThreadSanitizer (proves the lock-free hotpath); mutually exclusive with ASan and MSVC. |
Manual / CI thread-safety job. |
DISABLE_LTO is internal: forced ON for Flatpak/sandboxed builds (which also auto-enable
hardening). LTO is otherwise ON whenever PRODUCTION_OPTIMIZATION is.
Non-negotiable invariants (do not regress these)
These are deliberate choices baked into the modules. A flag change that breaks one is a bug,
not an optimization:
- IEEE-stable math, always. Every non-MSVC branch pins
-fno-fast-math -fno-unsafe-math-optimizations; MSVC/clang-cl use /fp:precise. Never add -ffast-math,
/fp:fast, -funsafe-math-optimizations, -Ofast, or -ffp-contract=fast -- telemetry
output must be bit-stable across builds and platforms. This is the one rule most likely to be
"helpfully" violated.
- Unwind tables on every TU (non-MSVC).
-fexceptions -funwind-tables -fasynchronous-unwind-tables; on macOS the lua54 target additionally opts out of LTO with
-fno-lto -femit-dwarf-unwind=no-compact-unwind (Xcode 26's ld drops fallback DWARF unwind
under -flto, llvm#135888, and made -Wl,-keep_dwarf_unwind a no-op). Lua is compiled as
C++ and throws across the VM stack; without unwind metadata, --gc-sections/-dead_strip
- LTO can drop a personality routine and turn a routine Lua error into
std::terminate. See
[[ss-cpp-modern]] and the Lua exception-safety setup. Do not remove these to "shrink" the
binary.
- x86-64-v2 is the conservative baseline (SSE4.2, 2012+ CPUs). Every non-MSVC x86-64
branch uses
-march=x86-64-v2; clang-cl passes /clang:-march=x86-64-v2; cl.exe gets no
-march (the x64 ABI's SSE2 baseline stands). Do not bump to v3/v4 or -march=native
for shipped binaries -- it would SIGILL on older CPUs. ARM baselines: aarch64 -> armv8-a
(+-latomic); armv7l -> armv7-a -mfpu=neon -mfloat-abi=hard (hardfloat pinned on purpose).
-msse4.1 is added for DSP/FFT on non-MSVC x86-64, separate from the -march baseline.
MSVC/clang-cl are skipped (the x64 ABI guarantees SSE2; no explicit SSE4.1 there).
- Hardening auto-enables for optimized configs. A manual
Release that forgets
-DENABLE_HARDENING=ON still gets hardened (belt-and-suspenders in Hardening.cmake). Don't
"simplify" that away.
- Per-toolchain branches are exclusive and order-sensitive. clang-cl reports
MSVC=ON, so
its branch must precede cl.exe's; MinGW-Clang precedes MinGW-GCC. Adding a branch in the
wrong order silently routes a compiler to the wrong flags.
The clang-cl / lld-link gotcha (Windows, MSVC ABI)
This trips up almost everyone, so it is stated once here and detailed in the reference:
CMake links this target with lld-link directly, not through the clang-cl driver. So:
- LTO/PGO compile flags use the
/clang: passthrough (/clang:-flto=thin,
/clang:-fprofile-generate=, /clang:-fprofile-use=) and go on the compile step only.
- The link step gets native lld-link flags only (
/lldltocache:, /OPT:REF, /OPT:ICF)
-- never driver-style -flto=thin / -fprofile-*, which lld-link rejects as bogus input.
- ThinLTO needs no link flag: bitcode
.objs are auto-detected; PGO instrumentation embeds an
/INCLUDE: that pulls in clang_rt.profile automatically.
- clang-cl rejects
/Qspectre /sdl /ZH:SHA_256 (those are cl.exe-only); it takes /GS /guard:cf and the link-side /GUARD:CF /DYNAMICBASE /CETCOMPAT.
When to use which reference
- Changing this repo's build, or "why is this flag here / who sets it" ->
references/serial-studio-build-flags.md. The authoritative per-module, per-toolchain map of
what Serial Studio actually passes, the PGO CI flow, mimalloc, and the rationale for each
invariant.
- "What does flag X do" / "what's the Clang equivalent of
/GL" / picking a new flag ->
references/flag-catalog.md. A cross-compiler catalog (GCC / Clang / MSVC families + the PGO
concept) distilled from the canonical upstream docs, with per-source accuracy caveats.
Always cross-check the catalog against the repo module before recommending a change: a flag
being valid is not a reason to add it here (see the IEEE-math invariant). Before proposing
any flag edit, name in chat which invariant above it sits nearest to and why it is safe
against it — an invariant named at the point of the proposal is one the proposal respects
(doc/claude/j-space.md); "it doesn't touch any" is an acceptable answer only after
checking each.
Output expectations
Follow CLAUDE.md's handoff rules: one-line statement of intent before a non-trivial change,
a one/two-sentence summary when you stop, no new doc files unless asked. Propose targeted
Edits to the cmake modules -- do not rewrite a whole module. If a change could shift hotpath
throughput, say so and point the developer at --benchmark-hotpath ([[ss-hotpath]]); you do
not run it. For style/structure on any C++ you touch alongside, defer to [[ss-verify]].
References
references/serial-studio-build-flags.md -- this repo's real flag layout and invariants.
references/flag-catalog.md -- cross-compiler flag catalog from upstream GCC/Clang/MSVC docs
- the PGO concept, with citations and caveats.
1---2name: cpp-compiler-flags3description: C++ compiler/linker flag guidance for Serial Studio's build (GCC, Clang, AppleClang, MSVC cl.exe, clang-cl, MinGW, IntelLLVM). Use when reading, changing, or reasoning about the cmake flag modules (Optimization/Hardening/Sanitizers/MiMalloc), tuning -O/-march/LTO/PGO, adding a per-toolchain branch, debugging a flag that one compiler rejects, or explaining what a flag does. Encodes this repo's actual flag layout and its non-negotiable invariants (IEEE-stable math, Lua unwind tables, x86-64-v2 baseline, the two-stage PGO flow). It does NOT build, configure, or run cmake -- the developer does that.4---56# Serial Studio -- C++ compiler flags78Serial Studio's release builds are aggressively optimized and hardened across seven9toolchains. The flag surface lives in **four cmake modules**, not scattered through10`CMakeLists.txt` or per-target calls, and those modules are the **single source of truth** --11read them before answering anything flag-related:1213| Module | Owns |14|--------|------|15| `cmake/Optimization.cmake` | `-O3`/`/O2`, `-march`, SIMD, LTO, and the full PGO (GENERATE/USE) flow, per toolchain. |16| `cmake/Hardening.cmake` | Stack protectors, FORTIFY, CFI/CET, RELRO/DEP, ASLR, `serial_studio_harden()` per-target deltas. |17| `cmake/Sanitizers.cmake` | ASan+UBSan (`DEBUG_SANITIZER`) and the separate TSan build (`ENABLE_TSAN`). |18| `cmake/MiMalloc.cmake` | The process-wide mimalloc allocator override (Windows/MSVC + Linux shared; macOS static interpose via `-Wl,-force_load`). |1920Wiring: the four user-facing options (`DEBUG_SANITIZER`, `PRODUCTION_OPTIMIZATION`,21`ENABLE_HARDENING`, `ENABLE_PGO`) are declared in `CMakeLists.txt` (around lines 79-87);22`PGO_STAGE` and the internal `DISABLE_LTO` live in `Optimization.cmake`, `ENABLE_TSAN` in23`Sanitizers.cmake`. The modules are `include()`d in `CMakeLists.txt` (MiMalloc ->24Optimization -> Sanitizers, then Hardening, then Signing). The PGO two-stage flow is driven25by `.github/workflows/ci.yml` (the only workflow file).2627## This skill does NOT build2829Per CLAUDE.md, **the developer runs cmake, the compiler, and the benchmark themselves.** Never30invoke `cmake`/`jom`/`clang`/`cl`/the compiler to "test" a flag. Reason from the modules and31the references; propose the edit; hand off. Flag changes that touch the hotpath are gated by32`--benchmark-hotpath` (256 kHz) -- flag it for the developer to re-run; see [[ss-hotpath]].3334## Build-option matrix (all default OFF except where noted)3536| Option | Effect | Set by |37|--------|--------|--------|38| `PRODUCTION_OPTIMIZATION` | The aggressive per-toolchain release branch (`-O3`/`/O2`, `-march=x86-64-v2`, LTO). | Release CI; manual. |39| `ENABLE_PGO` + `PGO_STAGE` | Profile-guided opt; `GENERATE` then `USE`. Needs `PRODUCTION_OPTIMIZATION`. | CI two-pass; manual. |40| `ENABLE_HARDENING` | Defense-in-depth (canaries, FORTIFY, CFI, RELRO, CFG). | CI; **auto-on** for Release/RelWithDebInfo and sandboxed builds. |41| `DEBUG_SANITIZER` | ASan + UBSan (Debug only, GCC/Clang). | Manual. |42| `ENABLE_TSAN` | ThreadSanitizer (proves the lock-free hotpath); mutually exclusive with ASan and MSVC. | Manual / CI thread-safety job. |4344`DISABLE_LTO` is internal: forced ON for Flatpak/sandboxed builds (which also auto-enable45hardening). LTO is otherwise ON whenever `PRODUCTION_OPTIMIZATION` is.4647## Non-negotiable invariants (do not regress these)4849These are deliberate choices baked into the modules. A flag change that breaks one is a bug,50not an optimization:5152- **IEEE-stable math, always.** Every non-MSVC branch pins `-fno-fast-math53 -fno-unsafe-math-optimizations`; MSVC/clang-cl use `/fp:precise`. **Never add `-ffast-math`,54 `/fp:fast`, `-funsafe-math-optimizations`, `-Ofast`, or `-ffp-contract=fast`** -- telemetry55 output must be bit-stable across builds and platforms. This is the one rule most likely to be56 "helpfully" violated.57- **Unwind tables on every TU (non-MSVC).** `-fexceptions -funwind-tables58 -fasynchronous-unwind-tables`; on macOS the lua54 target additionally opts out of LTO with59 `-fno-lto -femit-dwarf-unwind=no-compact-unwind` (Xcode 26's ld drops fallback DWARF unwind60 under `-flto`, llvm#135888, and made `-Wl,-keep_dwarf_unwind` a no-op). Lua is compiled as61 C++ and throws across the VM stack; without unwind metadata, `--gc-sections`/`-dead_strip`62 + LTO can drop a personality routine and turn a routine Lua error into `std::terminate`. See63 [[ss-cpp-modern]] and the Lua exception-safety setup. Do not remove these to "shrink" the64 binary.65- **x86-64-v2 is the conservative baseline (SSE4.2, 2012+ CPUs).** Every non-MSVC x86-6466 branch uses `-march=x86-64-v2`; clang-cl passes `/clang:-march=x86-64-v2`; cl.exe gets no67 `-march` (the x64 ABI's SSE2 baseline stands). Do not bump to `v3`/`v4` or `-march=native`68 for shipped binaries -- it would SIGILL on older CPUs. ARM baselines: aarch64 -> `armv8-a`69 (+`-latomic`); armv7l -> `armv7-a -mfpu=neon -mfloat-abi=hard` (hardfloat pinned on purpose).70- **`-msse4.1` is added for DSP/FFT on non-MSVC x86-64**, separate from the `-march` baseline.71 MSVC/clang-cl are skipped (the x64 ABI guarantees SSE2; no explicit SSE4.1 there).72- **Hardening auto-enables for optimized configs.** A manual `Release` that forgets73 `-DENABLE_HARDENING=ON` still gets hardened (belt-and-suspenders in `Hardening.cmake`). Don't74 "simplify" that away.75- **Per-toolchain branches are exclusive and order-sensitive.** clang-cl reports `MSVC=ON`, so76 its branch must precede cl.exe's; MinGW-Clang precedes MinGW-GCC. Adding a branch in the77 wrong order silently routes a compiler to the wrong flags.7879## The clang-cl / lld-link gotcha (Windows, MSVC ABI)8081This trips up almost everyone, so it is stated once here and detailed in the reference:82CMake links this target with **`lld-link` directly, not through the `clang-cl` driver.** So:8384- LTO/PGO **compile** flags use the `/clang:` passthrough (`/clang:-flto=thin`,85 `/clang:-fprofile-generate=`, `/clang:-fprofile-use=`) and go on the **compile step only**.86- The **link** step gets *native* lld-link flags only (`/lldltocache:`, `/OPT:REF`, `/OPT:ICF`)87 -- never driver-style `-flto=thin` / `-fprofile-*`, which lld-link rejects as bogus input.88- ThinLTO needs no link flag: bitcode `.obj`s are auto-detected; PGO instrumentation embeds an89 `/INCLUDE:` that pulls in `clang_rt.profile` automatically.90- clang-cl rejects `/Qspectre /sdl /ZH:SHA_256` (those are cl.exe-only); it takes `/GS91 /guard:cf` and the link-side `/GUARD:CF /DYNAMICBASE /CETCOMPAT`.9293## When to use which reference9495- **Changing this repo's build, or "why is this flag here / who sets it"** ->96 `references/serial-studio-build-flags.md`. The authoritative per-module, per-toolchain map of97 what Serial Studio actually passes, the PGO CI flow, mimalloc, and the rationale for each98 invariant.99- **"What does flag X do" / "what's the Clang equivalent of `/GL`" / picking a new flag** ->100 `references/flag-catalog.md`. A cross-compiler catalog (GCC / Clang / MSVC families + the PGO101 concept) distilled from the canonical upstream docs, with per-source accuracy caveats.102103Always cross-check the catalog against the repo module before recommending a change: a flag104being valid is not a reason to add it here (see the IEEE-math invariant). Before proposing105any flag edit, name in chat which invariant above it sits nearest to and why it is safe106against it — an invariant named at the point of the proposal is one the proposal respects107(`doc/claude/j-space.md`); "it doesn't touch any" is an acceptable answer only after108checking each.109110## Output expectations111112Follow CLAUDE.md's handoff rules: one-line statement of intent before a non-trivial change,113a one/two-sentence summary when you stop, no new doc files unless asked. Propose targeted114`Edit`s to the cmake modules -- do not rewrite a whole module. If a change could shift hotpath115throughput, say so and point the developer at `--benchmark-hotpath` ([[ss-hotpath]]); you do116not run it. For style/structure on any C++ you touch alongside, defer to [[ss-verify]].117118## References119120- `references/serial-studio-build-flags.md` -- this repo's real flag layout and invariants.121- `references/flag-catalog.md` -- cross-compiler flag catalog from upstream GCC/Clang/MSVC docs122 + the PGO concept, with citations and caveats.