Open-source project
shinh/elvm avatar
shinh/elvm

ELVM is an LLVM for esoteric languages, with sixty backends and one test

EsoLangVM Compiler Infrastructure

1,269 stars156 forksCMIT

At a glance

What is it?
ELVM takes C, compiles it to a deliberately tiny intermediate representation called EIR, and then translates that into any of sixty esoteric languages, from Brainfuck to Whitespace. The design is validated by a single test: a small Lisp interpreter has to run in every backend, and each backend can be checked for self hosting.
Who is it for?
ELVM suits someone studying compiler construction, since the whole pipeline from C to an intermediate representation to a target is small enough to read, and it suits a curious programmer who wants to see what a real program looks like in Piet or Unlambda.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 137 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • shinh/elvm1,269 ★ · +2
Star history
Repository2026-10-192026-11-07
shinh/elvm1.3k1.3k

2 Stars in the last 19 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The same shape as LLVM, for languages nobody compiles to

ELVM is similar to LLVM but dedicated to esoteric languages, and it consists of two components, a frontend and a backend.

The frontend is a modified version of 8cc, a small C compiler, and the modification is that it translates C into an internal representation format called EIR, the ELVM IR. The key design decision is stated directly: unlike LLVM bitcode, EIR is designed to be extremely simple, so there is more chance of writing a translator from EIR to an esoteric language. That sentence is the whole thesis of the project, because a translator into a language with 8 commands and a tape is a very different problem from a translator into a language with 8 commands and no memory at all.

The backend side is where the fan-out happens, and there are sixty of them. The list includes Brainfuck, Befunge, Piet, Unlambda, Whitespace, C-INTERCAL, LOLCODE, GolfScript adjacent entries such as Acc!! and Aheui, Turing machine, Lambda calculus, SUBLEQ, Grass, HeLL, J, Lazy K, and Kinx. It also includes languages that are hardly esoteric at all: C, C++, C#, Java, JavaScript, Go, Rust's neighbour Swift, Python, Ruby, Perl, PHP, Lua, Forth, Scheme, F#, Crystal, Common Lisp, Emacs Lisp, Fortran, TeX, Tcl, POSIX shell, and even a CMake backend, a GNU find backend, a sed backend, an arm-linux backend, an i386-linux backend, a TensorFlow backend marked as work in progress, and LLVM IR itself.

That last group is what makes the project a genuine test of the IR rather than a novelty. A pipeline that can emit GNU find or sed is a pipeline that expresses computation rather than a specific computation.

The audience is compiler people and language people, and the licence is MIT, with the code in C, the last push on 2026-06-23 and no tagged releases.

EIR is small on purpose, and the details are the design

The internals section lists the constraints of the intermediate representation, and the list reads like a set of deliberate denials.

The architecture is Harvard, not Neumann, and the reason is given: self-modifying code is hard. That single decision removes a class of backends, because a target whose model is a tape or a grid cannot express code that rewrites itself, and it makes the IR representable in those targets at all.

There are six registers, A, B, C, D, SP and BP, and the operation set is short:

text
Ops: mov, add, sub, load, store, setcc, jcc, putc, getc, and exit

with pseudo operations for the data sections, .text, .data, .long and .string. Multiplication, division and modulo are implemented with compiler builtins rather than being operations of their own.

Then the absences. There are no bit operations and no floating point arithmetic, and the size of a char, an int and a pointer are all one. The word size is backend dependent, with most backends using 24 bit words.

The final line is the one that explains the rest: a single programming counter may contain multiple operations. In a language like Brainfuck, where the only control flow is a moving pointer and a jump table, expressing a traditional instruction pointer means encoding a sequence of operations into one cell and advancing through them, and the backend does that bookkeeping.

Taken together, these constraints describe a machine small enough that every esoteric language in the list can implement it, which is the property that makes sixty backends possible rather than merely admirable.

One test, sixty times, and a self hosting check

The testing story is the part of the project that makes the sixty backends credible.

The interesting test case is a tiny Lisp interpreter in test/lisp.c, and all the language backends pass it, which means you can run Lisp on any of the languages in the list. That is a strong statement for a pipeline this varied, because a backend that compiles the IR but mis-implements the calling convention or the stack discipline will fail on real control flow rather than on a straight line of code.

The self hosting claim is stronger still. Eightcc and ELVM themselves are written in C, so the toolchain can compile itself, in any of the supported languages, which means you can run a C compiler written in an esoteric language to compile the compiler's own toolchain. The documentation is honest about the cost: such compilation takes a long time in some esoteric languages.

The Makefile turns that into a per backend workflow. The default build produces the tools, the eightcc frontend, the ELI interpreter and the ELC compiler, plus a set of helper binaries that the backends need, including a Befunge emitter, a Brainfuck optimiser, a CMake putc helper, a SUBLEQ emitter and a Whirl emitter. The per backend targets are then:

bash
make build-xxx
make test-xxx
make test-xxx-full

where the first compiles all the tests to that backend, the second runs them, and the third runs the backend's own program on the ELVM, which is the self hosting check the Makefile describes. There is also a make test target that builds and runs all tests, and it carries an explicit warning that it will take a lot of time and disk.

That warning is the honest summary of the project. Sixty backends times a test suite times a self hosting pass is a lot of compilation, and the project tells you so before you start rather than after.

Backends are contributed one language at a time

The list of backends carries the name of the contributor beside most of them, and the per language notes tell you what each target required.

Brainfuck was the first motivation for the project, through an earlier Lisp interpreter in Brainfuck, and the documentation says the IR was designed for Brainfuck, but that it turned out such a simple IR could be suitable for other esoteric languages. There is a dedicated Brainfuck compiler and optimiser in tools/bfopt.cc, because Brainfuck is slow and the project would rather you used an optimised implementation, and the note says you need implementations with 8 bit cells, with a flag to specify for one named implementation.

Unlambda was contributed by someone else and is tested against that contributor's own interpreter, and a script in tools/ automatically downloads it, which is a nice touch for a target you cannot assume is installed. The C-INTERCAL backend uses 16 bit registers and address space, which is a reminder that the IR's word size is a property of the backend, exactly as the internals section says.

Two of the more unusual targets are worth singling out. The WebAssembly System Interface backend targets an interface rather than a language, and the GNU find, sed and CMake backends are pipelines rather than languages, which raises the question of what a program in those targets even means. The Conway's Game of Life entry, reached through a quantum-flavoured assembly, is the clearest statement of the project's range: the target is not required to be a general purpose programming language, only to be able to express the IR.

The project hosts a demo site where the ELVM toolchain itself runs on the JavaScript backend, with the note that it is therefore slow, and a gallery of large example programs: the eightcc C compiler in JavaScript, in Brainfuck and in Unlambda, and the Lisp interpreter rendered as a Piet image as well as compiled to C-INTERCAL, Befunge and Whitespace.

Building the whole thing

The build is a plain make, and the first thing it does is fetch the submodules.

The Makefile has a target that runs git submodule update --init and touches a stamp file, and several build outputs depend on it, including the eightcc sources, the interpreter install script, the Whitespace interpreter source and the tinycc configure script. The frontend compiler is therefore not part of this repository: eightcc lives in a branch of its author's project, and Whitespace and tinycc are vendored as submodules.

The compiler flags are strict, with warnings treated as errors, the C code compiled as gnu99 and the C++ as c++11, and the architecture is detected at build time, with arm selected on a machine whose uname mentions arm and x86 otherwise, which decides the word size and the code generation for the built in libraries.

The output names are worth noting because they show the shape of the toolchain: out/eli for the interpreter, out/elc for the compiler, out/8cc for the frontend, and out/dump_ir for inspecting the intermediate representation.

The library the backends share is small, the IR parser and interpreter in ir/, and the backends in target/ use that parser to generate their code, which is why a backend does not have to know anything about the C front end. There is also an incomplete libc implementation, which the documentation explains is necessary to run the tests, since the real programs the backends compile expect a C library and the project does not want to translate all of libc into sixty languages.

For a reader who wants to try it, the smallest useful loop is to build the toolchain, pick one backend, and run the Lisp test for that backend, which is the same path the test suite takes.

Editorial conclusion

ELVM suits someone studying compiler construction, since the whole pipeline from C to an intermediate representation to a target is small enough to read, and it suits a curious programmer who wants to see what a real program looks like in Piet or Unlambda. It is the wrong choice as production tooling, because the whole point is to demonstrate the pipeline rather than to be efficient, and a poor match for a beginner, because the documentation assumes you already know what a compiler is doing. Before building, read the warnings about the time and disk the full test suite needs, and expect to install the submodules, since the frontend compiler and the test harnesses are not in the repository itself.

Frequently asked questions

What is ELVM and how does it work?

ELVM is a compiler infrastructure similar to LLVM but dedicated to esoteric languages. A modified 8cc translates C into EIR, an intermediate representation designed to be extremely simple, and sixty backends then translate EIR into languages as varied as Brainfuck, Piet, Whitespace, Unlambda and C.

What are the constraints of the ELVM IR?

It is Harvard rather than Neumann architecture so that self-modifying code is not needed, with six registers A, B, C, D, SP and BP, operations for moving, adding, subtracting, loading, storing, conditional setting and branching, character input and output and exit, no bit operations and no floating point, a size of one for char, int and pointer, and a backend dependent word size where most backends use 24 bit words.

How is ELVM tested across so many backends?

With a tiny Lisp interpreter in test/lisp.c that every backend passes, and with per backend make targets: build-xxx to compile the tests to a backend, test-xxx to run them, and test-xxx-full to run that backend's own program on the ELVM as a self hosting check. The full make test target carries a warning that it takes a lot of time and disk.

Why does ELVM use a Harvard architecture?

Because self-modifying code is hard, and the documentation gives that as the reason. Choosing a Harvard model removes a class of backends from consideration and is what makes the small IR representable in targets such as Brainfuck or Befunge at all.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. shinh/elvm on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shinh-elvm.svg)](https://hysenlabs.com/projects/shinh-elvm)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/shinh-elvm.svg)](https://hysenlabs.com/projects/shinh-elvm)

Was this useful?