{"id":13610960,"url":"https://github.com/arnetheduck/nlvm","last_synced_at":"2026-01-05T19:22:08.855Z","repository":{"id":3574439,"uuid":"49955741","full_name":"arnetheduck/nlvm","owner":"arnetheduck","description":"LLVM-based compiler for the Nim language","archived":false,"fork":false,"pushed_at":"2025-03-17T16:25:46.000Z","size":1419,"stargazers_count":739,"open_issues_count":13,"forks_count":43,"subscribers_count":32,"default_branch":"master","last_synced_at":"2025-04-07T03:15:29.641Z","etag":null,"topics":["compiler","language","llvm","nim"],"latest_commit_sha":null,"homepage":"","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arnetheduck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-01-19T13:51:23.000Z","updated_at":"2025-04-04T06:02:43.000Z","dependencies_parsed_at":"2024-01-06T01:04:39.723Z","dependency_job_id":"8e085939-8bff-499c-acbb-413d5600c469","html_url":"https://github.com/arnetheduck/nlvm","commit_stats":{"total_commits":255,"total_committers":6,"mean_commits":42.5,"dds":"0.019607843137254943","last_synced_commit":"89db076a7b2907d87463496017b5a5c2a7448686"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnetheduck%2Fnlvm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnetheduck%2Fnlvm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnetheduck%2Fnlvm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnetheduck%2Fnlvm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arnetheduck","download_url":"https://codeload.github.com/arnetheduck/nlvm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248889977,"owners_count":21178333,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","language","llvm","nim"],"created_at":"2024-08-01T19:01:49.988Z","updated_at":"2026-01-02T13:24:02.416Z","avatar_url":"https://github.com/arnetheduck.png","language":"Nim","funding_links":[],"categories":["Nim","Implementations","Language Features","Nim (13)"],"sub_categories":["Byte Size","Implementations"],"readme":"# Introduction\n\n[nlvm](https://github.com/arnetheduck/nlvm) (the nim-level virtual machine?)\nis an [LLVM-based](http://llvm.org) compiler for the [Nim](http://nim-lang.org)\nprogramming language.\n\nFrom Nim's point of view, it's a backend just like C or JavaScript - from\nLLVM's point of view, it's a language frontend that emits IR.\n\nQuestions, patches, improvement suggestions and reviews welcome. When\nyou find bugs, feel free to fix them as well :)\n\nFork and enjoy!\n\nJacek Sieka (arnetheduck on gmail point com)\n\n# Features\n\n`nlvm` works as a drop-in replacement for `nim` with the following notable differences:\n\n* Fast compile times - no intermediate `C` compiler step\n* DWARF (\"zero-cost\") exception handling\n* High-quality `gdb`/`lldb` debug information with source stepping, type\n  information etc\n* Smart code generation and optimisation\n  * LTO and whole-program optimisation out-of-the-box\n  * compiler-intrinsic guided optimisation for overflow checking, memory operations, exception handling\n  * heap allocation elision\n  * native constant initialization\n* Native `wasm32` support with no extra tooling\n* Native integrated fast linker (`lld`)\n* Just-in-time execution and REPL (`nlvm r`) using the LLVM [ORCv2 JIT](https://llvm.org/docs/ORCv2.html)\n\nMost things from `nim` work just fine (see the [porting guide](#porting-guide) below!):\n\n* the same standard library is used\n* similar command line options are supported (just change `nim` to `nlvm`!)\n* `C` header files are not used - the declaration in the `.nim` file needs to be accurate\n\nTest coverage is not too bad either:\n\n* bootstrapping and compiling itself\n* ~95% of all upstream tests - most failures can be traced to\n  the standard library and compiler relying on C implementation details - see\n  [skipped-tests.txt](skipped-tests.txt) for an updated list of issues\n* compiling most applications\n* platforms: linux/x86_64, wasm32 (pre-alpha!)\n* majority of the nim standard library (the rest can be fixed easily -\n  requires upstream changes however)\n\nHow you could contribute:\n\n* work on making [skipped-tests.txt](skipped-tests.txt) smaller\n* improve platform support (`osx` and `windows` should be easy, `arm` would be\n  nice)\n* help `nlvm` generate better IR - optimizations, builtins, exception handling..\n* help upstream make std library smaller and more `nlvm`-compatible\n* send me success stories :)\n* leave the computer for a bit and do something real for your fellow earthlings\n\n`nlvm` does _not_:\n\n* understand `C` - as a consequence, `header`, `emit` and similar pragmas\n  will not work - neither will the fancy `importcpp`/`C++` features - see the [porting guide](#porting-guide) below!\n* support all nim compiler flags and features - do file bugs for anything\n  useful that's missing\n\n# Compile instructions\n\nTo do what I do, you will need:\n* Linux\n* A C/C++ compiler (ironically, I happen to use `gcc` most of the time)\n\nStart with a clone:\n\n    cd $SRC\n    git clone https://github.com/arnetheduck/nlvm.git\n    cd nlvm \u0026\u0026 git submodule update --init\n\nWe will need a few development libraries installed, mainly due to how `nlvm`\nprocesses library dependencies (see dynlib section below):\n\n    # Fedora\n    sudo dnf install pcre-devel openssl-devel sqlite-devel ninja-build cmake\n\n    # Debian, ubuntu etc\n    sudo apt-get install libpcre3-dev libssl-dev libsqlite3-dev ninja-build cmake\n\nCompile `nlvm` (if needed, this will also build `nim` and `llvm`):\n\n    make\n\nCompile with itself and compare:\n\n    make compare\n\nRun test suite:\n\n    make test\n    make stats\n\nYou can link statically to LLVM to create a stand-alone binary - this will\nuse a more optimized version of LLVM as well, but takes longer to build:\n\n    make STATIC_LLVM=1\n\nIf you want a faster `nlvm`, you can also try the release build - it will be\ncalled `nlvmr`:\n\n    make STATIC_LLVM=1 nlvmr\n\nWhen you update `nlvm` from `git`, don't forget the submodule:\n\n    git pull \u0026\u0026 git submodule update\n\nTo build a docker image, use:\n\n    make docker\n\nTo run built `nlvm` docker image use:\n\n    docker run -v $(pwd):/code/ nlvm c -r /code/test.nim\n\n# Compiling your code\n\nOn the command line, `nlvm` is mostly compatible with `nim`.\n\nWhen compiling, `nlvm` will generate a single `.o` file with all code from your\nproject and link it using `$CC` - this helps it pick the right flags for\nlinking with the C library.\n\n    cd $SRC/nlvm/Nim/examples\n    ../../nlvm/nlvm c fizzbuzz\n\nIf you want to see the generated LLVM IR, use the `-c` option:\n\n    cd $SRC/nlvm/Nim/examples\n    ../../nlvm/nlvm c -c fizzbuzz\n    less fizzbuzz.ll\n\nYou can then run the LLVM optimizer on it:\n\n    opt -Os fizzbuzz.ll | llvm-dis\n\n... or compile it to assembly (`.s`):\n\n    llc fizzbuzz.ll\n    less fizzbuzz.s\n\nApart from the code of your `.nim` files, the compiler will also mix in the\ncompatibility found library in `nlvm-lib/`.\n\n## Pipeline\n\nGenerally, the `nim` compiler pipeline looks something like this:\n\n    nim --\u003e c files --\u003e IR --\u003e object files --\u003e linker --\u003e executable\n\nIn `nlvm`, we remove one step and bunch all the code together:\n\n    nim --\u003e single IR file --\u003e built-in LTO linker --\u003e executable\n\nGoing straight to the IR means it's possible to express nim constructs more\nclearly, allowing `llvm` to understand the code better and thus do a better\njob at optimization. It also helps keep compile times down, because the\n`c-to-IR` step can be avoided.\n\nThe practical effect of generating a single object file is similar to\n`clang -fwhole-program -flto` - it is a bit more expensive in terms of memory,\nbut results in slightly smaller and faster binaries. Notably, the\n`IR-to-machine-code` step, including any optimizations, is repeated in full for\neach recompile.\n\n## Porting guide\n\n### dynlib\n\n`nim` uses a runtime dynamic library loading scheme to gain access to shared\nlibraries. When compiling, no linking is done - instead, when running your\napplication, `nim` will try to open anything the user has installed.\n\n`nlvm` does not support the `{.dynlib.}` pragma - instead you can use\n`{.passL.}` using normal system linking.\n\n```nim\n# works with `nim`\nproc f() {. importc, dynlib: \"mylib\" .}\n\n# works with both `nim` and `nlvm`\n{.passL: \"-lmylib\".}\nproc f() {. importc .}\n```\n\n### {.header.}\n\nWhen `nim` compiles code, it will generate `c` code which may include other\n`c` code, from headers or directly via `emit` statements. This means `nim` has\ndirect access to symbols declared in the `c` file, which can be both a feature\nand a problem.\n\nIn `nlvm`, `{.header.}` directives are ignored - `nlvm` looks strictly at\nthe signature of the declaration, meaning the declaration must _exactly_ match\nthe `c` header file or subtly ABI issues and crashes ensue!\n\n```nim\n\n# When `nim` encounters this, it will emit `jmp_buf` in the `c` code without\n# knowing the true size of the type, letting the `c` compiler determine it\n# instead.\ntype C_JmpBuf {.importc: \"jmp_buf\", header: \"\u003csetjmp.h\u003e\".} = object\n\n# nlvm instead ignores the `header` directive completely and will use the\n# declaration as written. Failure to correctly declare the type will result\n# in crashes and subtle bugs - memory will be overwritten or fields will be\n# read from the wrong offsets.\n#\n# The following works with both `nim` and `nlvm`, but requires you to be\n# careful to match the binary size and layout exactly (note how `bycopy`\n# sometimes help to further nail down the ABI):\n\nwhen defined(linux) and defined(amd64):\n  type\n    C_JmpBuf {.importc: \"jmp_buf\", bycopy.} = object\n      abi: array[200 div sizeof(clong), clong]\n\n# In `nim`, `C` constant defines are often imported using the following trick,\n# which makes `nim` emit the right `C` code that the value from the header\n# can be read (no writing of course, even though it's a `var`!)\n#\n# assuming a c header with: `#define RTLD_NOW 2`\n# works for nim:\nvar RTLD_NOW* {.importc: \"RTLD_NOW\", header: \"\u003cdlfcn.h\u003e\".}: cint\n\n# both nlvm and nim (note how these values often can be platform-specific):\nwhen defined(linux) and defined(amd64):\n  const RTLD_NOW* = cint(2)\n```\n\n### {.emit.}\nTo deal with `emit`, the recommendation is to put the emitted code in a C file\nand `{.compile.}` it.\n\n```nim\nproc myEmittedFunction() {.importc.}\n{.compile: \"myemits.c\".}\n```\n\n```c\nvoid myEmittedFunction() {\n  /* ... */\n}\n```\n\n### {.asm.}\n\nSimilar to `{.emit.}`, `{.asm.}` functions must be moved to a separate file and\nincluded in the compilation with `{.compile.}` - this works both with `.S` and\n`.c` files.\n\n### wasm32 support\n\nUse `--cpu:wasm32 --os:standalone --gc:none` to compile Nim to (barebones) WASM.\n\nYou will need to provide a runtime (ie WASI) and use manual memory allocation as\nthe garbage collector hasn't yet been ported to WASM and the Nim standard\nlibrary lacks WASM / WASI support.\n\nTo compile wasm files, you will thus need a `panicoverride.nim` - a minimal\nexample looks like this and discards any errors:\n\n```nim\n# panicoverride.nim\nproc rawoutput(s: string) = discard\nproc panic(s: string) {.noreturn.} = discard\n```\n\nAfter placing the above code in your project folder, you can compile `.nim`\ncode to `wasm32`:\n\n```nim\n# myfile.nim\nproc adder*(v: int): int {.exportc.} =\n  v + 4\n```\n\n```sh\nnlvm c --cpu:wasm32 --os:standalone --gc:none --passl:--no-entry myfile.nim\nwasm2wat -l myfile.wasm\n```\n\nMost WASM-compile code ends up needing WASM [extensions](https://webassembly.org/roadmap/) -\nin particular, the bulk memory extension is needed to process data.\n\nExtensions are enabled by passing `--passc:-mattr=+feature,+feature2`, for example:\n\n```sh\nnlvm c --cpu:wasm32 --os:standalone --gc:none --passl:--no-entry --passc:-mattr=+bulk-memory\n```\n\nPassing `--passc:-mattr=help` will print available features (only works while compiling, for now!)\n\nTo use functions from the environment (with `importc`), compile with `--passl:-Wl,--allow-undefined`.\n\n# REPL / running your code\n\n`nlvm` supports directly running Nim code using just-in-time compilation:\n\n```sh\n# Compile and run `myfile.nim` without creating a binary first\nnlvm r myfile.nim\n```\n\nThis mode can also be used to run code directly from the standard input:\n\n```sh\n$ nlvm r\n.......................................................\n\u003e\u003e\u003e log2(100.0)\nstdin(1, 1) Error: undeclared identifier: 'log2'\ncandidates (edit distance, scope distance); see '--spellSuggest':\n (2, 2): 'low' [proc declared in /home/arnetheduck/src/nlvm/Nim/lib/system.nim(1595, 6)]\n...\n\u003e\u003e\u003e import math\n.....\n\u003e\u003e\u003e log2(100.0)\n6.643856189774724: float64\n```\n\n# Random notes\n\n* Upstream is pinned using a submodule - nlvm relies heavily on internals\n  that keep changing - it's unlikely that it works with any other versions,\n  patches welcome to update it\n* The nim standard library likes to import C headers directly which works\n  because the upstream nim compiler uses a C compiler underneath - ergo,\n  large parts of the standard library don't work with nlvm.\n* Happy to take patches for anything, including better platform support!\n* For development, it's convenient to build LLVM with assertions turned on -\n  the API is pretty unforgiving\n* When I started on this little project, I knew neither llvm nor Nim.\n  Therefore, I'd specially like to thank the friendly folks at the #nim\n  channel that never seemed to tire of my nooby questions.\n  Also, thanks to all tutorial writers out there, on llvm, programming\n  and other topics for providing such fine sources of copy-pa... er,\n  inspiration!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnetheduck%2Fnlvm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farnetheduck%2Fnlvm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnetheduck%2Fnlvm/lists"}