Skip to content

Flags

LLVM Flags (clang + lld) (Current) (Logical order then alphabetical based on appearance in –help and manual)

Section titled “LLVM Flags (clang + lld) (Current) (Logical order then alphabetical based on appearance in –help and manual)”
Terminal window
CFLAGS="-O2 -flto=thin -march=x86-64-v3 -mtls-dialect=gnu2 -fdata-sections -ffunction-sections -fno-plt -fstack-clash-protection -fstack-protector-strong -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-ident"
CPPFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -isystem /usr/include/fortify"
CXXFLAGS="$CFLAGS"
LDFLAGS="-Wl,-O2,--as-needed,--gc-sections,--icf=safe,-z,defs,-z,now,-z,separate-code,--build-id=none,--hash-style=gnu,--pack-dyn-relocs=relr,--strip-all $CFLAGS"
Terminal window
CFLAGS="-pipe -Os -fgcse-las -flto=auto -fuse-linker-plugin -ffunction-sections -fdata-sections -fstack-protector-strong -fstack-clash-protection -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-ident -fno-plt -march=x86-64-v3 -mtls-dialect=gnu2"
CXXFLAGS="$CFLAGS"
LDFLAGS="-Wl,-O1,-s,-z,defs,-z,noexecstack,-z,now,-z,pack-relative-relocs,-z,relro,-z,separate-code,-z,text,--as-needed,--gc-sections,--no-keep-memory,--relax,--sort-common,--enable-new-dtags,--hash-style=gnu,--build-id=none $CFLAGS"

CFLAGS (Ordered based on appearance in the GCC manual)

Section titled “CFLAGS (Ordered based on appearance in the GCC manual)”
  • Compiling with -g0 or without -g at all should in theory result in no debugging information in the binaries
  • Some build systems misinterpret -g0 as -g leaving debugging information in the binaries; this is a bug to be reported to the relative upstream
  • Speeds up compilation time when -g is used
  • Does not make sense when -g is not used
  • -O2 with some cherry-picked flags is the way to go
  • -O3 may beat -O2 in microbenchmarks, but in the context of a distribution it loses in terms of code size and overall performance
  • -O3 can cause executables to run slower as it would generate more machine code than -O2 which would make programs bigger and thus unable to fit in the L1 cache causing cache misses for instructions
  • openSUSE says that cmake enabling -O3 “by default” is not a good idea
  • gcc’s -Os is surprisingly close to -O2
  • Enabled for -O2 and disabled for -Os and -Oz
  • clang does not support -foptimize-strlen; clang implicitly performs this optimization

-fmodulo-sched, -fmodulo-sched-allow-regmoves and -fgcse-sm (store motion)

Section titled “-fmodulo-sched, -fmodulo-sched-allow-regmoves and -fgcse-sm (store motion)”
  • Aggressive common subexpression elimination (cse) and scheduling (particularly modulo scheduling) can dramatically increase register pressure
  • This leads to more loads and stores, causing spills and increasing code size
  • This makes performance worse than without them
  • It makes sense to have these off by default on register-starved machines like x86
  • Ignored by clang (see clang/test/Driver/clang_f_opts.c)
  • -fgcse is enabled by default at -O2 and -Os
  • -fgcse-lm (load motion) is enabled by default when -fgcse is enabled
  • -fgcse-las removes redundant load instructions which can reduce register pressure by reusing loaded values
  • Might reduce code size
  • Ignored by clang (see clang/test/Driver/clang_f_opts.c)
  • Enables simple constant folding optimizations
  • Enabled by default on most targets; no need to mess with it
  • x86-64 does not have delay slots rendering this “legacy” optimization irrelevant
  • Should in theory decrease register pressure before allocation
  • Can decrease code size by preventing register pressure and subsequent spills in register allocation
  • No idea if it works with -fschedule-insns (which is not enabled by default at -O2 and -Os)
  • No idea if it works with -fschedule-insns2 (which is enabled by default at -O2 and -Os)
  • Permits the speculative motion of some load instructions before register allocation to minimize execution stalls due to data dependencies
  • Works with -fschedule-insns
  • -fschedule-insns2 is enabled by default at -O2 and -Os
  • Similar to -fsched-spec-load
  • Avoid options with dangerous in the name..
  • Experimental option that might produce unreliable results and increase code size
  • Enabled by default at -O2
  • Do not enable manually; let PGO decide
  • Modulo scheduling is a software pipelining technique; thus it might increase code size with no proved performance gain
  • Most of x86-64-v3 have hardware pipelining

-fselective-scheduling, -fselective-scheduling2, -fsel-sched-pipelining and -fsel-sched-pipelining-outer-loops

Section titled “-fselective-scheduling, -fselective-scheduling2, -fsel-sched-pipelining and -fsel-sched-pipelining-outer-loops”
  • IA64 is probably the only target left requiring selective scheduling
  • Selective scheduling itself is in a poor state nowadays
  • -fsel-sched-pipelining has no effect without -fselective-scheduling or -fselective-scheduling2
  • -fsel-sched-pipelining-outer-loops has no effect without -fsel-sched-pipelining
  • https://gcc.gnu.org/pipermail/gcc-patches/2025-August/692322.html
  • This option is only for shared libraries/dynamic linking and breaks static binaries and libraries
  • Makes code built with -fPIC and LTO faster, and improves performance in general; might cause subtle ABI breakages
  • Breaks LD_PRELOAD which in turn breaks custom memory allocators like mimalloc
  • Contrary to popular belief, enabling this flag globally is safe (unless interposing symbols is required, for example when using different allocators on system libraries), but the reason for it not being enabled by default is to comply with the ELF standard. In contrast, this flag is part of the default when using Clang (see clang/test/CodeGen/semantic-interposition.c)
  • https://maskray.me/blog/2021-05-09-fno-semantic-interposition
  • Abandoned and needs a major redesign
  • Does not scale, at least for now (according to openSUSE)
  • Increases memory usage and compilation time
  • Prone to having the compiler segfault with an internal compiler error which leads to all kinds of weird errors like duplicate case value (affected packages: bash, gcc, inetutils, libarchive, libedit, netbsd-curses, util-linux)
  • Prevents a lot of optimizations from gcc to produce output suitable for live-patching
  • Does not work with -flto
  • Graphite is not well maintained in gcc and will likely end up being removed entirely
  • Most of its developers moved to LLVM’s Polly
  • Can’t effectively optimize compared to baseline gcc
  • The optimizations it is supposed to perform are being implemented via other methods
  • Not necessarily buggy, but its benefits are rather doubtful nowadays
  • https://dl.acm.org/doi/full/10.1145/3674735
  • Required for isl to work
  • Previously called -floop-optimize-isl
  • The newer way to implement Graphite
  • Replaces -floop-interchange, -ftree-loop-linear, -floop-strip-mine and -floop-block
  • Still considered experimental
  • Increases register pressure and should not be used without -fsched-pressure
  • This takes a number and by default GCC only enables it for PowerPC, but disables it for other architectures
  • Not supported by clang
  • https://reviews.llvm.org/D4565
  • Has no use when -falign-functions is not used
  • Without the linker plugin LTO will not happen (particularly for static libraries as you will get the same code without -flto)
  • Will spawn n threads based on the number of threads; similar to make -j
  • Use instead of -flto alone to get rid of the 128 LTRANS serial jobs message
  • gcc’s version of ThinLTO is WHOPR, previously it was enabled by using -fwhopr, but now it has become the default mode for LTO and -fwhopr was removed from gcc’s options; -fno-fat-lto-objects is now the default
  • clang’s -flto, -flto=auto and -flto=jobserver are all equivalent to -flto=full
  • ThinLTO is basically “slightly worse” LTO but makes compilation faster
  • https://clang.llvm.org/docs/ThinLTO.html
  • Available when zstd is the backend for LTO and it should in theory result in smaller binaries
  • clang does not support -flto-compression-level
  • Enabled by default if gcc is built with lto enabled
  • There is no actual guarantee that -fuse-linker-plugin will be used in cases where gcc is built without lto support and binutils is built without plugins support
  • This means that not using this flag as in the case above might cause -fwhole-program to be picked which is not a good idea; use -fuse-linker-plugin so programs can rely on a linker plugin and forward the lto stuff to some other linker (e.g. mold) successfully
  • Ignored by clang as lld does not rely on a plugin for lto to work, also clang passes -plugin and -plugin-opt automatically for lto to work with bfd and gold (see clang/test/Driver/clang_f_opts.c and lld/ELF/Options.td)
  • Enabled after decisions by PGO and shouldn’t be manually used everywhere; may cause regressions and produce bigger code that may or may not be fast

-ffunction-sections, -fdata-sections and -Wl,--gc-sections

Section titled “-ffunction-sections, -fdata-sections and -Wl,--gc-sections”
  • -O2 uses the value 5, while -Os uses 1 which is more aggressive
  • -fexceptions requires either libunwind or libgcc_s
  • gcc enables -funwind-tables by default and its documentation says that you normally do not need to enable this; instead, a language processor that needs this handling enables it on your behalf
  • -Qy and -Qn are obsolete synonyms for -fident and -fno-ident respectively (clang --help still shows the older options only..)
  • It is better not to explicitly specify these options globally as we don’t know whether they will be passed to build an executable or a shared library (passing -fpie/-fPIE when building a shared library is not a good thing..)
  • -fPIC generates much worse code on x86 (32-bit in particular) for loads/stores of data; might be tolerable for libraries but is undesirable for executables
  • It is better to have gcc configured with --enable-default-pie so that it knows when to pass these options
  • These options do not contradict with -fno-plt
  • -fno-pic can only be used by executables
  • -fpic can be used by both executables and shared objects
  • -fpie can only be used by executables
  • https://maskray.me/blog/2021-01-09-copy-relocations-canonical-plt-entries-and-protected
  • clang enables -fstrict-aliasing when -Ofast is used or by default on linux (non-windows non-uefi platform) even with -O2 (check for tbaa with clang -O2 -emit-llvm -S ... which does not appear when -fno-strict-aliasing is explicitly used)
  • May cause silent miscompilations and undefined behavior and is thus unsafe to use as a global flag
  • lld disables -fstrict-vtable-pointers by default as it is still experimental (see clang/docs/UsersManual.rst)
  • Only in CXXFLAGS
  • x86-64-v3 provides better performance and battery life
  • -mtune=generic is implicitly enabled
  • Automatically detected on modern 64-bit hosts and Linux targets
  • Automatically detected on modern 64-bit hosts and Linux targets
  • Remove -flto=thin
  • Remove -flto=auto -fuse-linker-plugin (and -flto-compression-level=3 if being used)
  • Packages that might fail with LTO (based on outdated information from Fedora, Gentoo and gentooLTO):
0ad alsa-lib amsn apt argyllcms aspell atk avahi
bcc binutils boost
cairo cbmc cdrdao cdrtools ceph chromium chrony clang clisp community-mysql coreutils crash criu curl cvs
dialog dolphin-emu dotnet dovecot dvisvgm
e2fsprogs efivar elfutils espresso
ffmpeg findutils firebird firefox flatpak FlightGear foo2zjs fwts
gawk gcl gdb giac glib gnucobol gpm grpc grub gtk guitarix
hdparm hedgewars hpx hwinfo hyperscan
icecat iperf
java js8call
kdelibs kdepim-runtime keepassxc kernel kernel-tools
libav libbpf libcxxabi libfplll libguestfs libint2 libkgapi libomp libpostproc libreoffice libvirt libxc lld llvm luatex lyx
mame mandoc mariadb mesa mlpack mono mozjs78 mplayer mplayer2 muffin mupdf musl nest
NetworkManager nheko nmap nodejs notify-python nss numactl nx
okular oniguruma openblas opencv openjdk openssh ostree
papi paraview pciutils pdns-recursor perl php polkit polly polymake postgresql primecount prusa-slicer pulseaudio python python-pyfastnoisesimd python-pyside2
qemu qsstv qt5-qt3d qt5-qtbase qt5-qtlocation qt5-qtscript qt5-qtwebkit qtcore qt-declarative qt-script qt-webkit
rarian rpm-ostree rsync ruby
scummvm seabios seamonkey sleuthkit spidermonkey strigi sudo systemd sysvinit
texlive-base texmaker thunderbird tk tkrzw tremulous
v8 valgrind
wavpack webkit wine wireshark wsjtx wxGTK
x264 xen xf86-video-intel xfconf xkbcomp xorg-server xrootd
zam-plugins

LDFLAGS (Ordered based on appearance in the GNU linker manual)

Section titled “LDFLAGS (Ordered based on appearance in the GNU linker manual)”
  • Enables linker optimizations which can reduce code size
  • bfd optimizes if a non-zero value was given with no differences between the values
  • lld has a higher level -O2, and it uses -O1 by default (see lld/ELF/Driver.cpp (ctx.arg.optimize = args::getInteger(args, OPT_O, 1);))
  • lto supports additional options --lto-O and --lto-CGO (the former defaults to 2 which is good enough and the latter defaults to the value of the former) (see lld/ELF/Driver.cpp):
...
ctx.arg.ltoo = args::getInteger(args, OPT_lto_O, 2);
...
if (ctx.arg.ltoo > 3)
ErrAlways(ctx) << "invalid optimization level for LTO: " << ctx.arg.ltoo;
unsigned ltoCgo =
args::getInteger(args, OPT_lto_CGO, args::getCGOptLevel(ctx.arg.ltoo));
if (auto level = CodeGenOpt::getLevel(ltoCgo))
ctx.arg.ltoCgo = *level;
else
ErrAlways(ctx) << "invalid codegen optimization level for LTO: " << ltoCgo;
...

-s, -x, --discard-all and -X, --discard-locals

Section titled “-s, -x, --discard-all and -X, --discard-locals”
  • -s, --strip-all already removes everything (including .symtab and .strtab)
  • lld considers -s an alias for --strip-all (not the opposite) (see lld/ELF/Options.td)
  • lld does not allow using --strip-all and --emit-relocs together
  • for -x and -X to work, .symtab needs to exist, which means using them alongside -s is redundant
  • -X is the default behavior for gnu ld ld --help
  • https://maskray.me/blog/2020-11-15-explain-gnu-linker-options

-z,relro, -z,separate-code and --rosegment

Section titled “-z,relro, -z,separate-code and --rosegment”
  • Using -z,separate-code is good for security
  • Adding --rosegment when -z,separate-code is used makes resulting binaries smaller
  • Using -z,noseparate-code is a bad idea; remember how passing --disable-separate-code to binutils bloated every executable and shared library by at least 2 MB (for better huge page support)
  • With traditional -z,noseparate-code, bfd defaults to a RX/R/RW program header layout
  • With -z,separate-code (default on Linux/x86 from binutils 2.31 onwards), bfd defaults to a R/RX/R/RW program header layout
  • lld defaults to R/RX/RW(RELRO)/RW(non-RELRO)
  • lld defaults to -z,relro (see lld/docs/ELF/linker_script.rst and lld/ELF/Driver.cpp (zRelro is true by default))
  • With --rosegment, lld uses RX/RW(RELRO)/RW(non-RELRO)
  • Placing all R before RX is preferable as it can save one program header and reduce alignment costs
  • lld’s split of RW saves one maxpagesize alignment and can make the linked image smaller
  • This breaks some assumptions that the (so-called) “text segment” precedes the (so-called) “data segment”
  • If you use bfd’s noseparate-code or lld’s --no-rosegment, .rodata and .text will be placed in the same PT_LOAD segment
  • lld defaults to noseparate-code (see lld/ELF/Driver.cpp (auto ret = SeparateSegmentKind::None;)):
...
ctx.arg.zSeparate = getZSeparate(args);
...
static SeparateSegmentKind getZSeparate(opt::InputArgList &args) {
auto ret = SeparateSegmentKind::None;
for (auto *arg : args.filtered(OPT_z)) {
StringRef v = arg->getValue();
if (v == "noseparate-code")
ret = SeparateSegmentKind::None;
else if (v == "separate-code")
ret = SeparateSegmentKind::Code;
else if (v == "separate-loadable-segments")
ret = SeparateSegmentKind::Loadable;
else
continue;
arg->claim();
}
return ret;
}
...
  • --no-rosegment combines the read-only and the RX segments (output file will consume less address space at run-time)
  • AArch64 and PowerPC64 have a default MAXPAGESIZE of 65536 so -z,noseparate-code default ensures that they will not experience unnecessary size increase
  • In -z,noseparate-code layouts waste half a huge page on unrelated content and switching to -z,separate-code reclaims the benefits of the half huge page but increases the file size
  • ld.bfd’s -z,separate-code is essentially split into two options in lld: -z,separate-code and --rosegment.
  • GitHub actions for rad uses ubuntu-latest which does not recognize --rosegment:
Nim Output /usr/bin/ld: unrecognized option '--rosegment'
... /usr/bin/ld: use the --help option for usage information
... collect2: error: ld returned 1 exit status

-z,noexecstack, -z,nognustack and -z,nodlopen

Section titled “-z,noexecstack, -z,nognustack and -z,nodlopen”
  • -z,noexecstack provides “Stack Execution Protection”
  • lld defaults to -z,noexecstack (see lld/ELF/Driver.cpp (auto ret = GnuStackKind::NoExec;) and lld/ELF/InputFiles.cpp (as it always adds PT_GNU_STACK))
  • Should be the default behavior by gcc; does not mark the stack as executable by default, and warns when that happens
  • -z,nognustack is lld specific and is for alternative systems like netbsd (using it on linux may default to an executable stack)
  • -z,nodlopen is a bit excessive for a global flag
  • https://github.com/llvm/llvm-project/issues/57009

-z,pack-relative-relocs and --pack-dyn-relocs=relr

Section titled “-z,pack-relative-relocs and --pack-dyn-relocs=relr”
  • -z,text enforces Write XOR Execute (W^X)
  • lld defaults to -z,text (see lld/ELF/Driver.cpp (ctx.arg.zText = getZFlag(args, "text", "notext", true);))
  • lld uses -z,defs by default when linking executables as --no-allow-shlib-undefined is enabled; however, when linking shared libraries -shared defaults to --allow-shlib-undefined which turns off -z,defs (see lld/ELF/Driver.cpp (bool diagRegular = !ctx.arg.shared, diagShlib = !ctx.arg.shared;) and lld/test/ELF/zdefs.s) and it also implies --unresolved-symbols=ignore-all because missing symbols are likely to be resolved at runtime
  • hardened_malloc builds with -z,text and -z,defs by default
  • https://maskray.me/blog/dependency-related-linker-options
  • lld does not support -z,x86-64-v3
  • lld defaults to --no-as-needed (see lld/ELF/Options.td)

-Wl,--gc-sections and -Wl,-z,start-stop-gc

Section titled “-Wl,--gc-sections and -Wl,-z,start-stop-gc”
  • bfd does not support icf
  • lld defaults to --icf=none (see lld/ELF/Options.td)

--no-keep-memory and --reduce-memory-overheads

Section titled “--no-keep-memory and --reduce-memory-overheads”
  • Make memory consumption reasonable especially with the optimizations we are using (mainly LTO), at the expense of a slight increase in link time
  • Silently ignored by lld as it uses a different memory model compared to bfd (see lld/ELF/Options.td and lld/test/ELF/silent-ignore.test)
  • lld and mold do not support --reduce-memory-overheads
  • Sorts COMMON symbols by decreasing alignment, which saves some padding resulting in minor size benefits
  • Can degrade performance if COMMON symbols in an object file have locality and --sort-common breaks that locality
  • Silently ignored by lld and mold (see lld/ELF/Options.td and lld/test/ELF/silent-ignore.test)
  • https://maskray.me/blog/2022-02-06-all-about-common-symbols
  • lld defaults to --enable-new-dtags as it already emits DT_RUNPATH instead of the older DT_RPATH (see lld/ELF/Driver.cpp (ctx.arg.enableNewDtags = args.hasFlag(OPT_enable_new_dtags, OPT_disable_new_dtags, true);) and lld/ELF/Options.td)
  • .hash (sysv) is an on-disk hash table
  • .gnu.hash (gnu) is more modern and has a bloom filter and a hash table to quickly skip DSOs
  • --hash-style=both is for for backwards compatibility
  • lld defaults to both (see lld/ELF/Driver.cpp)
  • https://maskray.me/blog/2022-08-21-glibc-and-dt-gnu-hash
  • It does not make sense to compress “nonexistant” debug sections as we’re stripping everything with -s