# nlvm, an LLVM backend for the Nim language

> arnetheduck/nlvm compiles Nim straight to LLVM IR and links with lld, skipping the intermediate C step that the official Nim compiler goes through. It aims to be a drop-in replacement for the nim command, but it drops the C interop that Nim programmers rely on, and building it means compiling LLVM yourself.

**arnetheduck/nlvm** — LLVM-based compiler for the Nim language

- Repository: https://github.com/arnetheduck/nlvm
- Stars: 780 · Forks: 45
- Language: Nim
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/arnetheduck-nlvm

## Why an LLVM backend for Nim exists at all

The standard Nim pipeline, as the README diagrams it, looks like this: nim, then C files, then IR, then object files, then linker, then executable. The C step is the interesting one. Nim compiles to C and hands that C to a system compiler, which means the C compiler must parse and optimise the generated source on every build, and Nim constructs that do not map cleanly onto C have to be expressed in terms C can carry.

nlvm removes that step. It is an LLVM-based compiler for the Nim language, and the README is precise about the framing: from Nim's point of view it is a backend just like C or JavaScript, and from LLVM's point of view it is a language frontend that emits IR.

The author's stated consequences are worth quoting in substance. Going straight to the IR lets Nim constructs be expressed more clearly so LLVM can understand the code better and optimise it better, and it keeps compile times down because the c-to-IR step is avoided. In the pipeline terms of the README, nlvm produces a single IR file that goes into a built-in LTO linker, and the practical effect of that single object file is similar to running a C compiler with whole-program and LTO enabled.

So the pitch is not a rewrite of Nim. It is a different second stage for an existing language, aimed at people who want LLVM's optimiser and Nim's syntax without the C detour.

## Drop-in replacement, minus the C interop

The README describes nlvm as a drop-in replacement for the nim command with several notable differences. The command line options are similar, with the instruction to just change nim to nlvm. The same standard library is used, and the tool claims to work as a REPL as well.

The differences are mostly around performance and debugging. The list names fast compile times with no intermediate C compiler step, DWARF zero-cost exception handling, high-quality gdb and lldb debug information with source stepping and type information, and a set of optimisation features: LTO and whole-program optimisation out of the box, compiler-intrinsic guided optimisation for overflow checking, memory operations and exception handling, heap allocation elision, and native constant initialisation.

What nlvm explicitly does not do is just as important. It does not understand C, and as a consequence the header, emit and similar pragmas are ignored, and the fancy importcpp and C++ features do not work. It does not support all Nim compiler flags and features, and the README asks for bug reports on anything useful that is missing. The consequence for a port is spelled out in the README: C header files are not used, so the declaration in the .nim file has to be accurate.

That is the sharp edge of the whole project. A Nim codebase that leans on C declarations, on emit blocks, or on importcpp has real porting work in front of it, and the README ships a porting guide section covering the header, emit and asm pragmas precisely because of that.

## Building the compiler from a clone

The README is unusually candid about the cost of a source build. You need a C/C++ compiler for building LLVM and the Nim compiler bootstrap, meaning gcc or clang on Linux and clang on Windows, and it lists a cup of tea and a good book alongside. Compiling LLVM takes about an hour the first time, after which it is cached.

The clone uses submodules:

```bash
cd $SRC
git clone https://github.com/arnetheduck/nlvm.git --recurse-submodules
cd nlvm
```

Then the development libraries. On Fedora:

```bash
sudo dnf install pcre-devel openssl-devel sqlite-devel ninja-build cmake clang libzstd-devel
```

On Debian and Ubuntu the equivalent list uses apt-get with libpcre3-dev, libssl-dev, libsqlite3-dev, ninja-build, cmake, clang and libzstd-dev.

The build itself is a single target:

```bash
make
```

That also builds nim and LLVM if they are missing. A static LLVM link produces a stand-alone binary and uses a more optimised LLVM, but takes longer:

```bash
make STATIC_LLVM=1
```

Two details catch people out. First, after pulling updates you must remember the submodule, and the README gives the exact incantation:

```bash
git pull && git submodule update
```

Second, the Makefile pins the compiler flags to clang with debuginfo and linedir on, and on Windows the shared LLVM build is disabled with a comment saying it seems to have trouble with C++. The Makefile also reads the LLVM version from llvm/llvm.version and derives library paths from it, so a partial checkout will fail at link time rather than compile time.

## Compiling your own Nim code and inspecting the IR

Because nlvm carries its own linker, compiling pure Nim needs no C compiler at all. The README demonstrates this on the bundled examples:

```bash
cd $SRC/nlvm/lib/nim/examples
../../nlvm/nlvm c fizzbuzz
```

To see the generated LLVM IR, add the -c option:

```bash
cd $SRC/nlvm/lib/nim/examples
../../nlvm/nlvm c -c fizzbuzz
less fizzbuzz.ll
```

The IR then goes through the LLVM tools directly, either the optimiser and the disassembler:

```bash
opt -Os fizzbuzz.ll | llvm-dis
```

or the code generator to assembly:

```bash
llc fizzbuzz.ll
```

Beyond the .nim files, the compiler mixes in its own runtime library in lib/nlvm.

The integrated C compiler is reached through a cc subcommand that passes arguments through to clang. The README marks this as preview and warns that it may change:

```sh
# Compile and link a C file
nlvm cc test.c -o test

# Compile to object file only
nlvm cc -c test.c -o test.o

# Cross-compile to windows With optimization and warnings
nlvm --os:windows cc -O3 -Wall test.c -o test.exe

# Compile C++
nlvm cc test.cpp -o test
```

That subcommand is a pragmatic answer to the C interop gap: you cannot embed C in a .nim file, but you can compile C files alongside and link them in.

## Test coverage, platform gaps and how to judge maturity

The README describes its own test coverage with unusual specificity, which is a good sign for a compiler project. Coverage includes bootstrapping and compiling itself, about 95 percent of all upstream tests, compiling most applications, the majority of the Nim standard library, and platforms with tests on Linux/x86_64 and Windows/x86_64.

The README also explains where the remaining failures come from: most can be traced to the standard library and the compiler relying on C implementation details. The project tracks them in a skipped-tests.txt file, with a windows variant, and one of the ways to contribute is to make that file smaller.

The platform gaps are explicit. The README names improving platform support as a contribution area, saying osx should be easy and arm would be nice. Other listed contributions include generating better IR around optimisations, builtins and exception handling, and helping upstream make the standard library smaller and more nlvm-compatible.

Two operational facts sit under all of this. The release list shows a single rolling release named continuous, dated on the same day as the last push on 2026-09-26, rather than numbered versions, so there is no upgrade ladder to plan around. And the licence field on the repository says it uses a custom licence that GitHub cannot classify, with details in the LICENSE file, which is worth reading before the binaries enter a company build.

## How nlvm compares with the standard Nim compiler

The alternative is the official Nim compiler, and the difference in approach is precise. The standard compiler lowers Nim to C and hands it to a system C compiler, which means it inherits whatever C compiler you have, its flags and its quirks, and it has to express Nim semantics through the C language. nlvm emits LLVM IR directly, which removes that translation and gives the LLVM optimiser the original program structure.

For most Nim users this is invisible. The Nim standard library is written with C implementation details in mind, and the README's own explanation of its remaining test failures points at exactly that. A project that leans on the standard library will inherit those assumptions whether it compiles through C or through IR.

The choice becomes interesting when you care about the toolchain around the language. With nlvm, the C compiler is part of the product: an integrated clang, an integrated lld linker, DWARF debugging output for gdb and lldb, and a bundled cross compiler including wasm32 support, plus a REPL backed by the LLVM ORCv2 JIT. With the standard compiler, those are separate tools you install and configure yourself.

The other alternative is not a compiler at all: a different language with a mature LLVM backend already. That comparison is unfavourable to nlvm on ecosystem size and honest to state. nlvm's case rests on Nim's syntax and semantics being the thing you want, and on the toolchain around them being tighter than the standard route provides.

## Conclusion

Adopt nlvm if your team already writes Nim and wants direct IR generation, whole-program optimisation and a bundled C compiler, and you are prepared to build LLVM from source. Do not adopt it if your code depends on Nim's C interop, because header, emit and importcpp are ignored and C headers are not used, so the declarations in your .nim files must be accurate on their own. Verify first by building with make, running make test to see how much of the upstream suite passes on your platform, and reading skipped-tests.txt to see which failures are accepted.

## FAQ

### What is nlvm and how does it relate to Nim?

nlvm is an LLVM-based compiler for the Nim language, created by Jacek Sieka. From Nim's point of view it is a backend like C or JavaScript, and from LLVM's point of view it is a frontend that emits IR, using the same Nim standard library.

### How do I build nlvm from source?

Clone the repository with git clone --recurse-submodules, install the development libraries listed in the README for your distribution, then run make. Compiling LLVM takes about an hour the first time, and after updating you must run git pull && git submodule update.

### Does nlvm support C interop from Nim source files?

No. The README states that nlvm does not understand C, so the header, emit and similar pragmas are ignored and the importcpp and C++ features do not work. C header files are not used, so declarations in the .nim file must be accurate. You can still compile C files through the integrated clang with the cc subcommand.

### How do I see the LLVM IR that nlvm generates?

Compile with the -c option, for example ../../nlvm/nlvm c -c fizzbuzz, then open the generated .ll file. The README shows feeding it to opt -Os fizzbuzz.ll | llvm-dis or to llc fizzbuzz.ll for assembly.

### How much of the Nim test suite does nlvm pass?

The README reports about 95 percent of all upstream tests passing, with most failures traced to the standard library and compiler relying on C implementation details. The remaining known issues are listed in skipped-tests.txt, and the README asks contributors to shrink that file.

## Sources

- [arnetheduck/nlvm on GitHub](https://github.com/arnetheduck/nlvm)
- [Issues](https://github.com/arnetheduck/nlvm/issues)
- [README](https://github.com/arnetheduck/nlvm/blob/master/README.md)
- [Releases](https://github.com/arnetheduck/nlvm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/arnetheduck-nlvm
