dsh-mlir-toolchain
MLIR toolchain tools for DeepSeek Harness (DSH) — probe, run, TableGen and parse-check MLIR without shell quotes or ENOENT surprises.
English | 简体中文
This is a DSH plugin. It turns a toolchain.json (generated by your MLIR build script) into four model-facing tools backed by the host subprocess service:
| Tool | What it does |
|---|---|
mlir_probe |
Reads toolchain.json, probes every binary in parallel with --version, and reports ok, missing vs. failing tools, version mismatches and the TableGen include directories that would be used |
mlir_run |
Runs mlir-opt, mlir-translate, mlir-tblgen, mlir-runner, mlir-reduce, llc, clang, filecheck, llvm-config or llvm-objdump with an argv array (no shell) |
mlir_tblgen |
-gen-* code generation with automatic mlir/IR/OpBase.td discovery, 35 named kinds, raw flag passthrough and auto-created output directories |
mlir_parse_check |
Parses/verifies MLIR with mlir-opt, optionally running a pass pipeline, and returns structured errors/warnings/notes |
Nothing is executed through a shell, no binaries are looked up on PATH, and the plugin has zero runtime dependencies.
Requirements
- Node.js
^22.19.0 || >=24.0.0 - DeepSeek Harness
>=0.2.1-rc.5 <0.3.0-0 - LLVM/MLIR binaries. Development and CI checks were done against LLVM 23.1.4, but any recent release that provides
mlir-opt,mlir-translate,mlir-tblgenandllcshould work. mlir-tblgenadditionally needs MLIR's TableGen headers (mlir/IR/OpBase.td) — from a source checkout or an installedmlir/include.
A toolchain.json is recommended. Its shape (extra fields are ignored):
{
"llvm": {
"version": "23.1.4",
"root": "/opt/llvm",
"mlirRoot": "/opt/llvm-build",
"tools": {
"mlirOpt": { "path": "/opt/llvm-build/bin/mlir-opt", "version": "23.1.4" },
"mlirTranslate": { "path": "/opt/llvm-build/bin/mlir-translate", "version": "23.1.4" },
"mlirTblgen": { "path": "/opt/llvm-build/bin/mlir-tblgen", "version": "23.1.4" },
"llc": { "path": "/opt/llvm/bin/llc", "version": "23.1.4" },
"mlirRunner": { "path": "/opt/llvm-build/bin/mlir-runner" },
"llvmConfig": { "path": "/opt/llvm/bin/llvm-config" }
}
},
"mlirDev": {
"include": "/opt/llvm-project/mlir/include",
"lib": "/opt/llvm-build/lib"
}
}
Recognised llvm.tools keys: mlirOpt, mlirTranslate, mlirTblgen, mlirRunner, mlirReduce, llc, opt, clang, llvmConfig, llvmObjdump, cmake, ninja, filecheck.
Install
# from the directory that contains this checkout
dsh plugin --profile desktop add ./dsh-mlir-toolchain
The CLI adds the package to the profile's dsh.profile.bundles; the plugin registers itself through its bundled cordis.patch.yml. Server-side plugins hot-reload — the four tools appear immediately. Uninstall with:
dsh plugin --profile desktop remove dsh-mlir-toolchain
If you are trying the plugin inside a sandboxed session (DSH
workspace-write), the profile directory may live outside the workspace. Install it from a normal terminal or grant the operation the permission it asks for.
Configuration
Zero configuration works when toolchain.json sits next to the plugin directory (the common mlir-lang/dsh-mlir-toolchain + mlir-lang/toolchain.json layout), inside it, in the session cwd, or in <cwd>/mlir-lang/. Discovery runs once at plugin load and again per call when no path was found — so a session opened in a different directory still picks it up.
Precedence: config.toolchainPath / MLIR_TOOLCHAIN_JSON > auto-discovery.
To point at a specific file (or override individual binaries):
- id: mlir-toolchain
name: 'dsh-mlir-toolchain'
config:
toolchainPath: /opt/llvm/toolchain.json # optional
includeDirs: # optional: extra -I roots
- /opt/llvm-project/mlir/include
timeoutMs: 120000
maxOutputBytes: 1048576
logKeep: 100
# Single-binary overrides (absolute or cwd-relative):
# mlirOptPath, mlirTranslatePath, mlirTblgenPath, mlirRunnerPath,
# mlirReducePath, llcPath, optPath, clangPath, filecheckPath,
# llvmObjdumpPath, llvmConfigPath
| Option | Default | Meaning |
|---|---|---|
toolchainPath |
auto | Absolute path to toolchain.json |
includeDirs |
[] |
Extra TableGen include roots |
timeoutMs |
120000 |
Per-call timeout (clamped to 1s–10min) |
maxOutputBytes |
1048576 |
stdout/stderr bytes returned per stream (clamped to 4 KiB–8 MiB) |
graceMs |
5000 |
Grace period before a timed-out process is killed |
logKeep |
100 |
Keep only the newest N files in .mlir-logs/ |
Environment variables: MLIR_TOOLCHAIN_JSON (path to toolchain.json), MLIR_INCLUDE_DIR (TableGen include roots, ;/: separated).
Tools
mlir_probe
Probes every tool referenced by the configuration, in parallel.
{ "ok": true, "source": "toolchain.json",
"llvm": { "version": "23.1.4", "root": "/opt/llvm", "mlirRoot": "/opt/llvm-build" },
"tools": { "mlir-opt": { "path": "...", "exists": true, "ok": true, "version": "23.1.4" } },
"missing": [], "failed": [], "optionalMissing": ["filecheck"],
"versionMismatch": [], "tblgenInclude": { "source": "auto", "dirs": ["..."] } }
missing means the file does not exist; failed means it exists but --version failed. TableGen include directories are reported here so you can spot a broken -I before generating code.
mlir_run
{ "tool": "mlir-opt", "args": ["--pass-pipeline=builtin.module(cse)"],
"inputText": "module { ... }", "cwd": ".", "timeoutMs": 30000 }
Returns { ok, exitCode, timedOut, durationMs, stdout, stderr, truncated, logPath, hint }. inputText is written to a temporary .mlir file and appended as the last argument; inputPath is resolved against the session cwd. Full output is written to .mlir-logs/ when it exceeds maxOutputBytes, and hint points at the log.
mlir_tblgen
{ "kind": "op-decls", "inputPath": "include/MyOps.td",
"outputPath": "build/MyOps.h.inc" }
kindaccepts named kinds (op-decls,op-defs,op-doc,op-interface-decls/defs/docs,dialect-decls/defs/doc,dialect-interface-decls,typedef-decls/defs/doc,attrdef-decls/defs/doc,enum-decls/defs/doc,attr-constraint-decls/defs,type-constraint-decls/defs,rewriters,pass-decls,pass-doc,pass-capi-header,pass-capi-impl,bytecode,directive-decl,llvmir-conversions,enum-from/to-llvmir-conversions,op-from-llvmir-conversions,convertible-llvmir-intrinsics,python-op-bindings,python-enum-bindings, …) or a rawgen:<name>/-gen-<name>flag.includeDirsis optional; omit it and the plugin finds the MLIR include root itself.outputPathis optional (stdout otherwise); parent directories are created automatically.- Returns
{ ok, exitCode, timedOut, outputPath, generatedLines, bytes, includeDirs, includeSource, logPath, hint }.
Include resolution order: tool argument → plugin includeDirs → MLIR_INCLUDE_DIR → automatic candidates. A candidate is used only if it actually contains mlir/IR/OpBase.td and is probed among: mlirDev.include/lib/cmakeDir (and their parents), <llvm.root>/include, <mlirRoot>/include, <mlirRoot>/tools/mlir/include, and llvm-src/*/mlir/include beside either root or one level up. When nothing is found, the hint lists every directory that was searched.
mlir_parse_check
{ "source": "module { func.func @f(%a: i32) -> i32 { return %a : i32 } }",
"passes": ["cse", "canonicalize"], "maxDiagnostics": 200 }
sourceandpathare mutually exclusive (supplying both is an error, nothing is spawned).passesaccepts a full pipeline string (builtin.module(cse,canonicalize)) or an array, which is wrapped automatically. It runs through--pass-pipeline=.- Returns structured
errors,warnings,notes(file,line,col,severity,message),exitCode,passPipeline,logPath. Diagnostics for inline sources use<source>as the file name. allowDiagnostics: truereportsok: trueeven when diagnostics exist (useful for expected-warning tests).
Design constraints
- Uses only the host services
ctx.subprocessandctx.tools; no imports from@deepseek-ai/dsh-*, no runtime dependencies. - Every command is an argv array — never a shell string.
- Working directory defaults to the session cwd; relative paths are resolved against it.
apply()performs no long blocking work: all probing happens insidemlir_probe(parallel spawns).- Failures are returned as data (
ok:false+ a hint), not thrown — a missing toolchain never prevents the host or the plugin from starting.
Development
npm test # unit tests (self-contained fixtures) + real integration tests (skipped when no toolchain.json / no spawn permission)
npm run smoke # real end-to-end check of all four tools
- Tests must run with
--test-isolation=noneand an explicit file list:node --test test/forks worker processes, and forking is denied inside DSH'sworkspace-writesandbox (spawn EPERM). If you run the suite there, the integration file skips itself. test/fixtures.mjsbuilds a temporary fake toolchain (bin/*.exeplaceholders,llvm-src/.../OpBase.td,toolchain.json), so unit tests never touch your machine's LLVM and never write logs into the repo.test/integration.test.mjsandtest/smoke.mjsexercise real binaries; they readMLIR_TOOLCHAIN_JSON(or the same auto-discovery) and skip/report clearly when none is available.
No comments yet. Be the first to write one.