NORA is an experimental Racket implementation written with a LLVM backend.
- CMake >= 3.24 and Ninja
- LLVM 22, built with either its matching Clang or GCC >= 13
- GMP including the C++ bindings (
gmpxx) - Python 3 with lit for the integration tests
The default build depends only on LLVM and GMP. The experimental NIR MLIR dialect is opt-in and is not needed to build or run the interpreter — see The NIR MLIR dialect below.
The project ships CMake presets:
$ git clone https://github.com/pmatos/nora
$ cd nora
$ cmake --preset debug
$ cmake --build --preset debug
$ ctest --preset debug
Other presets are available: release, asan, ubsan, coverage, and
mlir (builds the opt-in NIR dialect and requires MLIR to be installed).
Run the interpreter on a linklet directly:
$ ./build/debug/bin/norac test/integration/arithplus.rkt
2
If you modify the project you can run the CI test workflow locally with act:
$ act -P ubuntu-24.04=ghcr.io/catthehacker/ubuntu:act-24.04 -j test
NORA's planned compiler backend will lower to an MLIR dialect called NIR (Nora
IR; see the roadmap). It is currently an empty
scaffold that the interpreter does not use, so MLIR is opt-in and off by
default. Enable it with cmake --preset mlir or -DNORA_ENABLE_MLIR=ON once
MLIR is installed.
Everything in Racket land is compiled down into a linklet. The linklet uses the language of Fully Expanded Programs (FEP), therefore to compile Racket one needs an expander, a compiler for FEP, and a FEP runtime. The runtime of FEP is responsible to implement access to OS specific stuff like threads, filesystem, etc (which is what in RacketCS is being done by Rumble).
FEP (Fully Expanded Programs) are not the same as Linklets. According to Matthew Flatt in an email: "The benefit of the layer is that you can more easily arrive at an implementation where you can run the expander itself on the target platform.". For example, Pycket used FEP but transitioned to using linkets in pycket/pycket#232. Racketscript has this ongoing PR to compile to linklets: racketscript/racketscript#266
Racket is a special language to compile due to its dependency on an expander. This expander is currently implemented in Racket (a few years ago it was in C), and lives in the main racket repository.
Since this expander is written in Racket, it needs to bootstrap itself. In other words, we need a precompiled expander to expand the expander into a linklet so that we can then link that into the compiler to expand user programs. At the moment we are extracting the racket expander into our repo and until we implement our own, there's no reason we can't use Racket's.
So the MVP plan would be to have something that resembles:
Racket Source --(1)--> expander.rktl (linklet) --------------(4)------------\
| |
User Code ----------------------+(2)-----------> Expanded User Code ---------(5)--|
|
NIR Dialect
(MLIR)
|
|(7)
Runtime ------------------(3)-------------------> LLVM IR
|
(6)
|
JIT/Binary
The driver of the compilation is, unexpectedly, the expander. The expander holds the code to read a racket module and compile it. It is the job of NORA to quickstart that by starting to interpret the expander and provide the hooks required by the expander for everything to work.
Steps:
- We extract expander.rktl (the expander linklet through bootstrapping) from the Racket sources;
- User code is expander using the expander linklet (requires expander interpreter);
- Runtime is compiled using LLVM to LLVM IR;
- Expander linklet is compiled using LLVM to LLVM IR;
- The compiler frontend transforms the s-expr ast into an MLIR dialect called NIR (Nora IR);
- LLVM compiles the LLVM IR modules to Binary or we JIT them;
- NIR is lowered to LLVM IR;
- Brutus
- Suggested by https://github.com/femtomc
- PyTorch importer
- Suggested by https://github.com/stellaraccident
- Rise Lang
- Also video here RISE A functional pattern-based language in MLIR
- MLIR for Functional Programming (video)
- Clasp
- Also video: Common Lisp in LLVM