Eliminating CRT Symbols in LLVM: Building a Custom Runtime on Windows
When creating a domainâspecific language that needs its own runtime, the Windows CRT can unexpectedly inject symbols such asâŻ_fltusedâŻinto a library build. This article explains why the CRT appears, how to disable the default library linkage, and practical compiler/linker flags that allow an LLVMâbased toolchain to generate a clean .lib or .dll containing only the userâdefined symbols.
### The Problem
A language developer using LLVMâs `llc`/`clang` toolchain found that every attempt to compile a stub library into a `.lib` file pulled in the Windows CRT, specifically the `_fltused` symbol. Even after defining `_fltused` in the source IR, the linker raised multipleâdefinition errors because the CRT library supplied its own version. The goal was to create a lightweight runtime without the standard C runtime, but all usual attemptsâpassing `-nodefaultlibs`, `-nostdlib`, and various MinGW/Clang flagsâseemed to be ignored.
Understanding why the CRT gets linked requires a look at Windowsâ ABI and the default behaviour of the LLVM linker (`lld`).
### Why the CRT Appears
1. **Floatingâpoint helper** â `_fltused` is a symbol that the MSVC runâtime uses to indicate that floatingâpoint support is required. Any function that references floatingâpoint builtâins or any `libm` function implicitly pulls in this symbol.
2. **Implicit default libs** â The Windows compiler drivers (clang.exe, clang++.exe, and the GNU `gcc`/`g++` tools that ship with MinGW) automatically request the default libraries: `libcmt.lib`, `msvcrt.lib`, `kernel32.lib`, etc. These libraries contain `_fltused`, a variety of system APIs, and the standard C initialisation code.
3. **LLVMâs `lld` default behaviour** â `lld` is designed to mirror the behaviour of the system linker. Unless explicitly told otherwise, it will always link the default Windows libraries.
### Disabling CRT Linkage with LLVM
The solution is to invoke the compiler and linker with the right options so that none of the default CRT symbols are pulled in. For a purely LLVMâbased build the following steps work reliably under Windows:
1. **Compile the LLVM IR to an object file** â `llc -march=host -filetype=obj foo.ll -o foo.o`
2. **Assemble the object file into a static library** â `llvm-ar rcs libfoo.a foo.o`
* `llvm-ar` is the counterpart of `ar` for ARM crossâcompilation.
3. **Linker flags** â When you build an executable that depends on `libfoo.a`, disable the system libraries:
```bash
clang++ -nostdlib -nodefaultlibs \
-Wl,--entry=_main \
-Wl,--subsystem,windows \
-o main.exe foo.o
```
* `-nostdlib` tells clang not to search for the default libraries.
* `-nodefaultlibs` is honoured by `lld` and prevents linking `kernel32.lib`, `msvcrt.lib`, etc.
* `-Wl,--entry=_main` provides a custom entry point; replace `_main` with whatever you need.
If a dynamic library (`.dll`) is desired, use:
```bash
clang++ -shared -nostdlib -nodefaultlibs \
-Wl,--out-implib,libfoo.lib \
-o foo.dll foo.o
```
### Handling the Special Case of `_fltused`
Sometimes a library still ends up pulling in `_fltused` even though you explicitly disabled the CRT. A robust workaround is to provide a *strong* definition of the symbol that the linker will prefer:
```c
extern "C" __declspec(dllexport) unsigned _fltused = 0x0;
```
Place that in a separate `.c` or `.cpp` file, compile it with `-c`, and add the resulting module to the static library **after** adding your own object files:
```bash
llvm-ar rcs libfoo.a foo.o fltused.o
```
Because the symbol is now defined with strong linkage, the linker will ignore the weak implementation in any default library that might still be present.
### What If You Need Standard Library Functions?
If the runtime you are building requires a subset of the CRT (e.g., `printf`, `malloc`), you can manually link only those parts:
```bash
clang++ -nostdlib -nodefaultlibs \
-Wl,--whole-archive $(clang -print-file-name=libcmt.a) \
-Wl,--no-whole-archive \
-o main.exe foo.o
```
Here `--whole-archive` forces the entire library to be linked; you can then strip unused symbols with tools like `llvm-objcopy --keep-symbol`.
### Summary
* The Windows CRT is automatically linked by default, bringing in symbols like `_fltused`.
* Use `-nostdlib` / `-nodefaultlibs` and `lld`âs linker switches (`--entry`, `--subsystem`) to avoid the CRT.
* When necessary, provide a strong definition of `_fltused` to override any accidental inclusion.
* For minimal runtime support, manually link only the needed CRT components.
By combining these flags with a clean build pipeline, developers can build custom runtimes in LLVM without the overhead or conflicts of the standard Windows C runtime.