Installation
sass2mlir is a C++20/TableGen toolchain built against LLVM/MLIR, licensed AGPL-3.0+. The current release is 0.10.0 — see the changelog for what landed when. There are no prebuilt binaries yet — you build it from source, the same way you build MLIR itself. A full build takes roughly 15 minutes on a desktop; most of that is the generated multi-architecture decode tables.
Prerequisites
| Dependency | Version | Why |
|---|---|---|
| CMake | ≥ 3.17 | build system |
| C++ compiler | Clang ≥ 17 or GCC ≥ 13 (C++20) | the toolchain itself |
| LLVM / MLIR | 20.x, with MLIR headers | dialect, passes, parser/printer — not needed with SASS_MLIR=OFF |
| libelf | any (pkg-config) | cubin ELF parsing |
| Ninja | any | generator (make works, ninja is faster) |
| Python | ≥ 3.10 | probing & opcode-table tooling |
| CUDA toolkit | ≥ 12.0, optional | nvdisasm/cuobjdump cross-checks, intercept testing |
The C++ library dependencies (spdlog, fmt, tqdm-cpp) are not system
requirements — CMake’s FetchContent pulls pinned versions into the build
tree automatically; the fetched fmt stays private so it can’t shadow a
system libfmt. Only libelf needs to come from your package manager
(libelf-dev / libelf-devel).
The CUDA toolkit is a validation dependency, not a compile-time one: the
regression suite diffs the decoder against nvdisasm and the CFG recovery
against nvdisasm -bbcfg. Without it the build succeeds and the corpus tests
that need a GPU are skipped.
Clone and build
git clone https://github.com/mbuchel/sass2mlir.git
cd sass2mlir
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DMLIR_DIR=/path/to/llvm-build/lib/cmake/mlir
ninja -C buildThe build compiles the probe-derived opcode tables (opcodes/SM*/*.tsv) through
union-gen into the disassembler dispatch and the MLIR dialect in one step —
the ~270k-line generated multi-SM table is why the first build takes a while.
Incremental builds after that are seconds.
What you get in build/bin and build/lib:
| Artifact | Kind | Purpose |
|---|---|---|
| sass2mlir | binary | the whole toolchain: lift / lower / analyses / harvest / intercept / apply |
| sass2mlir-scanner | binary | corpus coverage scanner — measures decode/encode identity over cubin collections |
| union-gen | binary | opcode-table compiler (only needed when adding an architecture) |
| lib/sass2mlir/passes/*.so | pass plugins | the shipped SASS→SASS transforms loaded by apply |
The source tree mirrors that split, so it’s quick to navigate:
| Directory | Contents |
|---|---|
| exe/ | the sass2mlir / sass2mlir-scanner entry points |
| lib/ + inc/ | decoder/encoder core and public headers |
| common/ | shared infrastructure (cubin parser, disassembler support) |
| passes/ | the pass plugins, one directory per pass |
| preload/ | the intercept shim source |
| opcodes/ | probe-derived per-architecture tables (SM*/*.tsv) |
| tools/ | probing, harvesting, and corpus utilities |
Verify the install
The fastest end-to-end check is a round-trip on any cubin you have lying around — the test suite also ships a small sample corpus for exactly this:
# Byte-identity self-check on the bundled sample kernels:
ninja -C build check-sass # full corpus regression (needs CUDA for nvdisasm diffs)
# Or a single manual round-trip:
build/bin/sass2mlir roundtrip build/test/corpus/sm_90a/flash_fwd.cubin
# roundtrip: 1042/1042 instructions byte-identical (0 mismatches)roundtrip lifts the cubin to MLIR, prints it, reparses the printed text, and
lowers it back — then asserts the bytes match the input exactly. If that prints
zero mismatches, the lift/lower loop on your machine is sound.
Install system-wide
ninja -C build install # installs under /usr (CMAKE_INSTALL_PREFIX)
sass2mlir --version
# sass2mlir 0.10.0This installs sass2mlir — the intercept shim is part of the binary — plus
the pass-plugin directory. The plugins are found relative to the binary, so a
staged DESTDIR install works for packaging.
The .deb package
The preferred install on Debian/Ubuntu (including JetPack on the Orin) is the
package the build already produces — the tree is deliberately .deb-friendly:
prefix /usr, and the FetchContent’d fmt configured private so the
package can’t shadow a system libfmt-dev:
ninja -C build package
# CPack: Create package using DEB
# CPackDEB: ... sass2mlir_0.10.0_amd64.deb (arm64 on the Jetson)
sudo dpkg -i build/sass2mlir_0.10.0_amd64.debThe package installs the binaries under /usr/bin, the pass plugins under
/usr/lib/sass2mlir/passes/, and the headers under
/usr/include/sass2mlir/ — what the pass-plugin example
builds against. Because the plugins resolve relative to the binary, the
packaged layout and the build-tree layout behave identically. Releases also
attach prebuilt .debs for amd64 and arm64 once a version is tagged;
until then the package target above is three minutes of your own build.
Build configuration
| Option | Default | Effect |
|---|---|---|
| SASS_ACTIVE_SMS | all supported | compile decode tables only for the listed SMs (e.g. "sm_87" or "sm_87;sm_90a") — how edge builds fit in 8 GB |
| SASS_MLIR | ON | OFF builds against the internal simplified-MLIR IR — drops the LLVM/MLIR dependency entirely |
| SASS_BUILD_TESTS | ON | the corpus regression suite (check-sass) |
| SASS_ENABLE_NVDISASM_CROSSCHECK | ON if CUDA found | diff decoder/CFG output against nvdisasm as an oracle |
| CMAKE_BUILD_TYPE | Release | use RelWithDebInfo when developing passes |
SASS_ACTIVE_SMS takes a list, so a build carries exactly the architectures
you target — one SM for a Jetson, two for a mixed H100/Blackwell CI box. It’s
how the Jetson build fits in RAM — see the
on-device example.
The two IR backends
By default the dialect and passes build against upstream LLVM/MLIR. With
-DSASS_MLIR=OFF the tree compiles against the project’s own internal IR
instead — a deliberately simplified, MLIR-shaped core: modules, functions,
blocks, and ops with SSA operands, results, and attributes, but no dialect
registry, no regions, and none of the upstream pass infrastructure. Every
command (lift, the analyses, apply, intercept) works on either backend;
the choice is about footprint and interop:
- Internal IR — no LLVM checkout or build at all, a fraction of the
compile time and binary size. Pairs with
SASS_ACTIVE_SMSon edge targets. - Upstream MLIR — full dialect interop and MLIR tooling; where new dialect features land first.
When the build fights back
Could not find MLIR / MLIR_DIR
find_package(MLIR) needs the CMake package files, not just the headers. Point
at the build or install tree of an LLVM that was configured with
-DLLVM_ENABLE_PROJECTS=mlir:
cmake -S . -B build -DMLIR_DIR=/path/to/llvm-build/lib/cmake/mlir \
-DLLVM_DIR=/path/to/llvm-build/lib/cmake/llvmThe multi-SM table build runs out of memory
The generated decode table for all supported architectures is large; linking it
with debug info (-DCMAKE_BUILD_TYPE=Debug) can exhaust RAM on small machines.
Use Release/RelWithDebInfo, restrict the architecture set with
-DSASS_ACTIVE_SMS=sm_87, or lower parallelism (ninja -j4).
check-sass skips the cross-checks
That means the CUDA toolkit wasn’t found at configure time. Re-run cmake with
CUDAToolkit_ROOT=/usr/local/cuda set, or install the toolkit — the lift/lower
loop itself doesn’t need a GPU, but the oracle diffs do.
Next: the five-minute quickstart, or jump straight to the CLI reference.