A small Go-facing MLIR interface built on the MLIR C API.
The project stays close to upstream handles, adds construction helpers where the raw API gets awkward, and keeps low-level access available for cases where a future builder or dialect layer is not enough.
This repository is still early, but it is no longer documentation-only. The current slice covers:
- core handle-based bindings for context, module, operation, location, region, block, type, value, attribute, and identifier
- symbol table helpers for lookup, uniquing, visibility, and symbol-use rewrites
- builtin type and attribute constructors for common MLIR forms
- generic operation construction through
OperationState - a first
builderpackage for modules, functions, blocks, insertion points, and default locations - builder-friendly dialect emit helpers under
builder/arith,builder/func, andbuilder/cf - basic IR mutation paths for regions, blocks, and SSA use replacement
- diagnostic capture around parser and checked-construction failures
- first dialect convenience wrappers under
dialect/arith,dialect/func,dialect/cf,dialect/memref,dialect/tensor,dialect/scf,dialect/vector, anddialect/linalg - a first
cmd/mlir-go-tblgenentry point plus a checked-indialect_manifest.json - pass manager setup and pipeline execution
- execution engine creation and packed invocation
- Go tests plus
FileCheck-based textual IR checks
The first version is not trying to expose all upstream C APIs at once, bind directly to C++, or support multiple LLVM/MLIR major versions from one code line.
Real bindings require cgo and a local MLIR installation that exposes the MLIR C headers, libraries, and tools.
The repository also includes non-cgo fallback files so unsupported builds fail with explicit runtime errors instead of opaque linker failures.
To prepare a development shell:
eval "$(./scripts/dev-env.sh)"That script uses llvm-config from PATH by default.
To select a different installation:
eval "$(LLVM_CONFIG=/path/to/llvm-config ./scripts/dev-env.sh)"It exports:
CGO_CPPFLAGSCGO_LDFLAGSPATHwith the selected LLVM tool directory, soFileCheckis available when present
Run the full test suite with:
go test -count=1 ./...The test suite has two layers:
- regular Go tests for API behavior, ownership, mutation, passes, and execution
FileCheckfixtures for textual IR validation
The repository also includes a Dockerfile for a reproducible Linux development environment.
It targets ubuntu:24.04, installs LLVM/MLIR 22 from apt.llvm.org, and installs Go 1.25.0.
The image also provides unversioned llvm-config and FileCheck symlinks so the existing scripts work unchanged.
The GitHub Actions Linux CI job builds and runs this same image.
Build the image:
docker build -t mlir-go-dev .Run the test suite inside the container:
docker run --rm -it \
-v "$PWD":/workspace \
-w /workspace \
mlir-go-dev \
bash -lc 'eval "$(./scripts/dev-env.sh)" && go test -count=1 ./...'Run the downstream consumer smoke test in the same environment:
docker run --rm -it \
-v "$PWD":/workspace \
-w /workspace \
mlir-go-dev \
bash -lc 'eval "$(./scripts/dev-env.sh)" && bash ./scripts/test-downstream-consumer.sh'Check generated files inside the same environment:
docker run --rm -it \
-v "$PWD":/workspace \
-w /workspace \
mlir-go-dev \
bash -lc "eval \"\$(./scripts/dev-env.sh)\" && go run ./cmd/mlir-go-tblgen -mode validate-manifest && bash ./scripts/update-generated.sh && git diff --exit-code"The repository now includes a first generator entry point:
go run ./cmd/mlir-go-tblgen -mode validate-manifest
go run ./cmd/mlir-go-tblgen -mode emit-skip-report
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect arith
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect func
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect cf
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect memref
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect tensor
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect vector
go run ./cmd/mlir-go-tblgen -mode emit-dialect -dialect linalg
bash ./scripts/update-generated.sh
bash ./scripts/update-skip-report.shBy default the tool resolves the MLIR include root through llvm-config --includedir.
You can override that explicitly:
go run ./cmd/mlir-go-tblgen \
-mode emit-skip-report \
-mlir-include-root /path/to/mlir/include \
-o /tmp/mlir-go-skip-report.jsonThe checked-in dialect_manifest.json is the contract for supported generated surfaces. This first version validates the manifest, scans upstream TableGen operation definitions, and emits a machine-readable skip report for operations that are not generated yet.
The module path is:
import mlir "github.com/timmyyuan/mlir-go"The first convenience dialect layers live under:
import "github.com/timmyyuan/mlir-go/builder"
import builderarith "github.com/timmyyuan/mlir-go/builder/arith"
import buildercf "github.com/timmyyuan/mlir-go/builder/cf"
import builderfunc "github.com/timmyyuan/mlir-go/builder/func"
import arith "github.com/timmyyuan/mlir-go/dialect/arith"
import cf "github.com/timmyyuan/mlir-go/dialect/cf"
import funcdialect "github.com/timmyyuan/mlir-go/dialect/func"
import linalg "github.com/timmyyuan/mlir-go/dialect/linalg"
import memref "github.com/timmyyuan/mlir-go/dialect/memref"
import scf "github.com/timmyyuan/mlir-go/dialect/scf"
import tensor "github.com/timmyyuan/mlir-go/dialect/tensor"
import vector "github.com/timmyyuan/mlir-go/dialect/vector"Downstream projects need an equivalent cgo environment.
Use scripts/dev-env.sh as the reference setup; it selects -lc++ on Darwin and -lstdc++ on Linux.
A minimal setup for the current surface looks like:
cxx_runtime="-lc++" # use -lstdc++ on Linux
export CGO_CPPFLAGS="-I$(llvm-config --includedir)"
export CGO_LDFLAGS="-L$(llvm-config --libdir) -Wl,-rpath,$(llvm-config --libdir) -lMLIR -lMLIRCAPIIR -lMLIRCAPIRegisterEverything -lMLIRCAPITransforms -lMLIRCAPIConversion -lMLIRCAPIExecutionEngine -lMLIRExecutionEngine -lMLIRCAPIFunc -lMLIRCAPIArith -lMLIRCAPILLVM $(llvm-config --libs --system-libs) ${cxx_runtime}"The repository also includes scripts/test-downstream-consumer.sh as a reference downstream integration check. It creates a temporary module outside the repository tree, imports github.com/timmyyuan/mlir-go plus builder helper packages, and runs a minimal IR-generation program.
The current public API is still intentionally small, but it already supports parse, traversal, operand/attribute/successor queries, symbol lookup and rewrite flows, builtin type and attribute construction, shaped and memref type helpers, generic IR building, a first stateful builder with dialect emit helpers, verification, lowering, and JIT execution:
ctx, err := mlir.NewContext()
if err != nil {
// handle error
}
defer ctx.Close()
if err := ctx.RegisterAllDialects(); err != nil {
// handle error
}
i32, err := mlir.SignlessIntegerType(ctx, 32)
if err != nil {
// handle error
}
loc, err := mlir.UnknownLocation(ctx)
if err != nil {
// handle error
}
attr, err := mlir.IntegerAttribute(i32, 5)
if err != nil {
// handle error
}
constOp, err := arith.Constant(ctx, loc, i32, attr)
if err != nil {
// handle error
}
defer constOp.Close()
diags, err := ctx.CaptureDiagnostics(func() error {
_, err := mlir.ParseModule(ctx, "module {\n func.func @broken(\n}\n")
return err
})
if err != nil && len(diags) > 0 {
_ = diags[0].String()
}
mod, err := mlir.ParseModule(ctx, "module {\n}\n")
if err != nil {
// handle error
}
defer mod.Close()For a full builder-based example, see examples/build_add/main.go.
The design notes live under spec/.