Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 283 additions & 0 deletions .agent/plans/mqt-shared-infrastructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
# Consolidate shared MQT compiler infrastructure

This ExecPlan is a living document. The sections `Progress`,
`Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must
be kept up to date as work proceeds.

This ExecPlan must be maintained in accordance with `.agent/PLANS.md` from the
repository root.

## Purpose / Big Picture

The compiler collection currently stores MQT-owned passes and quantum-specific
helpers below the broad path `mlir/Dialect/Utils`. That path overlaps MLIR's own
shared dialect utilities and obscures which code owns each semantic contract.
After this change, MQT-owned cross-dialect transformations and IR-aware quantum
helpers live below `mlir/Dialect/MQT`, and the quantum-aware module equivalence
checker is built only for tests. The utility implementations form one leaf
`MLIRMQTUtils` library. Existing pass command names and behavior remain
unchanged.

The result is observable by building the same compiler and tests with no source
file below the project-owned `mlir/Dialect/Utils` directory. The generated MQT
documentation lists both the MQT metadata dialect and its cross-dialect passes.

## Progress

- [x] (2026-08-20 15:13Z) Create a dedicated worktree and branch from the
current head of the symbolic Qiskit parameter pull request.
- [x] (2026-08-20 15:13Z) Audit the shared transform, utility, verifier, test,
and build dependencies.
- [x] (2026-08-20 15:53Z) Move the existing MQT transform package and its
normalization tests.
- [x] (2026-08-20 15:53Z) Split quantum semantics and generic folding helpers by
owner.
- [x] (2026-08-20 15:53Z) Move the module equivalence checker into test support.
- [x] (2026-08-20 15:53Z) Update all includes, namespaces, CMake targets, and
documentation.
- [x] (2026-08-20 15:53Z) Run focused tests, the release build, the
documentation build, and lint.
- [x] (2026-08-20 15:53Z) Inspect the final diff and prepare signed, scoped
local commits.
- [x] (2026-08-20 17:30Z) Rebase the two follow-up commits onto the merged
symbolic Qiskit parameter change and repeat the publication checks.
- [x] (2026-08-20 19:30Z) Place MQT-owned folding support in `mlir::mqt` below
`mlir/Support/MQT` and address all review comments.
- [x] (2026-08-20 19:30Z) Repeat the release and non-unity builds, focused and
full tests, repository lint, and the full pull request Clang-Tidy diff.
- [x] (2026-08-20 21:45Z) Replace the installed support header and header-only
utility set with a cohesive `Dialect/MQT/Utils` package and leaf library.
- [x] (2026-08-20 22:20Z) Format the utility package, pass focused Clang-Tidy,
build the full release tree, and pass all 4,303 configured tests.
- [x] (2026-08-20 22:22Z) Inspect the final diff and prepare a signed local
commit for the utility package revision.

## Surprises & Discoveries

- Observation: The legacy transform package is already MQT-owned except for its
path. Its target is `MLIRMQTTransforms`, its generated pass group is `MQT`,
and its C++ namespace is `mlir::mqt`. Evidence: the files
`mlir/include/mlir/Dialect/MQT/Transforms/Passes.td` and
`mlir/lib/Dialect/Utils/Transforms/CMakeLists.txt`.
- Observation: The project adds custom headers to MLIR's existing
`mlir/Dialect/Utils` include hierarchy, which also supplies upstream headers
such as `StaticValueUtils.h`. A clean split must remove only project-owned
files and must continue using upstream headers from that path.
- Observation: `mlir/Support/IRVerification` is production-built but every
caller is a unit test. The implementation directly depends on QC, QCO,
QTensor, SCF, and LLVM IR details, so it is test infrastructure rather than a
generic production verifier.
- Observation: Exposing support helpers through `mlir::mqt` revealed unqualified
references to the repository's top-level `mqt::test` namespace in unit tests.
Root-qualifying those references as `::mqt::test` removes the ambiguity and
keeps the support helpers in their owning namespace.
- Observation: Pull request #2189 merged while this work was in progress and
expanded the global-phase tests. The follow-up branch was rebased onto the
updated pull request #2150 head, which already includes #2189. The moved test
retains those semantic checks.
- Observation: The release preset does not build the Python MLIR bindings. The
strict Sphinx build found stale Qiskit import and export includes after the
utility split. The corrected binding target and the full Sphinx build now
pass.
- Observation: Current upstream MLIR keeps `mlir/Support` independent of IR,
dialect, and interface libraries. IR-aware shared helpers instead use
dedicated utility libraries, such as `MLIRDialectUtils`, below the IR or
dialect hierarchy.
- Observation: `add_mlir_dialect_library` appends its target to
`MLIR_DIALECT_LIBS`. `MLIRMQTUtils` is not a dialect registration target and
must remain below the MQT, QC, and QCO dialect libraries, so
`add_mlir_library` describes its role and dependencies more accurately.

## Decision Log

- Decision: Keep `MLIRMQTDialect` and `MLIRMQTTransforms` as separate targets.
Rationale: an IR dialect library must not pull pass infrastructure and
cross-dialect rewrite dependencies into every metadata consumer. Date/Author:
2026-08-20 / Codex.
- Decision: Move both `NormalizeGlobalPhases` and `UnrollModifiers` as one
transform package. Rationale: both passes already share the MQT namespace,
generated pass group, target, and QC/QCO ownership. Moving only one would
retain an artificial package split. Date/Author: 2026-08-20 / Codex.
- Decision: Preserve the pass arguments, target names, and dependent dialect
lists. Rationale: ownership does not change runtime behavior, and MLIR pass
dependencies describe dialect entities that a pass may create. The MQT
transforms create Arith, QC, and QCO entities but no MQT entities.
Date/Author: 2026-08-20 / Codex.
- Decision: Do not introduce a shared QC/QCO unitary operation interface.
Rationale: QC has reference semantics and QCO has value semantics. The user
explicitly excluded that abstraction from this refactor. Date/Author:
2026-08-20 / Codex.
- Decision: Split the former `Utils.h` by semantic responsibility instead of
renaming the monolith. Rationale: a path move alone would preserve unclear
ownership and excessive include dependencies. Date/Author: 2026-08-20 / Codex.
- Decision: Do not preserve forwarding headers below the old project-owned
`mlir/Dialect/Utils` path. Rationale: the compiler collection and these APIs
are part of the unreleased general launch, and the requested cleanup should
remove the ambiguous project-owned include surface. Date/Author: 2026-08-20 /
Codex.
- Decision: Put all IR-aware shared helpers below `mlir/Dialect/MQT/Utils` in
`mlir::mqt`, including constant folding. Rationale: the helpers depend on MLIR
IR, dialects, or interfaces, so `mlir/Support` is the wrong dependency layer.
A named owner and a dedicated library match current MLIR organization without
creating loose headers or an umbrella include. Date/Author: 2026-08-20 /
Codex.
- Decision: Split the utility surface into `Angles`, `ConstantFolding`,
`DenseUnitary`, `GatePowering`, `Modifiers`, and `Parameters`. Rationale: each
header has one semantic responsibility and enough related declarations to
justify the file. `Math.h` and an umbrella `Utils.h` would hide those
contracts. Non-template implementations belong in matching source files.
Date/Author: 2026-08-20 / Codex.
- Decision: Build the helper package with `add_mlir_library(MLIRMQTUtils)` and
link only `MLIRArithDialect`, `MLIRIR`, and `MLIRSideEffectInterfaces`.
Rationale: the target is an IR-aware leaf library, not a dialect registration
library. It must not link the MQT, QC, or QCO dialects because those dialects
consume it. Date/Author: 2026-08-20 / Codex.
- Decision: Build the module equivalence checker in one concrete test-support
library and expose that library through `MLIRTestCaseUtils`. Rationale: every
caller is a unit test, while a single target avoids repeating its quantum
dialect dependencies across test directories. Date/Author: 2026-08-20 / Codex.

## Outcomes & Retrospective

The follow-up now has explicit ownership boundaries. MQT owns the cross-dialect
passes, IR-aware quantum semantics, and project-specific constant folding in a
dedicated utility library. Unit test support owns the module equivalence
checker. The implementation does not add a shared QC/QCO unitary interface.

After pull request #2150 merged, the follow-up commits were rebased onto its
squash commit. The final review update passed the release build and the
non-unity debug build, including the Python MLIR binding. Five focused binaries
passed 322 tests. CTest reported 100% success across 4,301 configured tests; one
QDMI test was skipped by its own condition. The repository lint suite and the
full pull request Clang-Tidy diff passed. Generated MLIR documentation and the
strict Sphinx documentation build passed before the final review update, which
does not change generated documentation or Sphinx inputs. The signed commits are
ready for review.

The utility package revision builds all six implementation files separately in
the non-unity lint configuration and builds the complete release tree. Focused
Clang-Tidy reports no project diagnostics across the six implementations and two
utility test files. The utility binary passes 20 tests. CTest reports 100%
success across all 4,303 configured tests; one QDMI test is skipped by its own
condition. Repository lint and the final diff check pass.

## Context and Orientation

MQT Core embeds an MLIR-based compiler collection below `mlir/`. QC represents
mutable qubit references. QCO represents linear qubit values. The MQT dialect,
defined in `mlir/include/mlir/Dialect/MQT/IR/MQTDialect.td`, owns metadata and
other infrastructure shared across those representations.

The files below `mlir/include/mlir/Dialect/MQT/Transforms` and
`mlir/lib/Dialect/Utils/Transforms` define two module passes. Global-phase
normalization combines and moves `qc.gphase` and `qco.gphase` operations while
preserving modifier semantics. Modifier unrolling splits multi-operation QC and
QCO control, inverse, and power regions. Their library is already named
`MLIRMQTTransforms`.

The MQT utility headers define angle and global-phase contracts, constant
folding, dense-unitary validation, gate powering, parameter handling, and
modifier-region rewrites. QC, QCO, builders, transforms, conversions, and the
Qiskit binding consume different subsets of those contracts. They need a shared
leaf target that does not depend on any consuming dialect.

The function `areModulesEquivalentWithPermutations` is declared in
`mlir/include/mlir/Support/IRVerification.h`, implemented in
`mlir/lib/Support/IRVerification.cpp`, and called only by tests. The test root
already provides an interface target named `MLIRTestCaseUtils`; a new concrete
`MLIRTestSupport` target can carry the checker implementation without exposing
it through the installed `MLIRSupportMQT` library.

## Plan of Work

Move the transform headers, TableGen file, generated include locations, source
files, and CMake files to matching `Dialect/MQT/Transforms` directories. Update
all includes and parent CMake files. Move the global-phase normalization test to
`mlir/unittests/Dialect/MQT/Transforms`. Leave QC- and QCO-specific modifier
tests with their respective dialect tests.

Create cohesive headers below `mlir/include/mlir/Dialect/MQT/Utils`: `Angles`,
`ConstantFolding`, `DenseUnitary`, `GatePowering`, `Modifiers`, and
`Parameters`. Do not add `Math.h` or an umbrella header. Put non-template
implementations in matching source files below `mlir/lib/Dialect/MQT/Utils`.
Build them as the leaf `MLIRMQTUtils` target and link direct consumers to it.
Keep the constant-folding tests with the utility package below
`mlir/unittests/Dialect/MQT/Utils`.

Move the quantum module equivalence header and implementation below
`mlir/unittests/Support`. Build them in a test-only `MLIRTestSupport` target and
make `MLIRTestCaseUtils` expose that target to configured test executables.
Remove the implementation and its test-only dependencies from `MLIRSupportMQT`.
Update every test include.

Remove the now-empty project-owned `Dialect/Utils` CMake subdirectories and
parent `add_subdirectory(Utils)` entries. Continue using upstream includes such
as `mlir/Dialect/Utils/StaticValueUtils.h`; those files are not part of this
repository and must not be changed.

Extend `docs/mlir/MQT.md` with the generated MQT pass reference. Do not add a
changelog entry because the change reorganizes unreleased compiler collection
internals without changing supported behavior or command-line interfaces.

## Concrete Steps

Run all commands from the repository root. Use `git status --short` before each
edit batch. Apply source edits with `apply_patch`; use ordinary `mv` only for
pure file relocation, followed by explicit content patches.

Configure and build the release preset:

cmake --preset release
cmake --build --preset release

During iteration, build and run the MQT transform, support, QC IR, QCO IR, and
compiler tests. The exact target names will be recorded after the CMake split.
Generate the pass and dialect documentation with:

cmake --build --preset release --target mlir-doc
uvx nox --non-interactive -s docs

Finish with:

ctest --preset release
uvx nox -s lint

Before each commit, inspect `git diff --check`, the staged diff, and the
complete commit message. Sign each commit and verify it with
`git verify-commit HEAD`. Do not push or create a pull request without separate
authorization.

## Validation and Acceptance

The repository contains no project-owned files below `mlir/Dialect/Utils`, and
no project source includes the removed custom paths. Includes of upstream MLIR
headers in that hierarchy remain valid.

The textual pass names `normalize-global-phases` and `unroll-modifiers` still
parse and run. Their focused QC and QCO tests pass with unchanged semantic
expectations. QC and QCO reject the same invalid global-phase values through one
shared helper, and both unitary interfaces apply one shared finite-parameter
validator. Dense-unitary and gate-power tests pass from their new include
locations. Constant-folding tests pass from the MQT utility test suite.

`MLIRMQTUtils` builds as an ordinary MLIR library. It links only MLIR Arith, IR,
and side-effect interfaces. It does not register as a dialect library and does
not link MQT, QC, or QCO. All direct consumers declare the utility target.

The production `MLIRSupportMQT` target no longer compiles or installs the
quantum module equivalence checker. All existing tests that compare modules
still link and pass through `MLIRTestSupport`.

The release build, configured CTest suite, generated MLIR documentation, full
Sphinx documentation, and repository lint suite complete successfully. The
worktree is clean after signed local commits, and no remote state changes are
made for this follow-up branch.

## Idempotence and Recovery

The include replacements, formatting, builds, and tests are repeatable. CMake
may retain stale generated include paths after the move; rerun
`cmake --preset release` before diagnosing missing generated files. If a move is
interrupted, use `git status --short` to identify source and destination files,
then complete the move without deleting unrelated work. Never reset the worktree
or discard changes from another task.
1 change: 1 addition & 0 deletions bindings/mlir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ if(NOT TARGET ${TARGET_NAME})
MQTCompilerQDMIAdapter
MQTCompilerPipeline
MLIRMQTDialect
MLIRMQTUtils
MLIRQCTranslationSupport)

if(MQT_QISKIT_CAPI_CANDIDATE_VERSION)
Expand Down
10 changes: 6 additions & 4 deletions bindings/mlir/qiskit/QiskitExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
#include "mlir/Dialect/CBit/IR/CBitDialect.h"
#include "mlir/Dialect/CBit/IR/CBitOps.h"
#include "mlir/Dialect/MQT/IR/MQTDialect.h"
#include "mlir/Dialect/MQT/Utils/ConstantFolding.h"
#include "mlir/Dialect/MQT/Utils/Parameters.h"
#include "mlir/Dialect/QC/IR/QCDialect.h"
#include "mlir/Dialect/QC/IR/QCInterfaces.h"
#include "mlir/Dialect/QC/IR/QCOps.h"
#include "mlir/Dialect/QC/Translation/StandardGate.h"
#include "mlir/Dialect/Utils/Utils.h"

#include <llvm/ADT/DenseMap.h>
#include <llvm/ADT/DenseSet.h>
Expand Down Expand Up @@ -117,7 +118,7 @@ using ExportedParameters = llvm::DenseMap<mlir::Value, Parameter>;
if (++nodes > MAX_PARAMETER_EXPRESSION_NODES) {
throwExportedParameterExpressionSizeError();
}
if (const auto number = mlir::utils::valueToDouble(value)) {
if (const auto number = mlir::mqt::valueToDouble(value)) {
auto result = numberParameter(*number);
parameters.try_emplace(value, result);
return result;
Expand Down Expand Up @@ -376,12 +377,13 @@ void addGlobalPhase(ExportState& state, const Parameter& phase) {
state.globalPhase = Parameter::number(sum);
return;
}
if (std::abs(number->value) <= mlir::utils::TOLERANCE) {
if (std::abs(number->value) <= mlir::mqt::PARAMETER_COMPARISON_TOLERANCE) {
return;
}
} else if (const auto* globalNumber = state.globalPhase.getNumber();
globalNumber != nullptr &&
std::abs(globalNumber->value) <= mlir::utils::TOLERANCE) {
std::abs(globalNumber->value) <=
mlir::mqt::PARAMETER_COMPARISON_TOLERANCE) {
state.globalPhase = phase;
return;
}
Expand Down
6 changes: 3 additions & 3 deletions bindings/mlir/qiskit/QiskitImport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
#include "mlir/Compiler/Programs.h"
#include "mlir/Dialect/CBit/IR/CBitDialect.h"
#include "mlir/Dialect/MQT/IR/MQTDialect.h"
#include "mlir/Dialect/MQT/Utils/DenseUnitary.h"
#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h"
#include "mlir/Dialect/QC/IR/QCDialect.h"
#include "mlir/Dialect/QC/Translation/StandardGate.h"
#include "mlir/Dialect/QCO/IR/QCODialect.h"
#include "mlir/Dialect/QTensor/IR/QTensorDialect.h"
#include "mlir/Dialect/Utils/DenseUnitary.h"

#include <llvm/ADT/APInt.h>
#include <llvm/ADT/DenseMap.h>
Expand Down Expand Up @@ -324,10 +324,10 @@ denseUnitaryArity(const Instruction& instruction) {
"Qiskit unitary instruction has an unsupported operand arity");
}
const auto targets = instruction.qubits.size() - controls;
if (targets > mlir::utils::MAX_DENSE_UNITARY_QUBITS) {
if (targets > mlir::mqt::MAX_DENSE_UNITARY_QUBITS) {
throw std::runtime_error(
"Qiskit unitary supports at most " +
std::to_string(mlir::utils::MAX_DENSE_UNITARY_QUBITS) + " qubits");
std::to_string(mlir::mqt::MAX_DENSE_UNITARY_QUBITS) + " qubits");
}
return {.controls = controls, .targets = targets};
}
Expand Down
6 changes: 6 additions & 0 deletions docs/mlir/MQT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ tocdepth: 3
```{include} Dialects/MQTDialect.md

```

## Passes

```{include} Passes/MQTTransforms.md

```
1 change: 0 additions & 1 deletion mlir/include/mlir/Dialect/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ add_subdirectory(QC)
add_subdirectory(QCO)
add_subdirectory(QIR)
add_subdirectory(QTensor)
add_subdirectory(Utils)
1 change: 1 addition & 0 deletions mlir/include/mlir/Dialect/MQT/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
# Licensed under the MIT License

add_subdirectory(IR)
add_subdirectory(Transforms)
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
namespace mlir::mqt {

#define GEN_PASS_DECL
#include "mlir/Dialect/Utils/Transforms/Passes.h.inc" // IWYU pragma: export
#include "mlir/Dialect/MQT/Transforms/Passes.h.inc" // IWYU pragma: export

#define GEN_PASS_REGISTRATION
#include "mlir/Dialect/Utils/Transforms/Passes.h.inc" // IWYU pragma: export
#include "mlir/Dialect/MQT/Transforms/Passes.h.inc" // IWYU pragma: export

} // namespace mlir::mqt
Loading
Loading