Getting started

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.

Step 0

Prerequisites

DependencyVersionWhy
CMake≥ 3.17build system
C++ compilerClang ≥ 17 or GCC ≥ 13 (C++20)the toolchain itself
LLVM / MLIR20.x, with MLIR headersdialect, passes, parser/printer — not needed with SASS_MLIR=OFF
libelfany (pkg-config)cubin ELF parsing
Ninjaanygenerator (make works, ninja is faster)
Python≥ 3.10probing & opcode-table tooling
CUDA toolkit≥ 12.0, optionalnvdisasm/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.

Step 1

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 build

The 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:

ArtifactKindPurpose
sass2mlirbinarythe whole toolchain: lift / lower / analyses / harvest / intercept / apply
sass2mlir-scannerbinarycorpus coverage scanner — measures decode/encode identity over cubin collections
union-genbinaryopcode-table compiler (only needed when adding an architecture)
lib/sass2mlir/passes/*.sopass pluginsthe shipped SASS→SASS transforms loaded by apply

The source tree mirrors that split, so it’s quick to navigate:

DirectoryContents
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
Step 2

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.

Step 3 · optional

Install system-wide

ninja -C build install             # installs under /usr (CMAKE_INSTALL_PREFIX)
sass2mlir --version
# sass2mlir 0.10.0

This 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.deb

The 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.

CMake options

Build configuration

OptionDefaultEffect
SASS_ACTIVE_SMSall supportedcompile decode tables only for the listed SMs (e.g. "sm_87" or "sm_87;sm_90a") — how edge builds fit in 8 GB
SASS_MLIRONOFF builds against the internal simplified-MLIR IR — drops the LLVM/MLIR dependency entirely
SASS_BUILD_TESTSONthe corpus regression suite (check-sass)
SASS_ENABLE_NVDISASM_CROSSCHECKON if CUDA founddiff decoder/CFG output against nvdisasm as an oracle
CMAKE_BUILD_TYPEReleaseuse 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_SMS on edge targets.
  • Upstream MLIR — full dialect interop and MLIR tooling; where new dialect features land first.
Troubleshooting

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/llvm
The 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.