Skip to content

Vendor pre-generated ODS output in the melior fork and solx-mlir — get tblgen out of proc-macro expansion for good #622

Description

@nebasuke

Summary

melior-macro's dialect! macro runs TableGen inside rustc's process: the proc-macro crate links LLVM's libTableGen (C++) via tblgen-rs, parses .td files at macro-expansion time, and emits the typed op wrappers. A proc-macro that links LLVM C++ is the architectural oddity behind an entire class of Windows CI breakage, of which we have now patched three symptoms individually:

Symptom Failure point Workaround currently in place
#550 link time — GCC 16 COMDAT collision linking the proc-macro cdylib against static libstdc++ rm libstdc++.a in build-toolchain/action.yml (forces shared libstdc++)
#589 load time — rustc's stale bundled libwinpthread-1.dll shadows MSYS2's runtime when LoadLibraryExW resolves the proc-macro's DLL chain rm libwinpthread-1.dll (#590)
NomicFoundation/solx-llvm#135 link time — no-assertions Release LLVM compiles out Record/RecordVal/Init::dump(), which tblgen-rs references; PE linking resolves eagerly keep dump() in no-assertions builds (LLVM fork patch, verified by #619)

Each fix is correct, but the class remains: the proc-macro is one toolchain-drift or cache-fingerprint change away from the next symptom. The structural fix flagged in #589's follow-ups is to remove tblgen from macro expansion entirely.

The output of dialect! is a pure function of pinned inputs — the .td files (LLVM pinned at 21.1; SolOps.td/YulOps.td pinned by the solx-llvm submodule) and the generator code (melior fork pinned by rev). Nothing about it needs to run on the consumer's machine, let alone inside rustc. So: run the generation offline, commit the pretty-printed Rust, and delete the macro invocations.

Current state

melior-macro exports eight proc-macros. Seven are pure Rust token generation. Only dialect! uses tblgen (macro/src/dialect.rs): it builds a TableGenParser, parses the named .td files out of LLVM_INCLUDE_DIRECTORY (baked in by the macro crate's build.rs via llvm-config --includedir), walks all Op-derived records, and emits a module of op wrappers, type-state builders, and a per-dialect operation enum. Because Cargo links a crate's dependencies into the proc-macro cdylib, this one macro is why melior_macro.dll carries LLVM C++.

Two invocation sites:

  • melior forkmelior/src/dialect/ods.rs: ~40 dialect! invocations for the upstream MLIR dialects, behind the ods-dialects feature.
  • solx-mlirsrc/ods.rs: two invocations for the sol and yul dialects, with .td files from solx-llvm's MLIR tree.

Plan

  1. Split melior-macro into a melior-ods-codegen library crate (keeps the tblgen dep; the entry point generate_dialect(DialectInput) -> TokenStream is already cleanly factored) and the melior-macro proc-macro crate, which keeps only the seven pure-Rust macros. melior-macro loses its tblgen dependency and its build.rs entirely — no native links, no LLVM needed to build it.
  2. Add a standalone generator binary (xtask-style, e.g. melior-ods-gen) depending on melior-ods-codegen: takes the same {name, files, include_directories} tuples, runs the existing code path as a normal process, pretty-prints with prettyplease, writes .rs files.
  3. Commit the generated output at both sites:
    • melior fork: replace the ods.rs invocations with committed generated modules. Consider trimming to the dialects solx-mlir actually uses — the full set is a lot of vendored code for dialects we will never touch.
    • solx-mlir: replace src/ods.rs with committed ods/sol.rs / ods/yul.rs. Natural home for the regen command is solx-dev, since the TABLEGEN_210_PREFIX/MLIR_SYS_210_PREFIX plumbing already exists in .cargo/config.toml.
  4. CI staleness check: regenerate and diff against the committed copy (same pattern as EDR's "typings file is up to date" job). Load-bearing, not optional — see cadence note below.
  5. Mark generated files linguist-generated in .gitattributes.

What this makes deletable

The failure class — "toolchain drift under the cache breaks the proc-macro" — dies on all platforms, instead of being patched per-symptom on Windows.

Caveats

  • Sol/Yul regen is not rare. The "LLVM is pinned, regeneration is rare" argument holds for melior's upstream MLIR dialects, but SolOps.td/YulOps.td are under active development — every new op means a regen commit riding along in the PR. Hence the CI check; same living arrangement as EDR's committed index.d.ts, including the same failure mode it catches (edit the .td, forget to regen).
  • Bulk. The full ODS set pretty-printed is large; trimming to used dialects helps materially.
  • Determinism. tblgen record order is deterministic given identical inputs and prettyplease is stable, but the generator must run against the same LLVM pin the .td files come from.
  • Fork divergence. The upstreamable variant (move tblgen to build.rs + include!; raviqqe/melior could take it) only fixes the load-time class — build scripts are separate processes, but they still link tblgen, so the solx-llvm#135 patch would have to stay. Since we already maintain the fork and pin LLVM, vendoring is the variant that pays for itself.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions