Custom asserts in LLVM
I tried (and failed) to convince the LLVM folks to allow for runtime togglable asserts. No biggie - the people much more involved with maintaining upstream didn’t want to have yet another codepath to maintain. They said that it’d cost 20% runtime performance to have asserts enabled, and that this cost would likely still be paid even if I did have asserts compiled off at runtime. Why you might ask - the answer to which is that LLVM guards a bunch of assert-checking code behind NDEBUG
preprocessor checks, and will store extra sideband information if you are running with asserts enabled so as to do some deeper checks.
My motivation in allowing asserts to be runtime togglable is that we tried with the custom clang compiler we are working on for Verse here at Epic Games to just always enable asserts in clang, and kept running into upstream clang bugs with asserts enabled. We decided to compile asserts off (like mostly everyone does), but it always annoys me that clang could benefit from these found bugs being reported and fixed and that we are now flying blind (like mostly everyone does!) in the face of the upstream bugs.
I deferred to their wisdom - but wondered if there was an easy way to check that their assertions (ba-dum-tish) were correct?
So I hacked the LLVM CMake to add:
add_definitions(-include AssertInjectoroo.h)
To let me include a header upfront in every source file. Then in AssertInjectoroo.h
I did:
#undef NDEBUG
To make sure the extra LLVM checks were always compiled in.
#include <assert.h>
Included assert.h
so that if any LLVM file included it, it wouldn’t be included again.
#undef assert
I undefined the original C assert
because I was going to provide my own.
#if defined(__cplusplus)
extern "C"
#endif
int ExposeAssertsEnabledToC(void) __attribute__((weak));
#define assert(x) if (__builtin_expect(ExposeAssertsEnabledToC(), false)) { if (!(x)) { llvm_unreachable(#x); } }
#if defined(__cplusplus) && __cplusplus >= 201703L
#include "llvm/Support/CommandLine.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
__attribute__ ((weak)) llvm::cl::opt<bool> AssertsEnabled("enable-asserts", llvm::cl::desc("enable asserts"), llvm::cl::init(false));
#pragma clang diagnostic pop
extern "C" int ExposeAssertsEnabledToC(void)
{
return AssertsEnabled;
}
#endif
Then I used this hack to provide my own assert
definition. Because I wanted to control whether the assert triggered with an LLVM option (so that the built clang would automagically be able to read my command line option and set this flag), and the LLVM option stuff is C++ metasoup code, I had to provide a hack to get at the definition from C files with the weak symbol.
The new assert
itself just checks whether the flag has been set, before doing the underlying asserting check and calling llvm_unreachable
if the assert fails.
So first off - executable size differences. My injected assert made the binary 1.85x bigger on a release build with clang. Ouch! If I compare my build to just compiling with LLVM_ENABLE_ASSERTIONS
on in the stock LLVM build my injected assert only made the binary 1.005x bigger though - so its just that asserts in LLVM are costly for binary size.
Next up - runtime cost. I used a bootstrap build of clang to test the runtime performance (thanks to Chandler Carruth for this suggestion), building my compiler and stock LLVM and then using these built compilers to rebuild clang. My injected assert that was toggled off at runtime made compilation time 14.00% slower than stock LLVM. If I had the injected asserts toggled on compilation time was 20.61% slower than stock LLVM.
So what did we learn? Doing the calls to assert
themselves results in a 6% performance hit. Processing all the other data structures that are hidden behind NDEBUG
checks is the main cost with a 14% performance hit attributed to that. This matches with what Renato Golin and Reid Kleckner said on the thread - that LLVM is just not setup to have runtime togglable asserts the way it is, and it’d be a monumental effort to make that work.