Simplifying clang flags with profiles

Clang Profiles: Composable compiler flag sets for Ubuntu

Getting compiler flags right is a project-by-project effort. Typical build configurations like security hardening, optimization, debug info, and static analysis all have reasonable sets of flags available in Clang, but there is no standard place that set can be defined. It often ends up scattered across build files, CI configs, or worse. The clang-profiles package is an effort to explore how this can be done more systematically, by setting up useful wrappers to invoke clang in useful ways with minimal effort.

The goal of sharing this is to gather feedback, whether positive or negative. In particular, the sets of flags chosen for various profiles could use input from folks engaged in that type of work.

The Mechanism: Clang Config Files

Clang has supported configuration files via --config since version 10. You can put a set of flags in a .cfg file and load it at compile time:

clang --config=/usr/lib/clang-profiles/release.cfg main.c -o main

Profiles compose. Later flags override earlier ones, and anything on the command line wins over the config:

clang --config=release.cfg --config=lto.cfg main.c -o main
clang --config=debug.cfg --config=asan.cfg test.c -o test

This is a useful primitive that doesn’t seem to get much use in practice. The missing piece is an agreed-upon set of profiles that capture what these flag sets should actually contain.

What’s in the Profiles

The package ships profiles for common scenarios. For example:

ubuntu-default is a baseline meant to replace bare clang invocations: -O2, standard security hardening flags, frame pointers preserved, and warnings enabled.

release adds aggressive optimization (-O3, NDEBUG) on top of the hardening flags.

debug goes the other direction: -O0 -g3, standalone debug info, macro definitions included, tail-call optimization disabled.

hardened enables CFI (-fsanitize=cfi), -D_FORTIFY_SOURCE=3, hidden visibility, and full RELRO. It requires LTO and has performance costs.

size is for size-optimized binaries.

The sanitizer profiles each bundle the flags needed for that tool to be useful, including -g and -fno-omit-frame-pointer so stack traces in error reports are readable:

  • asan - AddressSanitizer for heap/stack/global buffer overflows and use-after-free
  • ubsan - UndefinedBehaviorSanitizer for null dereference, integer overflow, and bounds violations
  • tsan - ThreadSanitizer for data races in multithreaded code
  • msan - MemorySanitizer for reads of uninitialized memory

Two profiles cover a PGO workflow. Build with pgo-generate, run against a representative workload, merge the collected data with llvm-profdata, then rebuild with pgo-use. The wrapper script reads a CLANG_PGO_PROFDATA environment variable to locate the profile data (this is a convention specific to these scripts, not a Clang built-in):

  • pgo-generate - instruments the binary at -O2 to collect execution counts
  • pgo-use - uses the merged profile data to build at -O3 with ThinLTO

The Wrapper Scripts

The Debian package installs wrapper scripts (clang-release, clang-debug, clang++-release, etc.) that embed the profile path so build systems can use them directly:

# autotools
CC=clang-release ./configure

# cmake
cmake -DCMAKE_C_COMPILER=clang-release -DCMAKE_CXX_COMPILER=clang++-release .

The idea is that the definition of “release” lives in one place, and updating the package updates every build that uses the wrapper. Whether that’s the right tradeoff is part of what I’d like feedback on.

Code and Feedback

Profiles: code.launchpad.net/~karljs/+git/clang-profiles
Packaging: code.launchpad.net/~karljs/+git/clang-profiles-deb

The package is also available in a PPA for testing directly: launchpad.net/~karljs/+archive/ubuntu/clang-profiles

The flag choices are the most important thing to get right and also the most subjective. If you have opinions about what should or shouldn’t be in these profiles, or about the approach in general, your input is welcome. Comments are open, or you can reach out to me directly.

2 Likes

I’m a little unsure who the target user of this package is – is it a user of Ubuntu doing their own development or a packager? If packaging, how does this interact with the flags that we get via dpkg-buildpackage? I guess dpkg-buildpackage does not “know” in general that a package is going to compile with gcc or clang – but this does sound rather like the things you can set via DEB_BUILD_OPTIONS.

Hi @mwhudson,

My target is Ubuntu users doing C/C++ development with clang. At a high level, the idea is just to explore whether Ubuntu could collect the set of profiles that make it easier to configure their builds correctly for different use cases without needing to construct those profiles in to their build system of choice. I agree with you that this isn’t a good fit for building Ubuntu archive packages.

If a developer wants to run some LLVM-based analysis tools, perhaps on a project which uses an unfamiliar build system, my hope is that this would help them by offering a drop-in way of achieving that.

1 Like