Exo compiles scheduling decisions into C for hardware accelerators
Exocompilation for productive programming of hardware accelerators
At a glance
- What is it?
- Exo is a scheduling language for array programs that is compiled, not interpreted: you write Python-like code that describes how loops and operations should be arranged on an accelerator, and the compiler works out a concrete schedule and emits C and headers. The compiler uses Z3 through PySMT to prove that the schedule you asked for is legal, so the emitted code can be compiled with an ordinary C toolchain.
- Who is it for?
- Adopt Exo if you are writing array-heavy numerical code for a CPU or GPU and want the schedule to be derived rather than hand-tuned, since the compiler searches for a legal one and proves it with a solver. Skip it if your program is not array-shaped, because the language's value comes entirely from operating on arrays and scheduling loops over them, and if you need a stable release, since the project is marked Development Status 3 - Alpha.
- 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 7 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The source looks like Python because it is meant to be read
Exo files can be directly executed with Python:
$ python exo_file.pyThat is the first thing the documentation says and it sets the tone of the project. Exo is described as Exocompilation for Productive Programming of Hardware Accelerators, and the source is Python so that a scheduling program reads like a program.
To generate C and header files, the `exocc` command is used:
$ exocc exo_file.pyRunning the command generates two files, `exo_file.c` and `exo_file.h`, created in a directory called `exo_file/` by default. Two optional arguments customise the output: the `-o` argument specifies a different directory name, and the `--stem` argument specifies custom names for the C file and header file.
So the workflow is a two-stage one. You write a scheduling program in Python syntax, run it directly to check it behaves, and then compile it to C when you want the performance. That is what the name means: the Exo file is the input to a compiler, not a script that gets interpreted in production.
The two outputs are the whole point. A C file and a header, handed to whatever build system you already use, means Exo does not need to own your toolchain or your deployment. If you can compile C, you can use the result.
The package is published on PyPI as `exo-lang`, the project version is 1.0.0, and the console script is `exocc = "exo.main:main"`.
A solver in the loop is what makes the schedules correct
The dependency list in the project file is short and one of the four entries tells you what kind of compiler this is. The dependencies are PySMT at 0.9.6 or newer, asdl-adt at 0.1, yapf at 0.43, and z3-solver at 4.15.
PySMT is a Python interface to SMT solvers, and z3-solver is Z3. So the compiler is doing symbolic reasoning as part of producing the schedule, and the documentation is upfront that this is a dependency you have to get right.
The PySMT section says that, depending on your setup, getting PySMT to work correctly may be difficult. You need to independently install a solver such as Z3 or CVC4, and even then getting the PySMT library to correctly locate that solver may be difficult. The project has included the `z3-solver` package as a requirement, which will hopefully avoid this issue, but you can also install z3, or your choice of solver, independently.
That is a specific and common failure mode in solver-based tooling, and the documentation naming it rather than glossing over it is useful.
The other two dependencies are worth a note. asdl-adt is the attribute definition system that ADTs, the abstract data types, are described with, and yapf is the code formatter. A compiler that emits C from a Python-like source needs a formatter in the loop, and a scheduling language needs a way to describe the shape of its intermediate values, which is what an ADT system provides.
The project file also declares the language is Programming Language :: C as well as Python 3, which is the clearest statement of what the output is.
Building from source means a venv, submodules and a wheel
The project makes active use of newer Python 3.x features, and the documentation asks you to use Python 3.9 or 3.10 if you are getting errors about unsupported features.
The setup sequence is a standard Python development setup with two extras. First the clone and the submodules:
$ git clone [email protected]:exo-lang/exo.git
$ cd exo/
$ git submodule update --init --recursiveThen the virtual environment, which the documentation strongly recommends:
$ python -m venv ~/.venv/exo
$ source ~/.venv/exo/bin/activate
(exo) $ python -m pip install -U pip setuptools wheel
(exo) $ python -m pip install -r requirements.txt
(exo) $ pre-commit installThe submodule step matters because the project vendors parts of its toolchain, and the pre-commit install is what wires the autoformatter into your commits, which the documentation describes as scripts that run an autoformatter, maybe other tools in the future.
Then the build and install:
(exo) $ python -m build .
(exo) $ pip install dist/*.whlThe repository layout matches a compiler project with a runtime library. There is a src/ directory for the compiler, an apps/ directory for the applications, a dependencies/ directory for the vendored pieces, tests/, examples/, docs/, and a .gitmodules file at the root. The project file marks the package as Development Status 3 - Alpha, which sets expectations about the API stability.
The requirements file is autogenerated by uv, and the header says so: it was autogenerated by uv via the `uv export --all-groups --no-hashes --no-emit-project -o requirements.txt` command.
Testing Exo means compiling C, so CMake and Ninja are requirements
The testing section is where the practical requirements of an Exocompiler become clear, because the test harness itself has to build native code.
The build system is required, and the reason is stated: the Exo test harness generates C code and as such needs to compile and link using an unknown, that is system, compiler. To do this, it generates CMake build files and invokes CMake behind the scenes. Therefore you must have CMake 3.21 or newer installed.
By default CMake will use Ninja as its backend, and this may be overridden by setting the environment variable `CMAKE_GENERATOR` to `Unix Makefiles`, in case you do not wish to install Ninja.
That is a dependency chain worth understanding before you install anything: Exo itself is pure Python, but anything that runs the generated code, including the test suite, needs a C compiler, CMake 3.21 or newer, and a generator.
Running the tests is simply:
pytestin the root of the project. And with coverage:
pytest --cov=./ --cov-report=htmlAfter which, if you want to see annotated source files, you open `./htmlcov/index.html`.
There is an optional dependency as well. For testing x86 features on processors which do not support them, such as AVX-512 or AMX, the project relies on the Intel Software Development Emulator. Tests which rely on this, namely for AMX, look for `sde64` either in the path defined by the `SDE_PATH` environment variable or in the system `PATH`, and are skipped if it is not available.
That skip behaviour is the friendly part. You do not need an emulator to run the suite, you just lose the tests that need one.
The examples and the papers describe what the language is for
The learning material is split between an examples directory and a docs directory. The examples are for scheduling examples, and the docs directory holds various documentation about Exo, including a Design.md that the README calls the summary of the design principles and features.
The example list gives a sense of the domain. There is an avx2_matmul directory, which is matrix multiplication on AVX2, so the target is a CPU vector unit. There is a rvm_conv1d directory, which is a 1D convolution for a RISC-V vector machine. A cursors directory, a quiz1, quiz2 and quiz3 set,
So the shape of the problem is: you have a computation over arrays, several ways to schedule it, and you want a compiler to pick one that is legal and fast for a particular instruction set.
The research record is the other half of the story, and the README asks you to cite both the compiler and the papers. The first is Exocompilation for Productive Programming of Hardware Accelerators, by Yuka Ikarashi, Gilbert Louis Bernstein, Alex Reinking, Hasan Genc and Jonathan Ragan-Kelley, published at PLDI 2022. The second is Exo 2: Growing a Scheduling Language, by Yuka Ikarashi, Kevin Qian, Samir Droubi, Alex Reinking, Gilbert Bernstein and Jonathan Ragan-Kelley, published at ASPLOS 2025.
The contact addresses are at MIT, [email protected] and [email protected], and the CITATION.cff file at the repository root is what a citation tool reads. The project also has a Godbolt instance at godbolt.exo-lang.dev, which is a Compiler Explorer, and that is a useful thing for a language whose output is C: you can paste the generated code and look at it.
Where Exo sits against hand-written intrinsics and against a plain DSL
The comparison worth making is with the two alternatives to a scheduling compiler, and each loses to Exo in a different scenario.
The first alternative is hand-written intrinsics. You write the loop yourself, with intrinsics for the vector unit, and you tune the loop order and the tiling by hand. The result is the fastest possible code and the most painful to maintain, because every change to the algorithm means re-tuning the schedule, and the knowledge of why a particular tiling is fast lives in one person's head. Exo exists to make that knowledge machine-checkable.
The second alternative is a plain DSL that emits loops without deciding anything, such as a tensor compiler that maps a whole tensor expression onto a library call. That is far less work than Exo and gets you most of the way for a fixed shape, but it cannot express the awkward cases, the ones where you need a particular fusion or a particular loop order, because a general-purpose mapper does not know what you want.
Exo's claim is in the middle: you state the scheduling intent in a language, and the compiler finds a legal schedule and proves it, rather than you writing the schedule and proving it by hand.
The costs of that position are visible in the documentation. You need a solver installed and locatable. You need CMake and a C compiler to test anything. The project is Alpha. And the language only makes sense for array programs, so it is not a general-purpose Python replacement. For a numerical kernel that will run many times over many shapes, that is a good trade. For anything else, it is a lot of machinery.
Editorial conclusion
Adopt Exo if you are writing array-heavy numerical code for a CPU or GPU and want the schedule to be derived rather than hand-tuned, since the compiler searches for a legal one and proves it with a solver. Skip it if your program is not array-shaped, because the language's value comes entirely from operating on arrays and scheduling loops over them, and if you need a stable release, since the project is marked Development Status 3 - Alpha. Verify first that you have CMake 3.21 or newer and a C compiler, because the test harness generates C and builds it, that a solver such as Z3 or CVC4 is installed and locatable, and that you read the design document before writing a large program, since the language's semantics are documented there rather than in the README.
Frequently asked questions
How do I install Exo and generate C code?
Install it with `pip install exo-lang` on Python 3.9 or above, then run `exocc exo_file.py` to generate `exo_file.c` and `exo_file.h` in a directory named after the file. The `-o` argument changes the output directory and `--stem` changes the file names.
What does Exo need in order to build and test?
CMake 3.21 or newer, because the test harness generates C code and invokes CMake behind the scenes, and a system C compiler. Ninja is the default generator and can be replaced with Unix Makefiles by setting CMAKE_GENERATOR. A solver such as Z3 or CVC4 must be installed and locatable by PySMT, though the z3-solver package is included as a dependency to reduce that problem.
How do I run the Exo test suite?
Run `pytest` in the root of the project, or `pytest --cov=./ --cov-report=html` for coverage, then open `./htmlcov/index.html` to see annotated source files. Tests that need the Intel Software Development Emulator look for `sde64` in the `SDE_PATH` environment variable or in the system PATH, and are skipped when it is not available.
Official sources
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.
[](https://hysenlabs.com/projects/exo-lang-exo)Was this useful?