From 5da4ba64372baf8a3315c66d8a1990401fa9c3c5 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 24 Sep 2025 10:27:40 +0200 Subject: [PATCH 001/419] =?UTF-8?q?=F0=9F=9A=A7=20initial=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 700 +++++++++++++++++++++++ 1 file changed, 700 insertions(+) create mode 100644 docs/mlir/quantum-dialect-revamp-plan.md diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md new file mode 100644 index 0000000000..370e6b32d2 --- /dev/null +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -0,0 +1,700 @@ +# Quantum Dialect Revamp — Implementation Plan (Revision 5) + +Author: Junie (auto-generated) +Date: 2025-09-24 +Status: Design document, no IR code changes committed yet +Scope: MLIR dialect hierarchy under mlir/, affecting MQTRef and MQTOpt dialects and shared Common utilities + +## 1. Context and Goals + +We currently have two working quantum dialects: + +- mqtref: reference/memory semantics +- mqtopt: value semantics + +Both support a wide set of unitary "basis" gates and basic resources (qubit, alloc/dealloc, reset, measure). However, usage is cumbersome, builders are verbose, and the treatment of modifiers (controls, inverse/adjoint, powering) is embedded into each gate op, which complicates composition, normalization, and ergonomics. + +This plan proposes a fundamental rework that: + +- Makes composition first-class: modifiers are explicit, uniform IR constructs that wrap gates or sequences and lazily defer evaluation. +- Unifies analysis via interfaces: a single UnitaryOpInterface to query gate properties (targets, controls, parameters, unitary, etc.) without pattern matching op kinds, while accommodating the different semantics of mqtref and mqtopt. +- Greatly improves ergonomics: C++ CircuitBuilder helpers and parser/printer sugar to write MLIR like mqtref.ctrl(%c) mqtref.x %q or C++ like builder.x(q).ctrl(c). +- Keeps dialect-specific semantics minimal and uniform while sharing as much as possible between mqtref and mqtopt via Common ODS, traits, and support libraries. +- Remains analysis- and transformation-friendly and provides canonicalization and verifiers to maintain a normalized IR. + +No backward compatibility is required. We do not change IR code yet; this document details the implementation plan. + +## 2. High-level Architecture + +2.1. Layering + +- Common (shared between both dialects) + - Traits and interfaces (TargetArity, ParameterArity, NoControl, UniqueSize/Index, plus new ones). + - UnitaryOpInterface (single definition in Common with dialect adapters). + - UnitaryExpr support library for symbolic unitary expressions and tiny fixed-size matrices. + - Parser/printer utils for modifiers, sequences, parameters, and sugar. +- mqtref (reference semantics) + - Types: Qubit only + - BaseGate ops (no controls/inverse/pow; only targets and parameters). + - Resource ops (allocQubit/deallocQubit, static qubit op), reset, measure. + - Sequence and Modifier wrapping ops. +- mqtopt (value semantics) + - Types: Qubit only + - BaseGate ops (mirrors mqtref base set but value results for targets). + - Resource/reset/measure counterparts with value semantics. + - Sequence and Modifier wrapping ops adapted to value semantics (linear threading of all qubits including controls). + + 2.2. Key Design Shifts + +- Controls, inverse/adjoint, and pow(r) are modeled as explicit ops that wrap gates or sequences. Base gates have no control operands or modifier attributes. +- Sequences of unitary gates are first-class via a region-based op that only allows UnitaryOpInterface ops (including wrapped sequences). Regions use implicit terminators; explicit yield is not required in textual IR. +- Query surface is unified through a single UnitaryOpInterface with dialect-agnostic semantics and idiomatic names, while exposing both inputs and outputs for value-semantics ops. +- Ergonomics are solved with a generic builder template layered with generated overloads, and parser sugar (e.g., mqtref.ccx %c0, %c1 %q; mqtref.mcx(%c0, %c1, %c2) %q). + +### 2.3. Marker Traits (Common) + +- Hermitian (self-inverse): marker trait for unitary gates U with U = U^†. + - Examples to tag: I, X, Y, Z, H (optionally SWAP, CZ, DCX after validation). + - Canonicalization use: inv(G) -> G if G has Hermitian. +- Diagonal (computational-basis diagonal): marker trait for gates whose matrix is diagonal. + - Examples to tag: I, Z, S, Sdg, T, Tdg, Rz, P/Phase, RZZ. + - Analysis use: commuting/phase aggregation and specialized lowering paths. +- ODS/C++ placement: + - CommonTraits.td: `def Hermitian : NativeOpTrait<"HermitianTrait">` and `def Diagonal : NativeOpTrait<"DiagonalTrait">`. + - CommonTraits.h: simple marker trait declarations (no verify function required). + +## 3. Types (Both Dialects) + +- Qubit: the only quantum type used directly by gates in both dialects. + +### 3.1 Register and Memory Handling (Quantum and Classical) + +We represent quantum and classical registers using MLIR-native memrefs. This integrates cleanly with MLIR's existing memory infrastructure and passes and avoids custom register ops. + +- Quantum registers: memref or memref (depending on dialect) + - Example (mqtref): + ```mlir + %qreg = memref.alloc() : memref<2x!mqtref.Qubit> + %i0 = arith.constant 0 : index + %i1 = arith.constant 1 : index + %q0 = memref.load %qreg[%i0] : memref<2x!mqtref.Qubit> + %q1 = memref.load %qreg[%i1] : memref<2x!mqtref.Qubit> + memref.dealloc %qreg : memref<2x!mqtref.Qubit> + ``` + - Example (mqtopt): + ```mlir + %qreg = memref.alloc() : memref<2x!mqtopt.Qubit> + %i0 = arith.constant 0 : index + %q0_in = memref.load %qreg[%i0] : memref<2x!mqtopt.Qubit> + %q0_out = mqtopt.h %q0_in : (!mqtopt.Qubit) -> !mqtopt.Qubit + memref.dealloc %qreg : memref<2x!mqtopt.Qubit> + ``` + +- Classical registers: memref + - Measurement values (`i1`) can be stored and loaded using standard memref ops. + - Example: + ```mlir + %creg = memref.alloc() : memref<1xi1> + %c = mqtref.measure %q + %i0 = arith.constant 0 : index + memref.store %c, %creg[%i0] : memref<1xi1> + memref.dealloc %creg : memref<1xi1> + ``` + +Notes: + +- Sequences and unitary ops act on qubit SSA values; bulk operations on registers (iteration, slicing) can be expressed with existing memref/affine utilities. + +## 4. Base Gate Ops (Both Dialects) + +4.1. Philosophy + +- Keep base gates minimal and uniform. +- No control operands or modifier attributes. +- Only accept: + - targets: fixed arity via TargetArity trait + - params: dynamic variadic plus optional static params via ParameterArity trait and params_mask; same as current, but simplified + + 4.2. Examples (mqtref) + +- mqtref.x: (q: !mqtref.Qubit) +- mqtref.rx: (q: !mqtref.Qubit, params: (theta: f64|static)) + + 4.3. Examples (mqtopt) + +- mqtopt.x: (%qin: !mqtopt.Qubit) -> (%qout: !mqtopt.Qubit) +- mqtopt.swap: (%qin0, %qin1) -> (%qout0, %qout1) + + 4.4. UnitaryExpr + +- Each base gate implements an interface method that returns a UnitaryExpr (symbolic, parameterized) and optionally a constant DenseElementsAttr when all parameters are static (no arity limit). + + 4.5. User-defined (Arbitrary) Unitaries and Gate Definitions — Dedicated Ops (Per-Dialect) + +- Motivation: Base ops intentionally cover only a curated set of primitive gates. To be universal, the IR must support user-defined unitaries in two complementary ways. We do this per dialect without introducing a separate lightweight "definitions" dialect. + +- Per-dialect symbol ops at module scope + - mqtref.unitary.def @Name [(params = [names])?] [(arity = i64)?] + - Purpose: Define a unitary once for reference semantics. The definition may provide a constant matrix, a composite body region, or both. + - Attributes: + - params: list of named f64 parameters (e.g., ["theta", "phi"]). + - arity?: required if no matrix is given; otherwise inferred from matrix shape 2^n x 2^n. + - matrix?: DenseElementsAttr tensor<2^n x 2^n x complex> (no arity limit; fast 2x2/4x4 paths exist in UnitaryExpr). + - traits?: ArrayAttr of StrAttr, e.g., ["Hermitian", "Diagonal"]. + - Region (optional): single-block region using mqtref.seq and unitary-capable mqtref ops. Implicit terminator. + + - mqtopt.unitary.def @Name [(params = [names])?] [(arity = i64)?] + - Purpose: Define a unitary once for value semantics. Same attributes as above. + - Region (optional): single-block region using mqtopt.seq and unitary-capable mqtopt ops. Must thread qubits linearly (including controls if used). Implicit terminator. + +- Invocation from dialects: unitary.apply + - mqtref.unitary.apply @Name(%q0, %q1, ...) (paramAttrs?) + - Operands: target qubits + - Attributes: callee = FlatSymbolRefAttr to mqtref.unitary.def; static parameters preferred (fold constants). + - Results: none (reference semantics) + - mqtopt.unitary.apply @Name(%qin0, %qin1, ...) (paramAttrs?) -> (%qout0, %qout1, ...) + - Attributes: callee = FlatSymbolRefAttr to mqtopt.unitary.def; static parameters preferred. + - Results: updated targets (and controls if wrapped by a modifier) + - Verifier: resolve symbol; check arity/parameter counts; for body-defined unitaries, verify body well-formedness (unitary-only, linear threading in mqtopt). + +- UnitaryExpr mapping + - For matrix-defined gates, getUnitaryExpr() materializes from the DenseElementsAttr (fast 2x2/4x4 paths plus general fallback). + - For composite-defined gates, getUnitaryExpr() is the product of child expressions in program order. Canonicalization may inline definitions at call sites when profitable. + +- Examples (MLIR) + - Fixed 1-qubit unitary via 2x2 matrix (mqtref) + + ```mlir + // Module-scope definition: Pauli-X as a 2x2 matrix + // tensor<2x2xcomplex> with rows [[0,1],[1,0]] + mqtref.unitary.def @X attributes { + matrix = dense<[[0.0+0.0i, 1.0+0.0i], [1.0+0.0i, 0.0+0.0i]]> : tensor<2x2xcomplex>, + traits = ["Hermitian"] + } + + // Use the definition in mqtref; modifiers can wrap it + mqtref.unitary.apply @X(%q) + mqtref.ctrl(%c) { mqtref.unitary.apply @X(%q) } + ``` + + - Parameterized 2-qubit composite unitary (CRZ) using a body region (mqtopt) + + ```mlir + mqtopt.unitary.def @CRZ(params = ["theta"], arity = 2) { + ^entry(%q0: !mqtopt.Qubit, %q1: !mqtopt.Qubit, %theta: f64): + mqtopt.seq { + %q1_1 = mqtopt.rz(%q1) (%theta/2.0) + %q0_1, %q1_2 = mqtopt.cnot %q0, %q1_1 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) + %q1_3 = mqtopt.rz(%q1_2) (-%theta/2.0) + %q0_2, %q1_4 = mqtopt.cnot %q0_1, %q1_3 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) + } + } + + // Instantiate from mqtopt (value semantics) + %q0_1, %q1_1 = mqtopt.unitary.apply @CRZ(%q0, %q1) (3.14159) + // Wrap with modifiers + %q0_2, %q1_2 = mqtopt.ctrl(%c) { mqtopt.unitary.apply @CRZ(%q0_1, %q1_1) (0.25) } + ``` + +- Notes + - We do not introduce a separate lightweight definitions dialect. Unitary definitions are owned by the respective dialects via unitary.def and referenced by unitary.apply. + - Base gate ops remain per dialect (mqtref and mqtopt). Duplication is minimized by shared traits/interfaces and TableGen catalogs, plus generated builders/sugar. + - Preliminary matrices for basis gates exist in src/dd/GateMatrixDefinitions.cpp and can seed unitary.def for standard gates during migration. + +## 5. Modifiers + +We introduce ops that wrap a region containing a single unitary op or a sequence op. Modifiers lazily defer; the effective unitary is composed only when queried or canonically simplified. + +5.1. inv (adjoint) + +- Op: mqtref.inv, mqtopt.inv +- Region: 1-region, single-block, must contain a single UnitaryOpInterface op (gate or sequence). Implicit terminator. +- Semantics: adjoint(unitary(child)). +- Canonicalization (updated): + - inv(inv(X)) -> X + - inv(pow(X, k)) -> pow(inv(X), k) + - inv(ctrl(X)) -> ctrl(inv(X)) + - inv(baseGate) -> baseGateInverse with adjusted parameters + + 5.2. ctrl / negctrl + +- Op: mqtref.ctrl, mqtref.negctrl, mqtopt.ctrl, mqtopt.negctrl +- Operands: variadic controls of qubit type; uniqueness enforced by verifier. +- Region: as above. +- Canonicalization: + - Merge adjacent ctrl and merge adjacent negctrl + - Do not deduplicate or reorder controls; verifier rejects duplicates + - Remove empty control lists +- Semantics: + - mqtref: read-only references to controls + - mqtopt: controls are linear values; modifiers and sequences must consume and produce new SSA values for controls. They are not simply forwarded unchanged; linear threading is enforced by types and verifiers. + + 5.3. pow(r) + +- Op: mqtref.pow, mqtopt.pow +- Exponent: f64 attribute or value (prefer static attribute). No parameter masks for pow. +- Canonicalization (updated): + - pow(X, 1) -> X + - pow(X, 0) -> identity of proper size + - pow(pow(X, a), b) -> pow(X, a\*b) + - pow(X, -k) -> pow(inv(X), k) + + 5.4. Modifier Ordering (normal form) + +- Controls must be the outermost modifiers; inverse must be the innermost. We use the normalized order: + - innermost: inv + - then: pow + - outermost: ctrl/negctrl (ctrl kinds grouped and internally ordered) +- Rewriters enforce this order to avoid oscillations. + + 5.5. Builder/Printer Sugar + +- Single-controlled sugar for all basis gates: prefix the mnemonic with a single `c`. + - Examples: `cx`, `cz`, `crx`, `cry`, `crz`, `cp`, `cu`, `cu2`, `cs`, `ct`, `csx`, `cswap`, … + - Expansion: `c %c, ` expands to `ctrl(%c) { }` +- Double-controlled sugar for common gates: `ccx` (Toffoli), `ccz`. + - Expansion: `ccx %c0, %c1 %t` expands to `ctrl(%c0, %c1) { x %t }` +- Multi-controlled sugar: `mcx`, `mcz`, and `mcp(theta)` with arbitrarily many controls. + - Examples: + - `mcx(%c0, %c1, %c2) %t` expands to `ctrl(%c0, %c1, %c2) { x %t }` + - `mcp(1.234)(%c0, %c1) %t` expands to `ctrl(%c0, %c1) { p(1.234) %t }` +- Additional compact forms remain supported: + - `mqtref.ctrl(%c0, %c1) { mqtref.x %q }` + - `mqtref.ctrl(%c) mqtref.inv mqtref.rx(%q) (3.14159)` + +## 6. Sequence Op + +6.1. Op: mqtref.seq, mqtopt.seq + +- Region: 1 region, single block, contains only UnitaryOpInterface ops (including modifiers and nested seqs). Implicit terminator; explicit yield is not required in textual form. +- Traits: IsolatedFromAbove, SingleBlockImplicitTerminator (terminator implicit in parser/printer), and inlining support. +- Canonicalization: inline nested seq; remove empty seq; fuse adjacent seqs; hoist/collapse modifiers across seqs when safe (e.g., inv(seq{...}) -> seq{ inv(...) reverse order }). + + 6.2. Semantics + +- mqtref: no results; acts on qubit references; relies on side-effect modeling as today (or NoMemoryEffect for unitary-only if desired). +- mqtopt: sequences consume and produce updated target qubits and also consume and produce controls (linear threading). Even if controls are unaffected, fresh SSA results are produced. + + 6.3. Interface for seq + +- The notion of "controls vs targets" is not meaningful for seq; instead expose: + - getAllOperandQubits(): ordered list of operand qubits the sequence acts on + - getAllResultQubits(): ordered list of result qubits the sequence produces (mqtopt only) + - getUnitaryExpr(): the product of child UnitaryExpr in program order (M_n … M_2 M_1) + +## 7. Unified UnitaryOpInterface (Common) + +Given the different semantics between mqtref (reference) and mqtopt (value), the interface must be uniform yet expressive. + +Idiomatic method set: + +- Identification and meta + - getIdentifier(): StringRef (op name without dialect) + - getNumTargets(): unsigned + - getNumControls(): unsigned + - hasControls(): bool + - isSingleQubit(), isTwoQubit(): bool + - hasParams(), hasDynamicParams(), isOnlyStaticParams(): bool +- Operands/results (dialect-adaptive) + - getTargetOperands(): OperandRange // always available + - getControlOperands(bool positive): OperandRange // split pos/neg via getPos/NegControlOperands() + - hasTargetResults(): bool // true in mqtopt + - getTargetResults(): ResultRange // only valid if hasTargetResults() + - getControlResults(bool positive): ResultRange // only in mqtopt; empty in mqtref +- Aggregate queries + - getAllOperandQubits(): SmallVector // targets + controls (operands) + - getAllResultQubits(): SmallVector // targets + controls (results, mqtopt) +- Unitary + - getUnitaryExpr(): UnitaryExpr + +Verification helpers in the interface ensure for value-semantics ops that in/out segment sizes are equal for targets and control kinds. + +## 8. UnitaryExpr Support Library (Common) + +A small C++ library to represent the underlying unitary for transformation/conversion passes only: + +- Data structures + - Tiny, fixed-size complex matrices: Mat2 (2x2), Mat4 (4x4) with stack storage (std::array, 4/16>) and constexpr helpers + - Symbolic expression nodes: Const(Mat2/Mat4), Mul, Adj, Pow, Control(Pos/Neg with k controls), Param(index), Trig, Exp, Embed (for control structure) +- Operations + - compose(a, b): matrix multiply / symbolic multiply + - adjoint(), power(double r) + - canMaterialize(): bool; materialize(Context\*) -> DenseElementsAttr for any arity (fast paths for 2x2 and 4x4) +- No external dependencies; rely on LLVM/MLIR support types (ArrayRef/SmallVector) only. +- Performance: optimized for 2x2 and 4x4; avoid heap allocs; inline-friendly. + +## 9. Ergonomic Builders and C++ CircuitBuilder + +Adopt a generic template design and layer nicer overloads on top. + +9.1. Generic template (preferred) + +- A dialect-scoped CircuitBuilder facade with a generic entry point: + - build(opTag, Span targets, ParamSet params = {}, ControlSet posCtrls = {}, ControlSet negCtrls = {}) -> Results +- Dispatch uses trait arities to verify counts at compile time where possible. + + 9.2. Generated overloads + +- From StdOps.td.inc, emit overloads like: + - x(Value q), rx(Value q, Value theta), rx(Value q, double theta) + - cx(Value c, Value t), ccx(Value c0, Value c1, Value t) + - mcx(ArrayRef ctrls, Value t), mcz(ArrayRef ctrls, Value t), mcp(ArrayRef ctrls, Value t, Value theta|double) +- Modifiers chain fluently: .ctrl({c...}).negctrl({c...}).inv().pow(k) + + 9.3. Parser/Printer Sugar + +- Compact forms supported and round-trip, including controlled sugar and multi-controlled variants. + +Examples (MLIR): + +```mlir +// Single-controlled +mqtref.crx %c, %t (1.234) + +// Double-controlled Toffoli +mqtref.ccx %c0, %c1 %t + +// Multi-controlled X and P(theta) +mqtref.mcx(%c0, %c1, %c2) %t +mqtref.mcp(1.234)(%c0, %c1) %t + +// Nested modifiers +mqtref.ctrl(%c) mqtref.inv mqtref.rx(%q) (3.14159) +``` + +## 10. Verification and Canonicalization + +10.1. Verifiers + +- Base gates: enforce TargetArity and ParameterArity +- Modifiers: + - inv: body contains exactly one UnitaryOpInterface op + - ctrl/negctrl: unique controls; mqtopt also verifies in/out control/result segment sizes + - pow: exponent valid; pow(0) emits identity sequence with correct arity +- Sequence: body ops implement UnitaryOpInterface; implicit terminator ok; mqtopt checks linear threading of all qubits (targets and controls) + + 10.2. Canonicalization and Folds (updated rules) + +- inv(inv(X)) -> X +- inv(pow(X, k)) -> pow(inv(X), k) +- inv(ctrl(X)) -> ctrl(inv(X)) +- pow(X, 1) -> X; pow(X, 0) -> id; pow(pow(X, a), b) -> pow(X, a\*b) +- pow(X, -k) -> pow(inv(X), k) +- inv(baseGate) -> baseGateInverse (parameters adjusted); each base gate declares its inverse +- Controls outermost; inverse innermost; reorder modifiers accordingly +- ctrl()/negctrl() with empty list -> drop; merge adjacent modifiers; do not deduplicate or reorder controls +- Flatten nested sequences; remove empty sequences +- Fold static-parameter gates to constant UnitaryExpr where profitable + + 10.3. Parameter Static Preference and Folding + +- Prefer static attributes for base-gate parameters whenever possible. When a dynamic parameter operand is a compile-time constant (e.g., defined by arith.constant), fold it into the op's static_params DenseF64ArrayAttr and update params_mask accordingly. +- Mixed parameters: maintain params_mask to indicate which positions are static; if all parameters become static, remove params and params_mask entirely. +- Power modifier has no parameter mask. + +Examples (mqtref): + +```mlir +// Before: dynamic parameter +%pi2 = arith.constant 1.5707963267948966 : f64 +mqtref.rx(%q) (%pi2) + +// After: folded to static attribute +mqtref.rx(%q) (1.5707963267948966) +``` + +Examples (mqtopt): + +```mlir +// Before: dynamic parameter with value semantics +affine.apply ... // some context producing %q_in : !mqtopt.Qubit +%pi4 = arith.constant 0.7853981633974483 : f64 +%q_out = mqtopt.rz(%q_in) (%pi4) : (!mqtopt.Qubit) -> !mqtopt.Qubit + +// After: static attribute preferred +%q_out = mqtopt.rz(%q_in) (0.7853981633974483) : (!mqtopt.Qubit) -> !mqtopt.Qubit +``` + +## 11. Passes and Pipelines + +- NormalizationPass: reach normal form (modifier ordering, sequence flattening) +- ControlPushdownPass: transforms ctrl(seq{...}) into seq{ ctrl(...) ... } when valid; inverse supported +- AdjointPropagationPass: moves inv() across sequences by reversing order and adjointing gates +- ParamConstFoldPass: constant folds parameterized gates when params static +- Optional: PowerDecompositionPass: decomposes pow(r) into native gates if backend constraints require it + +## 12. Conversions Between Dialects + +- mqtref -> mqtopt: map base gates and modifiers; sequences become mqtopt.seq with implicit terminator; controls and targets are threaded linearly and produce fresh SSA values (including controls) +- mqtopt -> mqtref: erase value results; controls become operands only; maintain sequence/modifier structure; use inlining/materialization where required + +## 13. Testing Strategy (TDD-first) + +- Philosophy + - Adopt test-driven development: write the failing LIT/unit tests first for every op, parser/printer, verifier, canonicalization, pass, and conversion; then implement the minimal code to pass them; finally refactor with tests green. + - Prefer readable MLIR FileCheck tests for IR shape, canonicalization normal forms, and parser/printer round-trips; supplement with targeted C++ unit tests for interfaces and libraries. + +- LIT: ODS/Parser/Printer and Verifiers + - Round-trip tests for all base gates (both dialects) with static/dynamic params; ensure constant-folding favors static attrs. + - Modifiers: nested forms, enforced ordering (controls outermost, inverse innermost), merges, and folds. + - Sequences: inlining/flattening; implicit terminator handling (no explicit terminators required in text form). + - Sugar: cx, cz, ccx/ccz, crx/cry/crz/cp, mcx/mcz/mcp variants; confirm desugaring to normalized IR. + - Errors: duplicate controls (rejected by verifier), non-unitary ops inside sequences/specs, mismatched yields in value semantics, arity/parameter mismatches in unitary.apply, undefined symbols, non-power-of-two matrices. + +- LIT: Unitary Definitions and Applications + - mqt.gate.def with a fixed 2x2 matrix (e.g., X) and its application from both dialects; include a Hermitian trait check via inv canonicalization. + - mqt.gate.def with parameters and composite spec region (e.g., CRZ); test both mqtref.unitary.apply and mqtopt.unitary.apply; wrap with modifiers and confirm canonical forms. + - Negative cases: missing matrix and body, arity mismatch between matrix and arity attribute, illegal ops in spec region, non-linear qubit use in spec when prohibited. + +- Canonicalization and Folds + - inv(pow(X,k)) -> pow(inv(X),k); pow(X,-k) -> pow(inv(X),k); inv(base) -> base-inverse; ctrl merging; removal of empty control lists. + - Ensure modifier ordering normalization is stable and unique. + +- Interface unit tests (C++) + - UnitaryOpInterface: target/control operand/result queries across base, modifiers, sequences, and unitary.apply. + - UnitaryExpr: adjoint, power, composition, 2x2/4x4 multiply, and dense materialization for larger arities. + +- Builder API (C++) + - Compile-time tests for the generic template and generated overloads. + - Runtime smoke tests building small circuits, including sugar (cx, ccx, mcx/mcz/mcp) and nested modifiers. + +- Pass and Conversion tests + - Normalization, ControlPushdown, AdjointPropagation, ParamConstFold. + - mqtref<->mqtopt conversions with modifiers/sequences and linear control threading; preserve normal form. + + 13.0. Checkstring Robustness and Idiomatic MLIR Testing + +To minimize false negatives from fragile FileCheck patterns and to keep tests maintainable: + +- Prefer round‑trip tests: `RUN: mlir-opt %s | FileCheck %s` and, where appropriate, `RUN: mlir-opt %s | mlir-opt | FileCheck %s` to ensure printers/parsers are robust. +- Normalize before checking when the exact textual form is not the goal: add `-canonicalize -cse` to the pipeline to check semantic shape rather than incidental formatting. +- Use `-split-input-file` to host many small, focused test cases in one file. +- For negative tests, use `-verify-diagnostics` with `// expected-error`/`// expected-note` comments instead of FileCheck. +- Avoid matching SSA value numbers; anchor on op names, attributes, and structural patterns. Use `CHECK-LABEL:` for sectioning, `CHECK-SAME:` to continue lines, and `CHECK-DAG:` for order‑insensitive matches where appropriate. +- Where printer formatting may change, prefer `-mlir-print-op-generic` to stabilize tests. +- For floating‑point literals, avoid brittle exact decimal matches; either rely on canonicalization (e.g., constants folded) or match with tolerant regex patterns. +- For parser/printer sugar, add paired tests: one that checks the sugared input parses to the normalized IR, and another that checks the normalized IR prints back to the sugared form when expected. +- Add programmatic C++ unit tests to construct and verify IR: + - Parse with `mlir::parseSourceString`, run passes with `mlir::PassManager`, and check invariants via `Operation::verify()`. + - Build IR with `OpBuilder` and the CircuitBuilder API, print to string, and compare with FileCheck‑style matchers or targeted substring assertions. + +Example (LIT skeleton): + +```mlir +// RUN: mlir-opt %s -mqtref-normalize -canonicalize | FileCheck %s + +// CHECK-LABEL: func @demo +func.func @demo() { + %q = mqtref.allocQubit + // CHECK: mqtref.ctrl( + mqtref.ctrl(%q) { mqtref.x %q } + return +} +``` + +## 14. File/Code Changes (Planned) + +14.1. Common (shared) + +- New: mlir/include/mlir/Dialect/Common/IR/QuantumInterfaces.td (consolidated UnitaryOpInterface) +- New: mlir/include/mlir/Dialect/Common/IR/UnitaryExpr.h/.cpp (support library) +- Update: mlir/include/mlir/Dialect/Common/IR/CommonTraits.td/.h (add helper traits if needed) +- New: mlir/include/mlir/Dialect/Common/IR/ParserPrinterUtils.h/.cpp for modifiers/params/sequence printing and sugar +- New: mqt definitions dialect for unitary definitions + - Headers: mlir/include/mlir/Dialect/MQT/IR/MQTDialect.td, MQTOps.td (mqt.gate.def, mqt.gate.decl) + - Impl: mlir/lib/Dialect/MQT/IR/MQTDialect.cpp, MQTOps.cpp (parser/printer/verifier) + + 14.2. mqtref + +- Update: MQTRefOps.td + - Redefine base UnitaryOp without control operands + - Add Modifiers: ctrl, negctrl, inv, pow (with implicit region terminators) + - Add SequenceOp (implicit terminator) + - Add Unitary invocation op: unitary.apply (call-site implementing UnitaryOpInterface) + - Use dedicated definition ops: mqt.gate.def (and optional mqt.gate.decl) in the new mqt definitions dialect; no func.func attributes + - Keep Resource/Reset/Measure as-is +- Update: MQTRefInterfaces.td -> adopt Common UnitaryOpInterface +- New: MQTRefCircuitBuilder.h/.cpp (generated from StdOps.td.inc) + + 14.3. mqtopt + +- Update: MQTOptOps.td + - Redefine base UnitaryOp without control operands; value results for targets + - Add Modifiers: ctrl/negctrl (linear threading of controls), inv, pow (implicit terminators) + - Add SequenceOp (implicit terminator) with linear threading + - Add Unitary invocation op: unitary.apply (call-site implementing UnitaryOpInterface) + - Use dedicated definition ops from the mqt definitions dialect: mqt.gate.def (and optional mqt.gate.decl); no func.func attributes +- Update: MQTOptInterfaces.td -> adopt Common UnitaryOpInterface; ensure queries expose both operands and results as specified +- New: MQTOptCircuitBuilder.h/.cpp (generated) + + 14.4. TableGen Codegen Enhancements + +- Extend StdOps.td.inc to also emit builder helpers via a new include (e.g., MQTRefCircuitBuilder.inc / MQTOptCircuitBuilder.inc) using preprocessor macros +- Prefer the generic template entry-point and generate thin overloads on top + + 14.5. Passes + +- New pass registrations and implementations in mlir/lib/.../Transforms for normalization, propagation, folding + + 14.6. Conversions + +- Update/refactor existing conversions to respect new modifier/sequence ops + + 14.7. Documentation + +- Update docs/mlir/Conversions.md and write new docs/mlir/Dialect.md for user-facing syntax and builder API +- Docstring guideline: every op and modifier must include fenced MLIR examples demonstrating sugar and canonicalized forms + +## 15. Migration and Deprecation + +- No backward compatibility required; remove controls from base gates and replace with modifiers. +- No temporary helper conversion will be provided; all code is expected to be rewritten and fixed accordingly (tests will be updated alongside). +- QubitRegisters have been replaced with memrefs; this plan reflects the current state. + +## 16. Risks and Mitigations + +- Interface uniformity: Ensure UnitaryOpInterface exposes both operand and (optional) result views; provide hasTargetResults() and control result accessors to avoid dialect-specific branching in clients. +- Performance of UnitaryExpr: Keep operations cheap; favor small fixed-size matrices; avoid materialization unless necessary; support dense materialization for any arity (with fast paths for 2x2 and 4x4). +- Modifier/sequence ordering: Define strict normal form (controls outermost, inverse innermost); add extensive LIT to prevent rewrite loops. +- Linear controls in mqtopt: Provide utilities for segment attrs and builders that always produce fresh SSA for controls. + +## 17. Milestones and Work Breakdown + +M1 — Foundations (Common) [~1–2 weeks] + +- Create QuantumInterfaces.td with UnitaryOpInterface ✓ +- Implement UnitaryExpr skeleton (adjoint, power, compose, 2x2/4x4 multiply) ✓ +- Parser/Printer utils for params, modifiers, sugar ✓ + +M2 — mqtref Base + Modifiers + Sequence [~2 weeks] + +- Redefine base UnitaryOp (no controls) ✓ +- Implement ctrl/negctrl/inv/pow with verifiers and canonicalization ✓ +- Implement seq with inliner and canonicalization; implicit terminator ✓ +- Implement unitary.apply (instantiation) ✓ +- Use dedicated mqt.gate.def definitions (and optional mqt.gate.decl); add verifiers/utilities ✓ +- Update std gate defs and assembly formats ✓ +- LIT tests: ops, parsers, canonicalization, mqt.gate.def and unitary.apply ✓ + +M3 — mqtopt Mirror [~2 weeks] + +- Redefine base gate ops with value outputs ✓ +- Implement modifiers with linear control threading; sequence with implicit terminator ✓ +- Implement unitary.apply (instantiation) ✓ +- Use mqt.gate.def for definitions (and optional mqt.gate.decl); add verifiers/utilities ✓ +- Update interface and verifiers ✓ +- LIT tests mirroring mqtref, including mqt.gate.def and unitary.apply ✓ + +M4 — Builders and Ergonomics [~1 week] + +- Generate CircuitBuilder helper API with generic template + overloads ✓ +- Parser/printer sugar for compact modifier nesting and cx/cz/ccx/ccz/mcx/mcz/mcp ✓ +- C++ smoke tests ✓ + +M5 — Passes and Conversions [~1–2 weeks] + +- Normalization, ControlPushdown, AdjointPropagation, ParamConstFold ✓ +- Update mqtref<->mqtopt conversions for modifiers/sequences with linear controls ✓ +- Tests ✓ + +M6 — Documentation and Polishing [~1 week] + +- Update docs and examples; ensure all docstrings contain fenced MLIR examples ✓ +- Final test stabilization ✓ + +Note: ✓ indicates planned completion within this revamp; actual sequencing will be tracked in the repository issue tracker. + +## 18. Acceptance Criteria + +- Both dialects compile and pass LIT tests covering base gates, modifiers, sequences, mqt.gate.def definitions, unitary.apply, and printers (including sugar like cx, ccx, mcx, mcp). +- Unified UnitaryOpInterface available, used by all unitary-capable ops, and provides dialect-adaptive input/output queries. +- Support for arbitrary unitary definitions via dedicated ops: mqt.gate.def (matrix-defined and composite-defined) and unitary.apply in both dialects; modifiers can wrap applications. +- CircuitBuilder API available, with generic template and overloads for core gates; sugar for single- and multi-controlled gates. +- Canonicalization suite ensures normalized IR: controls outermost; inverse innermost; pow/inv rules as specified; linear controls in mqtopt. +- Conversions between mqtref and mqtopt handle modifiers/sequences and linear threading of controls. + +## 19. Illustrative Examples + +19.1. mqtref (IR) + +```mlir +// prepare qubits +%q0 = mqtref.allocQubit +%q1 = mqtref.allocQubit +%c0 = mqtref.allocQubit +%c1 = mqtref.allocQubit +%c2 = mqtref.allocQubit + +// Single-controlled +mqtref.crz %c0, %q0 (0.25) + +// Double-controlled Toffoli +mqtref.ccx %c0, %c1 %q0 + +// Multi-controlled +mqtref.mcx(%c0, %c1, %c2) %q1 +mqtref.mcp(1.234)(%c0, %c1) %q0 + +// Nested modifiers & canonicalization +mqtref.inv mqtref.ctrl(%c0) { mqtref.rx(%q0) (3.14159) } +// -> mqtref.ctrl(%c0) { mqtref.inv mqtref.rx(%q0) (3.14159) } +``` + +19.2. mqtopt (IR) + +```mlir +%q0 = mqtopt.allocQubit +%q1 = mqtopt.allocQubit +%c = mqtopt.allocQubit + +// Sequence returns updated targets; controls are threaded linearly +mqtopt.seq { + %q0_1 = mqtopt.h %q0 + // ctrl threads control linearly: consumes %c, yields %c_1 + %q0_2, %c_1 = mqtopt.ctrl(%c) { mqtopt.x %q0_1 } +} +``` + +19.3. Canonicalization + +```mlir +// inv(pow(X, k)) -> pow(inv(X), k) +mqtref.inv mqtref.pow(3) { mqtref.x %q } // -> mqtref.pow(3) { mqtref.inv mqtref.x %q } + +// pow(X, -k) -> pow(inv(X), k) +mqtref.pow(-2) { mqtref.h %q } // -> mqtref.pow(2) { mqtref.inv mqtref.h %q } + +// inv(baseGate) -> baseGateInverse +mqtref.inv mqtref.s %q // -> mqtref.sdg %q + +// inv(ctrl(X)) -> ctrl(inv(X)) +mqtref.inv mqtref.ctrl(%c) { mqtref.rz(%q) (0.5) } +// -> mqtref.ctrl(%c) { mqtref.inv mqtref.rz(%q) (0.5) } +``` + +19.4. C++ Builder + +```cpp +using namespace mqt::ir::ref; +CircuitBuilder qb(b, loc); +auto q0 = qb.allocQubit(); +auto q1 = qb.allocQubit(); +auto c0 = qb.allocQubit(); +auto c1 = qb.allocQubit(); + +qb.cx(c0, q0); // CNOT sugar +qb.ccx(c0, c1, q0); // Toffoli sugar +qb.mcx({c0, c1}, q1); // Multi-controlled X +qb.mcp({c0}, q0, M_PI/4.0); // Multi-controlled phase +qb.rx(q0, M_PI/2).inv(); // R_x(pi/2)^-1 (inverse as innermost in normal form) +qb.seq([&]{ qb.h(q0); qb.rz(q0, t); }); +``` + +## 20. Documentation Guidelines + +- Every op (base, modifier, seq) must include fenced MLIR examples in the ODS docstrings, demonstrating both verbose and sugar forms and at least one canonicalization result. +- Use idiomatic names in MLIR, C++, and QC contexts: UnitaryOpInterface, getTargetOperands, getTargetResults, getPosControlOperands, getNegControlOperands, getUnitaryExpr, getAllOperandQubits, getAllResultQubits, etc. + +## 21. Next Steps + +- Proceed to implementation per milestones now that the revisions are confirmed. From 7fa3b436e2544ef485eef380120550ad6d0bd750 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 24 Sep 2025 15:08:44 +0200 Subject: [PATCH 002/419] =?UTF-8?q?=F0=9F=9A=A7=20consolidated=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 1060 ++++++++++------------ 1 file changed, 486 insertions(+), 574 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index 370e6b32d2..8db81a9cb9 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -1,700 +1,612 @@ -# Quantum Dialect Revamp — Implementation Plan (Revision 5) - -Author: Junie (auto-generated) -Date: 2025-09-24 -Status: Design document, no IR code changes committed yet -Scope: MLIR dialect hierarchy under mlir/, affecting MQTRef and MQTOpt dialects and shared Common utilities - -## 1. Context and Goals - -We currently have two working quantum dialects: - -- mqtref: reference/memory semantics -- mqtopt: value semantics - -Both support a wide set of unitary "basis" gates and basic resources (qubit, alloc/dealloc, reset, measure). However, usage is cumbersome, builders are verbose, and the treatment of modifiers (controls, inverse/adjoint, powering) is embedded into each gate op, which complicates composition, normalization, and ergonomics. - -This plan proposes a fundamental rework that: - -- Makes composition first-class: modifiers are explicit, uniform IR constructs that wrap gates or sequences and lazily defer evaluation. -- Unifies analysis via interfaces: a single UnitaryOpInterface to query gate properties (targets, controls, parameters, unitary, etc.) without pattern matching op kinds, while accommodating the different semantics of mqtref and mqtopt. -- Greatly improves ergonomics: C++ CircuitBuilder helpers and parser/printer sugar to write MLIR like mqtref.ctrl(%c) mqtref.x %q or C++ like builder.x(q).ctrl(c). -- Keeps dialect-specific semantics minimal and uniform while sharing as much as possible between mqtref and mqtopt via Common ODS, traits, and support libraries. -- Remains analysis- and transformation-friendly and provides canonicalization and verifiers to maintain a normalized IR. - -No backward compatibility is required. We do not change IR code yet; this document details the implementation plan. - -## 2. High-level Architecture - -2.1. Layering +# RFC: Quantum Dialect Revamp — Implementation Plan -- Common (shared between both dialects) - - Traits and interfaces (TargetArity, ParameterArity, NoControl, UniqueSize/Index, plus new ones). - - UnitaryOpInterface (single definition in Common with dialect adapters). - - UnitaryExpr support library for symbolic unitary expressions and tiny fixed-size matrices. - - Parser/printer utils for modifiers, sequences, parameters, and sugar. -- mqtref (reference semantics) - - Types: Qubit only - - BaseGate ops (no controls/inverse/pow; only targets and parameters). - - Resource ops (allocQubit/deallocQubit, static qubit op), reset, measure. - - Sequence and Modifier wrapping ops. -- mqtopt (value semantics) - - Types: Qubit only - - BaseGate ops (mirrors mqtref base set but value results for targets). - - Resource/reset/measure counterparts with value semantics. - - Sequence and Modifier wrapping ops adapted to value semantics (linear threading of all qubits including controls). +**Author:** MQT Core Development Team +**Date:** 2025-09-24 +**Status:** Design Document +**Scope:** MLIR dialect hierarchy affecting MQTRef and MQTOpt dialects - 2.2. Key Design Shifts - -- Controls, inverse/adjoint, and pow(r) are modeled as explicit ops that wrap gates or sequences. Base gates have no control operands or modifier attributes. -- Sequences of unitary gates are first-class via a region-based op that only allows UnitaryOpInterface ops (including wrapped sequences). Regions use implicit terminators; explicit yield is not required in textual IR. -- Query surface is unified through a single UnitaryOpInterface with dialect-agnostic semantics and idiomatic names, while exposing both inputs and outputs for value-semantics ops. -- Ergonomics are solved with a generic builder template layered with generated overloads, and parser sugar (e.g., mqtref.ccx %c0, %c1 %q; mqtref.mcx(%c0, %c1, %c2) %q). - -### 2.3. Marker Traits (Common) - -- Hermitian (self-inverse): marker trait for unitary gates U with U = U^†. - - Examples to tag: I, X, Y, Z, H (optionally SWAP, CZ, DCX after validation). - - Canonicalization use: inv(G) -> G if G has Hermitian. -- Diagonal (computational-basis diagonal): marker trait for gates whose matrix is diagonal. - - Examples to tag: I, Z, S, Sdg, T, Tdg, Rz, P/Phase, RZZ. - - Analysis use: commuting/phase aggregation and specialized lowering paths. -- ODS/C++ placement: - - CommonTraits.td: `def Hermitian : NativeOpTrait<"HermitianTrait">` and `def Diagonal : NativeOpTrait<"DiagonalTrait">`. - - CommonTraits.h: simple marker trait declarations (no verify function required). - -## 3. Types (Both Dialects) - -- Qubit: the only quantum type used directly by gates in both dialects. - -### 3.1 Register and Memory Handling (Quantum and Classical) - -We represent quantum and classical registers using MLIR-native memrefs. This integrates cleanly with MLIR's existing memory infrastructure and passes and avoids custom register ops. - -- Quantum registers: memref or memref (depending on dialect) - - Example (mqtref): - ```mlir - %qreg = memref.alloc() : memref<2x!mqtref.Qubit> - %i0 = arith.constant 0 : index - %i1 = arith.constant 1 : index - %q0 = memref.load %qreg[%i0] : memref<2x!mqtref.Qubit> - %q1 = memref.load %qreg[%i1] : memref<2x!mqtref.Qubit> - memref.dealloc %qreg : memref<2x!mqtref.Qubit> - ``` - - Example (mqtopt): - ```mlir - %qreg = memref.alloc() : memref<2x!mqtopt.Qubit> - %i0 = arith.constant 0 : index - %q0_in = memref.load %qreg[%i0] : memref<2x!mqtopt.Qubit> - %q0_out = mqtopt.h %q0_in : (!mqtopt.Qubit) -> !mqtopt.Qubit - memref.dealloc %qreg : memref<2x!mqtopt.Qubit> - ``` - -- Classical registers: memref - - Measurement values (`i1`) can be stored and loaded using standard memref ops. - - Example: - ```mlir - %creg = memref.alloc() : memref<1xi1> - %c = mqtref.measure %q - %i0 = arith.constant 0 : index - memref.store %c, %creg[%i0] : memref<1xi1> - memref.dealloc %creg : memref<1xi1> - ``` - -Notes: - -- Sequences and unitary ops act on qubit SSA values; bulk operations on registers (iteration, slicing) can be expressed with existing memref/affine utilities. - -## 4. Base Gate Ops (Both Dialects) - -4.1. Philosophy - -- Keep base gates minimal and uniform. -- No control operands or modifier attributes. -- Only accept: - - targets: fixed arity via TargetArity trait - - params: dynamic variadic plus optional static params via ParameterArity trait and params_mask; same as current, but simplified - - 4.2. Examples (mqtref) - -- mqtref.x: (q: !mqtref.Qubit) -- mqtref.rx: (q: !mqtref.Qubit, params: (theta: f64|static)) - - 4.3. Examples (mqtopt) - -- mqtopt.x: (%qin: !mqtopt.Qubit) -> (%qout: !mqtopt.Qubit) -- mqtopt.swap: (%qin0, %qin1) -> (%qout0, %qout1) - - 4.4. UnitaryExpr - -- Each base gate implements an interface method that returns a UnitaryExpr (symbolic, parameterized) and optionally a constant DenseElementsAttr when all parameters are static (no arity limit). - - 4.5. User-defined (Arbitrary) Unitaries and Gate Definitions — Dedicated Ops (Per-Dialect) - -- Motivation: Base ops intentionally cover only a curated set of primitive gates. To be universal, the IR must support user-defined unitaries in two complementary ways. We do this per dialect without introducing a separate lightweight "definitions" dialect. - -- Per-dialect symbol ops at module scope - - mqtref.unitary.def @Name [(params = [names])?] [(arity = i64)?] - - Purpose: Define a unitary once for reference semantics. The definition may provide a constant matrix, a composite body region, or both. - - Attributes: - - params: list of named f64 parameters (e.g., ["theta", "phi"]). - - arity?: required if no matrix is given; otherwise inferred from matrix shape 2^n x 2^n. - - matrix?: DenseElementsAttr tensor<2^n x 2^n x complex> (no arity limit; fast 2x2/4x4 paths exist in UnitaryExpr). - - traits?: ArrayAttr of StrAttr, e.g., ["Hermitian", "Diagonal"]. - - Region (optional): single-block region using mqtref.seq and unitary-capable mqtref ops. Implicit terminator. - - - mqtopt.unitary.def @Name [(params = [names])?] [(arity = i64)?] - - Purpose: Define a unitary once for value semantics. Same attributes as above. - - Region (optional): single-block region using mqtopt.seq and unitary-capable mqtopt ops. Must thread qubits linearly (including controls if used). Implicit terminator. - -- Invocation from dialects: unitary.apply - - mqtref.unitary.apply @Name(%q0, %q1, ...) (paramAttrs?) - - Operands: target qubits - - Attributes: callee = FlatSymbolRefAttr to mqtref.unitary.def; static parameters preferred (fold constants). - - Results: none (reference semantics) - - mqtopt.unitary.apply @Name(%qin0, %qin1, ...) (paramAttrs?) -> (%qout0, %qout1, ...) - - Attributes: callee = FlatSymbolRefAttr to mqtopt.unitary.def; static parameters preferred. - - Results: updated targets (and controls if wrapped by a modifier) - - Verifier: resolve symbol; check arity/parameter counts; for body-defined unitaries, verify body well-formedness (unitary-only, linear threading in mqtopt). - -- UnitaryExpr mapping - - For matrix-defined gates, getUnitaryExpr() materializes from the DenseElementsAttr (fast 2x2/4x4 paths plus general fallback). - - For composite-defined gates, getUnitaryExpr() is the product of child expressions in program order. Canonicalization may inline definitions at call sites when profitable. - -- Examples (MLIR) - - Fixed 1-qubit unitary via 2x2 matrix (mqtref) - - ```mlir - // Module-scope definition: Pauli-X as a 2x2 matrix - // tensor<2x2xcomplex> with rows [[0,1],[1,0]] - mqtref.unitary.def @X attributes { - matrix = dense<[[0.0+0.0i, 1.0+0.0i], [1.0+0.0i, 0.0+0.0i]]> : tensor<2x2xcomplex>, - traits = ["Hermitian"] - } +## 1. Overview and Goals - // Use the definition in mqtref; modifiers can wrap it - mqtref.unitary.apply @X(%q) - mqtref.ctrl(%c) { mqtref.unitary.apply @X(%q) } - ``` - - - Parameterized 2-qubit composite unitary (CRZ) using a body region (mqtopt) - - ```mlir - mqtopt.unitary.def @CRZ(params = ["theta"], arity = 2) { - ^entry(%q0: !mqtopt.Qubit, %q1: !mqtopt.Qubit, %theta: f64): - mqtopt.seq { - %q1_1 = mqtopt.rz(%q1) (%theta/2.0) - %q0_1, %q1_2 = mqtopt.cnot %q0, %q1_1 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) - %q1_3 = mqtopt.rz(%q1_2) (-%theta/2.0) - %q0_2, %q1_4 = mqtopt.cnot %q0_1, %q1_3 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) - } - } +### 1.1 Current State - // Instantiate from mqtopt (value semantics) - %q0_1, %q1_1 = mqtopt.unitary.apply @CRZ(%q0, %q1) (3.14159) - // Wrap with modifiers - %q0_2, %q1_2 = mqtopt.ctrl(%c) { mqtopt.unitary.apply @CRZ(%q0_1, %q1_1) (0.25) } - ``` - -- Notes - - We do not introduce a separate lightweight definitions dialect. Unitary definitions are owned by the respective dialects via unitary.def and referenced by unitary.apply. - - Base gate ops remain per dialect (mqtref and mqtopt). Duplication is minimized by shared traits/interfaces and TableGen catalogs, plus generated builders/sugar. - - Preliminary matrices for basis gates exist in src/dd/GateMatrixDefinitions.cpp and can seed unitary.def for standard gates during migration. - -## 5. Modifiers - -We introduce ops that wrap a region containing a single unitary op or a sequence op. Modifiers lazily defer; the effective unitary is composed only when queried or canonically simplified. - -5.1. inv (adjoint) - -- Op: mqtref.inv, mqtopt.inv -- Region: 1-region, single-block, must contain a single UnitaryOpInterface op (gate or sequence). Implicit terminator. -- Semantics: adjoint(unitary(child)). -- Canonicalization (updated): - - inv(inv(X)) -> X - - inv(pow(X, k)) -> pow(inv(X), k) - - inv(ctrl(X)) -> ctrl(inv(X)) - - inv(baseGate) -> baseGateInverse with adjusted parameters - - 5.2. ctrl / negctrl - -- Op: mqtref.ctrl, mqtref.negctrl, mqtopt.ctrl, mqtopt.negctrl -- Operands: variadic controls of qubit type; uniqueness enforced by verifier. -- Region: as above. -- Canonicalization: - - Merge adjacent ctrl and merge adjacent negctrl - - Do not deduplicate or reorder controls; verifier rejects duplicates - - Remove empty control lists -- Semantics: - - mqtref: read-only references to controls - - mqtopt: controls are linear values; modifiers and sequences must consume and produce new SSA values for controls. They are not simply forwarded unchanged; linear threading is enforced by types and verifiers. - - 5.3. pow(r) - -- Op: mqtref.pow, mqtopt.pow -- Exponent: f64 attribute or value (prefer static attribute). No parameter masks for pow. -- Canonicalization (updated): - - pow(X, 1) -> X - - pow(X, 0) -> identity of proper size - - pow(pow(X, a), b) -> pow(X, a\*b) - - pow(X, -k) -> pow(inv(X), k) - - 5.4. Modifier Ordering (normal form) - -- Controls must be the outermost modifiers; inverse must be the innermost. We use the normalized order: - - innermost: inv - - then: pow - - outermost: ctrl/negctrl (ctrl kinds grouped and internally ordered) -- Rewriters enforce this order to avoid oscillations. +We have two quantum dialects: - 5.5. Builder/Printer Sugar +- `mqtref`: Reference semantics (side-effect based) +- `mqtopt`: Value semantics (SSA based) -- Single-controlled sugar for all basis gates: prefix the mnemonic with a single `c`. - - Examples: `cx`, `cz`, `crx`, `cry`, `crz`, `cp`, `cu`, `cu2`, `cs`, `ct`, `csx`, `cswap`, … - - Expansion: `c %c, ` expands to `ctrl(%c) { }` -- Double-controlled sugar for common gates: `ccx` (Toffoli), `ccz`. - - Expansion: `ccx %c0, %c1 %t` expands to `ctrl(%c0, %c1) { x %t }` -- Multi-controlled sugar: `mcx`, `mcz`, and `mcp(theta)` with arbitrarily many controls. - - Examples: - - `mcx(%c0, %c1, %c2) %t` expands to `ctrl(%c0, %c1, %c2) { x %t }` - - `mcp(1.234)(%c0, %c1) %t` expands to `ctrl(%c0, %c1) { p(1.234) %t }` -- Additional compact forms remain supported: - - `mqtref.ctrl(%c0, %c1) { mqtref.x %q }` - - `mqtref.ctrl(%c) mqtref.inv mqtref.rx(%q) (3.14159)` +Both support basic gates and quantum operations but suffer from: -## 6. Sequence Op +- **Verbose builders and cumbersome usage**: Creating controlled gates requires lengthy builder calls +- **Gate modifiers embedded inconsistently**: Some gates have control operands, others use attributes, while others do not even exist yet, creating analysis complexity +- **Inconsistent and incomplete interfaces across dialects**: Different ways to query the same logical information +- **Limited composability**: No clean way to compose modifiers (e.g., controlled inverse gates) -6.1. Op: mqtref.seq, mqtopt.seq +### 1.2 Proposed Changes -- Region: 1 region, single block, contains only UnitaryOpInterface ops (including modifiers and nested seqs). Implicit terminator; explicit yield is not required in textual form. -- Traits: IsolatedFromAbove, SingleBlockImplicitTerminator (terminator implicit in parser/printer), and inlining support. -- Canonicalization: inline nested seq; remove empty seq; fuse adjacent seqs; hoist/collapse modifiers across seqs when safe (e.g., inv(seq{...}) -> seq{ inv(...) reverse order }). +This plan proposes a fundamental redesign that addresses these issues through: - 6.2. Semantics +1. **Compositional Design**: Modifiers become explicit wrapper operations that can be nested and combined +2. **Unified Interface**: Single `UnitaryOpInterface` for all quantum operations enabling uniform analysis +3. **Improved Ergonomics**: Builder APIs and parser sugar for common patterns without sacrificing expressiveness +4. **Shared Infrastructure**: Common traits, interfaces, and utilities to reduce code duplication -- mqtref: no results; acts on qubit references; relies on side-effect modeling as today (or NoMemoryEffect for unitary-only if desired). -- mqtopt: sequences consume and produce updated target qubits and also consume and produce controls (linear threading). Even if controls are unaffected, fresh SSA results are produced. +**Rationale**: The current approach of embedding modifiers directly into gate operations creates a multiplicative explosion of variants (X, CX, CCX, inverse-X, controlled-inverse-X, etc.). By making modifiers compositional, we get exponential expressiveness with linear implementation cost. It is also fairly close to how established languages like OpenQASM or Qiskit work, which makes it easier to transition to the new dialect. - 6.3. Interface for seq +## 2. Architecture Overview -- The notion of "controls vs targets" is not meaningful for seq; instead expose: - - getAllOperandQubits(): ordered list of operand qubits the sequence acts on - - getAllResultQubits(): ordered list of result qubits the sequence produces (mqtopt only) - - getUnitaryExpr(): the product of child UnitaryExpr in program order (M_n … M_2 M_1) +### 2.1 Dialect Structure -## 7. Unified UnitaryOpInterface (Common) +``` +Common/ +├── Interfaces (UnitaryOpInterface, etc.) +├── Traits (Hermitian, Diagonal, SingleTarget, etc.) +└── Support (UnitaryExpr library) + +mqtref/ +├── Types (Qubit) +├── Base Gates (X, RX, CNOT, etc.) +├── Modifiers (ctrl, inv, pow) +├── Sequences (seq) +└── Resources (alloc, measure, etc.) + +mqtopt/ +└── (Same structure with value semantics) +``` -Given the different semantics between mqtref (reference) and mqtopt (value), the interface must be uniform yet expressive. +### 2.2 Key Design Principles -Idiomatic method set: +1. **Base gates are minimal**: No control operands or modifier attributes - each gate does exactly one thing +2. **Modifiers wrap operations**: Controls, inverses, and powers become explicit wrapper operations with regions +3. **Uniform interface**: Single way to query any quantum operation regardless of dialect or complexity +4. **Dialect-specific semantics**: Reference vs value threading handled appropriately by each dialect -- Identification and meta - - getIdentifier(): StringRef (op name without dialect) - - getNumTargets(): unsigned - - getNumControls(): unsigned - - hasControls(): bool - - isSingleQubit(), isTwoQubit(): bool - - hasParams(), hasDynamicParams(), isOnlyStaticParams(): bool -- Operands/results (dialect-adaptive) - - getTargetOperands(): OperandRange // always available - - getControlOperands(bool positive): OperandRange // split pos/neg via getPos/NegControlOperands() - - hasTargetResults(): bool // true in mqtopt - - getTargetResults(): ResultRange // only valid if hasTargetResults() - - getControlResults(bool positive): ResultRange // only in mqtopt; empty in mqtref -- Aggregate queries - - getAllOperandQubits(): SmallVector // targets + controls (operands) - - getAllResultQubits(): SmallVector // targets + controls (results, mqtopt) -- Unitary - - getUnitaryExpr(): UnitaryExpr - -Verification helpers in the interface ensure for value-semantics ops that in/out segment sizes are equal for targets and control kinds. - -## 8. UnitaryExpr Support Library (Common) - -A small C++ library to represent the underlying unitary for transformation/conversion passes only: +**Rationale**: This separation of concerns makes analysis passes simpler - they can focus on the mathematical structure without worrying about the myriad ways modifiers might be encoded. It also makes the IR more predictable and easier to canonicalize. -- Data structures - - Tiny, fixed-size complex matrices: Mat2 (2x2), Mat4 (4x4) with stack storage (std::array, 4/16>) and constexpr helpers - - Symbolic expression nodes: Const(Mat2/Mat4), Mul, Adj, Pow, Control(Pos/Neg with k controls), Param(index), Trig, Exp, Embed (for control structure) -- Operations - - compose(a, b): matrix multiply / symbolic multiply - - adjoint(), power(double r) - - canMaterialize(): bool; materialize(Context\*) -> DenseElementsAttr for any arity (fast paths for 2x2 and 4x4) -- No external dependencies; rely on LLVM/MLIR support types (ArrayRef/SmallVector) only. -- Performance: optimized for 2x2 and 4x4; avoid heap allocs; inline-friendly. +## 3. Types and Memory Model -## 9. Ergonomic Builders and C++ CircuitBuilder +### 3.1 Quantum Types -Adopt a generic template design and layer nicer overloads on top. +Both dialects use a single `Qubit` type with different semantics: -9.1. Generic template (preferred) +- `!mqtref.qubit` (reference semantics): Represents a stateful quantum register location +- `!mqtopt.qubit` (value semantics): Represents an SSA value carrying quantum information -- A dialect-scoped CircuitBuilder facade with a generic entry point: - - build(opTag, Span targets, ParamSet params = {}, ControlSet posCtrls = {}, ControlSet negCtrls = {}) -> Results -- Dispatch uses trait arities to verify counts at compile time where possible. +**Rationale**: While conceptually similar, the semantic difference is crucial - mqtref qubits can be mutated in place, while mqtopt qubits follow single-static-assignment and must be "threaded" through operations. - 9.2. Generated overloads +### 3.2 Register Handling -- From StdOps.td.inc, emit overloads like: - - x(Value q), rx(Value q, Value theta), rx(Value q, double theta) - - cx(Value c, Value t), ccx(Value c0, Value c1, Value t) - - mcx(ArrayRef ctrls, Value t), mcz(ArrayRef ctrls, Value t), mcp(ArrayRef ctrls, Value t, Value theta|double) -- Modifiers chain fluently: .ctrl({c...}).negctrl({c...}).inv().pow(k) +Use standard MLIR `memref`s for quantum and classical registers: - 9.3. Parser/Printer Sugar +```mlir +// Quantum register allocation and access +%qreg = memref.alloc() : memref<2x!mqtref.qubit> +%q0 = memref.load %qreg[%c0] : memref<2x!mqtref.qubit> + +// Classical register for measurements +%creg = memref.alloc() : memref<2xi1> +%bit = mqtref.measure %q0 : i1 +memref.store %bit, %creg[%c0] : memref<2xi1> +``` -- Compact forms supported and round-trip, including controlled sugar and multi-controlled variants. +**Rationale**: Using standard MLIR constructs (memref) instead of custom quantum register types allows us to leverage existing MLIR analyses and transformations. This also provides a clear separation between the quantum computational model and classical memory management. -Examples (MLIR): +## 4. Base Gate Operations -```mlir -// Single-controlled -mqtref.crx %c, %t (1.234) +### 4.1 Design Philosophy -// Double-controlled Toffoli -mqtref.ccx %c0, %c1 %t +Base gates are the atomic quantum operations and contain only: -// Multi-controlled X and P(theta) -mqtref.mcx(%c0, %c1, %c2) %t -mqtref.mcp(1.234)(%c0, %c1) %t +- **Target operands**: Fixed arity determined by gate type (via traits) +- **Parameters**: Rotation angles, phases, etc. (both static and dynamic) +- **No control operands**: Controls are handled by wrapper operations +- **No modifier attributes**: Inverses and powers are handled by wrapper operations -// Nested modifiers -mqtref.ctrl(%c) mqtref.inv mqtref.rx(%q) (3.14159) -``` +**Rationale**: This minimal design makes base gates predictable and easy to analyze. Each gate operation corresponds exactly to a mathematical unitary operation without additional complexity from modifiers. -## 10. Verification and Canonicalization +### 4.2 Examples -10.1. Verifiers +**mqtref (Reference Semantics):** -- Base gates: enforce TargetArity and ParameterArity -- Modifiers: - - inv: body contains exactly one UnitaryOpInterface op - - ctrl/negctrl: unique controls; mqtopt also verifies in/out control/result segment sizes - - pow: exponent valid; pow(0) emits identity sequence with correct arity -- Sequence: body ops implement UnitaryOpInterface; implicit terminator ok; mqtopt checks linear threading of all qubits (targets and controls) +```mlir +mqtref.x %q0 // Pauli-X gate +mqtref.rx %q0 {angle = 1.57 : f64} // X-rotation with static parameter +mqtref.cnot %q0, %q1 // Controlled-NOT (2-qubit gate) +mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit gate +``` - 10.2. Canonicalization and Folds (updated rules) +**mqtopt (Value Semantics):** -- inv(inv(X)) -> X -- inv(pow(X, k)) -> pow(inv(X), k) -- inv(ctrl(X)) -> ctrl(inv(X)) -- pow(X, 1) -> X; pow(X, 0) -> id; pow(pow(X, a), b) -> pow(X, a\*b) -- pow(X, -k) -> pow(inv(X), k) -- inv(baseGate) -> baseGateInverse (parameters adjusted); each base gate declares its inverse -- Controls outermost; inverse innermost; reorder modifiers accordingly -- ctrl()/negctrl() with empty list -> drop; merge adjacent modifiers; do not deduplicate or reorder controls -- Flatten nested sequences; remove empty sequences -- Fold static-parameter gates to constant UnitaryExpr where profitable +```mlir +%q0_out = mqtopt.x %q0_in : !mqtopt.qubit +%q0_out = mqtopt.rx %q0_in {angle = 1.57 : f64} : !mqtopt.qubit +%q0_out, %q1_out = mqtopt.cnot %q0_in, %q1_in : !mqtopt.qubit, !mqtopt.qubit +``` - 10.3. Parameter Static Preference and Folding +**Key Differences**: In mqtref, operations have side effects on qubit references. In mqtopt, operations consume input qubits and produce new output qubits, following SSA principles. -- Prefer static attributes for base-gate parameters whenever possible. When a dynamic parameter operand is a compile-time constant (e.g., defined by arith.constant), fold it into the op's static_params DenseF64ArrayAttr and update params_mask accordingly. -- Mixed parameters: maintain params_mask to indicate which positions are static; if all parameters become static, remove params and params_mask entirely. -- Power modifier has no parameter mask. +### 4.3 Parameterization Strategy -Examples (mqtref): +Gates support flexible parameterization to handle both compile-time and runtime parameters: ```mlir -// Before: dynamic parameter -%pi2 = arith.constant 1.5707963267948966 : f64 -mqtref.rx(%q) (%pi2) +// Static parameters (preferred for optimization) +mqtref.rx %q0 {angle = 1.57 : f64} + +// Dynamic parameters (runtime values) +%angle = arith.constant 1.57 : f64 +mqtref.rx %q0, %angle : f64 -// After: folded to static attribute -mqtref.rx(%q) (1.5707963267948966) +// Mixed parameters with mask indicating which are static +mqtref.u3 %q0, %runtime_theta {phi = 0.0, lambda = 3.14159, static_mask = [false, true, true]} ``` -Examples (mqtopt): +**Rationale**: Static parameters enable constant folding and symbolic computation at compile time, while dynamic parameters provide runtime flexibility. The mixed approach allows gradual specialization as more information becomes available during compilation. -```mlir -// Before: dynamic parameter with value semantics -affine.apply ... // some context producing %q_in : !mqtopt.Qubit -%pi4 = arith.constant 0.7853981633974483 : f64 -%q_out = mqtopt.rz(%q_in) (%pi4) : (!mqtopt.Qubit) -> !mqtopt.Qubit +## 5. Modifier Operations -// After: static attribute preferred -%q_out = mqtopt.rz(%q_in) (0.7853981633974483) : (!mqtopt.Qubit) -> !mqtopt.Qubit -``` +Modifiers are wrapper operations that contain single-block regions holding the operations they modify. This design provides clean composition and nesting capabilities. -## 11. Passes and Pipelines +### 5.1 Control Operations (`ctrl` / `negctrl`) -- NormalizationPass: reach normal form (modifier ordering, sequence flattening) -- ControlPushdownPass: transforms ctrl(seq{...}) into seq{ ctrl(...) ... } when valid; inverse supported -- AdjointPropagationPass: moves inv() across sequences by reversing order and adjointing gates -- ParamConstFoldPass: constant folds parameterized gates when params static -- Optional: PowerDecompositionPass: decomposes pow(r) into native gates if backend constraints require it +Control operations add quantum control conditions to arbitrary quantum operations: -## 12. Conversions Between Dialects +**mqtref (Reference Semantics):** -- mqtref -> mqtopt: map base gates and modifiers; sequences become mqtopt.seq with implicit terminator; controls and targets are threaded linearly and produce fresh SSA values (including controls) -- mqtopt -> mqtref: erase value results; controls become operands only; maintain sequence/modifier structure; use inlining/materialization where required +```mlir +// Single control +mqtref.ctrl %c0 { + mqtref.x %q0 // Controlled-X (CNOT) +} -## 13. Testing Strategy (TDD-first) +// Multiple controls +mqtref.ctrl %c0, %c1 { + mqtref.x %q0 // Toffoli gate (CCX) +} -- Philosophy - - Adopt test-driven development: write the failing LIT/unit tests first for every op, parser/printer, verifier, canonicalization, pass, and conversion; then implement the minimal code to pass them; finally refactor with tests green. - - Prefer readable MLIR FileCheck tests for IR shape, canonicalization normal forms, and parser/printer round-trips; supplement with targeted C++ unit tests for interfaces and libraries. +// Negative controls +mqtref.negctrl %c0 { + mqtref.x %q0 // X gate triggered when c0 is |0⟩ +} +``` -- LIT: ODS/Parser/Printer and Verifiers - - Round-trip tests for all base gates (both dialects) with static/dynamic params; ensure constant-folding favors static attrs. - - Modifiers: nested forms, enforced ordering (controls outermost, inverse innermost), merges, and folds. - - Sequences: inlining/flattening; implicit terminator handling (no explicit terminators required in text form). - - Sugar: cx, cz, ccx/ccz, crx/cry/crz/cp, mcx/mcz/mcp variants; confirm desugaring to normalized IR. - - Errors: duplicate controls (rejected by verifier), non-unitary ops inside sequences/specs, mismatched yields in value semantics, arity/parameter mismatches in unitary.apply, undefined symbols, non-power-of-two matrices. +**mqtopt (Value Semantics):** -- LIT: Unitary Definitions and Applications - - mqt.gate.def with a fixed 2x2 matrix (e.g., X) and its application from both dialects; include a Hermitian trait check via inv canonicalization. - - mqt.gate.def with parameters and composite spec region (e.g., CRZ); test both mqtref.unitary.apply and mqtopt.unitary.apply; wrap with modifiers and confirm canonical forms. - - Negative cases: missing matrix and body, arity mismatch between matrix and arity attribute, illegal ops in spec region, non-linear qubit use in spec when prohibited. +```mlir +%c0_out, %q0_out = mqtopt.ctrl %c0_in { + %q0_new = mqtopt.x %q0_in : !mqtopt.qubit + mqtopt.yield %q0_new : !mqtopt.qubit +} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) +``` + +**Rationale**: By treating controls as explicit wrapper operations, we can uniformly add controls to any quantum operation, including sequences and other modifiers. The region-based design makes the controlled operation explicit in the IR and enables easy analysis of the controlled subcomputation. -- Canonicalization and Folds - - inv(pow(X,k)) -> pow(inv(X),k); pow(X,-k) -> pow(inv(X),k); inv(base) -> base-inverse; ctrl merging; removal of empty control lists. - - Ensure modifier ordering normalization is stable and unique. +**Design Note**: In value semantics, control qubits must be threaded through the operation even though they're not modified, maintaining SSA form and enabling dataflow analysis. -- Interface unit tests (C++) - - UnitaryOpInterface: target/control operand/result queries across base, modifiers, sequences, and unitary.apply. - - UnitaryExpr: adjoint, power, composition, 2x2/4x4 multiply, and dense materialization for larger arities. +### 5.2 Inverse Operations (`inv`) -- Builder API (C++) - - Compile-time tests for the generic template and generated overloads. - - Runtime smoke tests building small circuits, including sugar (cx, ccx, mcx/mcz/mcp) and nested modifiers. +Inverse operations compute the adjoint (Hermitian conjugate) of enclosed operations: -- Pass and Conversion tests - - Normalization, ControlPushdown, AdjointPropagation, ParamConstFold. - - mqtref<->mqtopt conversions with modifiers/sequences and linear control threading; preserve normal form. +```mlir +// mqtref: Inverse of S gate (equivalent to S-dagger) +mqtref.inv { + mqtref.s %q0 +} - 13.0. Checkstring Robustness and Idiomatic MLIR Testing +// mqtopt: Value-threaded inverse +%q0_out = mqtopt.inv { + %q0_temp = mqtopt.s %q0_in : !mqtopt.qubit + mqtopt.yield %q0_temp : !mqtopt.qubit +} : (!mqtopt.qubit) -> !mqtopt.qubit +``` -To minimize false negatives from fragile FileCheck patterns and to keep tests maintainable: +**Rationale**: Making inverse explicit allows analysis passes to reason about adjoint relationships and enables optimizations like `inv(inv(X)) → X`. It also provides a uniform way to express inverse operations without requiring dedicated inverse variants of every gate. -- Prefer round‑trip tests: `RUN: mlir-opt %s | FileCheck %s` and, where appropriate, `RUN: mlir-opt %s | mlir-opt | FileCheck %s` to ensure printers/parsers are robust. -- Normalize before checking when the exact textual form is not the goal: add `-canonicalize -cse` to the pipeline to check semantic shape rather than incidental formatting. -- Use `-split-input-file` to host many small, focused test cases in one file. -- For negative tests, use `-verify-diagnostics` with `// expected-error`/`// expected-note` comments instead of FileCheck. -- Avoid matching SSA value numbers; anchor on op names, attributes, and structural patterns. Use `CHECK-LABEL:` for sectioning, `CHECK-SAME:` to continue lines, and `CHECK-DAG:` for order‑insensitive matches where appropriate. -- Where printer formatting may change, prefer `-mlir-print-op-generic` to stabilize tests. -- For floating‑point literals, avoid brittle exact decimal matches; either rely on canonicalization (e.g., constants folded) or match with tolerant regex patterns. -- For parser/printer sugar, add paired tests: one that checks the sugared input parses to the normalized IR, and another that checks the normalized IR prints back to the sugared form when expected. -- Add programmatic C++ unit tests to construct and verify IR: - - Parse with `mlir::parseSourceString`, run passes with `mlir::PassManager`, and check invariants via `Operation::verify()`. - - Build IR with `OpBuilder` and the CircuitBuilder API, print to string, and compare with FileCheck‑style matchers or targeted substring assertions. +### 5.3 Power Operations (`pow`) -Example (LIT skeleton): +Power operations compute fractional or integer powers of quantum operations: ```mlir -// RUN: mlir-opt %s -mqtref-normalize -canonicalize | FileCheck %s +// Square root of X gate +mqtref.pow {exponent = 0.5 : f64} { + mqtref.x %q0 +} -// CHECK-LABEL: func @demo -func.func @demo() { - %q = mqtref.allocQubit - // CHECK: mqtref.ctrl( - mqtref.ctrl(%q) { mqtref.x %q } - return +// Dynamic exponent +%exp = arith.constant 0.25 : f64 +mqtref.pow %exp { + mqtref.ry %q0 {angle = 3.14159} } ``` -## 14. File/Code Changes (Planned) +**Rationale**: Power operations are essential for quantum algorithms (e.g., quantum phase estimation, Grover's algorithm) and appear frequently in decompositions. Making them first-class enables direct representation without approximation. -14.1. Common (shared) +### 5.4 Modifier Composition and Canonicalization -- New: mlir/include/mlir/Dialect/Common/IR/QuantumInterfaces.td (consolidated UnitaryOpInterface) -- New: mlir/include/mlir/Dialect/Common/IR/UnitaryExpr.h/.cpp (support library) -- Update: mlir/include/mlir/Dialect/Common/IR/CommonTraits.td/.h (add helper traits if needed) -- New: mlir/include/mlir/Dialect/Common/IR/ParserPrinterUtils.h/.cpp for modifiers/params/sequence printing and sugar -- New: mqt definitions dialect for unitary definitions - - Headers: mlir/include/mlir/Dialect/MQT/IR/MQTDialect.td, MQTOps.td (mqt.gate.def, mqt.gate.decl) - - Impl: mlir/lib/Dialect/MQT/IR/MQTDialect.cpp, MQTOps.cpp (parser/printer/verifier) +**Canonical Ordering**: To ensure consistent IR, we enforce a canonical nesting order: +`ctrl` (outermost) → `pow` → `inv` (innermost) - 14.2. mqtref +**Canonicalization Rules**: -- Update: MQTRefOps.td - - Redefine base UnitaryOp without control operands - - Add Modifiers: ctrl, negctrl, inv, pow (with implicit region terminators) - - Add SequenceOp (implicit terminator) - - Add Unitary invocation op: unitary.apply (call-site implementing UnitaryOpInterface) - - Use dedicated definition ops: mqt.gate.def (and optional mqt.gate.decl) in the new mqt definitions dialect; no func.func attributes - - Keep Resource/Reset/Measure as-is -- Update: MQTRefInterfaces.td -> adopt Common UnitaryOpInterface -- New: MQTRefCircuitBuilder.h/.cpp (generated from StdOps.td.inc) +- `inv(inv(X)) → X` (double inverse elimination) +- `pow(X, 1) → X` (identity power elimination) +- `pow(X, 0) → identity` (zero power simplification) +- `inv(pow(X, k)) → pow(inv(X), k)` (inverse-power commutation) +- `ctrl(inv(X)) → inv(ctrl(X))` when mathematically equivalent - 14.3. mqtopt +**Rationale**: Canonical ordering prevents equivalent operations from having different representations, which would complicate analysis and optimization. The canonicalization rules capture mathematical identities that enable simplification. -- Update: MQTOptOps.td - - Redefine base UnitaryOp without control operands; value results for targets - - Add Modifiers: ctrl/negctrl (linear threading of controls), inv, pow (implicit terminators) - - Add SequenceOp (implicit terminator) with linear threading - - Add Unitary invocation op: unitary.apply (call-site implementing UnitaryOpInterface) - - Use dedicated definition ops from the mqt definitions dialect: mqt.gate.def (and optional mqt.gate.decl); no func.func attributes -- Update: MQTOptInterfaces.td -> adopt Common UnitaryOpInterface; ensure queries expose both operands and results as specified -- New: MQTOptCircuitBuilder.h/.cpp (generated) +## 6. Sequence Operations - 14.4. TableGen Codegen Enhancements +Sequences group multiple quantum operations into logical units that can be analyzed and transformed as a whole. -- Extend StdOps.td.inc to also emit builder helpers via a new include (e.g., MQTRefCircuitBuilder.inc / MQTOptCircuitBuilder.inc) using preprocessor macros -- Prefer the generic template entry-point and generate thin overloads on top +### 6.1 Basic Sequences (Reference Semantics) - 14.5. Passes +```mlir +mqtref.seq { + mqtref.h %q0 // Hadamard gate + mqtref.ctrl %q0 { // Controlled operation + mqtref.x %q1 + } + mqtref.h %q0 // Another Hadamard +} +``` -- New pass registrations and implementations in mlir/lib/.../Transforms for normalization, propagation, folding +**Rationale**: Sequences provide a natural grouping mechanism for subcircuits that should be treated as units during optimization. They also enable hierarchical analysis - passes can choose to operate at the sequence level or dive into individual operations. - 14.6. Conversions +### 6.2 Value Semantics Threading -- Update/refactor existing conversions to respect new modifier/sequence ops +In `mqtopt`, sequences must explicitly thread all qubit values through the computation: - 14.7. Documentation +```mlir +%q0_out, %q1_out = mqtopt.seq %q0_in, %q1_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { +^entry(%q0: !mqtopt.qubit, %q1: !mqtopt.qubit): + %q0_h = mqtopt.h %q0 : !mqtopt.qubit + %q0_cx, %q1_cx = mqtopt.cnot %q0_h, %q1 : !mqtopt.qubit, !mqtopt.qubit + %q0_final = mqtopt.h %q0_cx : !mqtopt.qubit + mqtopt.yield %q0_final, %q1_cx : !mqtopt.qubit, !mqtopt.qubit +} +``` -- Update docs/mlir/Conversions.md and write new docs/mlir/Dialect.md for user-facing syntax and builder API -- Docstring guideline: every op and modifier must include fenced MLIR examples demonstrating sugar and canonicalized forms +**Explanation of ^entry syntax**: This is standard MLIR region syntax where `^entry` names the basic block and `(%q0: !mqtopt.qubit, %q1: !mqtopt.qubit)` declares the block arguments with their types. The sequence operation's operands become the block arguments, enabling proper SSA value threading within the region. -## 15. Migration and Deprecation +**Rationale**: Explicit value threading in sequences maintains SSA form and enables dataflow analysis. The block argument syntax is standard MLIR and clearly shows how values flow into and out of the sequence region. -- No backward compatibility required; remove controls from base gates and replace with modifiers. -- No temporary helper conversion will be provided; all code is expected to be rewritten and fixed accordingly (tests will be updated alongside). -- QubitRegisters have been replaced with memrefs; this plan reflects the current state. +## 7. Unified Interface Design -## 16. Risks and Mitigations +### 7.1 UnitaryOpInterface -- Interface uniformity: Ensure UnitaryOpInterface exposes both operand and (optional) result views; provide hasTargetResults() and control result accessors to avoid dialect-specific branching in clients. -- Performance of UnitaryExpr: Keep operations cheap; favor small fixed-size matrices; avoid materialization unless necessary; support dense materialization for any arity (with fast paths for 2x2 and 4x4). -- Modifier/sequence ordering: Define strict normal form (controls outermost, inverse innermost); add extensive LIT to prevent rewrite loops. -- Linear controls in mqtopt: Provide utilities for segment attrs and builders that always produce fresh SSA for controls. +All quantum operations implement a unified interface that abstracts away dialect differences: -## 17. Milestones and Work Breakdown +```cpp +class UnitaryOpInterface { +public: + // Gate identification + StringRef getIdentifier(); + + // Qubit counts + size_t getNumTargets(); + size_t getNumQubits(); + + // Control queries + bool isControlled(); + size_t getNumPosControls(); + size_t getNumNegControls(); + size_t getNumControls(); + + // Operand access + OperandRange getTargetOperands(); + OperandRange getPosControlOperands(); + OperandRange getNegControlOperands(); + OperandRange getControlOperands(); + OperandRange getQubitOperands(); + OperandRange getOperands(); + + // Result access (value semantics only) + bool hasResults(); + ResultRange getTargetResults(); + ResultRange getPosControlResults(); + ResultRange getNegControlResults(); + ResultRange getControlResults(); + ResultRange getQubitResults(); + ResultRange getResults(); + + // Mathematical representation + UnitaryMatrix getUnitaryMatrix(); + bool hasStaticUnitary(); + + // Parameter access + size_t getNumParams(); + bool isParameterized(); + ArrayAttr getStaticParameters(); + OperandRange getDynamicParameters(); +}; +``` -M1 — Foundations (Common) [~1–2 weeks] +**Rationale**: This interface allows analysis passes to work uniformly across dialects without knowing whether they're dealing with reference or value semantics. The `hasResults()` method provides a clean way to branch when needed. -- Create QuantumInterfaces.td with UnitaryOpInterface ✓ -- Implement UnitaryExpr skeleton (adjoint, power, compose, 2x2/4x4 multiply) ✓ -- Parser/Printer utils for params, modifiers, sugar ✓ +### 7.2 Implementation Strategy -M2 — mqtref Base + Modifiers + Sequence [~2 weeks] +- **Base Gates**: Implement interface directly with simple delegation to operands/results +- **Modifiers**: Delegate to wrapped operations with appropriate adjustments (e.g., ctrl adds to control count) +- **Sequences**: Provide aggregate information (total qubits touched, combined unitary matrix) -- Redefine base UnitaryOp (no controls) ✓ -- Implement ctrl/negctrl/inv/pow with verifiers and canonicalization ✓ -- Implement seq with inliner and canonicalization; implicit terminator ✓ -- Implement unitary.apply (instantiation) ✓ -- Use dedicated mqt.gate.def definitions (and optional mqt.gate.decl); add verifiers/utilities ✓ -- Update std gate defs and assembly formats ✓ -- LIT tests: ops, parsers, canonicalization, mqt.gate.def and unitary.apply ✓ +**Rationale**: This delegation pattern means that complex nested structures (e.g., controlled inverse sequences) automatically provide correct interface responses without manual bookkeeping. -M3 — mqtopt Mirror [~2 weeks] +## 8. User-Defined Gates -- Redefine base gate ops with value outputs ✓ -- Implement modifiers with linear control threading; sequence with implicit terminator ✓ -- Implement unitary.apply (instantiation) ✓ -- Use mqt.gate.def for definitions (and optional mqt.gate.decl); add verifiers/utilities ✓ -- Update interface and verifiers ✓ -- LIT tests mirroring mqtref, including mqt.gate.def and unitary.apply ✓ +User-defined gates enable custom quantum operations with reusable definitions. -M4 — Builders and Ergonomics [~1 week] +### 8.1 Matrix-Based Definitions -- Generate CircuitBuilder helper API with generic template + overloads ✓ -- Parser/printer sugar for compact modifier nesting and cx/cz/ccx/ccz/mcx/mcz/mcp ✓ -- C++ smoke tests ✓ +```mlir +// Define a custom single-qubit gate via its unitary matrix +mqtref.gate_def @pauli_y : tensor<2x2xcomplex> = + dense<[[0.0+0.0i, 0.0-1.0i], [0.0+1.0i, 0.0+0.0i]]> : tensor<2x2xcomplex> -M5 — Passes and Conversions [~1–2 weeks] +// Use the defined gate +mqtref.apply_gate @pauli_y %q0 +``` -- Normalization, ControlPushdown, AdjointPropagation, ParamConstFold ✓ -- Update mqtref<->mqtopt conversions for modifiers/sequences with linear controls ✓ -- Tests ✓ +**Rationale**: Matrix definitions provide an exact specification for custom gates, enabling precise simulation and analysis. They're particularly useful for gates that don't have natural decompositions into standard gates. -M6 — Documentation and Polishing [~1 week] +### 8.2 Composite Definitions -- Update docs and examples; ensure all docstrings contain fenced MLIR examples ✓ -- Final test stabilization ✓ +```mlir +// Define a gate as a sequence of existing operations +mqtref.gate_def @bell_prep %q0 : !mqtref.qubit, %q1 : !mqtref.qubit { + mqtref.h %q0 + mqtref.cnot %q0, %q1 +} + +// Apply the composite gate +mqtref.apply_gate @bell_prep %q0, %q1 +``` + +**Rationale**: Composite definitions enable hierarchical design and code reuse. They can be inlined during lowering or kept as high-level constructs for analysis, depending on optimization needs. + +### 8.3 Parameterized Gates + +```mlir +// Define a parameterized rotation gate +mqtref.gate_def @custom_rotation %q : !mqtref.qubit attributes {params = ["theta", "phi"]} { + mqtref.rz %q {angle = %phi} + mqtref.ry %q {angle = %theta} + mqtref.rz %q {angle = %phi} +} + +// Apply with specific parameters +mqtref.apply_gate @custom_rotation %q0 {theta = 1.57 : f64, phi = 0.78 : f64} +``` -Note: ✓ indicates planned completion within this revamp; actual sequencing will be tracked in the repository issue tracker. +**Rationale**: Parameterized gates enable template-like definitions that can be instantiated with different parameters, supporting gate libraries and algorithmic patterns. -## 18. Acceptance Criteria +## 9. Parser Sugar and Builder APIs -- Both dialects compile and pass LIT tests covering base gates, modifiers, sequences, mqt.gate.def definitions, unitary.apply, and printers (including sugar like cx, ccx, mcx, mcp). -- Unified UnitaryOpInterface available, used by all unitary-capable ops, and provides dialect-adaptive input/output queries. -- Support for arbitrary unitary definitions via dedicated ops: mqt.gate.def (matrix-defined and composite-defined) and unitary.apply in both dialects; modifiers can wrap applications. -- CircuitBuilder API available, with generic template and overloads for core gates; sugar for single- and multi-controlled gates. -- Canonicalization suite ensures normalized IR: controls outermost; inverse innermost; pow/inv rules as specified; linear controls in mqtopt. -- Conversions between mqtref and mqtopt handle modifiers/sequences and linear threading of controls. +### 9.1 Parser Sugar for Common Patterns -## 19. Illustrative Examples +Instead of complex chaining syntax, we provide natural abbreviations for frequent patterns: -19.1. mqtref (IR) +**Controlled Gate Shortcuts:** ```mlir -// prepare qubits -%q0 = mqtref.allocQubit -%q1 = mqtref.allocQubit -%c0 = mqtref.allocQubit -%c1 = mqtref.allocQubit -%c2 = mqtref.allocQubit - -// Single-controlled -mqtref.crz %c0, %q0 (0.25) - -// Double-controlled Toffoli -mqtref.ccx %c0, %c1 %q0 - -// Multi-controlled -mqtref.mcx(%c0, %c1, %c2) %q1 -mqtref.mcp(1.234)(%c0, %c1) %q0 - -// Nested modifiers & canonicalization -mqtref.inv mqtref.ctrl(%c0) { mqtref.rx(%q0) (3.14159) } -// -> mqtref.ctrl(%c0) { mqtref.inv mqtref.rx(%q0) (3.14159) } +// Standard controlled gates (parser expands these automatically) +mqtref.cx %c, %t // → mqtref.ctrl %c { mqtref.x %t } +mqtref.cz %c, %t // → mqtref.ctrl %c { mqtref.z %t } +mqtref.ccx %c0, %c1, %t // → mqtref.ctrl %c0, %c1 { mqtref.x %t } + +// Multi-controlled variants +mqtref.mcx (%c0, %c1, %c2), %t // → mqtref.ctrl %c0, %c1, %c2 { mqtref.x %t } +mqtref.mcp (%c0, %c1), %t {phase = 1.57} // Controlled phase with multiple controls ``` -19.2. mqtopt (IR) +**Modifier Shortcuts in Region Context:** ```mlir -%q0 = mqtopt.allocQubit -%q1 = mqtopt.allocQubit -%c = mqtopt.allocQubit - -// Sequence returns updated targets; controls are threaded linearly -mqtopt.seq { - %q0_1 = mqtopt.h %q0 - // ctrl threads control linearly: consumes %c, yields %c_1 - %q0_2, %c_1 = mqtopt.ctrl(%c) { mqtopt.x %q0_1 } +// Within regions, allow modifier keywords as operation prefixes +mqtref.seq { + inv s %q0 // → mqtref.inv { mqtref.s %q0 } + ctrl(%c) x %q1 // → mqtref.ctrl %c { mqtref.x %q1 } + pow(0.5) h %q2 // → mqtref.pow {exponent = 0.5} { mqtref.h %q2 } } ``` -19.3. Canonicalization +**Rationale**: This approach provides ergonomic shortcuts for common cases without introducing complex chaining operators. The shortcuts expand to the full form during parsing, so all downstream processing sees the canonical representation. -```mlir -// inv(pow(X, k)) -> pow(inv(X), k) -mqtref.inv mqtref.pow(3) { mqtref.x %q } // -> mqtref.pow(3) { mqtref.inv mqtref.x %q } +### 9.2 C++ Builder API + +```cpp +// Fluent builder interface +class QuantumCircuitBuilder { +public: + // Basic gates with natural names + QuantumCircuitBuilder& x(Value qubit); + QuantumCircuitBuilder& h(Value qubit); + QuantumCircuitBuilder& cnot(Value control, Value target); + + // Modifier combinators + QuantumCircuitBuilder& ctrl(ValueRange controls, std::function body); + QuantumCircuitBuilder& inv(std::function body); + QuantumCircuitBuilder& pow(double exponent, std::function body); + + // Convenient combinations + QuantumCircuitBuilder& ccx(Value c1, Value c2, Value target); + QuantumCircuitBuilder& toffoli(Value c1, Value c2, Value target) { return ccx(c1, c2, target); } +}; + +// Example usage +QuantumCircuitBuilder builder(mlirBuilder, location); +builder.h(q0) + .ctrl({c0}, [&]() { builder.x(q1); }) + .ccx(c0, c1, q2); +``` + +**Rationale**: The builder API provides a natural C++ interface that maps cleanly to the IR structure. Lambda functions for modifier bodies give clear scoping and enable complex nested structures. + +## 10. Analysis and Optimization Infrastructure + +### 10.1 UnitaryMatrix Support Library + +```cpp +class UnitaryMatrix { +private: + // Efficient representations for common cases + std::variant< + Matrix2x2, // Single-qubit gates + Matrix4x4, // Two-qubit gates + SymbolicExpr, // Larger or parameterized gates + LazyProduct // Composition chains + > representation; + +public: + // Composition operations + UnitaryMatrix compose(const UnitaryMatrix& other) const; + UnitaryMatrix adjoint() const; + UnitaryMatrix power(double exponent) const; + UnitaryMatrix control(unsigned num_controls) const; + + // Materialization + DenseElementsAttr toDenseElements(MLIRContext* ctx) const; + bool isIdentity() const; + bool isUnitary() const; // Verification +}; +``` + +**Rationale**: This library provides efficient computation for the unitary matrices that drive quantum optimization. The multi-representation approach keeps small matrices fast while supporting larger compositions through symbolic expressions. + +### 10.2 Canonicalization Passes + +**NormalizationPass**: Enforces canonical modifier ordering and eliminates redundancies + +- Reorders nested modifiers to canonical form (ctrl → pow → inv) +- Eliminates identity operations (`pow(X, 1) → X`, `inv(inv(X)) → X`) +- Merges adjacent compatible modifiers +- Simplifies power modifiers wherever feasible (`pow(RZ(pi/2), 2) → RZ(pi)`) + +**SequenceFlatteningPass**: Inlines nested sequences when beneficial -// pow(X, -k) -> pow(inv(X), k) -mqtref.pow(-2) { mqtref.h %q } // -> mqtref.pow(2) { mqtref.inv mqtref.h %q } +- Removes sequence boundaries that don't provide optimization barriers +- Preserves sequences that are referenced by symbols or have special annotations -// inv(baseGate) -> baseGateInverse -mqtref.inv mqtref.s %q // -> mqtref.sdg %q +**ConstantFoldingPass**: Folds static parameters and eliminates trivial operations -// inv(ctrl(X)) -> ctrl(inv(X)) -mqtref.inv mqtref.ctrl(%c) { mqtref.rz(%q) (0.5) } -// -> mqtref.ctrl(%c) { mqtref.inv mqtref.rz(%q) (0.5) } +- Combines adjacent rotations with static angles +- Eliminates gates with zero rotation angles +- Evaluates static matrix expressions + +**Rationale**: These passes work together to keep the IR in a canonical form that simplifies subsequent analysis and optimization. Each pass has a focused responsibility and can be run independently or as part of a pipeline. + +## 11. Dialect Conversions + +### 11.1 mqtref ↔ mqtopt Conversion + +The conversion between reference and value semantics requires careful handling of SSA values and side effects. + +**mqtref → mqtopt (Adding SSA Values)**: + +```cpp +// Pattern: Convert side-effect operations to value-producing operations +mqtref.x %q → %q_new = mqtopt.x %q : !mqtopt.qubit + +// Control conversion requires threading control qubits +mqtref.ctrl %c { mqtref.x %t } → + %c_out, %t_out = mqtopt.ctrl %c_in { + %t_new = mqtopt.x %t_in : !mqtopt.qubit + mqtopt.yield %t_new : !mqtopt.qubit + } : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) ``` -19.4. C++ Builder +**mqtopt → mqtref (Removing SSA Values)**: ```cpp -using namespace mqt::ir::ref; -CircuitBuilder qb(b, loc); -auto q0 = qb.allocQubit(); -auto q1 = qb.allocQubit(); -auto c0 = qb.allocQubit(); -auto c1 = qb.allocQubit(); - -qb.cx(c0, q0); // CNOT sugar -qb.ccx(c0, c1, q0); // Toffoli sugar -qb.mcx({c0, c1}, q1); // Multi-controlled X -qb.mcp({c0}, q0, M_PI/4.0); // Multi-controlled phase -qb.rx(q0, M_PI/2).inv(); // R_x(pi/2)^-1 (inverse as innermost in normal form) -qb.seq([&]{ qb.h(q0); qb.rz(q0, t); }); +// Drop results and convert to side-effect form +%q_out = mqtopt.x %q_in : !mqtopt.qubit → mqtref.x %q_in + +// Ensure proper dominance and liveness in the converted code ``` -## 20. Documentation Guidelines +**Key Challenges**: + +- **SSA Value Threading**: mqtopt requires explicit threading of all qubit values +- **Region Structure**: Converting between region-based and flat representations +- **Dominance Preservation**: Ensuring converted code maintains MLIR dominance requirements + +**Rationale**: These conversions enable using the same quantum algorithm in different contexts - mqtref for simulation and debugging, mqtopt for optimization and compilation to hardware. + +### 11.2 Lowering to Hardware Targets + +Standard conversion patterns lower high-level operations to target-specific instruction sets: + +```cpp +// Example: Lowering controlled gates to native basis +mqtref.ctrl %c { mqtref.ry %t {angle = θ} } → + mqtref.rz %t {angle = θ/2} + mqtref.cnot %c, %t + mqtref.rz %t {angle = -θ/2} + mqtref.cnot %c, %t +``` + +**Rationale**: Hardware targets have limited native gate sets, so high-level operations must be decomposed into available primitives while preserving mathematical equivalence. + +## 12. Testing Strategy + +### 12.1 Unit Tests (C++) + +- **Interface implementations**: Verify UnitaryOpInterface methods return consistent results +- **UnitaryMatrix library operations**: Test composition, adjoint, and power operations +- **Builder API functionality**: Ensure generated IR matches expected patterns + +### 12.2 Integration Tests (LIT) + +- **Parser/printer round-trips**: Verify text representation preserves semantics +- **Canonicalization correctness**: Test that canonical forms are stable and unique +- **Conversion patterns**: Verify dialect conversions preserve quantum semantics +- **Error handling and verification**: Test malformed IR is properly rejected + +### 12.3 Quantum Semantics Tests + +```mlir +// RUN: mlir-opt %s -test-quantum-canonicalize | FileCheck %s + +// Test double inverse elimination +func.func @test_double_inverse() { + %q = mqtref.alloc : !mqtref.qubit + + // CHECK: mqtref.x %{{.*}} + // CHECK-NOT: mqtref.inv + mqtref.inv { + mqtref.inv { + mqtref.x %q + } + } + return +} + +// Test control threading in value semantics +func.func @test_control_threading() { + %c = mqtopt.alloc : !mqtopt.qubit + %t = mqtopt.alloc : !mqtopt.qubit + + // CHECK: %[[C_OUT:.*]], %[[T_OUT:.*]] = mqtopt.ctrl %{{.*}} { + // CHECK: %[[T_NEW:.*]] = mqtopt.x %{{.*}} + // CHECK: mqtopt.yield %[[T_NEW]] + // CHECK: } + %c_out, %t_out = mqtopt.ctrl %c { + %t_new = mqtopt.x %t : !mqtopt.qubit + mqtopt.yield %t_new : !mqtopt.qubit + } : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) + + return +} +``` + +**Rationale**: Quantum semantics are subtle and error-prone. Comprehensive testing with both positive and negative test cases ensures the implementation correctly handles edge cases and maintains mathematical correctness. + +## 13. Conclusion + +This comprehensive redesign addresses the fundamental limitations of the current quantum MLIR infrastructure while positioning it for future growth. The key innovations—compositional modifiers, unified interfaces, and enhanced ergonomics—will significantly improve developer productivity and enable more sophisticated quantum compiler optimizations. -- Every op (base, modifier, seq) must include fenced MLIR examples in the ODS docstrings, demonstrating both verbose and sugar forms and at least one canonicalization result. -- Use idiomatic names in MLIR, C++, and QC contexts: UnitaryOpInterface, getTargetOperands, getTargetResults, getPosControlOperands, getNegControlOperands, getUnitaryExpr, getAllOperandQubits, getAllResultQubits, etc. +The modular implementation plan reduces project risk by maintaining working functionality at each milestone. The emphasis on testing and performance ensures that the new system will be both reliable and efficient. -## 21. Next Steps +By aligning with MLIR best practices and providing clean abstractions, this design creates a solid foundation for quantum compiler research and development within the MQT ecosystem. -- Proceed to implementation per milestones now that the revisions are confirmed. +The expected outcome is a quantum compiler infrastructure that is easier to use, more analyzable, and better positioned for the evolving needs of quantum computing research and application development. From 51505ecfad5ec252da0c89a85ba9b1d8c057f10e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 24 Sep 2025 15:10:36 +0200 Subject: [PATCH 003/419] =?UTF-8?q?=F0=9F=9A=A7=20commit=20misc.=20other?= =?UTF-8?q?=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/QuantumDialectRevamp-Rationale.md | 197 +++++++++++++ .../quantum-dialect-revamp-tracking-issue.md | 265 ++++++++++++++++++ mlir-quantum-dialect.md | 38 +++ prompt.md | 54 ++++ 4 files changed, 554 insertions(+) create mode 100644 docs/mlir/QuantumDialectRevamp-Rationale.md create mode 100644 docs/mlir/quantum-dialect-revamp-tracking-issue.md create mode 100644 mlir-quantum-dialect.md create mode 100644 prompt.md diff --git a/docs/mlir/QuantumDialectRevamp-Rationale.md b/docs/mlir/QuantumDialectRevamp-Rationale.md new file mode 100644 index 0000000000..0224759ce5 --- /dev/null +++ b/docs/mlir/QuantumDialectRevamp-Rationale.md @@ -0,0 +1,197 @@ +# Quantum Dialect Revamp — Design Rationale and Background + +Author: Junie (auto-generated) +Date: 2025-09-23 +Status: Supplementary design rationale to the implementation plan +Scope: Rationale for the revamped MLIR quantum dialect architecture under mlir/ + +## Abstract + +This document motivates and explains the major design decisions in the planned revamp of the MQTRef and MQTOpt MLIR dialects for hybrid classical–quantum computing. The goals are to improve composability, analysis, and ergonomics by introducing first-class modifier ops (controls, inverse, power), a sequence op, a unified UnitaryOpInterface, and a lightweight UnitaryExpr support library. The rationale emphasizes alignment with idiomatic MLIR, predictable normalization via canonicalization, and performance-conscious design for common 2×2 and 4×4 unitaries used in compiler transformations. + +## 1. Background and Objectives + +Current challenges: + +- Controls and other modifiers are embedded into each unitary gate, complicating analysis and normalization. +- Builder ergonomics are cumbersome; textual IR lacks compact sugar for controlled variants. +- Two dialects (reference semantics vs value semantics) diverge where interfaces should be uniform. + +Primary objectives: + +- Treat composition as a first-class concern (explicit modifier and sequence ops). +- Unify analysis via a single interface (UnitaryOpInterface) with dialect-adaptive in/out views. +- Preserve performance for frequent 2×2/4×4 unitaries without adding heavy dependencies. +- Keep the IR normalized and ergonomic through canonicalization and parser/printer sugar. + +## 2. Why Explicit Modifiers and Sequences? + +### 2.1. Explicit inv/ctrl/negctrl/pow as wrapper ops + +- Composability: Modifiers become uniform constructs that can wrap either single gates or entire sequences. +- Laziness: Evaluation is deferred; transformations can rewrite at the IR level without materializing matrices. +- Normal forms: Canonicalization can enforce a stable modifier order (inverse innermost, controls outermost), ensuring predictable patterns and avoiding rewrite loops. + +Illustration (MLIR): + +```mlir +// Before (implicit modifiers baked into gates) +// mqtref.x %q ctrl(%c) + +// After (explicit wrappers) +mqtref.ctrl(%c) { mqtref.x %q } +``` + +### 2.2. Sequence op + +- Expressive power: A seq region contains only unitary operations (including wrapped sequences). It captures common subcircuits as first-class objects. +- Implicit terminator: Smoother user experience and more idiomatic MLIR text. +- Analysis: The UnitaryExpr for a sequence is the product of child unitaries in program order. + +Example: + +```mlir +mqtref.seq { + mqtref.h %q0 + mqtref.ctrl(%c) { mqtref.x %q0 } +} +``` + +## 3. Interface Unification Across Dialects + +- MQTRef (reference semantics) and MQTOpt (value semantics) share the same high-level queries via UnitaryOpInterface. +- Dialect-adaptive accessors: + - getTargetOperands(), getPosControlOperands(), getNegControlOperands() — always available. + - getTargetResults(), getPosControlResults(), getNegControlResults() — meaningful for MQTOpt; empty for MQTRef. + - Aggregates: getAllOperandQubits() and getAllResultQubits() (the latter empty in MQTRef). +- Consistency: Clients (passes, analyses) no longer branch on the concrete dialect; they check hasTargetResults(). + +## 4. Linear Controls in Value Semantics + +- In MQTOpt, all qubits—including controls—are value-semantics and should be treated linearly. +- Modifiers and sequences consume and produce fresh SSA values for controls even if the control action is semantically identity. +- Benefits: Enforces single-use constraints, simplifies dataflow reasoning, and aligns with classical SSA-style optimizations. + +Example: + +```mlir +// Controls are threaded linearly in mqtopt +%q1, %c1 = mqtopt.ctrl(%c0) { mqtopt.x %q0 } +``` + +## 5. Hermitian and Diagonal Traits + +- Hermitian (self-inverse) gates: I, X, Y, Z, H (and possibly others after validation). Canonicalization shortcut: inv(G) -> G. +- Diagonal gates (in computational basis): I, Z, S, Sdg, T, Tdg, Rz, P/Phase, RZZ. These enable commuting analyses and targeted lowers. +- Both are marker traits; they carry no runtime cost and inform canonicalization and analysis. + +## 6. Canonicalization Strategy and Modifier Ordering + +- Ordering: inverse (innermost) — power — controls (outermost). Rewriters maintain this normal form. +- Selected canonicalization: + - inv(inv(X)) -> X + - inv(pow(X, k)) -> pow(inv(X), k) + - pow(X, -k) -> pow(inv(X), k) + - inv(ctrl(X)) -> ctrl(inv(X)) + - inv(baseGate) -> baseGateInverse (parameters adjusted) + - ctrl/negctrl with empty lists are removed; adjacent modifiers of the same kind are merged (no dedup or reorder of controls). + +Examples: + +```mlir +// inv(pow(X, k)) -> pow(inv(X), k) +mqtref.inv mqtref.pow(3) { mqtref.x %q } +// -> mqtref.pow(3) { mqtref.inv mqtref.x %q } + +// pow(X, -k) -> pow(inv(X), k) +mqtref.pow(-2) { mqtref.h %q } +// -> mqtref.pow(2) { mqtref.inv mqtref.h %q } +``` + +## 7. Parameters: Prefer Static, Fold Constants + +- Base-gate parameters should be static attributes whenever possible. +- If a dynamic operand is defined by a constant, fold it into static_params and adjust params_mask (or drop mask if all static). +- Benefits: Enables constant UnitaryExpr materialization and more effective downstream rewrites. + +Examples: + +```mlir +// Before: dynamic parameter +%pi2 = arith.constant 1.5707963267948966 : f64 +mqtref.rx(%q) (%pi2) + +// After: static attribute preferred +mqtref.rx(%q) (1.5707963267948966) +``` + +## 8. UnitaryExpr: Lightweight and Performant + +- Tiny fixed-size complex matrices (2×2, 4×4) plus a compact expression graph (Mul, Adj, Pow, Control, Const, Param, Trig, Exp, Embed). +- No external dependencies; leverages LLVM/MLIR support and std::complex. +- Materialization to DenseElementsAttr for any arity (no artificial limit), with fast paths for 2×2/4×4. +- Intended usage: transformation and conversion passes; not a runtime simulator. + +Example (C++ sketch): + +```cpp +UnitaryExpr u = UnitaryExpr::Const(Mat2::X()); +UnitaryExpr v = UnitaryExpr::Pow(u, 3); +UnitaryExpr w = UnitaryExpr::Adj(v); +auto maybeDense = w.materializeIfStatic(ctx); // DenseElementsAttr when all params static +``` + +## 9. Sequence Unitary and Footprint + +- The UnitaryExpr of a seq is the product of child UnitaryExpr in program order (M_n … M_2 M_1). +- Sequences expose their overall qubit footprint only via getAllOperandQubits()/getAllResultQubits(). The notions of "controls vs targets" are not meaningful for seq as a whole. + +Example: + +```mlir +// The sequence's unitary is the product of the inner unitaries in program order +mqtref.seq { + mqtref.h %q + mqtref.rz(%q) (0.5) +} +``` + +## 10. Ergonomics: Builders and Sugar + +- Generic builder template that dispatches using trait arities; layered with generated overloads per base gate. +- Parser/printer sugar provides concise forms like cx/cz/ccx/ccz and mcx/mcz/mcp(theta). + +Examples: + +```mlir +// Sugar for controlled gates +mqtref.ccx %c0, %c1 %t +mqtref.mcp(1.234)(%c0, %c1) %t + +// Nested modifiers, implicit terminators +mqtref.ctrl(%c) { mqtref.inv mqtref.rx(%q) (3.14159) } +``` + +## 11. Alternatives Considered + +- Single shared base-gate dialect vs per-dialect mirrored base gates: A shared dialect reduces op duplication but clashes with semantics (mqtref operands-only vs mqtopt operands+results), forcing optional results or wrappers and complicating verifiers/canonicalizations; modifiers/seq still require dialect-specific threading. Decision: keep per-dialect base gates; minimize duplication via shared traits/interfaces, TableGen catalogs, and generated builders; use symbol-based unitary.def/apply for custom gates. +- Embedding modifiers into every base gate: rejected due to poor composability and normalization challenges. +- Heavy linear-algebra backends for unitary composition: rejected to avoid dependencies and preserve compile-time performance. +- Treating controls in mqtopt as pass-through results: rejected; linear threading yields better dataflow properties and SSA adherence. + +## 12. Scope Boundaries + +- QubitRegister handling is out of scope; a separate PR will replace it with memref. All new design elements operate solely on Qubit. +- No temporary helper conversion layers; all affected code will be updated directly. + +## 13. Expected Impact + +- More predictable, analyzable IR with explicit composition mechanisms. +- Better usability for both textual IR authors and C++ clients through sugar and builders. +- Robust foundations for optimization passes (normalization, propagation, constant folding) and dialect conversions. + +## 14. References and Further Reading + +- MLIR Interfaces and Traits (mlir.llvm.org) +- QIRO: A Static Single Assignment based Quantum Program Representation for Optimization (doi:10.1145/3491247) +- Project plan: docs/mlir/quantum-dialect-revamp-plan.md diff --git a/docs/mlir/quantum-dialect-revamp-tracking-issue.md b/docs/mlir/quantum-dialect-revamp-tracking-issue.md new file mode 100644 index 0000000000..760453fed9 --- /dev/null +++ b/docs/mlir/quantum-dialect-revamp-tracking-issue.md @@ -0,0 +1,265 @@ +# Tracking: Quantum Dialect Revamp (Self‑Contained) + +This issue tracks the design and implementation of a revamped MLIR quantum dialect stack for MQT. It is self‑contained and does not assume any other local files exist. + +## Context and Goals + +We maintain two quantum dialects: + +- mqtref — reference/memory semantics +- mqtopt — value semantics + +Problems with the current setup: + +- Modifiers (controls, inverse/adjoint, power) are baked into each gate op. +- Composition is clumsy; interfaces are not uniform across dialects. +- Builders are verbose; parser/printer sugar is limited. + +High‑level goals: + +- Make composition first‑class: explicit IR ops for modifiers that can wrap gates or sequences and lazily defer evaluation. +- Provide a single, idiomatic UnitaryOpInterface for analysis across both dialects. +- Dramatically improve ergonomics via a generic builder template and parser/printer sugar (e.g., `cx`, `ccx`, `mcx`, `mcp(theta)`). +- Keep dialects minimal and uniform; push optional features into wrappers/regions. +- Prefer MLIR‑native infrastructure (traits, interfaces, canonicalization, folds, symbol tables). +- QubitRegisters have been replaced by memrefs; only Qubit is used here. + +## Architecture Overview + +- Common layer + - Traits and utilities shared by both dialects (TargetArity, ParameterArity, NoControl, Hermitian, Diagonal). + - A unified UnitaryOpInterface with dialect adapters (exposes both operands and results for value semantics). + - Parser/printer utilities for modifiers, parameters, sequences, and sugar. + - A lightweight UnitaryExpr library is planned for transformation/conversion passes (no external dependencies, fast 2×2/4×4 paths). Its concrete implementation is deferred to execution of the plan. +- mqtref dialect (reference semantics) + - BaseGate ops with only targets and parameters (no embedded modifiers/controls). + - Resource ops (alloc/dealloc, static qubit), reset, measure. + - Sequence and Modifier wrapper ops. +- mqtopt dialect (value semantics) + - Mirrors mqtref base set but with value results for targets. + - Sequence and Modifier wrapper ops with linear threading of all qubits, including controls. + +## Marker Traits (Common) + +- Hermitian — self‑inverse gates (e.g., I, X, Y, Z, H). Canonicalization short‑circuit: `inv(G) -> G`. +- Diagonal — diagonal in computational basis (e.g., I, Z, S, Sdg, T, Tdg, Rz, Phase, RZZ). Enables commutation/aggregation analyses. + +## Types + +- Qubit is the only type used by gates in this revamp. +- QubitRegister is assumed absent and will be handled in a separate PR. + +## Base Gate Ops (Both Dialects) + +- Minimal, uniform base gates with fixed target arity (TargetArity) and parameter counts (ParameterArity). No control operands or modifier attributes. +- Parameters prefer static attributes; constant folding turns dynamic constant operands into static attributes. No mask for `pow`. +- Each base gate provides its UnitaryExpr, and can materialize a DenseElementsAttr when fully static (no arity limit by design). +- Base-gate placement decision: keep base gates per dialect (mqtref and mqtopt); do not introduce a shared executable base-gate dialect. Rationale: semantics mismatch (operands-only vs operands+results) would force optional results/wrappers and complicate verifiers/canonicalizations; duplication is minimized via shared traits/interfaces, a Common TableGen catalog, and generated builders. For custom gates, use symbol-based unitary.def/apply. + +## Modifiers + +Explicit wrapper ops that accept a single unitary op (gate or sequence) in a region with implicit terminator: + +- inv — adjoint of the child unitary +- ctrl / negctrl — positive/negative controls (variadic) +- pow(r) — real exponent; prefer static attribute + +Canonicalization and folds: + +- inv(inv(X)) -> X +- inv(pow(X,k)) -> pow(inv(X), k) +- inv(ctrl(X)) -> ctrl(inv(X)) +- pow(X,1) -> X; pow(X,0) -> identity +- pow(pow(X,a), b) -> pow(X, a\*b) +- pow(X, -k) -> pow(inv(X), k) +- inv(baseGate) -> baseGateInverse (each base gate defines its inverse; parameters updated) +- Controls are outermost modifiers; inverse is innermost. Rewriters normalize order. +- Controls are not deduplicated or reordered by canonicalization; duplicates are rejected by verifiers. +- In mqtopt, controls are linear SSA values and must be consumed/produced (not forwarded unchanged). + +## Sequence Op + +- `seq` is a region (single block, implicit terminator) containing only UnitaryOpInterface ops (including modifiers and nested sequences). +- Canonicalization: inline nested sequences; remove empty sequences; fuse adjacent sequences; move `inv` across sequences by reversing order and adjointing. +- Querying: `getAllOperandQubits()` and `getAllResultQubits()` (mqtopt) expose the overall set of qubits the sequence acts on. `seq` itself does not distinguish controls vs targets. +- The UnitaryExpr of a `seq` is the product of child unitaries in program order (M_n … M_2 M_1). + +## Unified UnitaryOpInterface (Common) + +Idiomatic methods (dialect‑adaptive): + +- Identification/meta: `getIdentifier()`, `getNumTargets()`, `getNumControls()`, `hasControls()`, `isSingleQubit()`, `isTwoQubit()`, `hasParams()`, `hasDynamicParams()`, `isOnlyStaticParams()`. +- Operands/results: `getTargetOperands()`, `getPosControlOperands()`, `getNegControlOperands()`, `hasTargetResults()`, `getTargetResults()`, `getPosControlResults()`, `getNegControlResults()`. +- Aggregates: `getAllOperandQubits()`, `getAllResultQubits()`. +- Unitary: `getUnitaryExpr()`. +- Value‑semantics verification ensures in/out segment sizes match for targets and controls. + +## Arbitrary Unitaries and Gate Definitions + +Not all operations are covered by base ops. The IR supports user‑defined unitaries in two complementary ways. Definitions are declared once (as symbols) and instantiated many times. + +- Matrix‑defined unitary (feasible for small n): supply a 2^n×2^n complex matrix attribute. Target arity n is inferred from the matrix shape. Parameters may be modeled via attributes; prefer static folding. No arity limit on materialization. +- Composite‑defined unitary: supply a region with a `seq` of known gates and modifiers; formal parameters (f64) and formal qubit arguments are allowed. This covers parameterized gates universally. + +IR surface (per dialect; names are idiomatic to MLIR): + +- `mqtref.unitary.def` (SymbolOpInterface, optional matrix attr, optional body region; body contains `mqtref.seq`). +- `mqtref.unitary.apply @sym (params?)` on target qubits; no results (reference semantics). Modifiers can wrap apply. +- `mqtopt.unitary.def` (mirror using value‑semantic ops; body must thread values linearly). +- `mqtopt.unitary.apply @sym (params?)` on target qubit values; yields updated target values. + +Verification: + +- Matrix shape must be square and power‑of‑two; infer n. If both matrix and region are provided, check consistency when possible. +- Regions must contain only UnitaryOpInterface ops and correct argument/result counts. + +Canonicalization: + +- Inline small composite definitions at call sites when profitable to expose further simplifications. +- Materialize matrix‑defined unitaries to UnitaryExpr on demand. + +## Builders and Parser/Printer Sugar + +- Generic builder template that accepts targets as Span and parameters as a ParamSet, dispatching based on trait arities; layered overloads provide ergonomic APIs: `x(q)`, `rx(q, theta)`, `cx(c, t)`, `ccx(c0, c1, t)`, `mcx(ctrls, t)`, `mcp(ctrls, t, theta)`. +- Parser sugar for: `cx`, `cz`, `ccx`, `ccz`, `mcx`, `mcz`, `mcp(theta)`; nested forms like `ctrl(%c) inv rx(%q) (pi/2)` print and round‑trip. + +## Testing Strategy and Robustness + +- TDD first: write failing tests (LIT and C++) before implementation; keep them small and focused. +- Prefer round‑trip `mlir-opt` tests; normalize with `-canonicalize -cse` when textual formatting is not the target of the test. +- Use `-split-input-file` to group many small tests; use `-verify-diagnostics` for negative cases with `// expected-error`. +- Avoid brittle SSA numbering in FileCheck; anchor on op names/attributes and use `CHECK-LABEL`, `CHECK-SAME`, `CHECK-DAG` appropriately. Use `-mlir-print-op-generic` where necessary for stability. +- Add C++ unit tests that parse/build IR programmatically and verify via Operation::verify() and pass runs; this catches checkstring mistakes by asserting structural invariants. + +## Verification and Canonicalization Summary + +- Base gates: enforce TargetArity and ParameterArity; static parameter preference and folding. +- Modifiers: enforce single child unitary; linear control threading in mqtopt; pow has no mask. +- Sequence: implicit terminator; only unitary ops allowed. +- Normal form: controls outermost; inverse innermost; merges of adjacent like‑modifiers; empty controls dropped; nested sequences flattened. +- Hermitian/Diagonal traits guide short‑circuits and analyses. + +## Passes and Conversions + +- Normalization, ControlPushdown, AdjointPropagation, ParamConstFold passes. +- mqtref → mqtopt conversion: map base gates/modifiers; `seq` with implicit terminator; thread controls linearly and produce fresh SSA results (including for controls). +- mqtopt → mqtref conversion: erase value results; controls become operands only; maintain modifier/sequence structure. + +## Milestones and Tasks + +Use this checklist to plan and track progress. + +M1 — Foundations (Common) + +- [ ] Unified UnitaryOpInterface (Common). +- [ ] Parser/Printer utilities for params, modifiers, sequences, sugar. +- [ ] Marker traits: Hermitian, Diagonal. +- [ ] UnitaryExpr library skeleton planned (implementation deferred to execution of the plan). + +M2 — mqtref Base + Modifiers + Sequence + +- [ ] Redefine base UnitaryOp (no embedded controls). +- [ ] Implement modifiers: ctrl/negctrl/inv/pow with implicit terminators. +- [ ] Implement seq with implicit terminator; inliner + canonicalization. +- [ ] Implement unitary.def (symbol) and unitary.apply (instantiation). +- [ ] Update std gate defs and assembly formats; add sugar (cx/cz/ccx/ccz/mcx/mcz/mcp). +- [ ] LIT tests: ops, parsers, canonicalization, unitary.def/apply. + +M3 — mqtopt Mirror + +- [ ] Redefine base gate ops with value results for targets. +- [ ] Modifiers with linear control threading; seq with implicit terminator. +- [ ] Implement unitary.def (symbol) and unitary.apply (instantiation) with value semantics. +- [ ] Interface/verifier updates for in/out segment size equality. +- [ ] LIT tests mirroring mqtref, including unitary.def/apply. + +M4 — Builders and Ergonomics + +- [ ] Generate CircuitBuilder helper API (generic template + overloads). +- [ ] Parser/printer sugar for compact modifier nesting and controlled variants. +- [ ] C++ smoke tests for builders. + +M5 — Passes and Conversions + +- [ ] Implement Normalization, ControlPushdown, AdjointPropagation, ParamConstFold. +- [ ] Update conversions mqtref↔mqtopt for modifiers/sequences and linear controls. +- [ ] Tests for conversions and passes. + +M6 — Documentation and Polishing + +- [ ] Ensure all ODS docstrings have fenced MLIR examples. +- [ ] Update user docs and examples; final test stabilization. + +## Acceptance Criteria + +- [ ] Dialects compile; LIT tests cover base gates, modifiers, sequences, unitary.def/unitary.apply, and sugar. +- [ ] Unified UnitaryOpInterface used across unitary‑capable ops, exposing operand/result views as appropriate. +- [ ] Arbitrary unitary support: symbol‑based unitary.def with matrix‑ and composite‑defined forms; unitary.apply in both dialects; modifiers wrap applications. +- [ ] Builder API (template + overloads) with sugar for cx, cz, ccx/ccz, mcx/mcz/mcp. +- [ ] Canonicalization suite maintains normal form; mqtopt controls are linearly threaded and not forwarded unchanged. +- [ ] Conversions mqtref↔mqtopt handle modifiers/sequences and control threading. + +## Examples (MLIR) + +mqtref, modifiers and sugar: + +```mlir +// Single‑controlled rotation +mqtref.crx %c, %t (1.234) + +// Double‑controlled Toffoli +mqtref.ccx %c0, %c1 %t + +// Multi‑controlled X and P(theta) +mqtref.mcx(%c0, %c1, %c2) %t +mqtref.mcp(1.234)(%c0, %c1) %t + +// Nested modifiers +mqtref.ctrl(%c) mqtref.inv mqtref.rx(%q) (3.14159) +``` + +mqtopt, sequence and linear controls: + +```mlir +%q0 = mqtopt.allocQubit +%c = mqtopt.allocQubit +mqtopt.seq { + %q0_1 = mqtopt.h %q0 + // ctrl threads control linearly: consumes %c, yields %c_1 + %q0_2, %c_1 = mqtopt.ctrl(%c) { mqtopt.x %q0_1 } +} +``` + +User‑defined unitaries: + +```mlir +// mqtref: define via region and apply +mqtref.unitary.def @H2 { + ^entry(%q: !mqtref.Qubit): + mqtref.seq { + mqtref.rx(%q) (1.5707963267948966) + mqtref.z %q + } +} +mqtref.unitary.apply @H2 %q0 +mqtref.ctrl(%c) { mqtref.unitary.apply @H2 %q1 } + +// mqtopt: parameterized composite definition and application +mqtopt.unitary.def @CRZ(%theta: f64) { + ^entry(%q0: !mqtopt.Qubit, %q1: !mqtopt.Qubit): + mqtopt.seq { + %q1_1 = mqtopt.rz(%q1) (%theta/2.0) + %q0_1, %q1_2 = mqtopt.cnot %q0, %q1_1 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) + %q1_3 = mqtopt.rz(%q1_2) (-%theta/2.0) + %q0_2, %q1_4 = mqtopt.cnot %q0_1, %q1_3 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) + } +} +%q0_1, %q1_1 = mqtopt.unitary.apply @CRZ(%q0, %q1) (3.14159) +%q0_2, %q1_2 = mqtopt.ctrl(%c) { mqtopt.unitary.apply @CRZ(%q0_1, %q1_1) (0.25) } +``` + +## Notes + +- Controls must be outermost modifiers; inverse innermost; power in between. Rewriters enforce normalized order. +- Controls are not deduplicated by canonicalization; verifiers reject duplicates. +- QubitRegister is assumed absent in this revamp; only Qubit is used. A separate PR will replace any remaining register usage with memref. diff --git a/mlir-quantum-dialect.md b/mlir-quantum-dialect.md new file mode 100644 index 0000000000..b1cec37499 --- /dev/null +++ b/mlir-quantum-dialect.md @@ -0,0 +1,38 @@ +What I want to be able to describe: + +- A `Qubit` type including operations to dynamically `allocate` and `deallocate` qubits as well as a static `qubit` operation that yields a qubit reference from an index +- A `reset` operation that acts on a qubit +- A `measure` operation that takes a qubit and produces an `i1` classical measurement result +- A way to describe unitary operations that drive the quantum computation. + +The last part is the most important and critical part as it forms the center of the quantum dialect. +These operations (also referred to as quantum gates or simply gates) have the following properties + +- they are unitary, which is an essential trait to match to +- each type of gate has a compile time fixed number of target qubits (typically one or two). This is regulated by a target arity trait at the moment, which doesn't seem perfectly feasible, but maybe it is. +- each type of gate may have a compile time fixed number of parameters. This is currently indicated similarly to the target arity by a parameter arity trait. Parameters may either be defined statically or dynamically through values. Mixtures are possible. Canonicalization should ensure that parameters that can be statically defined are indeed statically defined. +- each type of gate has a unitary matrix associated with it. The size of the matrix is 2^n\*2^n with n being the target arity. For gates without parameters or with only static parameters this is compile-time fixed and known. For gates with dynamic parameters, this description is symbolic (mostly in terms of rotation angles and trigonometric functions). +- several types of modifiers may be applied to unitary gates to transform and extend them. An `inv` (or `adj`) modifier can be added to invert the gate, which corresponds to forming the adjoint of the underlying matrix. A control modifier may be used to add a variable list of qubits as control qubits to the operation. Control qubits may be positive or negative, which means they trigger on the control qubit being in state |1> or |0>, respectively. Last but not least, the `pow(r)` powering modifier can be used to compute powers of the unitary gate. For simplicity, `r` is mostly assumed to be an integer, but any real number is feasible through computing the principle logarithm. Modifiers are generally evaluated lazily, that is, they are simply tagged onto the gate and only resolved once such a resolution is necessary. There should be a way to query the effective unitary of a gate, which takes into account the modifiers. Modifiers should have canonicalization rules. They all commute. Control qubits must be unique and multiple control modifiers can be combined into one. Two inverses cancel another. Negative powers can be translated to an inverse modifier and the positive power. powers of 1 can be removed. powers of 0 can be replaced by an identity gate. + +The difference between the MQTRef and the MQTOpt dialects is that the MQTRef dialect uses reference/memory semantics for its operations, while the MQTOpt dialect uses value semantics. The MQTOpt dialect is used for optimization and transformation passes, while the MQTRef dialect is used for initial representation and code generation. + +I want to (at least) + +- query an operation for the qubits it acts on +- query an operation whether it is a single-qubit gate (single target, no controls), two-qubit, single-target, two-target, no-controls, no-parameters, only static parameters, or dynamic parameters gate. +- query an operation for its underlying unitary +- apply `inv`, `ctrl`, `negctrl` and `pow(r)` modifiers to the gates +- conveniently construct individual basis gates in C++ without much coding overhead (e.g. mqtref.swap(q0, q1) or mqtref.rx(q0, theta)); the current setup makes this increadibly hard through the builder interface. The target and parameter arity traits should provide the necessary information to generate the correct builder overloads. +- have a construct for sequences of (unitary) gates that modifiers can also be applied to. these "compound" operations or sequences may only contain unitary operations. These should be inlinable in an MLIR native way. Canonicalization should remove empty sequences and merge nested sequences as part of inlining. +- have convenient shortcuts for constructing common gates such as the CNOT (=ctrl X) or CZ (=ctrl Z) and potentially have some syntactic sugar for these. + +### Guiding principles + +- Prefer idiomatic MLIR: keep base ops minimal and uniform, push optional features into wrapper ops/regions, and express semantics via interfaces and canonicalization rather than baking everything into each gate. +- Make composition first-class: modifiers are explicit IR constructs that can wrap single gates or entire sequences, and they compose/lazily defer. +- Ergonomics: provide sugar in the parser/printer and rich C++ builders so that writing and constructing circuits is pleasant. +- Analysis-friendly: expose queries through a single interface so passes never need to pattern-match concrete ops. +- If possible, there shouldn't be too much duplication between both dialects as they essentially share a lot of structure. +- Make use of MLIR-native traits and interfaces wherever they are useful. For example, `AttrSizedOperandSegments` or `InferTypeOpInterface`. +- Provide canonicalization where they make sense to keep the IR normalized and simple. +- Provide verifiers for IR constructs diff --git a/prompt.md b/prompt.md new file mode 100644 index 0000000000..c836816bc2 --- /dev/null +++ b/prompt.md @@ -0,0 +1,54 @@ +I am working on an MLIR dialect hierarchy for hybrid classical quantum computing. I have a working version of the two quantum dialects, but I am not happy because it is cumbersome to use and lack a lot of features. + +The relevant files are contained solely in the `/Users/burgholzer/CLionProjects/mqt-core/mlir` directory. + +The mlir-quantum-dialects.md file contains a detailed sketch of requirements for the envisioned architecture. + +Take the existing setup as inspiration and think very hard and deep on how to replace it with a setup that facilitates the above requirements. +Nothing in the existing setup is set in stone. +There needs to be no migration path or backwards compatibility. +A fundamental rework is possible. + +Do not (yet) perform any changes on the MLIR code itself. Rather provide an extensive implementation plan in a separate Markdown document. + +--- + +I'd like to do one more iteration on the project plan before starting any edits. Address the following points + +- QubitRegisters will be entirely replaced by using `memref` in a separate PR. The respective code here should not be touched as part of this refactor and the project plan might as well assume that QubitRegisters do not exist in both dialects +- I am unsure how uniform the UnitaryInterface can really be given how the dialects use different semantics and there would need to be a way to query input und output operands of the MQTOpt operations +- I like `UnitaryExpr` more than `UnitarySpec`. This description should really be as idiomatic as possible. I do not want to reinvent the wheel here. At the same time, I want to avoid further external dependencies. Most matrices will be 2*2 or 4*4 here and I need to be able to multiply them. It is very important that this is performant but lightweight. These matrices are truly only to be used for transformation or conversion passes. +- Instead of inv(pow(X, k)) -> pow(X, -k), perform inv(pow(X, k)) -> pow(inv(X), k) as a canonicalization +- Control SSA values should not be simply yielded as unchanged. Qubits should generally be treated as linear types wherever feasible +- The power modifier does not need a mask +- Perform pow(X, -k) -> pow(inv(X), k) as canonicalization +- Controls should be the outermost modifiers, inverse the innermost. +- Inverse modifiers can be canonicalized with base gates by changing the gate to the inverse. Each base gate has its inverse defined as a base gate (which might be the same gate but with different parameters) +- Allow even more syntactic sugar, e.g. mqtref.ccx %c0, %c1 %q +- Terminators of regions should be implicit and need not be explicit. +- The notion of controls and targets does not really make sense for `seq` operations. However, it makes sense to query the overall qubits a seq acts on. +- The UnitaryExpr of a `seq` operation is the product of the `UnitaryExpr` of the child operations. +- try to pick idiomatic names for the interface functions with regard to MLIR, C++ and Quantum Computing +- "Alternatively, keep a generic template that accepts targets as Span and parameters as ParamSet and dispatches based on trait arities; then layer nicer overloads on top." i like this solution +- make sure to always include examples in fenced mlir code blocks in the to be generated docstrings +- use canonicalization and folds as much as possible and idiomatic for MLIR. The goal should be to use as much of MLIR's native infrastructure as possible. +- there is no need to provide a temporary helper conversion. All code is expected to be rewritten and fixed + +--- + +One last round of revisions and additions: + +- I want to add an additional trait for "Hermitian" gates, i.e., gates that are self-inverse. These may be used as a short-circuit in inverse canonicalization. Popular hermitian operations are: I, X, Y, Z, H +- I want to add an additional trait for diagonal gates, i.e., gates whose underlying matrix is diagonal in the computational basis. Prominent examples include: I, Z, S, Sdg, T, Tdg, Rz, Phase (or P), RZZ +- parameters of base gate ops should be preferred to be static, constant folding (or another appropriate MLIR concpet) should be responsible for transforming dynamic operands that are constants to static attributes. +- do not put a limit on the arity for the constant DenseElementsAttr for the unitary. This will hold in practice by construction +- controls do not need deduplication as they must not be duplicated from the beginning, as also ensured by a verifier +- getQubitSet is redundant if `getAllOperandQubits()` and `getAllResultQubits()` are implemented properly. Qubits that are not acted on by a seq should not be part of the operands/results of the seq. + +--- + +A couple of further requirements to factor into the plan: + +- There should be extensive documentation on all the relevant design decisions that went into this project plan. This documentation should be present in the doxygen comments that will be included in the overall project documentation build. In addition, more elaborate rationale; especially on aspects that do not end up in the doxygen comments, should be added to the mlir docs infrastructrure under `docs/mlir`. The result should be a gentle introduction to all the necessary concepts (also including the ones not necessarily modified in this plan). The writing should be of a quality that could go into a (software focused) scientific journal. Always try to include examples in the documentation. +- Check the project plan for consistency and correct any ambiguity. +- I want to post the project plan as a high-level tracking issue on GitHub. Prepare it for that, e.g., replace all local paths with relative paths. From dee3f1c6097bd9fcc9137a6df421d6330c3c89bd Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 29 Sep 2025 17:45:29 +0200 Subject: [PATCH 004/419] =?UTF-8?q?=F0=9F=91=8C=20updates=20due=20to=20rev?= =?UTF-8?q?iew=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 40 +++++++----------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index 8db81a9cb9..08ac5e9d2b 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -1,10 +1,3 @@ -# RFC: Quantum Dialect Revamp — Implementation Plan - -**Author:** MQT Core Development Team -**Date:** 2025-09-24 -**Status:** Design Document -**Scope:** MLIR dialect hierarchy affecting MQTRef and MQTOpt dialects - ## 1. Overview and Goals ### 1.1 Current State @@ -30,7 +23,7 @@ This plan proposes a fundamental redesign that addresses these issues through: 3. **Improved Ergonomics**: Builder APIs and parser sugar for common patterns without sacrificing expressiveness 4. **Shared Infrastructure**: Common traits, interfaces, and utilities to reduce code duplication -**Rationale**: The current approach of embedding modifiers directly into gate operations creates a multiplicative explosion of variants (X, CX, CCX, inverse-X, controlled-inverse-X, etc.). By making modifiers compositional, we get exponential expressiveness with linear implementation cost. It is also fairly close to how established languages like OpenQASM or Qiskit work, which makes it easier to transition to the new dialect. +**Rationale**: By making modifiers compositional, we get exponential expressiveness with linear implementation cost. It is also fairly close to how established languages like OpenQASM or Qiskit work, which makes it easier to transition to the new dialect. ## 2. Architecture Overview @@ -110,7 +103,7 @@ Base gates are the atomic quantum operations and contain only: ```mlir mqtref.x %q0 // Pauli-X gate mqtref.rx %q0 {angle = 1.57 : f64} // X-rotation with static parameter -mqtref.cnot %q0, %q1 // Controlled-NOT (2-qubit gate) +mqtref.cx %q0, %q1 // Controlled-NOT (2-qubit gate) mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit gate ``` @@ -119,7 +112,7 @@ mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit ```mlir %q0_out = mqtopt.x %q0_in : !mqtopt.qubit %q0_out = mqtopt.rx %q0_in {angle = 1.57 : f64} : !mqtopt.qubit -%q0_out, %q1_out = mqtopt.cnot %q0_in, %q1_in : !mqtopt.qubit, !mqtopt.qubit +%q0_out, %q1_out = mqtopt.cx %q0_in, %q1_in : !mqtopt.qubit, !mqtopt.qubit ``` **Key Differences**: In mqtref, operations have side effects on qubit references. In mqtopt, operations consume input qubits and produce new output qubits, following SSA principles. @@ -261,7 +254,7 @@ In `mqtopt`, sequences must explicitly thread all qubit values through the compu %q0_out, %q1_out = mqtopt.seq %q0_in, %q1_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { ^entry(%q0: !mqtopt.qubit, %q1: !mqtopt.qubit): %q0_h = mqtopt.h %q0 : !mqtopt.qubit - %q0_cx, %q1_cx = mqtopt.cnot %q0_h, %q1 : !mqtopt.qubit, !mqtopt.qubit + %q0_cx, %q1_cx = mqtopt.cx %q0_h, %q1 : !mqtopt.qubit, !mqtopt.qubit %q0_final = mqtopt.h %q0_cx : !mqtopt.qubit mqtopt.yield %q0_final, %q1_cx : !mqtopt.qubit, !mqtopt.qubit } @@ -355,7 +348,7 @@ mqtref.apply_gate @pauli_y %q0 // Define a gate as a sequence of existing operations mqtref.gate_def @bell_prep %q0 : !mqtref.qubit, %q1 : !mqtref.qubit { mqtref.h %q0 - mqtref.cnot %q0, %q1 + mqtref.cx %q0, %q1 } // Apply the composite gate @@ -399,17 +392,6 @@ mqtref.mcx (%c0, %c1, %c2), %t // → mqtref.ctrl %c0, %c1, %c2 { mqtref.x %t } mqtref.mcp (%c0, %c1), %t {phase = 1.57} // Controlled phase with multiple controls ``` -**Modifier Shortcuts in Region Context:** - -```mlir -// Within regions, allow modifier keywords as operation prefixes -mqtref.seq { - inv s %q0 // → mqtref.inv { mqtref.s %q0 } - ctrl(%c) x %q1 // → mqtref.ctrl %c { mqtref.x %q1 } - pow(0.5) h %q2 // → mqtref.pow {exponent = 0.5} { mqtref.h %q2 } -} -``` - **Rationale**: This approach provides ergonomic shortcuts for common cases without introducing complex chaining operators. The shortcuts expand to the full form during parsing, so all downstream processing sees the canonical representation. ### 9.2 C++ Builder API @@ -421,7 +403,7 @@ public: // Basic gates with natural names QuantumCircuitBuilder& x(Value qubit); QuantumCircuitBuilder& h(Value qubit); - QuantumCircuitBuilder& cnot(Value control, Value target); + QuantumCircuitBuilder& cx(Value control, Value target); // Modifier combinators QuantumCircuitBuilder& ctrl(ValueRange controls, std::function body); @@ -527,8 +509,6 @@ mqtref.ctrl %c { mqtref.x %t } → **Key Challenges**: - **SSA Value Threading**: mqtopt requires explicit threading of all qubit values -- **Region Structure**: Converting between region-based and flat representations -- **Dominance Preservation**: Ensuring converted code maintains MLIR dominance requirements **Rationale**: These conversions enable using the same quantum algorithm in different contexts - mqtref for simulation and debugging, mqtopt for optimization and compilation to hardware. @@ -540,15 +520,19 @@ Standard conversion patterns lower high-level operations to target-specific inst // Example: Lowering controlled gates to native basis mqtref.ctrl %c { mqtref.ry %t {angle = θ} } → mqtref.rz %t {angle = θ/2} - mqtref.cnot %c, %t + mqtref.cx %c, %t mqtref.rz %t {angle = -θ/2} - mqtref.cnot %c, %t + mqtref.cx %c, %t ``` **Rationale**: Hardware targets have limited native gate sets, so high-level operations must be decomposed into available primitives while preserving mathematical equivalence. ## 12. Testing Strategy +Generally, it should be part of this endeavor to come up with a testing strategy that we can exercise across our efforts going forward. +It has already become quite clear that we do not want to extensively write FileCheck strings as it is very error prone. We are currently likely spending more time on fixing FileCheck strings than actually developing features. +Hence, even the integration tests down below should be considered to be realized differently in C++. + ### 12.1 Unit Tests (C++) - **Interface implementations**: Verify UnitaryOpInterface methods return consistent results From 1410cfe9a89ccac7357222efa71c704a13e643fc Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 29 Sep 2025 17:59:58 +0200 Subject: [PATCH 005/419] =?UTF-8?q?=F0=9F=8E=89=20initial=20discussions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/mlir-discussions.md | 794 ++++++++++++++++++++++++++++++++++ 1 file changed, 794 insertions(+) create mode 100644 docs/mlir/mlir-discussions.md diff --git a/docs/mlir/mlir-discussions.md b/docs/mlir/mlir-discussions.md new file mode 100644 index 0000000000..6cf738e70e --- /dev/null +++ b/docs/mlir/mlir-discussions.md @@ -0,0 +1,794 @@ +ystade +3 days ago +Maintainer +// Multiple controls +mqtref.ctrl %c0, %c1 { +mqtref.x %q0 // Toffoli gate (CCX) +} +Should the ctrl modifier really support multiple controls at once? Still, I can also wrap control in control, and so forth. At least there should be a fixed canonical form. + +Edit: Also the canonical order of pos. and neg. controls should be fixed. + +4 replies 1 new +@burgholzer +burgholzer +3 days ago +Maintainer +Author +I think this is touched on somewhere above. + +NormalizationPass: Enforces canonical modifier ordering and eliminates redundancies + +Reorders nested modifiers to canonical form (ctrl → pow → inv) +Eliminates identity operations (pow(X, 1) → X, inv(inv(X)) → X) +Merges adjacent compatible modifiers +Simplifies power modifiers wherever feasible (pow(RZ(pi/2), 2) → RZ(pi)) +Canonicalization ensure that there is a normal form of modifiers, that adjacent modifiers are merged. +I believe that aggregation of controls in a single modifier makes sense because it helps to achieve a compact IR that needs less traversal. I also don't really see a downside of doing that. + +@ystade +ystade +3 days ago +Maintainer +Agreed, then just the precedence of neg. controls must be defined. + +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Yeah. Essentially, we the normalization pass above should read + +(ctrl → negctrl → pow → inv) +I was briefly thinking (and had that in the RFC for a short period) if it would make sense to combine the ctrl and negctrl wrapper operations and a single ctrl wrapper that has both negative and positive controls similar to how we currently have it in our UnitaryOp. But this did not feel as clean to me as the separate solution. +We could also implement a transformation pass that turns negative controls into positive controls sandwhiched by X gates, so that there would only be positive controls anymore. +Naturally, the opposite direction is also imaginable, i.e., aggregating controls sandwhiched by X gates into a negative control. +Any opinions on that? + +@ystade +ystade +4 hours ago +Maintainer +Yeah, I was already thinking about the same but could not imagine what flows better with the design of MLIR. My proposal would be to go with it as it stands right now and keep the other option in mind if the current one does not feel ergonomic. + +--- + +In general, we might want to discuss, whether we actually need gates like x, rx, ... or whether we adopt the OpenQASM 3 way and just have on u3 gate plus all modifiers. + +Or whether this is a sensible canoncalization pass? And which direction actually is the canonicalisation, to or from only u3 gates? + +4 replies 1 new +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Just the U3 gate alone is quite cumbersome to use. And defining everything in terms of the U3 gate also gets annoying rather quickly as it invites numerical errors. +I am also not quite sure I'd like that the treatment of single-qubit gates would be different than for multi-qubit gates, because I doubt, we will describe two-qubit gates by their 17+ parameter KAK decomposition. + +Even OpenQASM (and Qiskit) defines a standard library that is being heavily used and includes all kinds of gates. Over there, it's "just" the definition of the unitary of the gates that can be recursively boiled down to the global phase gate and modifiers. + +Lastly, dropping the individual gates also makes traits such as "Hermitian" or "Diagonal" much harder to determine. And I would believe these are quite useful in the analysis of programs. + +@ystade +ystade +3 days ago +Maintainer +The last point is a very good point. Perhaps, we should have a Canonicalization pass that transforms every gate in the simplest possible (similar to the behaviour when parsing QASM files in MQT Core currently). Maybe even the other way around is needed for passes that only understand u3 gates and nothing else. For the latter, I do not know whether this is even desired. + +@burgholzer +burgholzer +3 days ago +Maintainer +Author +This canonicalization pass is something that, to some degree, fits well with the custom gate definitions, which are explained in one of the sections. There could be an inlining pass that inlines gate definitions until it reaches well known basis gates (=our standard library). + +At the same time, we have some kind of canonicalization for individual gates as part of MQT Core IR already. Namely, here: + +core/src/ir/operations/StandardOperation.cpp + +Lines 36 to 186 in 9109b56 + +OpType StandardOperation::parseU3(fp& theta, fp& phi, fp& lambda) { +if (std::abs(theta) < PARAMETER_TOLERANCE && +std::abs(phi) < PARAMETER_TOLERANCE) { +parameter = {lambda}; +return parseU1(parameter[0]); +} + +if (std::abs(theta - PI_2) < PARAMETER_TOLERANCE) { +parameter = {phi, lambda}; +return parseU2(parameter[0], parameter[1]); +} + +if (std::abs(lambda) < PARAMETER_TOLERANCE) { +lambda = 0.L; +if (std::abs(phi) < PARAMETER_TOLERANCE) { +checkInteger(theta); +checkFractionPi(theta); +parameter = {theta}; +return RY; +} +} + +if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { +lambda = PI_2; +if (std::abs(phi + PI_2) < PARAMETER_TOLERANCE) { +checkInteger(theta); +checkFractionPi(theta); +parameter = {theta}; +return RX; +} + + if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { + phi = PI_2; + if (std::abs(theta - PI) < PARAMETER_TOLERANCE) { + parameter.clear(); + return Y; + } + } + +} + +if (std::abs(lambda + PI_2) < PARAMETER_TOLERANCE) { +lambda = -PI_2; +if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { +phi = PI_2; +parameter = {-theta}; +return RX; +} +} + +if (std::abs(lambda - PI) < PARAMETER_TOLERANCE) { +lambda = PI; +if (std::abs(phi) < PARAMETER_TOLERANCE) { +phi = 0.L; +if (std::abs(theta - PI) < PARAMETER_TOLERANCE) { +parameter.clear(); +return X; +} +} +} + +// parse a real u3 gate +checkInteger(lambda); +checkFractionPi(lambda); +checkInteger(phi); +checkFractionPi(phi); +checkInteger(theta); +checkFractionPi(theta); + +return U; +} + +OpType StandardOperation::parseU2(fp& phi, fp& lambda) { +if (std::abs(phi) < PARAMETER_TOLERANCE) { +phi = 0.L; +if (std::abs(std::abs(lambda) - PI) < PARAMETER_TOLERANCE) { +parameter.clear(); +return H; +} +if (std::abs(lambda) < PARAMETER_TOLERANCE) { +parameter = {PI_2}; +return RY; +} +} + +if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { +lambda = PI_2; +if (std::abs(phi + PI_2) < PARAMETER_TOLERANCE) { +parameter.clear(); +return V; +} +} + +if (std::abs(lambda + PI_2) < PARAMETER_TOLERANCE) { +lambda = -PI_2; +if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { +parameter.clear(); +return Vdg; +} +} + +checkInteger(lambda); +checkFractionPi(lambda); +checkInteger(phi); +checkFractionPi(phi); + +return U2; +} + +OpType StandardOperation::parseU1(fp& lambda) { +if (std::abs(lambda) < PARAMETER_TOLERANCE) { +parameter.clear(); +return I; +} +const bool sign = std::signbit(lambda); + +if (std::abs(std::abs(lambda) - PI) < PARAMETER_TOLERANCE) { +parameter.clear(); +return Z; +} + +if (std::abs(std::abs(lambda) - PI_2) < PARAMETER_TOLERANCE) { +parameter.clear(); +return sign ? Sdg : S; +} + +if (std::abs(std::abs(lambda) - PI_4) < PARAMETER_TOLERANCE) { +parameter.clear(); +return sign ? Tdg : T; +} + +checkInteger(lambda); +checkFractionPi(lambda); + +return P; +} + +void StandardOperation::checkUgate() { +if (parameter.empty()) { +return; +} +if (type == P) { +assert(parameter.size() == 1); +type = parseU1(parameter.at(0)); +} else if (type == U2) { +assert(parameter.size() == 2); +type = parseU2(parameter.at(0), parameter.at(1)); +} else if (type == U) { +assert(parameter.size() == 3); +type = parseU3(parameter.at(0), parameter.at(1), parameter.at(2)); +} +} + +This is parsing arbitrary U3 gates and translating them to the simplest matching operation. I do see value in that and have relied on that functionality quite a lot in the past. +At the same time, there has actually been (currently unmet) demand for the opposite direction. Namely, here: #910 +While this specific issue only talks about phase gates, I'd probably generalize this to U3. +Do both of these points make sense? + +@ystade +ystade +4 hours ago +Maintainer +That is exactly along my reasoning. + +--- + +ystade +3 days ago +Maintainer +// Control queries +bool isControlled(); +size_t getNumPosControls(); +size_t getNumNegControls(); +size_t getNumControls(); +I do not get it, I thought the single operation does not have controls anymore. Then, the functions above would already implement a transversal of the wrapping control modifiers. But where does this transversal end? At the boundaries of functions/sequences? Or should it go beyond? Furthermore, why are there no function to check for inv or pow modifiers? + +7 replies 7 new +@burgholzer +burgholzer +3 days ago +Maintainer +Author +The unitary interface is intended to be implemented by all concepts here. + +The base operations would always report 0 controls. +A control modifier reports its own controls + all controls of the contained operation (which again implements the UnitaryInterface). +pow and inv just forward to the contained operation +seq (up for debate) will report 0 controls as it represents a block of operations conceptionally +unitary definition will also report 0 controls +Hope that covered everything. This seems like a well terminating sequence of rules to me. + +As for the inv and pow modifiers: neither of them change the number of qubits of the underlying operation. I haven't really found a use case for exposing these. +inv on base operations will be canonicalized away, inv on inv cancels, inv and pow commute, inv on sequence either remains like it is or is canonicalized to reversed sequence of inverted operations; inverse of a unitary definition is up for debate. +Do you have a use case in mind where having generic access to these details in the interface makes sense? And a proposal for the interface function? + +@ystade +ystade +3 days ago +Maintainer +A control modifier reports its own controls + all controls of the contained operation (which again implements the UnitaryInterface). +This gives rise to another question: So, the block within a ctrl modifier is only allowed to contain exactly one operation. I was assuming that this can be an arbitrary block... + +@ystade +ystade +3 days ago +Maintainer +Do you have a use case in mind where having generic access to these details in the interface makes sense? And a proposal for the interface function? + +No, I do have not and what you wrote before makes absolutely sense. We can always add such functionality if we are missing it. + +@burgholzer +burgholzer +3 days ago +Maintainer +Author +A control modifier reports its own controls + all controls of the contained operation (which again implements the UnitaryInterface). +This gives rise to another question: So, the block within a ctrl modifier is only allowed to contain exactly one operation. I was assuming that this can be an arbitrary block... + +It can be anything that implements the UnitaryInterface. +So it can be a single basis operation, another modifier operation, a unitary, or... a mqtref.seq, which covers the use case of a modifier applying to a group of operations. +I believe this covers all imaginable scenarios. + +@ystade +ystade +4 hours ago +Maintainer +Makes sense. However, then it is not entirely clear to me, how to count the controls of a seq. + +@DRovara +DRovara +52 minutes ago +Collaborator +I'd say a seq does not have any controls, as its essentially a one-time-use custom gate, right? + +@burgholzer +burgholzer +30 minutes ago +Maintainer +Author +I'd say a seq does not have any controls, as its essentially a one-time-use custom gate, right? + +Exactly! A sequence will always report 0 controls. + +--- + +ystade +3 days ago +Maintainer +8.1 Matrix-Based Definitions + +Is that needed? I am not sure whether we overengineer here our dialect. If I am not mistaken, the u3 gate should already cover all possible unitary gates. When defining gates via matrices, one would also need to implement a validation pass, validating that every matrix is unitary, something that is by default satisfied with the u3 gate. + +1 reply +@burgholzer +burgholzer +3 days ago +Maintainer +Author +The u3 gate only covers single-qubit matrices. I'd consider it feasible to represent up to 3-qubit gates (maybe even more) as a unitary and I would imagine that especially the 1- and 2-qubit cases could be popular. +This point was mainly inspired by some discussions at QCE with Ed from the BQSKit team and the motivation is mostly grounded in numerical synthesis, where it is just more efficient and straight forward to describe the unitary of the operation as opposed to a sequence of (known) gates that make up the unitary functionality. + +Overall, I think this is not a must-have, but a very-nice-to-have. And it is definitely a unique and unambiguous way of describing the functionality of a gate. + +The validation pass is a good idea. Although I'd personally also put some of the responsibility for that on the developer that uses these custom gates. + +--- + +Regarding testing: I think we should consider switching to the CHECK-NEXT directive to ensure that we don't have unexpected statements in our IR. + +3 replies +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Yeah. I agree. Maybe even taking this one step further: we should compare entire programs are fully equivalent to what we would expect. We define complete input programs and the passes that should be run on them. Based on that there is a clear expected output program that we could be writing down explicitly. +Maybe with the help of the circuit builder, we can actually not do this on top of the textual IR at all, but rather construct these test cases programmatically in a googletest suite similar to the translation tests. + +@DRovara +DRovara +3 days ago +Collaborator +Actually I really like the idea of using the builders to construct MLIR representations of expected outcomes and then using those to check for equivalence in normal unit tests. + +In fact, this is the way I have seen such language tests being used outside of the MLIR framework and it just feels really clean and straightforward - if we ever change the representation, the tests won't be affected as the builders should still be able to construct equal programs. + +@burgholzer +burgholzer +3 days ago +Maintainer +Author +In fact, this is the way I have seen such language tests being used outside of the MLIR framework and it just feels really clean and straightforward - if we ever change the representation, the tests won't be affected as the builders should still be able to construct equal programs. + +That is a good point! And a great argument for stability. I will try to include this in the overall project plan and afterwards will hide this conversation as resolved. + +--- + +denialhaag +3 days ago +Collaborator +We should also think about which, if any, default passes we want to run. The -canonicalize and -remove-dead-values passes seem useful in most cases to me. Slightly related to the comment above, this would, for example, ensure that constants are always defined at the top of the module in our tests. + +1 reply +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Yes, totally. Especially if we define further canonicalization for the operations in our dialects. +-canonicalize is a no-brainer in my opinion; we should definitely do that. +Probably the same holds for the -remove-dead-values pass. + +--- + +4.2 + +mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit gate +Is this naming scheme of parameters actually valid? Does this still correlate with the parameter-arity traits? + +The existing solution also (semi-)elegantly handles situations where some parameters may be static and others dynamic operands. Can this still be done with we represent the gates like this? +Or maybe in general, how do such gates still support dynamic operands for angles etc? +Sorry, just saw that this is covered in 4.3 + +mqtref.rx %q0, %angle : f64 +But some extra questions to that: +Do we maybe want some syntactic sugar to distinguish %q0 as a qubit argument from %angle as a parameter? +And why do we need the f64 type indication for the angle, but not the qubit? + +1 reply +@burgholzer +burgholzer +3 days ago +Maintainer +Author +I think a lot of the examples are inconsistent at the moment because they are hallucinations. +I'd actually like to keep the parameter handling almost exactly the same compared to now +Syntactic sugar for distinguishing between parameters and qubits makes a lof of sense. I do like how we currently handle this. +The type indication is supposedly just a hallucination. +I'll try to get a little bit more consensus on some of the general design decisions before I try to consolidate the examples and flesh them out more. + +--- + +denialhaag +3 days ago +Collaborator +Just to have it mentioned: We already have a way to translate QuantumComputation IR containing classically controlled operations to mqtref. We do not use custom operations for that, but we rely on the scf dialect. If I'm not mistaken, we have not yet attempted to convert a mqtref module containing classically controlled operations to mqtopt. I'm not sure if this is something we also want to be part of the C++builder. + +3 replies 1 new +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Hm. I was, maybe naively, assuming that any program that we can currently express through the QuantumComputation -> MQTRef translation can also be translated to MQTOpt. And I am pretty sure we actually can because this is what @DRovara's qubit reuse relies on. But maybe I am mistaken here. +Please feel free to chime in on this. + +In my mind, the C++ builder API would be very, very similar to the QuantumComputation API. It would let you add quantum and classical registers, quantum gates (potentially involving classical controls), measurements and resets. That API would practically be the evolution/continuation of the QuantumComputation API. + +@DRovara +DRovara +3 days ago +Collaborator +In MQTOpt we can use scf.if the same way as in MQTDyn, we just need to yield the result afterwards. + +@denialhaag +denialhaag +3 days ago +Collaborator +Okay, this sounds good to me then! 🙂 + +--- + +DRovara +3 days ago +Collaborator +Regarding section 5: I think this idea of "maintaining simple dataflow analysis" should not be taken for granted. I'm not saying that this is reason enough to refute the whole concept, but let's look at this example: + +%q0_0 = mqtopt.alloc : !mqtopt.qubit +%c0_out, %q0_1 = mqtopt.ctrl %c0_in { +%q0_new = mqtopt.x %q0_0 : !mqtopt.qubit +mqtopt.yield %q0_new : !mqtopt.qubit +} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) +%q0_2 = mqtopt.x %q0_1 : !mqtopt.qubit +Now, if we run a transformation pass on the final x gate, how do we follow the def-use-chain to reach the alloc? + +First, we can just access the definingOp of %q0_1 (the input of the x gate) +Now, we are at mqtopt.ctrl. How do we find out where %q0_1 came from? We programmatically need to search for the yield +Now at the yield we can once again access definingOp to get the x gate above. +Finally, from there, we can again access definingOp to get the alloc. +All in all, this is certainly not impossible, but this idiom will have to be implemented for every single transformation pass. + +Further, this is just one example where we might "struggle" that comes to mind immediately. There might be further not so obvious ones too. +For instance, how easy is it if we need to replace controlled operation by a non-controlled one? rewriter.replaceOp will no longer work, because the def-use-chain is no longer consistent due to the break at the yield. + +Again, not saying this means that the suggestion is unusable, but it will undoubtedly make stuff at least a bit harder. + +2 replies +@burgholzer +burgholzer +3 days ago +Maintainer +Author +@DRovara: Why do sequences require block arguments for inputs and controlled gates do not? +Thinking about it, requiring block arguments for controlled gates as well might eliminate (or at least reduce) the issue + +Very good point. I think this is merely an oversight. The control modifiers should definitely also receive block arguments similar to the sequence threading. +Do we also need to perform similar threading for the inv and pow modifier? + +@DRovara +DRovara +3 days ago +Collaborator +Good point. Probably we need the same for all of these blocks. + +--- + +DRovara +3 days ago +Collaborator +7.1 UnitaryOpInterface + +For mqtopt operations we definitely need a getCorrespondingInput(outQubit) and getCorrespondingOutput(inQubit) function (see my qubit reuse PR) that takes a qubit (either an output or input of the operation) and gets the corresponding qubit at the other side (i.e. the result it turns into or the input it is based on) + +Also, I believe getStaticParameters and getDynamicParameters is not enough to correctly identify parameters when we have a parameter mask. It would be cool to already have a function that handles that and gives you the corresponding parameter correctly, whether it be static or dynamic. + +3 replies +@burgholzer +burgholzer +3 days ago +Maintainer +Author +For mqtopt operations we definitely need a getCorrespondingInput(outQubit) and getCorrespondingOutput(inQubit) function (see my qubit reuse PR) that takes a qubit (either an output or input of the operation) and gets the corresponding qubit at the other side (i.e. the result it turns into or the input it is based on) + +That makes a lot of sense! I will add this in the next iteration on the document! + +Also, I believe getStaticParameters and getDynamicParameters is not enough to correctly identify parameters when we have a parameter mask. + +Agreed. I'll think a bit about it. Probably something rather close to what we have at the moment would work sufficiently well. + +It would be cool to already have a function that handles that and gives you the corresponding parameter correctly, whether it be static or dynamic. + +I am not quite sure I follow. What is it that you would expect here? + +@DRovara +DRovara +3 days ago +Collaborator +I am not quite sure I follow. What is it that you would expect here? + +I'm not quite sure if it makes sense, but something like getParameter(2) returning the second parameter, whether it is an operand or a static attribute. + +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Ah. Yeah, something like that would make sense. The only question is what that getter would be returning. In the dynamic case, it would be a mlir::Value, in the other case it would be an f64 attribute. +One could think about returning a std::variant of exactly those types. + +--- + +DRovara +3 days ago +Collaborator +8.2 Composite Definitions + +Just for clarity, we should probably also suggest a syntax for mqtopt here (by the way, this example also uses cnot) + +// Define a gate as a sequence of existing operations +mqtopt.gate_def @bell_prep %q0 : !mqtopt.qubit, %q1 : !mqtopt.qubit { +%q0_1 = mqtopt.h %q0 : !mqtopt.qubit +%q1_1, %q0_2 = mqtopt.ctrl %q1 { +%q0_new = mqtopt.x %q0_1 : !mqtopt.qubit +mqtopt.yield %q0_new : !mqtopt.qubit +} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) +mqtopt.yield %q0_2, %q1_1 :!mqtopt.qubit, !mqtopt.qubit +} + +// Apply the composite gate +%q0_1, %q1_1 = mqtopt.apply_gate @bell_prep %q0_0, %q1_0 : !mqtopt.qubit, !mqtopt.qubit +1 reply +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Agreed! I'll add this to the next iteration. +(already replaced the cnot with a cx for now) + +--- + +DRovara +3 days ago +Collaborator +5.1 + +Is there any way to simplify the syntax for mqtopt? + +%c0_out, %q0_out = mqtopt.ctrl %c0_in { +%q0_new = mqtopt.x %q0_in : !mqtopt.qubit +mqtopt.yield %q0_new : !mqtopt.qubit +} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) +Do we really need the (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit)? + +3 replies 2 new +@burgholzer +burgholzer +3 days ago +Maintainer +Author +I suppose we don't need that. Typically, everything behind the colon is optional as long as we can get the type inference right. This is one of the aspects that will probably become clearer when this moves closer to implementation. + +@denialhaag +denialhaag +3 days ago +Collaborator +When I tried getting type inference to work in conjunction with AttrSizedResultSegments, I ran into quite some problems, but I'm sure there's a solution to that. 😌 + +@DRovara +DRovara +8 hours ago +Collaborator +Maybe at least we can make it so the (!mqtopt.qubit, !mqtopt.qubit) -> part is not necessary, similarly to how we did it for other operations. + +Actually, this type annotation seems weird anyways (probably the LLM at fault again): (!mqtopt.qubit, !mqtopt.qubit) -> suggests two inputs being used, but the way it is written, only one variable is directly used by mqtopt.ctrl (%c0_in) + +--- + +DRovara +3 days ago +Collaborator +8.3 +The syntax seems a bit messed up here. + +// Define a parameterized rotation gate +mqtref.gate_def @custom_rotation %q : !mqtref.qubit attributes {params = ["theta", "phi"]} { +mqtref.rz %q {angle = %phi} +mqtref.ry %q {angle = %theta} +mqtref.rz %q {angle = %phi} +} + +// Apply with specific parameters +mqtref.apply_gate @custom_rotation %q0 {theta = 1.57 : f64, phi = 0.78 : f64}` +Why can the rotation gates suddenly take a dynamic operand inside curly braces with angle = ...? That's different from how it was in previous sections. + +Also, something feels uncomfortable about defining the variables as strings and then just using them as e.g. %phi. I get the idea as to why we would want that, but I wonder if there is a cleaner approach. + +1 reply +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Yeah. Thinking about it, this is very much hallucinated and should be more grounded in reality. +I suppose it's more so about the general idea of being able to define a custom gate with parameters. + +--- + +DRovara +3 days ago +Collaborator +Maybe one last general point: + +From a language perspective, I love all of these suggestions. +But I find it highly unlikely that we can just make these changes without major overhauls of our existing transformation passes. +I'm pretty sure that the passes we have so far will not only require rewriting but also rethinking, in some contexts. + +That doesn't mean we shouldn't do these changes, but it should be clear in advance. + +1 reply +@burgholzer +burgholzer +3 days ago +Maintainer +Author +Oh yeah, definitely. This changes the very fundamentals that we operate on. +So changes will definitely be necessary to the transformations as well. Especially in places where operations are constructed. +I'd still hope that some transformations will remain moderately untouched because of the extensive use of the UnitaryInterface that abstracts over many of the details here. + +And I also believe that this is now the culmination of spending over 9 months working on MLIR. To me, this feels like it would build a close-to-feature-complete basis architecture and infrastructure that we can rely on in the future. + +--- + +MatthiasReumann +9 hours ago +Collaborator +Hello there! 👀 + +Some thoughts I had while reading this. + +5.1 Control Operations + +Should this really be a %c0? I think this is a mistake, right? + +// Single control +mqtref.ctrl %c0 { // <--- Shouldn't this be a quantum value, e.g., %q1? +mqtref.x %q0 // Controlled-X (CNOT) +} + +... // Similarly for the other examples. 5) Modifier Operations and 7) Unified Interface Design: + +I keep asking myself, because I've been working on this recently, how do I apply these changes to the placement and routing passes (MQTOpt). Right now, a UnitaryInterface acts like a gate - it has inputs and outputs. Easy. An IR-Walker can simply ::advance() when encountering it. + +With the proposed changes, which I very much like, it's a bit more cumbersome - or at least less intuitive. A (pre-order) traversal would have to skip all elements inside a UnitaryInterface and only work with the interface (getInputs, getOutputs) of the "shell". However, these interface methods will probably have to traverse the IR internally nonetheless. + +I've been thinking if it could make sense to define and implement a custom top-down driver for traversing quantum-classical programs canonically 1. This would have the advantage that details such as the one above are hidden away. Following the same argument as in the routing pass this driver will most likely not be pattern based. + +Anyhow, either I'm missing something here (and this isn't really a problem) or the current dialects are advantageous over the changes proposed at least for this one particular aspect. + +@DRovara was probably also hinting at this: + +Further, this is just one example where we might "struggle" that comes to mind immediately. There might be further not so obvious ones too. + +Footnotes +The placement and routing passes already implement a naive-form of such a driver, separately. ↩ + +5 replies 1 new +@DRovara +DRovara +8 hours ago +Collaborator +Should this really be a %c0? I think this is a mistake, right? + +I think it's called "c" for "control", not "classical". But yeah, I definitely also had to do a double-take. + +@DRovara +DRovara +8 hours ago +Collaborator +Regarding the transformations: I feel like, what we need is to just try out how much more effort it is to write transformations this way. Unfortunately, that can only really be done once we have implemented everything, I guess. + +But yeah, the way you would likely have to do is, if you walk along the def-use chain, is to check, at each step, whether prev->getBlock() == current->getBlock(), and, as long as this is not the case, apply current = current->getBlock()->getParentOp() (more or less, don't remember the names specifically). + +That repeated check might very well also lead to a reduced efficiency and uglier code, but I don't really see a way around it. + +@DRovara +DRovara +5 hours ago +Collaborator +Maybe to this end, as an extension to "7.1 UnitaryOpInterface", we should also add getPredecessors() or getPredecessor(int operandIndex) or getPredecessor(mlir::Value qubit) as well as getSuccessors(...) methods to the UnitaryInterface that handle this automatically (note that for the successors, the result always has to be a collection and cannot be a single item due to branches allowing multiple users of the same variable). + +@burgholzer +burgholzer +4 hours ago +Maintainer +Author +Regarding the first point: yeah, the c here was meant for "control" but is also merely an LLM artifact. I'll try to pay attention to this when rewriting the proposal. + +As for the transformations: I was hoping that this kind of hierarchical structuring would work very naturally. Especially since we are performing these transformations on the MQTOpt dialect with value semantics. This should still allow us to fairly easily traverse the def-use chains of the hierarchical operations quite similar to how one would traverse an scf.if instruction. Such instructions consume SSA values and produce new ones. +I don't particularly see the problem of relying on the interface methods; in the end, that's what they are for. +But maybe I am overlooking things here. +As Damian already pointed out, I have a feeling that one can only really tell once one tries implementing a rather large part of this proposal. + +@DRovara +DRovara +1 hour ago +Collaborator +This should still allow us to fairly easily traverse the def-use chains of the hierarchical operations quite similar to how one would traverse an scf.if instruction + +If we define some sort of block arguments similar to your suggestion for mqtopt.seq, then it might actually work quite naturally (but I still didn't think much about it). If we don't then we have to do it the way we handle scf.if. + +The thing is, from my understanding working with scf.if so far, contrary to your statement: yes, scf.if produces new values, but it does not explicitly consume any. For that, we have to look at the corresponding yield operation. That's what I meant in another comment about breaking the def-use chain. Whenever we have scf.if, we have two chains, like this: + +op1 --> op2 --> scf.yield +scf.if --> op3 --> op4 +And we have to "manually" write the traversal methods so that the chains are merged with the correct yield/if + +op1 --> op2 --> scf.if/scf.yield --> op3 --> op4 +The same thing would also happen with the control/inv/pow regions. + +--- + +flowerthrower +5 hours ago +Collaborator +Disclaimer: I am not an expert on fault-tolerant quantum computing (so take this with a grain of salt). From what I've seen in inconspiquos and FTQC literature, codes like surface/color codes and LDPC stabilizer codes often use logical code blocks—sometimes as geometric patches, sometimes more abstractly. With the MLIR dialect design in mind, several bundled feature areas seem broadly relevant: + +Logical Block and Reconfiguration Semantics: +FTQC protocols depend on defining, manipulating, and reconfiguring logical code blocks (e.g., merge/split operations in lattice surgery, code switching, gauge fixing). E.g. inconspiquos has regions of qubits that can be arbitrarily merged and split. +How this could be done with the proposed model: +The dialect's compositional sequences and user-defined gates allow grouping and modular reuse of operations—these can mimic logical blocks and some forms of reconfiguration via higher-level composition. However, native support for explicit logical block annotation and dynamic reconfiguration (merge/split/code transitions) would require further dialect extensions or metadata. + +Bulk Operations and Scheduling: +Expressing cycle-based or parallel operations (like batches of parity checks and measurements) is essential for scalability in LDPC and surface code protocols. E.g., one can apply a gate operation to such a region in inconspiquos (under the hood all qubits in that region are addressed) +How this could be done with the proposed model: +Sequences, loop constructs, and composite gates in the dialect can encapsulate repeated or batch operations, but efficient bulk scheduling primitives for large codes may go beyond what's ergonomic now. + +Syndrome Processing, Decoder Integration, and Code-Aware Semantics: +Modern FTQC needs first-class support for syndrome acquisition, Pauli-frame tracking, and integration with classical decoders, along with the ability to annotate protocols with code parameters, gate set constraints, and locality/transversality information. +How this could be done with the proposed model: +The dialect's classical register support and compositional logic enable measurement pipelines and feedback routines. Code-aware semantics and decoder integration could be layered on top with attributes, annotations, or additional IR ops. Though many of these things go beyond the concepts of this IR discussion, at least one should keep them in mind. + +Overall, I really like the proposed changes and there is not much from my end that has not already been discussed in the above comments. In order to make it as future-proof as possible, however, it could be sensible to talk about the FT requirements with the error correction folks at the chair. Although I acknowledge that it might be difficult for them to fully follow the discussion here—as it is for me (us) to understand all the error correction details. + +1 reply 1 new +@burgholzer +burgholzer +4 hours ago +Maintainer +Author +Thanks for the input on this! Certainly an important point. +From a very birds-eye view, I see no major roadblocks for incorporating these features on top of the existing proposal. As identified, one could probably even "abuse" the existing concepts without much adaptation. However, I have a feeling that one might rather want to define separate dialects and/or operations for some of these concepts. +Having a (separate) discussion on this definitely makes sense; probably after another one or two iterations on the actual proposal. From 467f9505ba5164f471a9c10920e5b68620c7390a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 29 Sep 2025 22:04:28 +0200 Subject: [PATCH 006/419] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/mlir-discussions-summary.json | 136 ++++ docs/mlir/mlir-discussions.md | 838 ++++------------------- docs/mlir/plan-sync-agent-prompt.md | 195 ++++++ docs/mlir/quantum-dialect-revamp-plan.md | 672 +++++++++--------- 4 files changed, 789 insertions(+), 1052 deletions(-) create mode 100644 docs/mlir/mlir-discussions-summary.json create mode 100644 docs/mlir/plan-sync-agent-prompt.md diff --git a/docs/mlir/mlir-discussions-summary.json b/docs/mlir/mlir-discussions-summary.json new file mode 100644 index 0000000000..debcb80014 --- /dev/null +++ b/docs/mlir/mlir-discussions-summary.json @@ -0,0 +1,136 @@ +{ + "version": 3, + "generated": "2025-09-29", + "sections": { + "modifierControlSemantics": { + "multipleControls": { + "decision": "Allow multiple controls per ctrl/negctrl; normalization flattens nesting.", + "rationale": [ + "Compact IR", + "Less traversal", + "Still supports nesting for composability" + ], + "openQuestions": [] + }, + "canonicalOrder": { + "orderOuterToInner": ["ctrl", "negctrl", "pow", "inv"], + "rationale": [ + "Deterministic structure", + "Aids hashing/CSE", + "Semantic precedence" + ], + "status": "decided" + }, + "negativeControls": { + "representation": "Distinct ctrl and negctrl ops retained; no automatic normalization to positives.", + "transforms": [ + "Backend-specific rewrites may still introduce/remove X gate sandwiches" + ], + "autoNormalization": false + }, + "controlCounting": { + "rules": { + "baseOp": 0, + "ctrl": "own + inner", + "negctrl": "own + inner", + "pow": "forward", + "inv": "forward", + "seq": 0, + "unitaryDefinition": 0 + }, + "seqRationale": "Acts like one-off composite; prevents double counting.", + "powInvInterfaceNeeded": false + } + }, + "gateSet": { + "decision": "Retain rich named gate set plus bidirectional canonicalization passes.", + "rationale": [ + "Readability", + "Numerical stability", + "Trait inference", + "Ecosystem alignment", + "Backend flexibility" + ], + "passes": { + "namedSimplification": "U3/U2/U1 -> simplest named gate (tolerance-based)", + "universalExpansion": "Named single-qubit gate -> canonical U3 form (optional)" + }, + "status": "decided" + }, + "matrixBasedGates": { + "status": "included", + "decision": "Matrix-based gates part of initial spec; no hard size limit (practically ≤3 qubits).", + "mvpOptimizations": ["2x2 fast path", "4x4 fast path"], + "attribute": "Flat dense array (row-major) of complex numbers", + "validationPass": true, + "pros": ["Direct synthesized result expression", "Unambiguous semantics"], + "considerations": [ + "Exponential growth cost accepted", + "Future compression out-of-scope for MVP" + ], + "openQuestions": [] + }, + "normalizationPass": { + "responsibilities": [ + "Enforce modifier order", + "Merge adjacent compatible modifiers", + "Eliminate identities", + "Algebraic simplifications (e.g. pow(RZ(pi/2),2)->RZ(pi))", + "Flatten nested control wrappers" + ], + "integration": "Runs always before other transformations via canonicalization pipeline.", + "additional": [ + "Gate definition inlining", + "Matrix unitarity validation", + "Named simplification", + "Universal expansion", + "Matrix decomposition (future)" + ] + }, + "testing": { + "primary": "googletest builder-based structural/semantic equivalence tests", + "textualTests": "Minimal parser round-trip smoke tests only", + "buildersCoverage": "All ops, modifiers, matrix unitaries, unitary definitions", + "helpers": ["assertEquivalent", "fixture utilities", "roundTrip tests"], + "avoid": ["Over-reliance on CHECK lines", "SSA name comparisons"] + }, + "pipelines": { + "baseline": [ + "-canonicalize (includes normalization)", + "-remove-dead-values", + "Named Simplification" + ], + "universalBackend": ["baseline", "Universal Expansion"], + "normalizationAlwaysFirst": true + }, + "openQuestions": [], + "decisionsChronological": [ + "Distinct ctrl and negctrl retained", + "Canonical modifier order established", + "seq has zero controls", + "Named gate set retained (no U3-only)", + "Normalization + simplification planned", + "Builder-based structural testing adopted", + "Baseline pass pipeline selected", + "Bidirectional single-qubit canonicalization decided", + "Matrix-based gates included in initial spec", + "Normalization enforced before all other transformations", + "googletest builder-based testing prioritized", + "Negative controls remain first-class (no auto-normalization)" + ], + "futureWork": [ + "Implement NormalizationPass", + "Implement named simplification & universal expansion passes", + "Structural equivalence utilities", + "Matrix-based unitary op & validation pass", + "Matrix decomposition pass (basis lowering)", + "Trait inference for composites & matrix ops", + "Basis gate registry" + ], + "metadata": { + "source": "Condensed & updated from mlir-discussions.md v3", + "intendedUse": "Automated querying / LLM grounding", + "stability": "Will evolve; track version key" + } + } +} diff --git a/docs/mlir/mlir-discussions.md b/docs/mlir/mlir-discussions.md index 6cf738e70e..22c7122363 100644 --- a/docs/mlir/mlir-discussions.md +++ b/docs/mlir/mlir-discussions.md @@ -1,794 +1,208 @@ -ystade -3 days ago -Maintainer -// Multiple controls -mqtref.ctrl %c0, %c1 { -mqtref.x %q0 // Toffoli gate (CCX) -} -Should the ctrl modifier really support multiple controls at once? Still, I can also wrap control in control, and so forth. At least there should be a fixed canonical form. - -Edit: Also the canonical order of pos. and neg. controls should be fixed. - -4 replies 1 new -@burgholzer -burgholzer -3 days ago -Maintainer -Author -I think this is touched on somewhere above. - -NormalizationPass: Enforces canonical modifier ordering and eliminates redundancies - -Reorders nested modifiers to canonical form (ctrl → pow → inv) -Eliminates identity operations (pow(X, 1) → X, inv(inv(X)) → X) -Merges adjacent compatible modifiers -Simplifies power modifiers wherever feasible (pow(RZ(pi/2), 2) → RZ(pi)) -Canonicalization ensure that there is a normal form of modifiers, that adjacent modifiers are merged. -I believe that aggregation of controls in a single modifier makes sense because it helps to achieve a compact IR that needs less traversal. I also don't really see a downside of doing that. - -@ystade -ystade -3 days ago -Maintainer -Agreed, then just the precedence of neg. controls must be defined. - -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Yeah. Essentially, we the normalization pass above should read - -(ctrl → negctrl → pow → inv) -I was briefly thinking (and had that in the RFC for a short period) if it would make sense to combine the ctrl and negctrl wrapper operations and a single ctrl wrapper that has both negative and positive controls similar to how we currently have it in our UnitaryOp. But this did not feel as clean to me as the separate solution. -We could also implement a transformation pass that turns negative controls into positive controls sandwhiched by X gates, so that there would only be positive controls anymore. -Naturally, the opposite direction is also imaginable, i.e., aggregating controls sandwhiched by X gates into a negative control. -Any opinions on that? - -@ystade -ystade -4 hours ago -Maintainer -Yeah, I was already thinking about the same but could not imagine what flows better with the design of MLIR. My proposal would be to go with it as it stands right now and keep the other option in mind if the current one does not feel ergonomic. +# MLIR Quantum Dialect Discussions Digest ---- - -In general, we might want to discuss, whether we actually need gates like x, rx, ... or whether we adopt the OpenQASM 3 way and just have on u3 gate plus all modifiers. - -Or whether this is a sensible canoncalization pass? And which direction actually is the canonicalisation, to or from only u3 gates? - -4 replies 1 new -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Just the U3 gate alone is quite cumbersome to use. And defining everything in terms of the U3 gate also gets annoying rather quickly as it invites numerical errors. -I am also not quite sure I'd like that the treatment of single-qubit gates would be different than for multi-qubit gates, because I doubt, we will describe two-qubit gates by their 17+ parameter KAK decomposition. - -Even OpenQASM (and Qiskit) defines a standard library that is being heavily used and includes all kinds of gates. Over there, it's "just" the definition of the unitary of the gates that can be recursively boiled down to the global phase gate and modifiers. - -Lastly, dropping the individual gates also makes traits such as "Hermitian" or "Diagonal" much harder to determine. And I would believe these are quite useful in the analysis of programs. - -@ystade -ystade -3 days ago -Maintainer -The last point is a very good point. Perhaps, we should have a Canonicalization pass that transforms every gate in the simplest possible (similar to the behaviour when parsing QASM files in MQT Core currently). Maybe even the other way around is needed for passes that only understand u3 gates and nothing else. For the latter, I do not know whether this is even desired. - -@burgholzer -burgholzer -3 days ago -Maintainer -Author -This canonicalization pass is something that, to some degree, fits well with the custom gate definitions, which are explained in one of the sections. There could be an inlining pass that inlines gate definitions until it reaches well known basis gates (=our standard library). - -At the same time, we have some kind of canonicalization for individual gates as part of MQT Core IR already. Namely, here: - -core/src/ir/operations/StandardOperation.cpp - -Lines 36 to 186 in 9109b56 - -OpType StandardOperation::parseU3(fp& theta, fp& phi, fp& lambda) { -if (std::abs(theta) < PARAMETER_TOLERANCE && -std::abs(phi) < PARAMETER_TOLERANCE) { -parameter = {lambda}; -return parseU1(parameter[0]); -} - -if (std::abs(theta - PI_2) < PARAMETER_TOLERANCE) { -parameter = {phi, lambda}; -return parseU2(parameter[0], parameter[1]); -} - -if (std::abs(lambda) < PARAMETER_TOLERANCE) { -lambda = 0.L; -if (std::abs(phi) < PARAMETER_TOLERANCE) { -checkInteger(theta); -checkFractionPi(theta); -parameter = {theta}; -return RY; -} -} - -if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { -lambda = PI_2; -if (std::abs(phi + PI_2) < PARAMETER_TOLERANCE) { -checkInteger(theta); -checkFractionPi(theta); -parameter = {theta}; -return RX; -} - - if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { - phi = PI_2; - if (std::abs(theta - PI) < PARAMETER_TOLERANCE) { - parameter.clear(); - return Y; - } - } - -} - -if (std::abs(lambda + PI_2) < PARAMETER_TOLERANCE) { -lambda = -PI_2; -if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { -phi = PI_2; -parameter = {-theta}; -return RX; -} -} - -if (std::abs(lambda - PI) < PARAMETER_TOLERANCE) { -lambda = PI; -if (std::abs(phi) < PARAMETER_TOLERANCE) { -phi = 0.L; -if (std::abs(theta - PI) < PARAMETER_TOLERANCE) { -parameter.clear(); -return X; -} -} -} - -// parse a real u3 gate -checkInteger(lambda); -checkFractionPi(lambda); -checkInteger(phi); -checkFractionPi(phi); -checkInteger(theta); -checkFractionPi(theta); - -return U; -} - -OpType StandardOperation::parseU2(fp& phi, fp& lambda) { -if (std::abs(phi) < PARAMETER_TOLERANCE) { -phi = 0.L; -if (std::abs(std::abs(lambda) - PI) < PARAMETER_TOLERANCE) { -parameter.clear(); -return H; -} -if (std::abs(lambda) < PARAMETER_TOLERANCE) { -parameter = {PI_2}; -return RY; -} -} - -if (std::abs(lambda - PI_2) < PARAMETER_TOLERANCE) { -lambda = PI_2; -if (std::abs(phi + PI_2) < PARAMETER_TOLERANCE) { -parameter.clear(); -return V; -} -} - -if (std::abs(lambda + PI_2) < PARAMETER_TOLERANCE) { -lambda = -PI_2; -if (std::abs(phi - PI_2) < PARAMETER_TOLERANCE) { -parameter.clear(); -return Vdg; -} -} - -checkInteger(lambda); -checkFractionPi(lambda); -checkInteger(phi); -checkFractionPi(phi); - -return U2; -} - -OpType StandardOperation::parseU1(fp& lambda) { -if (std::abs(lambda) < PARAMETER_TOLERANCE) { -parameter.clear(); -return I; -} -const bool sign = std::signbit(lambda); - -if (std::abs(std::abs(lambda) - PI) < PARAMETER_TOLERANCE) { -parameter.clear(); -return Z; -} - -if (std::abs(std::abs(lambda) - PI_2) < PARAMETER_TOLERANCE) { -parameter.clear(); -return sign ? Sdg : S; -} - -if (std::abs(std::abs(lambda) - PI_4) < PARAMETER_TOLERANCE) { -parameter.clear(); -return sign ? Tdg : T; -} - -checkInteger(lambda); -checkFractionPi(lambda); - -return P; -} - -void StandardOperation::checkUgate() { -if (parameter.empty()) { -return; -} -if (type == P) { -assert(parameter.size() == 1); -type = parseU1(parameter.at(0)); -} else if (type == U2) { -assert(parameter.size() == 2); -type = parseU2(parameter.at(0), parameter.at(1)); -} else if (type == U) { -assert(parameter.size() == 3); -type = parseU3(parameter.at(0), parameter.at(1), parameter.at(2)); -} -} - -This is parsing arbitrary U3 gates and translating them to the simplest matching operation. I do see value in that and have relied on that functionality quite a lot in the past. -At the same time, there has actually been (currently unmet) demand for the opposite direction. Namely, here: #910 -While this specific issue only talks about phase gates, I'd probably generalize this to U3. -Do both of these points make sense? - -@ystade -ystade -4 hours ago -Maintainer -That is exactly along my reasoning. +Purpose: Curated, compact, duplication‑free summary of prior threaded discussions to serve as a stable knowledge base for design, implementation, and future LLM queries. --- -ystade -3 days ago -Maintainer -// Control queries -bool isControlled(); -size_t getNumPosControls(); -size_t getNumNegControls(); -size_t getNumControls(); -I do not get it, I thought the single operation does not have controls anymore. Then, the functions above would already implement a transversal of the wrapping control modifiers. But where does this transversal end? At the boundaries of functions/sequences? Or should it go beyond? Furthermore, why are there no function to check for inv or pow modifiers? - -7 replies 7 new -@burgholzer -burgholzer -3 days ago -Maintainer -Author -The unitary interface is intended to be implemented by all concepts here. - -The base operations would always report 0 controls. -A control modifier reports its own controls + all controls of the contained operation (which again implements the UnitaryInterface). -pow and inv just forward to the contained operation -seq (up for debate) will report 0 controls as it represents a block of operations conceptionally -unitary definition will also report 0 controls -Hope that covered everything. This seems like a well terminating sequence of rules to me. - -As for the inv and pow modifiers: neither of them change the number of qubits of the underlying operation. I haven't really found a use case for exposing these. -inv on base operations will be canonicalized away, inv on inv cancels, inv and pow commute, inv on sequence either remains like it is or is canonicalized to reversed sequence of inverted operations; inverse of a unitary definition is up for debate. -Do you have a use case in mind where having generic access to these details in the interface makes sense? And a proposal for the interface function? - -@ystade -ystade -3 days ago -Maintainer -A control modifier reports its own controls + all controls of the contained operation (which again implements the UnitaryInterface). -This gives rise to another question: So, the block within a ctrl modifier is only allowed to contain exactly one operation. I was assuming that this can be an arbitrary block... - -@ystade -ystade -3 days ago -Maintainer -Do you have a use case in mind where having generic access to these details in the interface makes sense? And a proposal for the interface function? - -No, I do have not and what you wrote before makes absolutely sense. We can always add such functionality if we are missing it. - -@burgholzer -burgholzer -3 days ago -Maintainer -Author -A control modifier reports its own controls + all controls of the contained operation (which again implements the UnitaryInterface). -This gives rise to another question: So, the block within a ctrl modifier is only allowed to contain exactly one operation. I was assuming that this can be an arbitrary block... - -It can be anything that implements the UnitaryInterface. -So it can be a single basis operation, another modifier operation, a unitary, or... a mqtref.seq, which covers the use case of a modifier applying to a group of operations. -I believe this covers all imaginable scenarios. - -@ystade -ystade -4 hours ago -Maintainer -Makes sense. However, then it is not entirely clear to me, how to count the controls of a seq. - -@DRovara -DRovara -52 minutes ago -Collaborator -I'd say a seq does not have any controls, as its essentially a one-time-use custom gate, right? - -@burgholzer -burgholzer -30 minutes ago -Maintainer -Author -I'd say a seq does not have any controls, as its essentially a one-time-use custom gate, right? - -Exactly! A sequence will always report 0 controls. - ---- +## 1. Modifier & Control Semantics -ystade -3 days ago -Maintainer -8.1 Matrix-Based Definitions +### 1.1 Multiple Controls in a Single Modifier -Is that needed? I am not sure whether we overengineer here our dialect. If I am not mistaken, the u3 gate should already cover all possible unitary gates. When defining gates via matrices, one would also need to implement a validation pass, validating that every matrix is unitary, something that is by default satisfied with the u3 gate. +- We allow a single `ctrl` (and `negctrl`) modifier to list multiple controls for compact IR and reduced traversal. +- Nesting remains valid but a normalization pass will flatten/merge where possible. -1 reply -@burgholzer -burgholzer -3 days ago -Maintainer -Author -The u3 gate only covers single-qubit matrices. I'd consider it feasible to represent up to 3-qubit gates (maybe even more) as a unitary and I would imagine that especially the 1- and 2-qubit cases could be popular. -This point was mainly inspired by some discussions at QCE with Ed from the BQSKit team and the motivation is mostly grounded in numerical synthesis, where it is just more efficient and straight forward to describe the unitary of the operation as opposed to a sequence of (known) gates that make up the unitary functionality. +### 1.2 Canonical Modifier Ordering -Overall, I think this is not a must-have, but a very-nice-to-have. And it is definitely a unique and unambiguous way of describing the functionality of a gate. +Canonical order (outer → inner): -The validation pass is a good idea. Although I'd personally also put some of the responsibility for that on the developer that uses these custom gates. +1. `ctrl` +2. `negctrl` +3. `pow` +4. `inv` ---- +Rationale: -Regarding testing: I think we should consider switching to the CHECK-NEXT directive to ensure that we don't have unexpected statements in our IR. +- Groups positive and negative controls first (most structural impact on qubit usage / control sets). +- Power and inverse are algebraic refinements on the underlying operation. +- Deterministic order enables straightforward structural hashing, CSE, pattern rewrites, and simplifies equality queries. -3 replies -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Yeah. I agree. Maybe even taking this one step further: we should compare entire programs are fully equivalent to what we would expect. We define complete input programs and the passes that should be run on them. Based on that there is a clear expected output program that we could be writing down explicitly. -Maybe with the help of the circuit builder, we can actually not do this on top of the textual IR at all, but rather construct these test cases programmatically in a googletest suite similar to the translation tests. +### 1.3 Negative Controls Representation -@DRovara -DRovara -3 days ago -Collaborator -Actually I really like the idea of using the builders to construct MLIR representations of expected outcomes and then using those to check for equivalence in normal unit tests. +Current decision: keep distinct `ctrl` and `negctrl` wrappers (cleaner semantics than a combined mixed-control list variant). -In fact, this is the way I have seen such language tests being used outside of the MLIR framework and it just feels really clean and straightforward - if we ever change the representation, the tests won't be affected as the builders should still be able to construct equal programs. +Decision Update: Negative controls are first-class citizens; we WILL NOT automatically normalize them into positive controls in canonical pipelines. Transformations involving X gate sandwiches remain optional backend-specific rewrites, not part of normalization. -@burgholzer -burgholzer -3 days ago -Maintainer -Author -In fact, this is the way I have seen such language tests being used outside of the MLIR framework and it just feels really clean and straightforward - if we ever change the representation, the tests won't be affected as the builders should still be able to construct equal programs. +### 1.4 Control Counting Semantics (UnitaryInterface) -That is a good point! And a great argument for stability. I will try to include this in the overall project plan and afterwards will hide this conversation as resolved. +- Base (leaf) operations: 0 controls. +- `ctrl` / `negctrl`: number of (pos / neg) controls they introduce + recursively added controls of wrapped op. +- `pow`, `inv`: do not change control counts; they forward queries to the wrapped op. +- `seq` (sequence construct): always reports 0 controls (acts like a one-off composite/inline body rather than a modifier). +- User-defined unitary (definition): reports 0 controls. ---- +Rationale: Control counting terminates cleanly; `seq` neutrality simplifies reasoning and prevents accidental double counting when sequences are wrapped by modifiers. -denialhaag -3 days ago -Collaborator -We should also think about which, if any, default passes we want to run. The -canonicalize and -remove-dead-values passes seem useful in most cases to me. Slightly related to the comment above, this would, for example, ensure that constants are always defined at the top of the module in our tests. - -1 reply -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Yes, totally. Especially if we define further canonicalization for the operations in our dialects. --canonicalize is a no-brainer in my opinion; we should definitely do that. -Probably the same holds for the -remove-dead-values pass. +Open Question (closed): Interface exposure for enumerating `pow` / `inv` layers not needed now. --- -4.2 +## 2. Gate Set vs. Single Universal Gate (U3-Only Approach) -mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit gate -Is this naming scheme of parameters actually valid? Does this still correlate with the parameter-arity traits? +Proposal Considered: Collapse to only a universal single-qubit gate (U3) + modifiers (OpenQASM 3 style). -The existing solution also (semi-)elegantly handles situations where some parameters may be static and others dynamic operands. Can this still be done with we represent the gates like this? -Or maybe in general, how do such gates still support dynamic operands for angles etc? -Sorry, just saw that this is covered in 4.3 +Decision: Retain a rich set of named primitive gates (X, Y, Z, H, S, T, RX, RY, RZ, P, etc.) AND provide bidirectional canonicalization passes bridging named gates and universal U3 forms. -mqtref.rx %q0, %angle : f64 -But some extra questions to that: -Do we maybe want some syntactic sugar to distinguish %q0 as a qubit argument from %angle as a parameter? -And why do we need the f64 type indication for the angle, but not the qubit? +Rationale: -1 reply -@burgholzer -burgholzer -3 days ago -Maintainer -Author -I think a lot of the examples are inconsistent at the moment because they are hallucinations. -I'd actually like to keep the parameter handling almost exactly the same compared to now -Syntactic sugar for distinguishing between parameters and qubits makes a lof of sense. I do like how we currently handle this. -The type indication is supposedly just a hallucination. -I'll try to get a little bit more consensus on some of the general design decisions before I try to consolidate the examples and flesh them out more. +- Readability & ergonomic authoring. +- Numerical stability: Avoids always instantiating via floating-point U3 params (reduces drift / canonicalization complexity). +- Trait inference (e.g., Hermitian, Diagonal) is straightforward on symbolic gates; harder on generic parametrized forms. +- Alignment with existing ecosystems (OpenQASM library, Qiskit standard gates) while still permitting decomposition. +- Bidirectional passes satisfy both optimization introspection (simplify to named) and backend uniformity (expand to U3) requirements. ---- - -denialhaag -3 days ago -Collaborator -Just to have it mentioned: We already have a way to translate QuantumComputation IR containing classically controlled operations to mqtref. We do not use custom operations for that, but we rely on the scf dialect. If I'm not mistaken, we have not yet attempted to convert a mqtref module containing classically controlled operations to mqtopt. I'm not sure if this is something we also want to be part of the C++builder. - -3 replies 1 new -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Hm. I was, maybe naively, assuming that any program that we can currently express through the QuantumComputation -> MQTRef translation can also be translated to MQTOpt. And I am pretty sure we actually can because this is what @DRovara's qubit reuse relies on. But maybe I am mistaken here. -Please feel free to chime in on this. - -In my mind, the C++ builder API would be very, very similar to the QuantumComputation API. It would let you add quantum and classical registers, quantum gates (potentially involving classical controls), measurements and resets. That API would practically be the evolution/continuation of the QuantumComputation API. - -@DRovara -DRovara -3 days ago -Collaborator -In MQTOpt we can use scf.if the same way as in MQTDyn, we just need to yield the result afterwards. - -@denialhaag -denialhaag -3 days ago -Collaborator -Okay, this sounds good to me then! 🙂 +Committed Passes: ---- +1. Named Simplification Pass: Simplify generic U3/U2/U1 into simplest named gates (tolerance-based) — deterministic, part of canonicalization pipeline. +2. Universal Expansion Pass: Expand named single‑qubit gates into canonical U3 (optionally gated by a flag / backend pipeline requirement). -DRovara -3 days ago -Collaborator -Regarding section 5: I think this idea of "maintaining simple dataflow analysis" should not be taken for granted. I'm not saying that this is reason enough to refute the whole concept, but let's look at this example: - -%q0_0 = mqtopt.alloc : !mqtopt.qubit -%c0_out, %q0_1 = mqtopt.ctrl %c0_in { -%q0_new = mqtopt.x %q0_0 : !mqtopt.qubit -mqtopt.yield %q0_new : !mqtopt.qubit -} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) -%q0_2 = mqtopt.x %q0_1 : !mqtopt.qubit -Now, if we run a transformation pass on the final x gate, how do we follow the def-use-chain to reach the alloc? - -First, we can just access the definingOp of %q0_1 (the input of the x gate) -Now, we are at mqtopt.ctrl. How do we find out where %q0_1 came from? We programmatically need to search for the yield -Now at the yield we can once again access definingOp to get the x gate above. -Finally, from there, we can again access definingOp to get the alloc. -All in all, this is certainly not impossible, but this idiom will have to be implemented for every single transformation pass. - -Further, this is just one example where we might "struggle" that comes to mind immediately. There might be further not so obvious ones too. -For instance, how easy is it if we need to replace controlled operation by a non-controlled one? rewriter.replaceOp will no longer work, because the def-use-chain is no longer consistent due to the break at the yield. - -Again, not saying this means that the suggestion is unusable, but it will undoubtedly make stuff at least a bit harder. - -2 replies -@burgholzer -burgholzer -3 days ago -Maintainer -Author -@DRovara: Why do sequences require block arguments for inputs and controlled gates do not? -Thinking about it, requiring block arguments for controlled gates as well might eliminate (or at least reduce) the issue - -Very good point. I think this is merely an oversight. The control modifiers should definitely also receive block arguments similar to the sequence threading. -Do we also need to perform similar threading for the inv and pow modifier? - -@DRovara -DRovara -3 days ago -Collaborator -Good point. Probably we need the same for all of these blocks. +Status: Dual canonicalization direction DECIDED (no longer an open question). --- -DRovara -3 days ago -Collaborator -7.1 UnitaryOpInterface +## 3. Matrix-Based Gate Definitions (Arbitrary Unitaries) -For mqtopt operations we definitely need a getCorrespondingInput(outQubit) and getCorrespondingOutput(inQubit) function (see my qubit reuse PR) that takes a qubit (either an output or input of the operation) and gets the corresponding qubit at the other side (i.e. the result it turns into or the input it is based on) +Decision: INCLUDED in initial specification (not deferred). No programmatic hard limit on qubit count; practical performance naturally constrains usage. Provide specialized fast paths and optimization focus for 1‑qubit (2×2) and 2‑qubit (4×4) matrices as part of the MVP (not postponed). -Also, I believe getStaticParameters and getDynamicParameters is not enough to correctly identify parameters when we have a parameter mask. It would be cool to already have a function that handles that and gives you the corresponding parameter correctly, whether it be static or dynamic. +Representation: -3 replies -@burgholzer -burgholzer -3 days ago -Maintainer -Author -For mqtopt operations we definitely need a getCorrespondingInput(outQubit) and getCorrespondingOutput(inQubit) function (see my qubit reuse PR) that takes a qubit (either an output or input of the operation) and gets the corresponding qubit at the other side (i.e. the result it turns into or the input it is based on) +- Attribute: Flat dense array (row-major) of complex numbers (candidate: MLIR DenseElementsAttr with complex element type). Potential specialized attributes may still be explored later if profiling indicates need. +- Optional metadata: dimension (inferred) and qubit arity (validation cross-check). -That makes a lot of sense! I will add this in the next iteration on the document! +Validation: -Also, I believe getStaticParameters and getDynamicParameters is not enough to correctly identify parameters when we have a parameter mask. +- Unitarity Validation Pass: (Configurable) numerical tolerance check; may be skipped in trusted build modes. -Agreed. I'll think a bit about it. Probably something rather close to what we have at the moment would work sufficiently well. +Pros: -It would be cool to already have a function that handles that and gives you the corresponding parameter correctly, whether it be static or dynamic. +- Direct expression of synthesized results & externally imported unitaries. +- Unambiguous semantics and suitable anchor for decomposition passes. -I am not quite sure I follow. What is it that you would expect here? +Considerations: -@DRovara -DRovara -3 days ago -Collaborator -I am not quite sure I follow. What is it that you would expect here? - -I'm not quite sure if it makes sense, but something like getParameter(2) returning the second parameter, whether it is an operand or a static attribute. - -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Ah. Yeah, something like that would make sense. The only question is what that getter would be returning. In the dynamic case, it would be a mlir::Value, in the other case it would be an f64 attribute. -One could think about returning a std::variant of exactly those types. +- Large matrices quickly infeasible (exponential growth); accepted as user responsibility—no current heuristic advisory needed (≤3 qubits covers practical scope). +- Potential future compression (factorizations, tensor products) is out-of-scope for MVP. --- -DRovara -3 days ago -Collaborator -8.2 Composite Definitions - -Just for clarity, we should probably also suggest a syntax for mqtopt here (by the way, this example also uses cnot) - -// Define a gate as a sequence of existing operations -mqtopt.gate_def @bell_prep %q0 : !mqtopt.qubit, %q1 : !mqtopt.qubit { -%q0_1 = mqtopt.h %q0 : !mqtopt.qubit -%q1_1, %q0_2 = mqtopt.ctrl %q1 { -%q0_new = mqtopt.x %q0_1 : !mqtopt.qubit -mqtopt.yield %q0_new : !mqtopt.qubit -} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) -mqtopt.yield %q0_2, %q1_1 :!mqtopt.qubit, !mqtopt.qubit -} - -// Apply the composite gate -%q0_1, %q1_1 = mqtopt.apply_gate @bell_prep %q0_0, %q1_0 : !mqtopt.qubit, !mqtopt.qubit -1 reply -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Agreed! I'll add this to the next iteration. -(already replaced the cnot with a cx for now) +## 4. Normalization & Canonicalization Passes (Current & Planned) ---- +NormalizationPass (part of canonicalization pipeline; always scheduled before other transformations): -DRovara -3 days ago -Collaborator -5.1 - -Is there any way to simplify the syntax for mqtopt? - -%c0_out, %q0_out = mqtopt.ctrl %c0_in { -%q0_new = mqtopt.x %q0_in : !mqtopt.qubit -mqtopt.yield %q0_new : !mqtopt.qubit -} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) -Do we really need the (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit)? - -3 replies 2 new -@burgholzer -burgholzer -3 days ago -Maintainer -Author -I suppose we don't need that. Typically, everything behind the colon is optional as long as we can get the type inference right. This is one of the aspects that will probably become clearer when this moves closer to implementation. - -@denialhaag -denialhaag -3 days ago -Collaborator -When I tried getting type inference to work in conjunction with AttrSizedResultSegments, I ran into quite some problems, but I'm sure there's a solution to that. 😌 - -@DRovara -DRovara -8 hours ago -Collaborator -Maybe at least we can make it so the (!mqtopt.qubit, !mqtopt.qubit) -> part is not necessary, similarly to how we did it for other operations. - -Actually, this type annotation seems weird anyways (probably the LLM at fault again): (!mqtopt.qubit, !mqtopt.qubit) -> suggests two inputs being used, but the way it is written, only one variable is directly used by mqtopt.ctrl (%c0_in) +- Enforce modifier ordering: `ctrl` → `negctrl` → `pow` → `inv`. +- Merge adjacent identical/compatible modifiers (e.g., consecutive `ctrl` merges control sets; nested `pow` multiplies exponents; nested `inv` cancels). +- Eliminate neutral identities: `pow(X, 1) → X`, `inv(inv(X)) → X`. +- Simplify algebraic forms when safe (e.g., `pow(RZ(pi/2), 2) → RZ(pi)`). +- Flatten nested control wrappers. ---- - -DRovara -3 days ago -Collaborator -8.3 -The syntax seems a bit messed up here. - -// Define a parameterized rotation gate -mqtref.gate_def @custom_rotation %q : !mqtref.qubit attributes {params = ["theta", "phi"]} { -mqtref.rz %q {angle = %phi} -mqtref.ry %q {angle = %theta} -mqtref.rz %q {angle = %phi} -} - -// Apply with specific parameters -mqtref.apply_gate @custom_rotation %q0 {theta = 1.57 : f64, phi = 0.78 : f64}` -Why can the rotation gates suddenly take a dynamic operand inside curly braces with angle = ...? That's different from how it was in previous sections. - -Also, something feels uncomfortable about defining the variables as strings and then just using them as e.g. %phi. I get the idea as to why we would want that, but I wonder if there is a cleaner approach. - -1 reply -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Yeah. Thinking about it, this is very much hallucinated and should be more grounded in reality. -I suppose it's more so about the general idea of being able to define a custom gate with parameters. +Additional Passes: ---- +- Gate inlining pass for user-defined unitary definitions until reaching standard basis gates. +- Named Simplification Pass (Section 2). +- Universal Expansion Pass (Section 2). +- Matrix Unitarity Validation Pass (Section 3). +- Future: Decomposition of matrix-based unitaries into chosen basis (configurable backend pass). -DRovara -3 days ago -Collaborator -Maybe one last general point: +(Note: Negative control elimination explicitly omitted per Section 1.3 decision.) -From a language perspective, I love all of these suggestions. -But I find it highly unlikely that we can just make these changes without major overhauls of our existing transformation passes. -I'm pretty sure that the passes we have so far will not only require rewriting but also rethinking, in some contexts. +--- -That doesn't mean we shouldn't do these changes, but it should be clear in advance. +## 5. Testing Strategy -1 reply -@burgholzer -burgholzer -3 days ago -Maintainer -Author -Oh yeah, definitely. This changes the very fundamentals that we operate on. -So changes will definitely be necessary to the transformations as well. Especially in places where operations are constructed. -I'd still hope that some transformations will remain moderately untouched because of the extensive use of the UnitaryInterface that abstracts over many of the details here. +Updated Direction (Stronger Commitment): -And I also believe that this is now the culmination of spending over 9 months working on MLIR. To me, this feels like it would build a close-to-feature-complete basis architecture and infrastructure that we can rely on in the future. +- Primary mechanism: googletest-based infrastructure constructing IR programmatically via builder API (see spec Section 9.2) and asserting structural / semantic equivalence. +- Textual `CHECK` tests reduced to minimal smoke tests for parser/round-trip coverage only. +- Emphasis on deterministic canonicalization pre-check (run normalization + canonicalization pipeline before equivalence assertions). ---- +Guidelines: -MatthiasReumann -9 hours ago -Collaborator -Hello there! 👀 +- Provide helper utilities for: module creation, gate application, modifier wrapping, matrix unitary creation, control aggregation. +- Structural Equivalence: Use MLIR IR equivalence if available; otherwise implement recursive op/attribute/operand/result comparison normalized by canonical pass. +- Avoid reliance on incidental attribute ordering or SSA naming. -Some thoughts I had while reading this. +Action Items (refined): -5.1 Control Operations +1. Ensure comprehensive builder coverage (all ops, modifiers, matrix unitaries, unitary definitions). +2. Implement `assertEquivalent(afterPasses, expected)` helper. +3. Provide fixture functions for common gate patterns & modifier compositions. +4. Add round-trip (print→parse) smoke tests for representative IR forms (including matrix unitaries & modifiers nesting). -Should this really be a %c0? I think this is a mistake, right? +--- -// Single control -mqtref.ctrl %c0 { // <--- Shouldn't this be a quantum value, e.g., %q1? -mqtref.x %q0 // Controlled-X (CNOT) -} +## 6. Default / Recommended Pass Pipelines -... // Similarly for the other examples. 5) Modifier Operations and 7) Unified Interface Design: +Baseline (tests & examples): -I keep asking myself, because I've been working on this recently, how do I apply these changes to the placement and routing passes (MQTOpt). Right now, a UnitaryInterface acts like a gate - it has inputs and outputs. Easy. An IR-Walker can simply ::advance() when encountering it. +- `-canonicalize` (includes NormalizationPass via registration hook) +- `-remove-dead-values` +- Named Simplification Pass (enabled by default unless backend requests universal form) -With the proposed changes, which I very much like, it's a bit more cumbersome - or at least less intuitive. A (pre-order) traversal would have to skip all elements inside a UnitaryInterface and only work with the interface (getInputs, getOutputs) of the "shell". However, these interface methods will probably have to traverse the IR internally nonetheless. +Backend Universalization Pipeline (example): -I've been thinking if it could make sense to define and implement a custom top-down driver for traversing quantum-classical programs canonically 1. This would have the advantage that details such as the one above are hidden away. Following the same argument as in the routing pass this driver will most likely not be pattern based. +- Baseline passes +- Universal Expansion Pass (if backend requires uniform U3) -Anyhow, either I'm missing something here (and this isn't really a problem) or the current dialects are advantageous over the changes proposed at least for this one particular aspect. +Decision: Normalization (modifier ordering & merging) ALWAYS runs before any other transformation stages; integrated as early canonicalization pattern population. -@DRovara was probably also hinting at this: +--- -Further, this is just one example where we might "struggle" that comes to mind immediately. There might be further not so obvious ones too. +## 7. Open Questions (Unresolved Summary) -Footnotes -The placement and routing passes already implement a naive-form of such a driver, separately. ↩ +Currently none. All previously tracked questions have been resolved for the MVP scope. New questions will re-open this section when they arise. -5 replies 1 new -@DRovara -DRovara -8 hours ago -Collaborator -Should this really be a %c0? I think this is a mistake, right? +--- -I think it's called "c" for "control", not "classical". But yeah, I definitely also had to do a double-take. +## 8. Decision Log (Chronological Core Decisions) -@DRovara -DRovara -8 hours ago -Collaborator -Regarding the transformations: I feel like, what we need is to just try out how much more effort it is to write transformations this way. Unfortunately, that can only really be done once we have implemented everything, I guess. +1. Keep distinct `ctrl` and `negctrl` modifiers (no combined mixed-control wrapper for now). +2. Establish canonical modifier ordering: `ctrl`, `negctrl`, `pow`, `inv`. +3. Represent sequences (`seq`) as control-neutral (0 controls) composites. +4. Retain named gate set; do not reduce to U3-only model. +5. Plan normalization & simplification passes mirroring legacy MQT Core canonicalization behavior. +6. Adopt builder-based structural testing strategy; textual tests become supplementary. +7. Recommend baseline pipeline: `-canonicalize` + `-remove-dead-values` (expand later). +8. Decide on bidirectional single-qubit canonicalization (Named Simplification & Universal Expansion passes). +9. Include matrix-based gate definitions in initial spec (flat dense array attribute; no hard size cap; unitarity validation pass). +10. Enforce normalization (modifier ordering + merging) always-before transformations via canonicalization integration. +11. Elevate googletest builder-based testing as primary; textual tests minimized. -But yeah, the way you would likely have to do is, if you walk along the def-use chain, is to check, at each step, whether prev->getBlock() == current->getBlock(), and, as long as this is not the case, apply current = current->getBlock()->getParentOp() (more or less, don't remember the names specifically). +--- -That repeated check might very well also lead to a reduced efficiency and uglier code, but I don't really see a way around it. +## 9. Future Work (Backlog Candidates) -@DRovara -DRovara -5 hours ago -Collaborator -Maybe to this end, as an extension to "7.1 UnitaryOpInterface", we should also add getPredecessors() or getPredecessor(int operandIndex) or getPredecessor(mlir::Value qubit) as well as getSuccessors(...) methods to the UnitaryInterface that handle this automatically (note that for the successors, the result always has to be a collection and cannot be a single item due to branches allowing multiple users of the same variable). +- Implement NormalizationPass (ordering, merging, identity elimination). +- Implement Named Simplification Pass & Universal Expansion Pass. +- Structural equivalence utilities & test harness helpers. +- Matrix-based unitary op implementation + unitarity validation pass. +- Performance specialization for 2×2 / 4×4 matrix attributes (fast paths). +- Gate definition inlining + matrix-to-basis decomposition passes. +- Trait inference for composites & matrix-based ops (Hermitian, Diagonal detection heuristics / numerical tolerance). +- Basis gate registry with canonical decomposition recipes. -@burgholzer -burgholzer -4 hours ago -Maintainer -Author -Regarding the first point: yeah, the c here was meant for "control" but is also merely an LLM artifact. I'll try to pay attention to this when rewriting the proposal. +(Removed: negative-control rewriting pass, apply_gate exploration.) -As for the transformations: I was hoping that this kind of hierarchical structuring would work very naturally. Especially since we are performing these transformations on the MQTOpt dialect with value semantics. This should still allow us to fairly easily traverse the def-use chains of the hierarchical operations quite similar to how one would traverse an scf.if instruction. Such instructions consume SSA values and produce new ones. -I don't particularly see the problem of relying on the interface methods; in the end, that's what they are for. -But maybe I am overlooking things here. -As Damian already pointed out, I have a feeling that one can only really tell once one tries implementing a rather large part of this proposal. +--- -@DRovara -DRovara -1 hour ago -Collaborator -This should still allow us to fairly easily traverse the def-use chains of the hierarchical operations quite similar to how one would traverse an scf.if instruction +## 10. References to Legacy Logic -If we define some sort of block arguments similar to your suggestion for mqtopt.seq, then it might actually work quite naturally (but I still didn't think much about it). If we don't then we have to do it the way we handle scf.if. +Legacy MQT Core IR already performs simplification of U3/U2/U1 to simpler gates (see former `StandardOperation::checkUgate` logic). New dialect passes replicate this within MLIR canonicalization infrastructure (Named Simplification Pass). -The thing is, from my understanding working with scf.if so far, contrary to your statement: yes, scf.if produces new values, but it does not explicitly consume any. For that, we have to look at the corresponding yield operation. That's what I meant in another comment about breaking the def-use chain. Whenever we have scf.if, we have two chains, like this: +--- -op1 --> op2 --> scf.yield -scf.if --> op3 --> op4 -And we have to "manually" write the traversal methods so that the chains are merged with the correct yield/if +## 11. (Optional) Raw Discussion Log -op1 --> op2 --> scf.if/scf.yield --> op3 --> op4 -The same thing would also happen with the control/inv/pow regions. +The original threaded conversation has been condensed into this digest. Retrieve the prior version from version control if verbatim context is ever required. --- -flowerthrower -5 hours ago -Collaborator -Disclaimer: I am not an expert on fault-tolerant quantum computing (so take this with a grain of salt). From what I've seen in inconspiquos and FTQC literature, codes like surface/color codes and LDPC stabilizer codes often use logical code blocks—sometimes as geometric patches, sometimes more abstractly. With the MLIR dialect design in mind, several bundled feature areas seem broadly relevant: - -Logical Block and Reconfiguration Semantics: -FTQC protocols depend on defining, manipulating, and reconfiguring logical code blocks (e.g., merge/split operations in lattice surgery, code switching, gauge fixing). E.g. inconspiquos has regions of qubits that can be arbitrarily merged and split. -How this could be done with the proposed model: -The dialect's compositional sequences and user-defined gates allow grouping and modular reuse of operations—these can mimic logical blocks and some forms of reconfiguration via higher-level composition. However, native support for explicit logical block annotation and dynamic reconfiguration (merge/split/code transitions) would require further dialect extensions or metadata. - -Bulk Operations and Scheduling: -Expressing cycle-based or parallel operations (like batches of parity checks and measurements) is essential for scalability in LDPC and surface code protocols. E.g., one can apply a gate operation to such a region in inconspiquos (under the hood all qubits in that region are addressed) -How this could be done with the proposed model: -Sequences, loop constructs, and composite gates in the dialect can encapsulate repeated or batch operations, but efficient bulk scheduling primitives for large codes may go beyond what's ergonomic now. - -Syndrome Processing, Decoder Integration, and Code-Aware Semantics: -Modern FTQC needs first-class support for syndrome acquisition, Pauli-frame tracking, and integration with classical decoders, along with the ability to annotate protocols with code parameters, gate set constraints, and locality/transversality information. -How this could be done with the proposed model: -The dialect's classical register support and compositional logic enable measurement pipelines and feedback routines. Code-aware semantics and decoder integration could be layered on top with attributes, annotations, or additional IR ops. Though many of these things go beyond the concepts of this IR discussion, at least one should keep them in mind. - -Overall, I really like the proposed changes and there is not much from my end that has not already been discussed in the above comments. In order to make it as future-proof as possible, however, it could be sensible to talk about the FT requirements with the error correction folks at the chair. Although I acknowledge that it might be difficult for them to fully follow the discussion here—as it is for me (us) to understand all the error correction details. - -1 reply 1 new -@burgholzer -burgholzer -4 hours ago -Maintainer -Author -Thanks for the input on this! Certainly an important point. -From a very birds-eye view, I see no major roadblocks for incorporating these features on top of the existing proposal. As identified, one could probably even "abuse" the existing concepts without much adaptation. However, I have a feeling that one might rather want to define separate dialects and/or operations for some of these concepts. -Having a (separate) discussion on this definitely makes sense; probably after another one or two iterations on the actual proposal. +End of digest. diff --git a/docs/mlir/plan-sync-agent-prompt.md b/docs/mlir/plan-sync-agent-prompt.md new file mode 100644 index 0000000000..92fddc140a --- /dev/null +++ b/docs/mlir/plan-sync-agent-prompt.md @@ -0,0 +1,195 @@ +# GPT-5 Agent Prompt: Integrate Discussions Summary (v3) into `quantum-dialect-revamp-plan.md` + +## Mission + +Synchronize the existing dialect plan (`quantum-dialect-revamp-plan.md`) with the authoritative design decisions encoded in `mlir-discussions-summary.json` (version 3), while _respecting and reusing_ all still-relevant material from the existing plan. Produce a fully revised, internally consistent markdown document that becomes the new single source of truth. + +You MUST: + +- Use BOTH the existing plan AND the JSON spec as inputs. +- Treat the JSON (version 3) as authoritative where conflicts arise. +- Preserve valuable explanatory prose from the current plan unless it contradicts decisions. +- Update, relocate, or condense sections for clarity—but do not silently drop useful context. +- Ensure EVERY MLIR code example is syntactically valid, semantically coherent, and consistent with the described semantics (no stale modifier order, no missing yields in value semantics, no invalid types). +- Fail the task (i.e., do not produce a final doc) if you cannot ensure MLIR validity; instead, explicitly list corrections needed. + +--- + +## Authoritative Decisions (From JSON v3 — Must Be Reflected) + +1. Canonical modifier nesting order (outer → inner): `ctrl`, `negctrl`, `pow`, `inv`. +2. Negative controls (`negctrl`) are first-class; DO NOT auto-normalize them into positive controls in canonical passes. +3. Named single-qubit gates are retained; implement _bidirectional_ canonicalization: + - Named Simplification: `U3/U2/U1 → simplest named gate` (tolerance-based). + - Universal Expansion: `named gate → canonical U3` (backend-optional). +4. Matrix-based gates ARE part of the MVP (no deferral); practical usage up to 3 qubits; optimize 2×2 and 4×4 cases. +5. Matrix unitary attribute: flat, row-major dense array of complex numbers. +6. Normalization (modifier ordering, merging, identity elimination) ALWAYS runs before other transformations (integrated into canonicalization). +7. Testing strategy: googletest builder-based structural/semantic equivalence is PRIMARY; textual FileCheck / lit tests limited to parser/printer smoke cases. +8. Sequences are control-neutral (always report 0 controls). +9. Interface currently does NOT expose explicit enumeration for pow/inv layers (deferred need). +10. No negative-control rewriting pass in MVP. +11. Open questions list is EMPTY for MVP—new ambiguities must be clearly introduced if discovered. + +--- + +## Required Output + +Produce TWO artifacts in a single response: + +1. The **fully revised** `quantum-dialect-revamp-plan.md` (complete file content; no partial diff). +2. A trailing JSON change summary object: + ```json + { + "decisionSyncVersion": 3, + "addedSections": [...], + "modifiedSections": [...], + "removedSections": [...], + "notes": [...], + "mlirExampleAudit": { + "checkedExamples": N, + "invalidExamplesFound": 0 + } + } + ``` + +If any MLIR example cannot be validated conceptually (e.g., invalid region signature, missing yield, wrong result count), set `"invalidExamplesFound" > 0` and DO NOT produce the revised document—produce instead a diagnostic section listing every problematic snippet and how to fix it. + +--- + +## Transformation Tasks (Execute in Order) + +1. **Parse Inputs** + - Load and internalize the JSON spec (v3). + - Read the existing markdown plan fully. + - Build an internal map of sections: Overview, Architecture, Types, Base Gates, Modifiers, Sequences, Interface, User-Defined Gates, Builders, Canonicalization/Passes, Testing, Conversions, Conclusion. + +2. **Conflict Audit** + Identify and correct in the existing plan: + - Outdated canonical order (`ctrl → pow → inv` must become `ctrl → negctrl → pow → inv`). + - Any implication that matrix-based gates are "future" or "optional". + - Any claim or example suggesting negative controls are normalized away. + - Missing mention of bidirectional canonicalization. + - Over-reliance on FileCheck in testing rationale (must be re-scoped). + - Redundant or conflicting descriptions of modifiers. + +3. **Structural Enhancements** + Add / modify sections: + - "Design Decisions (Synchronized with Discussions Digest v3)" near the top (concise bullet list referencing decisions above). + - "Pass Inventory" with a table: + | Pass | Purpose | Phase | Idempotent | Mandatory | Notes | + Include: NormalizationPass, Named Simplification Pass, Universal Expansion Pass, Matrix Unitarity Validation Pass. + Add a "Deferred" subsection listing: Matrix decomposition, Basis gate registry, Extended trait inference. + - Integrate matrix-unitary support into user-defined gates AND core architecture (not just an isolated advanced feature). + +4. **Modifiers Section Rewrite** + - Ensure examples reflect canonical nesting order. + - Provide at least one nested example: + ```mlir + mqtref.ctrl %c0, %c1 { + mqtref.negctrl %c2 { + mqtref.pow {exponent = 2.0 : f64} { + mqtref.inv { + mqtref.x %t + } + } + } + } + ``` + - Provide corresponding value-semantics form if appropriate, ensuring `yield` operands/results match region signatures. + +5. **Matrix Gates Integration** + - Document attribute form with a valid example: + ```mlir + // 2x2 example (Pauli-Y) + %u = mqtref.matrix %q0 { + matrix = dense<[[0.0+0.0i, 0.0-1.0i], + [0.0+1.0i, 0.0+0.0i]]> : tensor<2x2xcomplex> + } + ``` + Adjust syntax to match your dialect conventions (if a different op naming scheme like `mqtref.unitary` is used, align accordingly). + - Show 4×4 example (two-qubit) with shape validation. + - Clarify unitarity validation pass (tolerance-based; optional strict mode). + +6. **Interface Section Adjustments** + - Remove or annotate any obsolete pow/inv enumeration ambitions. + - Clarify control counting (wrappers aggregate; seq = 0; matrix + composite definitions = 0 controls themselves unless wrapped). + +7. **Canonicalization Section Update** + - Split into: + - "Normalization (Early Canonical Form)" + - "Named Simplification" + - "Universal Expansion" + - "Matrix Validation" + - (Deferred) "Matrix Decomposition" + - Remove obsolete examples (e.g., automatic negctrl erasure). + - Ensure algebraic simplification examples only include decisions currently endorsed (e.g., `inv(inv(X))`, `pow(RZ(pi/2), 2)`). + +8. **Testing Strategy Rewrite** + Must include: + - Structural equivalence assertion workflow: build → run canonical pipeline → compare IR structurally (ignore SSA names). + - Idempotence test pattern (run Normalization twice). + - Round-trip (parser → printer → parser) smoke tests for: base gates, modifiers, matrix gate, nested modifiers. + +9. **MLIR Example Validation** + For every example: + - Ensure region form is valid: block arguments present if operands thread through (value semantics). + - Ensure all yields match enclosing op result arity/types. + - Ensure attributes syntactically valid (e.g., `exponent = 0.5 : f64`). + - Ensure tensor element types use `complex` where required. + - Ensure no stray `%q0_new` without use or yield in SSA form examples. + +10. **Conclusion Update** + - Reflect finalized scope: MVP includes matrix ops, dual canonicalization, early normalization, builder-first strategy. + - Remove speculative phrasing about deferring matrix support. + +11. **Change Summary JSON** + - Populate arrays precisely (section titles or approximate headings). + - Notes should list any semantic clarifications (e.g., replacing previous canonical order, introducing Pass Inventory). + +--- + +## Style & Consistency Rules + +- Prefer concise declarative sentences in decision summary sections. +- Avoid re-defining the same rationale in multiple places—link or reference earlier sections. +- Use consistent dialect namespace prefixes (`mqtref.` / `mqtopt.`). If any example mixes them incorrectly, fix it. +- Use fenced code blocks with `mlir` language tag for MLIR IR. +- Do not include TODOs for MVP features—only for explicitly deferred future items under a "Deferred" heading. + +--- + +## Explicit Prohibitions + +DO NOT: + +- Invent new passes or features not sanctioned by JSON v3. +- Remove valuable context silently—if removed for redundancy, capture that in the change summary's `removedSections` or `notes`. +- Produce partial file content. +- Output explanations after the final JSON summary. + +--- + +## Validation Checklist (Self-Check Before Emitting Output) + +Mark each internally (do not include the checklist in final output): + +- All examples MLIR-valid. +- No contradictions about negative controls. +- Matrix gates clearly part of MVP. +- Pass inventory present & accurate. +- Testing section prioritizes builder-based structural tests. +- Canonical modifier order consistent across narrative + examples. +- No obsolete canonicalization rules remain. +- JSON summary structurally valid. + +--- + +## Final Output Format + +1. Full revised markdown (beginning at first line with title—retain or improve existing heading). +2. JSON change summary object (standalone; no extra prose after it). + +If ANY blocking inconsistency remains → output ONLY a diagnostic section titled `BLOCKED` with a bullet list of unresolved issues (no partial plan). + +BEGIN. diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index 08ac5e9d2b..5b89bc5c6d 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -7,23 +7,24 @@ We have two quantum dialects: - `mqtref`: Reference semantics (side-effect based) - `mqtopt`: Value semantics (SSA based) -Both support basic gates and quantum operations but suffer from: +Both currently provide basic gates and quantum operations but suffer from: -- **Verbose builders and cumbersome usage**: Creating controlled gates requires lengthy builder calls -- **Gate modifiers embedded inconsistently**: Some gates have control operands, others use attributes, while others do not even exist yet, creating analysis complexity -- **Inconsistent and incomplete interfaces across dialects**: Different ways to query the same logical information -- **Limited composability**: No clean way to compose modifiers (e.g., controlled inverse gates) +- Verbose / inconsistent builder ergonomics (especially for controlled forms) +- Modifier semantics encoded inconsistently (implicit attributes, ad‑hoc controls) +- Fragmented interfaces for querying structural properties +- Limited composability for combined modifiers (e.g., controlled powers of inverses) -### 1.2 Proposed Changes +### 1.2 Proposed Direction -This plan proposes a fundamental redesign that addresses these issues through: +This revamp establishes a uniform, compositional IR that: -1. **Compositional Design**: Modifiers become explicit wrapper operations that can be nested and combined -2. **Unified Interface**: Single `UnitaryOpInterface` for all quantum operations enabling uniform analysis -3. **Improved Ergonomics**: Builder APIs and parser sugar for common patterns without sacrificing expressiveness -4. **Shared Infrastructure**: Common traits, interfaces, and utilities to reduce code duplication +1. Treats all gate modifiers (controls, negative controls, powers, inverses) as explicit wrapper ops with regions. +2. Provides a single structural interface (`UnitaryOpInterface`) across both dialects. +3. Normalizes modifier ordering, merging, and identity elimination early and deterministically. +4. Supports both a rich named gate set AND matrix-based unitaries. +5. Emphasizes builder-based structural/semantic testing over brittle text-based checks. -**Rationale**: By making modifiers compositional, we get exponential expressiveness with linear implementation cost. It is also fairly close to how established languages like OpenQASM or Qiskit work, which makes it easier to transition to the new dialect. +--- ## 2. Architecture Overview @@ -33,81 +34,67 @@ This plan proposes a fundamental redesign that addresses these issues through: Common/ ├── Interfaces (UnitaryOpInterface, etc.) ├── Traits (Hermitian, Diagonal, SingleTarget, etc.) -└── Support (UnitaryExpr library) +└── Support (UnitaryExpr / UnitaryMatrix utilities) mqtref/ -├── Types (Qubit) -├── Base Gates (X, RX, CNOT, etc.) -├── Modifiers (ctrl, inv, pow) +├── Types (qubit) +├── Base Gates (x, rx, cx, u1/u2/u3, etc.) +├── Modifiers (ctrl, negctrl, pow, inv) ├── Sequences (seq) -└── Resources (alloc, measure, etc.) +└── Resources (alloc, measure, ...) mqtopt/ -└── (Same structure with value semantics) +└── Parallel set with value semantics (results thread through) ``` -### 2.2 Key Design Principles +### 2.2 Principles -1. **Base gates are minimal**: No control operands or modifier attributes - each gate does exactly one thing -2. **Modifiers wrap operations**: Controls, inverses, and powers become explicit wrapper operations with regions -3. **Uniform interface**: Single way to query any quantum operation regardless of dialect or complexity -4. **Dialect-specific semantics**: Reference vs value threading handled appropriately by each dialect +1. Base gate ops contain only their target operands and parameters (no embedded controls / modifiers). +2. Modifiers are explicit region wrapper ops enabling arbitrary nesting in canonical order. +3. Interface / traits provide uniform structural queries (control counts, target counts, matrix form when available). +4. Reference vs value semantics differences are localized to operation signatures & result threading. -**Rationale**: This separation of concerns makes analysis passes simpler - they can focus on the mathematical structure without worrying about the myriad ways modifiers might be encoded. It also makes the IR more predictable and easier to canonicalize. +**Rationale:** Predictable structural form enables simpler canonicalization, hashing, CSE, and semantic equivalence checks. + +--- ## 3. Types and Memory Model ### 3.1 Quantum Types -Both dialects use a single `Qubit` type with different semantics: - -- `!mqtref.qubit` (reference semantics): Represents a stateful quantum register location -- `!mqtopt.qubit` (value semantics): Represents an SSA value carrying quantum information - -**Rationale**: While conceptually similar, the semantic difference is crucial - mqtref qubits can be mutated in place, while mqtopt qubits follow single-static-assignment and must be "threaded" through operations. +- `!mqtref.qubit`: Stateful reference (in-place mutation semantics). +- `!mqtopt.qubit`: SSA value (must be threaded; operations consume & produce qubits). ### 3.2 Register Handling -Use standard MLIR `memref`s for quantum and classical registers: +Standard MLIR types (e.g., `memref`) manage collections: ```mlir -// Quantum register allocation and access %qreg = memref.alloc() : memref<2x!mqtref.qubit> %q0 = memref.load %qreg[%c0] : memref<2x!mqtref.qubit> - -// Classical register for measurements %creg = memref.alloc() : memref<2xi1> %bit = mqtref.measure %q0 : i1 memref.store %bit, %creg[%c0] : memref<2xi1> ``` -**Rationale**: Using standard MLIR constructs (memref) instead of custom quantum register types allows us to leverage existing MLIR analyses and transformations. This also provides a clear separation between the quantum computational model and classical memory management. +--- ## 4. Base Gate Operations -### 4.1 Design Philosophy - -Base gates are the atomic quantum operations and contain only: - -- **Target operands**: Fixed arity determined by gate type (via traits) -- **Parameters**: Rotation angles, phases, etc. (both static and dynamic) -- **No control operands**: Controls are handled by wrapper operations -- **No modifier attributes**: Inverses and powers are handled by wrapper operations - -**Rationale**: This minimal design makes base gates predictable and easy to analyze. Each gate operation corresponds exactly to a mathematical unitary operation without additional complexity from modifiers. +### 4.1 Philosophy -### 4.2 Examples +Minimal, parameterized primitives; all composition via wrappers. -**mqtref (Reference Semantics):** +### 4.2 Examples (Reference Semantics) ```mlir -mqtref.x %q0 // Pauli-X gate -mqtref.rx %q0 {angle = 1.57 : f64} // X-rotation with static parameter -mqtref.cx %q0, %q1 // Controlled-NOT (2-qubit gate) -mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit gate +mqtref.x %q0 +mqtref.rx %q0 {angle = 1.57 : f64} +mqtref.cx %q0, %q1 +mqtref.u3 %q0 {theta = 0.0 : f64, phi = 0.0 : f64, lambda = 3.14159 : f64} ``` -**mqtopt (Value Semantics):** +### 4.3 Examples (Value Semantics) ```mlir %q0_out = mqtopt.x %q0_in : !mqtopt.qubit @@ -115,140 +102,155 @@ mqtref.u3 %q0 {theta = 0.0, phi = 0.0, lambda = 3.14159} // Generic single-qubit %q0_out, %q1_out = mqtopt.cx %q0_in, %q1_in : !mqtopt.qubit, !mqtopt.qubit ``` -**Key Differences**: In mqtref, operations have side effects on qubit references. In mqtopt, operations consume input qubits and produce new output qubits, following SSA principles. - -### 4.3 Parameterization Strategy - -Gates support flexible parameterization to handle both compile-time and runtime parameters: +### 4.4 Parameterization Strategy ```mlir -// Static parameters (preferred for optimization) -mqtref.rx %q0 {angle = 1.57 : f64} - -// Dynamic parameters (runtime values) -%angle = arith.constant 1.57 : f64 -mqtref.rx %q0, %angle : f64 - -// Mixed parameters with mask indicating which are static -mqtref.u3 %q0, %runtime_theta {phi = 0.0, lambda = 3.14159, static_mask = [false, true, true]} +mqtref.rx %q0 {angle = 1.57 : f64} // static +%theta = arith.constant 1.57 : f64 +mqtref.rx %q0, %theta : f64 // dynamic +%dyn_theta = arith.constant 0.5 : f64 +mqtref.u3 %q0, %dyn_theta {phi = 0.0 : f64, lambda = 3.14159 : f64, + static_mask = [false, true, true]} ``` -**Rationale**: Static parameters enable constant folding and symbolic computation at compile time, while dynamic parameters provide runtime flexibility. The mixed approach allows gradual specialization as more information becomes available during compilation. +--- ## 5. Modifier Operations -Modifiers are wrapper operations that contain single-block regions holding the operations they modify. This design provides clean composition and nesting capabilities. - -### 5.1 Control Operations (`ctrl` / `negctrl`) +Modifiers are single-region wrappers; canonical nesting: `ctrl` → `negctrl` → `pow` → `inv`. -Control operations add quantum control conditions to arbitrary quantum operations: +### 5.1 Controls (`ctrl`) & Negative Controls (`negctrl`) -**mqtref (Reference Semantics):** +Reference semantics: ```mlir -// Single control mqtref.ctrl %c0 { - mqtref.x %q0 // Controlled-X (CNOT) + mqtref.x %t0 // CNOT } -// Multiple controls mqtref.ctrl %c0, %c1 { - mqtref.x %q0 // Toffoli gate (CCX) + mqtref.x %t0 // Toffoli (CCX) } -// Negative controls mqtref.negctrl %c0 { - mqtref.x %q0 // X gate triggered when c0 is |0⟩ + mqtref.x %t0 // Fires when %c0 is |0> } ``` -**mqtopt (Value Semantics):** +Value semantics (control + target operands; region blocks thread values explicitly): ```mlir -%c0_out, %q0_out = mqtopt.ctrl %c0_in { - %q0_new = mqtopt.x %q0_in : !mqtopt.qubit - mqtopt.yield %q0_new : !mqtopt.qubit -} : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) +%c_out, %t_out = mqtopt.ctrl %c_in, %t_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { +^entry(%c: !mqtopt.qubit, %t: !mqtopt.qubit): + %t_new = mqtopt.x %t : !mqtopt.qubit + mqtopt.yield %c, %t_new : !mqtopt.qubit, !mqtopt.qubit +} ``` -**Rationale**: By treating controls as explicit wrapper operations, we can uniformly add controls to any quantum operation, including sequences and other modifiers. The region-based design makes the controlled operation explicit in the IR and enables easy analysis of the controlled subcomputation. - -**Design Note**: In value semantics, control qubits must be threaded through the operation even though they're not modified, maintaining SSA form and enabling dataflow analysis. +Negative controls are NOT rewritten into positive controls (no implicit X-sandwich canonicalization). -### 5.2 Inverse Operations (`inv`) - -Inverse operations compute the adjoint (Hermitian conjugate) of enclosed operations: +### 5.2 Power (`pow`) ```mlir -// mqtref: Inverse of S gate (equivalent to S-dagger) -mqtref.inv { - mqtref.s %q0 +mqtref.pow {exponent = 0.5 : f64} { + mqtref.x %q0 // sqrt(X) } -// mqtopt: Value-threaded inverse -%q0_out = mqtopt.inv { - %q0_temp = mqtopt.s %q0_in : !mqtopt.qubit - mqtopt.yield %q0_temp : !mqtopt.qubit -} : (!mqtopt.qubit) -> !mqtopt.qubit +%exp = arith.constant 0.25 : f64 +mqtref.pow %exp { + mqtref.ry %q0 {angle = 3.14159 : f64} +} ``` -**Rationale**: Making inverse explicit allows analysis passes to reason about adjoint relationships and enables optimizations like `inv(inv(X)) → X`. It also provides a uniform way to express inverse operations without requiring dedicated inverse variants of every gate. +Value semantics single-qubit power: -### 5.3 Power Operations (`pow`) +```mlir +%q_out = mqtopt.pow {exponent = 0.5 : f64} %q_in : (!mqtopt.qubit) -> !mqtopt.qubit { +^entry(%q: !mqtopt.qubit): + %q_x = mqtopt.x %q : !mqtopt.qubit + mqtopt.yield %q_x : !mqtopt.qubit +} +``` -Power operations compute fractional or integer powers of quantum operations: +### 5.3 Inverse (`inv`) ```mlir -// Square root of X gate -mqtref.pow {exponent = 0.5 : f64} { - mqtref.x %q0 +mqtref.inv { + mqtref.s %q0 // S† } -// Dynamic exponent -%exp = arith.constant 0.25 : f64 -mqtref.pow %exp { - mqtref.ry %q0 {angle = 3.14159} +%q0_out = mqtopt.inv %q0_in : (!mqtopt.qubit) -> !mqtopt.qubit { +^entry(%q0: !mqtopt.qubit): + %q0_s = mqtopt.s %q0 : !mqtopt.qubit + mqtopt.yield %q0_s : !mqtopt.qubit +} +``` + +### 5.4 Nested Modifier Example (Canonical Order) + +Reference semantics nested chain: + +```mlir +mqtref.ctrl %c0, %c1 { + mqtref.negctrl %c2 { + mqtref.pow {exponent = 2.0 : f64} { + mqtref.inv { + mqtref.x %t0 + } + } + } } ``` -**Rationale**: Power operations are essential for quantum algorithms (e.g., quantum phase estimation, Grover's algorithm) and appear frequently in decompositions. Making them first-class enables direct representation without approximation. +Value semantics counterpart (illustrative; block arguments explicit at each nesting level): -### 5.4 Modifier Composition and Canonicalization +```mlir +%c_out, %t_out = mqtopt.ctrl %c_in, %t_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { +^entry(%c: !mqtopt.qubit, %t: !mqtopt.qubit): + %c_neg_out, %t_neg = mqtopt.negctrl %c, %t : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { + ^entry(%cn: !mqtopt.qubit, %tn: !mqtopt.qubit): + %t_pow = mqtopt.pow {exponent = 2.0 : f64} %tn : (!mqtopt.qubit) -> !mqtopt.qubit { + ^entry(%tp: !mqtopt.qubit): + %t_inv = mqtopt.inv %tp : (!mqtopt.qubit) -> !mqtopt.qubit { + ^entry(%ti: !mqtopt.qubit): + %t_x = mqtopt.x %ti : !mqtopt.qubit + mqtopt.yield %t_x : !mqtopt.qubit + } + mqtopt.yield %t_inv : !mqtopt.qubit + } + mqtopt.yield %cn, %t_pow : !mqtopt.qubit, !mqtopt.qubit + } + mqtopt.yield %c_neg_out, %t_neg : !mqtopt.qubit, !mqtopt.qubit +} +``` -**Canonical Ordering**: To ensure consistent IR, we enforce a canonical nesting order: -`ctrl` (outermost) → `pow` → `inv` (innermost) +(Assumption: Each wrapper result list mirrors its operand list order for pass-through + transformed targets.) -**Canonicalization Rules**: +### 5.5 Modifier Semantics Summary -- `inv(inv(X)) → X` (double inverse elimination) -- `pow(X, 1) → X` (identity power elimination) -- `pow(X, 0) → identity` (zero power simplification) -- `inv(pow(X, k)) → pow(inv(X), k)` (inverse-power commutation) -- `ctrl(inv(X)) → inv(ctrl(X))` when mathematically equivalent +- Control count = sum of outer wrappers + inner (flattened by normalization). +- `pow` & `inv` forward control counts unchanged. +- No negctrl → ctrl auto-normalization. -**Rationale**: Canonical ordering prevents equivalent operations from having different representations, which would complicate analysis and optimization. The canonicalization rules capture mathematical identities that enable simplification. +--- -## 6. Sequence Operations +## 6. Sequence Operations (`seq`) -Sequences group multiple quantum operations into logical units that can be analyzed and transformed as a whole. +Sequences group subcircuits; they are control-neutral (always 0 controls) by decision. -### 6.1 Basic Sequences (Reference Semantics) +Reference semantics: ```mlir mqtref.seq { - mqtref.h %q0 // Hadamard gate - mqtref.ctrl %q0 { // Controlled operation + mqtref.h %q0 + mqtref.ctrl %q0 { mqtref.x %q1 } - mqtref.h %q0 // Another Hadamard + mqtref.h %q0 } ``` -**Rationale**: Sequences provide a natural grouping mechanism for subcircuits that should be treated as units during optimization. They also enable hierarchical analysis - passes can choose to operate at the sequence level or dive into individual operations. - -### 6.2 Value Semantics Threading - -In `mqtopt`, sequences must explicitly thread all qubit values through the computation: +Value semantics: ```mlir %q0_out, %q1_out = mqtopt.seq %q0_in, %q1_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { @@ -260,337 +262,327 @@ In `mqtopt`, sequences must explicitly thread all qubit values through the compu } ``` -**Explanation of ^entry syntax**: This is standard MLIR region syntax where `^entry` names the basic block and `(%q0: !mqtopt.qubit, %q1: !mqtopt.qubit)` declares the block arguments with their types. The sequence operation's operands become the block arguments, enabling proper SSA value threading within the region. - -**Rationale**: Explicit value threading in sequences maintains SSA form and enables dataflow analysis. The block argument syntax is standard MLIR and clearly shows how values flow into and out of the sequence region. +--- ## 7. Unified Interface Design -### 7.1 UnitaryOpInterface - -All quantum operations implement a unified interface that abstracts away dialect differences: +### 7.1 `UnitaryOpInterface` (Conceptual Extract) ```cpp class UnitaryOpInterface { public: - // Gate identification StringRef getIdentifier(); - - // Qubit counts size_t getNumTargets(); size_t getNumQubits(); - - // Control queries - bool isControlled(); size_t getNumPosControls(); size_t getNumNegControls(); - size_t getNumControls(); - - // Operand access + size_t getNumControls(); // pos + neg (seq = 0) OperandRange getTargetOperands(); OperandRange getPosControlOperands(); OperandRange getNegControlOperands(); - OperandRange getControlOperands(); + OperandRange getControlOperands(); // concatenated OperandRange getQubitOperands(); - OperandRange getOperands(); - - // Result access (value semantics only) bool hasResults(); ResultRange getTargetResults(); - ResultRange getPosControlResults(); - ResultRange getNegControlResults(); - ResultRange getControlResults(); - ResultRange getQubitResults(); - ResultRange getResults(); - - // Mathematical representation - UnitaryMatrix getUnitaryMatrix(); + // Control results present in value semantics wrappers + UnitaryMatrix getUnitaryMatrix(); // Static or computed bool hasStaticUnitary(); - - // Parameter access size_t getNumParams(); - bool isParameterized(); ArrayAttr getStaticParameters(); OperandRange getDynamicParameters(); }; ``` -**Rationale**: This interface allows analysis passes to work uniformly across dialects without knowing whether they're dealing with reference or value semantics. The `hasResults()` method provides a clean way to branch when needed. +### 7.2 Notes + +- No explicit enumeration for pow/inv stack layers (decision: unnecessary for MVP). +- Sequences report 0 controls even if containing controlled ops. +- Matrix or composite definitions report 0 intrinsic controls; wrappers add controls. -### 7.2 Implementation Strategy +--- -- **Base Gates**: Implement interface directly with simple delegation to operands/results -- **Modifiers**: Delegate to wrapped operations with appropriate adjustments (e.g., ctrl adds to control count) -- **Sequences**: Provide aggregate information (total qubits touched, combined unitary matrix) +## 8. Matrix & User-Defined Gates -**Rationale**: This delegation pattern means that complex nested structures (e.g., controlled inverse sequences) automatically provide correct interface responses without manual bookkeeping. +Matrix-based unitary expression is part of MVP (NOT deferred). -## 8. User-Defined Gates +### 8.1 Matrix Unitary Operation -User-defined gates enable custom quantum operations with reusable definitions. +Attribute: flat row‑major dense tensor `tensor<(2^(n)*2^(n)) x complex>`. -### 8.1 Matrix-Based Definitions +2×2 example (Pauli-Y): ```mlir -// Define a custom single-qubit gate via its unitary matrix -mqtref.gate_def @pauli_y : tensor<2x2xcomplex> = - dense<[[0.0+0.0i, 0.0-1.0i], [0.0+1.0i, 0.0+0.0i]]> : tensor<2x2xcomplex> +// Row-major: [0 -i ; i 0] +mqtref.unitary %q0 { matrix = dense<[0.0+0.0i, 0.0-1.0i, 0.0+1.0i, 0.0+0.0i]> : tensor<4xcomplex> } +``` -// Use the defined gate -mqtref.apply_gate @pauli_y %q0 +Value semantics: + +```mlir +%q0_out = mqtopt.unitary %q0_in { matrix = dense<[0.0+0.0i, 0.0-1.0i, 0.0+1.0i, 0.0+0.0i]> : tensor<4xcomplex> } : !mqtopt.qubit ``` -**Rationale**: Matrix definitions provide an exact specification for custom gates, enabling precise simulation and analysis. They're particularly useful for gates that don't have natural decompositions into standard gates. +4×4 example (identity on two qubits): -### 8.2 Composite Definitions +```mlir +mqtref.unitary %q0, %q1 { matrix = dense<[ + 1.0+0.0i, 0.0+0.0i, 0.0+0.0i, 0.0+0.0i, + 0.0+0.0i, 1.0+0.0i, 0.0+0.0i, 0.0+0.0i, + 0.0+0.0i, 0.0+0.0i, 1.0+0.0i, 0.0+0.0i, + 0.0+0.0i, 0.0+0.0i, 0.0+0.0i, 1.0+0.0i]> : tensor<16xcomplex> } +``` + +### 8.2 Gate Definitions (Symbolic / Composite) ```mlir -// Define a gate as a sequence of existing operations -mqtref.gate_def @bell_prep %q0 : !mqtref.qubit, %q1 : !mqtref.qubit { - mqtref.h %q0 - mqtref.cx %q0, %q1 +// Composite definition +mqtref.gate_def @bell_prep %a : !mqtref.qubit, %b : !mqtref.qubit { + mqtref.h %a + mqtref.cx %a, %b } -// Apply the composite gate +// Application mqtref.apply_gate @bell_prep %q0, %q1 ``` -**Rationale**: Composite definitions enable hierarchical design and code reuse. They can be inlined during lowering or kept as high-level constructs for analysis, depending on optimization needs. - -### 8.3 Parameterized Gates +### 8.3 Parameterized Definitions ```mlir -// Define a parameterized rotation gate -mqtref.gate_def @custom_rotation %q : !mqtref.qubit attributes {params = ["theta", "phi"]} { - mqtref.rz %q {angle = %phi} - mqtref.ry %q {angle = %theta} - mqtref.rz %q {angle = %phi} +// Parameters modeled as additional operands (value semantics for numeric params) +mqtref.gate_def @custom_rotation(%q: !mqtref.qubit, %theta: f64, %phi: f64) { + mqtref.rz %q, %phi : f64 + mqtref.ry %q, %theta : f64 + mqtref.rz %q, %phi : f64 } -// Apply with specific parameters -mqtref.apply_gate @custom_rotation %q0 {theta = 1.57 : f64, phi = 0.78 : f64} +%theta = arith.constant 1.57 : f64 +%phi = arith.constant 0.78 : f64 +mqtref.apply_gate @custom_rotation %q0, %theta, %phi : (!mqtref.qubit, f64, f64) ``` -**Rationale**: Parameterized gates enable template-like definitions that can be instantiated with different parameters, supporting gate libraries and algorithmic patterns. +Definitions themselves are control-neutral; wrapping ops add controls. -## 9. Parser Sugar and Builder APIs +### 8.4 Unitarity Validation -### 9.1 Parser Sugar for Common Patterns +A dedicated validation pass (Matrix Unitary Validation) verifies numerical unitarity within tolerance; strict mode (optional) can enforce tighter bounds. -Instead of complex chaining syntax, we provide natural abbreviations for frequent patterns: +--- -**Controlled Gate Shortcuts:** +## 9. Parser Sugar & Builder APIs + +### 9.1 Parser Sugar Examples ```mlir -// Standard controlled gates (parser expands these automatically) -mqtref.cx %c, %t // → mqtref.ctrl %c { mqtref.x %t } -mqtref.cz %c, %t // → mqtref.ctrl %c { mqtref.z %t } -mqtref.ccx %c0, %c1, %t // → mqtref.ctrl %c0, %c1 { mqtref.x %t } - -// Multi-controlled variants -mqtref.mcx (%c0, %c1, %c2), %t // → mqtref.ctrl %c0, %c1, %c2 { mqtref.x %t } -mqtref.mcp (%c0, %c1), %t {phase = 1.57} // Controlled phase with multiple controls +// Sugar → canonical expansion +mqtref.cx %c, %t // expands to: mqtref.ctrl %c { mqtref.x %t } +mqtref.cz %c, %t // expands to: mqtref.ctrl %c { mqtref.z %t } +mqtref.ccx %c0, %c1, %t // expands to: mqtref.ctrl %c0, %c1 { mqtref.x %t } ``` -**Rationale**: This approach provides ergonomic shortcuts for common cases without introducing complex chaining operators. The shortcuts expand to the full form during parsing, so all downstream processing sees the canonical representation. - -### 9.2 C++ Builder API +### 9.2 C++ Builder (Sketch) ```cpp -// Fluent builder interface class QuantumCircuitBuilder { public: - // Basic gates with natural names - QuantumCircuitBuilder& x(Value qubit); - QuantumCircuitBuilder& h(Value qubit); - QuantumCircuitBuilder& cx(Value control, Value target); - - // Modifier combinators - QuantumCircuitBuilder& ctrl(ValueRange controls, std::function body); - QuantumCircuitBuilder& inv(std::function body); - QuantumCircuitBuilder& pow(double exponent, std::function body); - - // Convenient combinations - QuantumCircuitBuilder& ccx(Value c1, Value c2, Value target); - QuantumCircuitBuilder& toffoli(Value c1, Value c2, Value target) { return ccx(c1, c2, target); } + QuantumCircuitBuilder &x(Value q); + QuantumCircuitBuilder &h(Value q); + QuantumCircuitBuilder &cx(Value c, Value t); + QuantumCircuitBuilder &ctrl(ValueRange controls, function_ref body); + QuantumCircuitBuilder &negctrl(ValueRange controls, function_ref body); + QuantumCircuitBuilder &pow(double exponent, function_ref body); + QuantumCircuitBuilder &inv(function_ref body); }; -// Example usage -QuantumCircuitBuilder builder(mlirBuilder, location); builder.h(q0) - .ctrl({c0}, [&]() { builder.x(q1); }) - .ccx(c0, c1, q2); + .ctrl({c0}, [&](){ builder.x(q1); }) + .ctrl({c0, c1}, [&](){ builder.x(q2); }); ``` -**Rationale**: The builder API provides a natural C++ interface that maps cleanly to the IR structure. Lambda functions for modifier bodies give clear scoping and enable complex nested structures. +--- -## 10. Analysis and Optimization Infrastructure +## 10. Canonicalization & Transformation Stages -### 10.1 UnitaryMatrix Support Library +### 10.1 Normalization (Early Canonical Form) -```cpp -class UnitaryMatrix { -private: - // Efficient representations for common cases - std::variant< - Matrix2x2, // Single-qubit gates - Matrix4x4, // Two-qubit gates - SymbolicExpr, // Larger or parameterized gates - LazyProduct // Composition chains - > representation; +Responsibilities (always first): -public: - // Composition operations - UnitaryMatrix compose(const UnitaryMatrix& other) const; - UnitaryMatrix adjoint() const; - UnitaryMatrix power(double exponent) const; - UnitaryMatrix control(unsigned num_controls) const; +- Enforce modifier order `ctrl` → `negctrl` → `pow` → `inv`. +- Flatten nested same-kind control wrappers (aggregate control lists) while preserving neg/pos distinction. +- Merge adjacent compatible `pow` / eliminate identities (`pow(exp=1)`, `inv(inv(X))`). +- Algebraic simplifications (e.g., `pow(RZ(pi/2), 2) → RZ(pi)`). +- Remove dead identity operations (e.g., `pow(exp=0)` → (implicit identity) if allowed by semantics). +- Prepare IR for subsequent named gate passes. - // Materialization - DenseElementsAttr toDenseElements(MLIRContext* ctx) const; - bool isIdentity() const; - bool isUnitary() const; // Verification -}; -``` +### 10.2 Named Simplification + +- Convert `U3/U2/U1` parameter sets to simplest named gate when within numeric tolerance. +- Improves readability & hashing stability. + +### 10.3 Universal Expansion + +- Expand named single-qubit gates to canonical `U3` (backend / pipeline selectable). +- Typically applied only in universal backends or downstream lowering flows. -**Rationale**: This library provides efficient computation for the unitary matrices that drive quantum optimization. The multi-representation approach keeps small matrices fast while supporting larger compositions through symbolic expressions. +### 10.4 Matrix Validation -### 10.2 Canonicalization Passes +- Verify matrix attribute size matches `2^(n)*2^(n)`. +- Check numerical unitarity within tolerance (fast path for 2×2 & 4×4). -**NormalizationPass**: Enforces canonical modifier ordering and eliminates redundancies +### 10.5 (Deferred) Matrix Decomposition -- Reorders nested modifiers to canonical form (ctrl → pow → inv) -- Eliminates identity operations (`pow(X, 1) → X`, `inv(inv(X)) → X`) -- Merges adjacent compatible modifiers -- Simplifies power modifiers wherever feasible (`pow(RZ(pi/2), 2) → RZ(pi)`) +- Future: Decompose large / arbitrary matrix unitaries into basis gates (NOT in MVP). -**SequenceFlatteningPass**: Inlines nested sequences when beneficial +**No pass rewrites `negctrl` into positive controls.** -- Removes sequence boundaries that don't provide optimization barriers -- Preserves sequences that are referenced by symbols or have special annotations +--- -**ConstantFoldingPass**: Folds static parameters and eliminates trivial operations +## 11. Pass Inventory -- Combines adjacent rotations with static angles -- Eliminates gates with zero rotation angles -- Evaluates static matrix expressions +| Pass | Purpose | Phase | Idempotent | Mandatory | Notes | +| --------------------------- | ------------------------------------------------------------------------------------ | ------------------------ | ------------------------ | --------------------------- | ------------------------------------- | +| NormalizationPass | Enforce modifier order; merge & simplify; flatten controls; early algebraic cleanups | Canonicalization (first) | Yes | Yes | Integrated with canonicalize pipeline | +| NamedSimplificationPass | Replace `U3/U2/U1` with simplest named gate | Simplification | Yes (post-normalization) | Baseline (recommended) | Tolerance-based | +| UniversalExpansionPass | Expand named gate → `U3` | Lowering / Backend Prep | Yes | Optional | Backend / pipeline controlled | +| MatrixUnitaryValidationPass | Verify matrix size + unitarity | Verification / Early | Yes | Yes (if matrix ops present) | Fast paths 2×2 & 4×4 | -**Rationale**: These passes work together to keep the IR in a canonical form that simplifies subsequent analysis and optimization. Each pass has a focused responsibility and can be run independently or as part of a pipeline. +### Deferred (Not MVP) -## 11. Dialect Conversions +- Matrix decomposition pass +- Basis gate registry +- Extended trait inference for composites & matrix ops +- Advanced symbolic parameter algebra -### 11.1 mqtref ↔ mqtopt Conversion +--- -The conversion between reference and value semantics requires careful handling of SSA values and side effects. +## 12. Dialect Conversions -**mqtref → mqtopt (Adding SSA Values)**: +### 12.1 `mqtref` → `mqtopt` ```cpp -// Pattern: Convert side-effect operations to value-producing operations -mqtref.x %q → %q_new = mqtopt.x %q : !mqtopt.qubit +// Side-effect → value threading +mqtref.x %q → %q_new = mqtopt.x %q : !mqtopt.qubit -// Control conversion requires threading control qubits +// Controlled example mqtref.ctrl %c { mqtref.x %t } → - %c_out, %t_out = mqtopt.ctrl %c_in { - %t_new = mqtopt.x %t_in : !mqtopt.qubit + %c_out, %t_out = mqtopt.ctrl %c, %t { + %t_new = mqtopt.x %t : !mqtopt.qubit mqtopt.yield %t_new : !mqtopt.qubit } : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) ``` -**mqtopt → mqtref (Removing SSA Values)**: +### 12.2 `mqtopt` → `mqtref` ```cpp -// Drop results and convert to side-effect form -%q_out = mqtopt.x %q_in : !mqtopt.qubit → mqtref.x %q_in - -// Ensure proper dominance and liveness in the converted code +%q_out = mqtopt.x %q_in : !mqtopt.qubit → mqtref.x %q_in +// Drop result, preserve ordering & effects ``` -**Key Challenges**: +**Challenges:** Ensuring correct dominance & preserving semantic ordering during un-nesting / region translation. -- **SSA Value Threading**: mqtopt requires explicit threading of all qubit values +--- -**Rationale**: These conversions enable using the same quantum algorithm in different contexts - mqtref for simulation and debugging, mqtopt for optimization and compilation to hardware. +## 13. Testing Strategy -### 11.2 Lowering to Hardware Targets +Priority shift: structural & semantic equivalence via builders (googletest) > textual pattern checks. -Standard conversion patterns lower high-level operations to target-specific instruction sets: +### 13.1 Structural / Semantic Tests (Primary) -```cpp -// Example: Lowering controlled gates to native basis -mqtref.ctrl %c { mqtref.ry %t {angle = θ} } → - mqtref.rz %t {angle = θ/2} - mqtref.cx %c, %t - mqtref.rz %t {angle = -θ/2} - mqtref.cx %c, %t -``` +- Use IR builders to construct original & expected forms, run normalization + optional passes, then compare via: + - Shape / op sequence equivalence (ignoring SSA names). + - Control & target counts via `UnitaryOpInterface`. + - Optional unitary matrix equivalence (numerical tolerance) for small ops. +- Idempotence: Run NormalizationPass twice; assert no further changes. -**Rationale**: Hardware targets have limited native gate sets, so high-level operations must be decomposed into available primitives while preserving mathematical equivalence. +### 13.2 Parser / Printer Smoke (Minimal Textual Tests) -## 12. Testing Strategy +- Round-trip for: base gates, each modifier, nested modifier chain, matrix unitary, composite definition, sequence. +- FileCheck limited to presence/absence of key ops (avoid brittle SSA checks). -Generally, it should be part of this endeavor to come up with a testing strategy that we can exercise across our efforts going forward. -It has already become quite clear that we do not want to extensively write FileCheck strings as it is very error prone. We are currently likely spending more time on fixing FileCheck strings than actually developing features. -Hence, even the integration tests down below should be considered to be realized differently in C++. +Example smoke test snippet: + +```mlir +// CHECK-LABEL: func @nested_mods +func.func @nested_mods(%c: !mqtref.qubit, %n: !mqtref.qubit, %t: !mqtref.qubit) { + mqtref.ctrl %c { + mqtref.negctrl %n { + mqtref.pow {exponent = 2.0 : f64} { + mqtref.inv { mqtref.x %t } + } + } + } + return +} +``` -### 12.1 Unit Tests (C++) +### 13.3 Utility Helpers (C++) -- **Interface implementations**: Verify UnitaryOpInterface methods return consistent results -- **UnitaryMatrix library operations**: Test composition, adjoint, and power operations -- **Builder API functionality**: Ensure generated IR matches expected patterns +- `assertEquivalent(Op a, Op b, EquivalenceOptions opts)` +- `buildNestedModifiers(builder, patternSpec)` +- Matrix validation harness (inject near-unitary perturbations and assert failures). -### 12.2 Integration Tests (LIT) +### 13.4 Negative Tests -- **Parser/printer round-trips**: Verify text representation preserves semantics -- **Canonicalization correctness**: Test that canonical forms are stable and unique -- **Conversion patterns**: Verify dialect conversions preserve quantum semantics -- **Error handling and verification**: Test malformed IR is properly rejected +- Malformed matrix size +- Non-unitary matrix beyond tolerance +- Disallowed modifier order (e.g., `inv` wrapping `ctrl` directly → should be reordered by normalization) -### 12.3 Quantum Semantics Tests +### 13.5 Coverage Summary -```mlir -// RUN: mlir-opt %s -test-quantum-canonicalize | FileCheck %s +All ops (base, modifiers, sequences, unitary/matrix, composite definitions) must have: -// Test double inverse elimination -func.func @test_double_inverse() { - %q = mqtref.alloc : !mqtref.qubit +- Builder creation tests +- Normalization idempotence tests +- Interface query sanity tests - // CHECK: mqtref.x %{{.*}} - // CHECK-NOT: mqtref.inv - mqtref.inv { - mqtref.inv { - mqtref.x %q - } - } - return -} +--- -// Test control threading in value semantics -func.func @test_control_threading() { - %c = mqtopt.alloc : !mqtopt.qubit - %t = mqtopt.alloc : !mqtopt.qubit +## 14. Analysis & Optimization Infrastructure - // CHECK: %[[C_OUT:.*]], %[[T_OUT:.*]] = mqtopt.ctrl %{{.*}} { - // CHECK: %[[T_NEW:.*]] = mqtopt.x %{{.*}} - // CHECK: mqtopt.yield %[[T_NEW]] - // CHECK: } - %c_out, %t_out = mqtopt.ctrl %c { - %t_new = mqtopt.x %t : !mqtopt.qubit - mqtopt.yield %t_new : !mqtopt.qubit - } : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) +### 14.1 `UnitaryMatrix` Utility (Sketch) - return -} +```cpp +class UnitaryMatrix { + // Variant representations: small fixed (2x2, 4x4), symbolic, lazy product +public: + UnitaryMatrix compose(const UnitaryMatrix &rhs) const; + UnitaryMatrix adjoint() const; + UnitaryMatrix power(double exponent) const; + UnitaryMatrix control(unsigned numPos, unsigned numNeg) const; + DenseElementsAttr toDenseElements(MLIRContext* ctx) const; + bool isIdentity() const; + bool isUnitary(double tol = 1e-10) const; +}; ``` -**Rationale**: Quantum semantics are subtle and error-prone. Comprehensive testing with both positive and negative test cases ensures the implementation correctly handles edge cases and maintains mathematical correctness. +Fast paths for 2×2 & 4×4 feed both transformation heuristics and validation. + +--- + +## 15. Canonical Identities & Algebraic Rules (Non-Exhaustive) + +Guaranteed (within normalization) where semantics preserved: + +- `inv(inv(X)) → X` +- `pow(X, 1) → X` +- `pow(X, 0) → identity` (subject to representation policy; may drop operation) +- `pow(inv(X), k) ↔ inv(pow(X, k))` (normalized placement enforces `pow` outside `inv` → `pow(inv(X), k)` canonicalizes to `pow` wrapping `inv` only if order rule maintained) +- Consecutive `pow` merges: `pow(pow(X, a), b) → pow(X, a*b)` +- Control aggregation: nested `ctrl` of `ctrl` flattens; same for nested `negctrl`; mixed pos/neg preserve order lists separately. + +No rule rewrites `negctrl` into a positive control sandwich. + +--- -## 13. Conclusion +## 16. Conclusion -This comprehensive redesign addresses the fundamental limitations of the current quantum MLIR infrastructure while positioning it for future growth. The key innovations—compositional modifiers, unified interfaces, and enhanced ergonomics—will significantly improve developer productivity and enable more sophisticated quantum compiler optimizations. +The revamp solidifies a compositional, analyzable, and canonical quantum IR: -The modular implementation plan reduces project risk by maintaining working functionality at each milestone. The emphasis on testing and performance ensures that the new system will be both reliable and efficient. +- Modifier wrappers with a fixed canonical order including explicit negative controls. +- Early, mandatory normalization ensuring deterministic structure for all downstream passes. +- Rich named gate ecosystem retained alongside matrix-based unitaries (with validation) in the MVP. +- Bidirectional single-qubit canonicalization allows readability and backend flexibility. +- Testing focus shifts to robust builder-based structural equivalence; textual tests minimized. -By aligning with MLIR best practices and providing clean abstractions, this design creates a solid foundation for quantum compiler research and development within the MQT ecosystem. +This plan supersedes earlier notions that matrix gates or negative controls might be deferred or normalized away. The resulting infrastructure provides a stable foundation for future extensions (matrix decomposition, basis registries, advanced trait inference) without compromising current clarity or performance. -The expected outcome is a quantum compiler infrastructure that is easier to use, more analyzable, and better positioned for the evolving needs of quantum computing research and application development. +No open questions remain for the MVP scope defined here. From 883b36e90870f34c80d8f2a338b1ff6809690098 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 30 Sep 2025 11:30:41 +0200 Subject: [PATCH 007/419] =?UTF-8?q?=F0=9F=9A=A7=20iteration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/feedback-prompt.md | 238 +++++ docs/mlir/mlir-discussions-summary.json | 136 --- docs/mlir/mlir-discussions.md | 208 ----- docs/mlir/plan-sync-agent-prompt.md | 195 ----- docs/mlir/quantum-dialect-revamp-plan.md | 823 ++++++++---------- .../quantum-dialect-revamp-tracking-issue.md | 265 ------ mlir-quantum-dialect.md | 38 - prompt.md | 54 -- 8 files changed, 597 insertions(+), 1360 deletions(-) create mode 100644 docs/mlir/feedback-prompt.md delete mode 100644 docs/mlir/mlir-discussions-summary.json delete mode 100644 docs/mlir/mlir-discussions.md delete mode 100644 docs/mlir/plan-sync-agent-prompt.md delete mode 100644 docs/mlir/quantum-dialect-revamp-tracking-issue.md delete mode 100644 mlir-quantum-dialect.md delete mode 100644 prompt.md diff --git a/docs/mlir/feedback-prompt.md b/docs/mlir/feedback-prompt.md new file mode 100644 index 0000000000..60f095f8c0 --- /dev/null +++ b/docs/mlir/feedback-prompt.md @@ -0,0 +1,238 @@ +Design an LLM prompt for revising the RFC dialect proposal based on the following feedback: + +## 1.1 Current State + +- the existing dialects only implement the control (pos+neg) modifier, but no power or inverse modifier. That's the main drawback of the existing solution with regard to modifiers, not that they modifiers are encoded inconsistently +- The interfaces aren't really fragmented, they are just not expressive enough yet and do not cover all the use cases we would want to cover +- There is also no way currently to define custom gates, to get the unitary of a gate, or to compose multiple gates + +## 1.2 Proposed direction + +Aims to address all the drawbacks from the previous section + +## 2.1 Dialect structure + +Measurements and reset are not resource operations. They belong in their own category. +Resource operations should be the allocation (`alloc`) and deallocation (`dealloc`) of qubits as well as the definition of static qubits (`qubit`). + +UnitaryExpr and UnitaryMatrix are actually the same concept. The proposal should be entirely phrased in terms of UnitaryExpr. + +## 2.2 Principles + +This section should be fleshed out a little more based on the remaining proposal + +## 3.1 Quantum Types + +all types should start with a capital letter (e.g., mqtref.Qubit) to not conflict with the static qubit allocation + +## 3.2 Register Handling + +should be fleshed out a little bit more based on the existing (working) implementation + +## 7. Unified Interface Design + +This section should come as section 4 before describing all various unitary operations and to clearly set the stage for what to expect from every following section on the unitary operations. Each following section should indicate how it expects to implement the various interface functions. + +Combine the existing 7.1 and 7.2 into one single more elaborative proposal that includes more interface functions. +Especially functions for + +- getting the i-th input operand, getting the i-th output operand, getting the i-th parameter (somehow handling the fact that the parameter might be defined as an attribute or a dynamic value). getting the output value corresponding to an input value and vice versa, get all parameters (combining static and dynamic parameters in the right order + +## 4. Base Gate Operations + +the `cx` gate is not a base gate, but only syntactic sugar that is explained later. Do not use it as an example of a two-qubit gate in this section, but rather use the parametrized rzz(theta) gate. + +## 4.1 Philosophy + +This should be fleshed out more. The base gates should heavily use the traits for target and parameter arity to define clean operations with efficient builders and custom assembly formats. These should be defined with as little code duplication as possible. +Each base gate defines an explicit UnitaryExpr representing its matrix. Gates without parameters always have a statically defined matrix. Those gates with parameters have a statically defined matrix representation if and only if all parameters are statically specified. Otherwise, the matrix representation depends on dynamic `mlir::Value`s + +Parameters and qubit parameters should be distinguished in the text-based format similar to the existing implementation, where parameters are depicted in `(...)` directly after the gate name, with qubit arguments being listed as "regular" SSA values afterwards, e.g., `%q0_out = mqtopt.rx(%theta) %q0_in`. This should work with attributes and dynamic values. + +## 5. Modifier Operations + +Swap `negctrl` and `ctrl` in the canonical nesting order. + +All subsections should have custom assembly format that enables more compact and MLIR idiomatic text-based IR descriptions. All subsections should also include examples in the reference and the value semantics dialects. These examples should be consistent and correct. + +All modifiers should define canonicalization patterns. + +## 5.1 Controls (`ctrl`) & Negative Controls (`negctrl`) + +The unitary representation of these is the accordingly extended unitary of the underlying unitary operation. +It reports controls of the modifier plus any controls of the contained operation. +Parameters are just passed through. +Operands of the target operation are just passed through, control qubits are explicitly handled. +directly nested control modifiers are merged through canonicalization or folds +Control applied to a `seq` equals to a control applied to every gate of the sequence. +There may be an optional transformation pass that checks consecutive gates for shared controls and may extract these into a shared control modifier on a `seq`. + +## 5.2 Power (`pow`) + +Similar to the base gate parameters, `pow` modifiers should list the parameter in round brackets `(...)`. + +The unitary of the pow modifier may be explicitly computed. Integer powers are just repetitions. Floating point values are computed based on the principal logarithm. Negative powers are canonicalized to an inverse modifier and a power modifier with a positive exponent. consecutive power modifiers are merged by multiplying the factors. +Specializations of powers of base gates with known analytical forms are to be implemented (e.g., rotation gates) + +## 5.3 Inverse (`inv`) + +The unitary of base gates may be explicitly computed (conjugate adjoint). This may be specialized for when the inverse is another explicit base gate as part of canonicalization. All qubit values are simply passed through. +The inverse of a sequence is the reversed sequence with every gate inverted. Consecutive `inv` cancel as part of the canonicalization pipeline + +## 5.4 Nested Modifier Example (Canonical Order) + +The example needs to be updated. It is also not yet correct as the blocks do not have the right block arguments (the nested sequence applies two controls to a single-qubit gate, so the outside modifier needs to have three qubit arguments. + +## 5.5 + +Remove this subsection + +## 6. Sequence Operations (`seq`) + +Similar to Section 5, these operations should also have a custom assembly format that is as compact as possible and as idiomatic for MLIR as possible. + +## 8. Matrix & User-Defined Gates + +Should be written as if this was naturally part of the proposal and not being discussed on. +The matrix defined gates should also be proper definitions of gate symbols similar to section 8.2, i.e., they should contain a symbol name and it should be possible to apply them (similar to 8.2). +A unified interface for this would be appropriate so that the application operation can be reused between unitary and gate based definitions. +All of the operations should have convenient assembly formats. +These gates report no controls, expose their parameters, and their unitary is either directly available (unitary definition) or can be computed as a product of the unitaries in the definition (U_M,...U_1). + +## 8.1 Matrix Unitary Operation + +Tensors should be 2d shaped to reflect that these are matrices. these operations should also have a custom assembly format that is more compact. +Matrix definitions may also be parametrized by dynamic values. + +## 9.1 Parser Sugar Examples + +Provide more details on how these canonical expansions are actually realized in the MLIR framework. + +## 9.2 9.2 C++ Builder (Sketch) + +This builder is incredibly important for the test related changes. Ensure this is as easy to use as possible. Enrich the available interface by further methods based on the previous sections. This should not just be a sketch. + +## 10 Canonicalization & Transformation Stages + +Remove this section for now. Any canonicalization patterns should be directly described in the respective sections. They should not be dedicated passes, but integrated into the dedicated canonicalization pipeline in MLIR. +Any optional transformation passes should also be mentioned in the individual sections. + +## 11. Pass Inventory + +Similar to section 10, remove this section + +## 12. Dialect Conversions + +Remove this dedicated section but add the respective information about converting from one representation to the other to the individual sections on the operations. The individual sections should be almost self contained and contain detailed information on all relevant aspects. + +## 13. Testing Strategy + +Rephrase and iterate on this section based on feedback above + +## 14. Analysis & Optimization Infrastructure + +include the UnitaryMatrix subsection in a revised form based on feedback into a previous section that is suitable. + +## 15. Canonical Identities & Algebraic Rules (Non-Exhaustive) + +These should be part of the canonicalization subsections in previous sections and can be removed here after folding them into the previous sections. + +## 16. Conclusion + +Revamp the conclusions based on the updated proposal + +--- + +Generated prompt: +You are an expert MLIR and quantum compiler technical writer. Rewrite the RFC for the quantum dialect revamp incorporating the structured feedback below. Produce a self‑contained, updated proposal (single document) with precise MLIR operation specifications, canonicalization rules inline, and consistent terminology. + +Overall goals: +\- Clarify current limitations (only control modifiers exist; missing pow/inv; insufficient interface expressiveness; no custom gate definitions, composition, or unitary extraction). +\- Introduce a coherent UnitaryExpr abstraction used everywhere (replace any split between UnitaryMatrix and UnitaryExpr). +\- Reorder and restructure sections as specified. +\- Integrate canonicalization rules directly inside each operation/section (no separate pass inventory section). +\- Provide clear interface contracts before defining concrete ops. +\- Use capitalized types (e.g., mqtref.Qubit, mqtopt.Qubit). +\- Distinguish parameter operands (in parentheses) from qubit operands in examples: `%q_out = mqtopt.rx(%theta) %q_in`. +\- Provide both reference semantics (mqtref) and value semantics (mqtopt) examples for every unitary + modifier + sequence construct. +\- Ensure examples are correct: block arguments, result threading, modifier nesting order, etc. + +Required new document structure (renumber all sections accordingly): + +1. Overview and Goals +2. Current State and Limitations +3. Dialect Structure and Categories + 3.1 Resource Operations (alloc, dealloc, static qubit definition) + 3.2 Measurement and Reset (separate category) + 3.3 UnitaryExpr Concept (single abstraction) +4. Unified Unitary Interface Design + \- Merge old 7.1 and 7.2; expand. + \- Specify: identification, arities, controls (pos/neg), target access, parameter model (static + dynamic merged ordering), i\-th input/output access, mapping input↔output for value semantics, collecting all parameters, static vs dynamic unitary availability, matrix extraction, inversion, power, control extension hooks. + \- Define how operations report: getNumTargets, getNumPosControls, getNumNegControls, getNumParams, getParameter(i), getInput(i), getOutput(i), mapOutputToInput(i), hasStaticUnitary, getOrBuildUnitaryExpr, etc. +5. Base Gate Operations + 5.1 Philosophy (traits for target/parameter arity, minimal duplication, explicit UnitaryExpr definition; static matrix when all params static; dynamic composition otherwise). + 5.2 Gate List (single\-qubit: x, y, z, h, s, sdg, t, tdg, rx, ry, rz, u, etc.) + 5.3 Multi\-qubit illustrative example: rzz(theta) instead of cx (cx deferred to sugar section). + 5.4 Syntax and Assembly Formats (parameter parentheses + qubits). + 5.5 Builders (static param overloads, dynamic param variants, combined convenience helpers). + 5.6 Canonicalization & Identities (parameter folding, specialization to simpler named gates). + 5.7 Value vs Reference Semantics mapping. +6. Modifier Operations + 6.1 Canonical Nesting Order (negctrl → ctrl → pow → inv) [swap per feedback]. + 6.2 negctrl (semantics, unitary extension, merging/flattening). + 6.3 ctrl (same pattern; merging directly nested; seq lifting rule). + 6.4 pow (parentheses exponent, integer repetition, float principal log, negative exponent → inv+pow normalization, merge consecutive powers, analytical specializations). + 6.5 inv (adjoint computation, folding to known inverse base gates, double inverse elimination, sequence inversion reversal). + 6.6 Nested Example (correct threading, updated order, both dialects). +7. Sequence Operation (seq) + \- Custom assembly format. + \- Control lifting semantics (external control equivalence). + \- Inversion + power behavior (defer to contained unitaries). +8. User Defined Gates & Matrix / Composite Definitions + 8.1 Symbolic matrix gate definitions (2D tensor shape, optional dynamic parameters). + 8.2 Composite gate definitions (sequence body; derive unitary via ordered product). + 8.3 Unified apply operation (applies any gate symbol: matrix or composite). + 8.4 Parameter handling (static/dynamic mix; ordering rules). + 8.5 Canonicalization (inline trivial identity, fold consecutive applies of same static gate if allowed). + 8.6 Unitary extraction rules (static matrix availability or lazy composition). +9. Parser & Builder Sugar + 9.1 Sugar expansions (cx, cz, ccx) → explicit modifier wrapping; specify how parse hooks lower to canonical form. + 9.2 Detailed builder API (fluent style): gate, param, controls (pos/neg), power, inverse, sequence, defineGate, defineMatrixGate, applyGate, withControls, withNegControls, withPow, withInv. Provide ergonomic overloads and RAII region helpers. +10. Testing Strategy + \- Emphasize builder\-driven structural tests. + \- Matrix correctness tests (unitarity, dimension). + \- Interface conformance tests per op category. + \- Canonicalization idempotence (run canonicalization twice). + \- Sugar round trip tests. + \- Negative tests (bad arity, mismatched params, invalid matrix shape, non\-unitary). +11. Integrated Canonicalization Rules Summary (collected references only; full definitions live inline above). +12. Conclusions and Future Work + \- Summarize resolved limitations and extensibility (basis decomposition, advanced symbolic algebra, shared control extraction pass). + +For every operation/modifier: +\- Provide: Purpose, Signature (ref + value semantics), Assembly Format, Builder Variants, Interface Implementation Notes, Canonicalization Rules, Examples (static + dynamic params), Conversion (ref↔value). +\- Ensure consistency of naming, capitalization, and parameter ordering. + +Conventions: +\- Types capitalized (mqtref.Qubit). +\- Parameters in parentheses right after op mnemonic. +\- Use 2D tensor<2^n x 2^n> for matrices. +\- Do not reintroduce removed standalone sections (old passes, separate canonicalization inventory, etc.). +\- All canonicalization logic described inline; mention MLIR pattern categories (fold vs pattern). +\- Avoid speculative future work beyond concise Future Work subsection. + +Deliverables: + +1. Rewritten RFC text (single cohesive document). +2. No extraneous commentary; fully integrated narrative. +3. All examples syntactically valid MLIR. + +Quality checklist before finalizing: +\- Section ordering matches specification. +\- No leftover references to UnitaryMatrix separate from UnitaryExpr. +\- All modifier examples updated to new nesting order. +\- cx not used as base gate example (only as sugar). +\- Block arguments correct for region wrappers (value semantics). +\- Canonicalization rules phrased as declarative transformations. + +Now produce the full revised RFC accordingly. diff --git a/docs/mlir/mlir-discussions-summary.json b/docs/mlir/mlir-discussions-summary.json deleted file mode 100644 index debcb80014..0000000000 --- a/docs/mlir/mlir-discussions-summary.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "version": 3, - "generated": "2025-09-29", - "sections": { - "modifierControlSemantics": { - "multipleControls": { - "decision": "Allow multiple controls per ctrl/negctrl; normalization flattens nesting.", - "rationale": [ - "Compact IR", - "Less traversal", - "Still supports nesting for composability" - ], - "openQuestions": [] - }, - "canonicalOrder": { - "orderOuterToInner": ["ctrl", "negctrl", "pow", "inv"], - "rationale": [ - "Deterministic structure", - "Aids hashing/CSE", - "Semantic precedence" - ], - "status": "decided" - }, - "negativeControls": { - "representation": "Distinct ctrl and negctrl ops retained; no automatic normalization to positives.", - "transforms": [ - "Backend-specific rewrites may still introduce/remove X gate sandwiches" - ], - "autoNormalization": false - }, - "controlCounting": { - "rules": { - "baseOp": 0, - "ctrl": "own + inner", - "negctrl": "own + inner", - "pow": "forward", - "inv": "forward", - "seq": 0, - "unitaryDefinition": 0 - }, - "seqRationale": "Acts like one-off composite; prevents double counting.", - "powInvInterfaceNeeded": false - } - }, - "gateSet": { - "decision": "Retain rich named gate set plus bidirectional canonicalization passes.", - "rationale": [ - "Readability", - "Numerical stability", - "Trait inference", - "Ecosystem alignment", - "Backend flexibility" - ], - "passes": { - "namedSimplification": "U3/U2/U1 -> simplest named gate (tolerance-based)", - "universalExpansion": "Named single-qubit gate -> canonical U3 form (optional)" - }, - "status": "decided" - }, - "matrixBasedGates": { - "status": "included", - "decision": "Matrix-based gates part of initial spec; no hard size limit (practically ≤3 qubits).", - "mvpOptimizations": ["2x2 fast path", "4x4 fast path"], - "attribute": "Flat dense array (row-major) of complex numbers", - "validationPass": true, - "pros": ["Direct synthesized result expression", "Unambiguous semantics"], - "considerations": [ - "Exponential growth cost accepted", - "Future compression out-of-scope for MVP" - ], - "openQuestions": [] - }, - "normalizationPass": { - "responsibilities": [ - "Enforce modifier order", - "Merge adjacent compatible modifiers", - "Eliminate identities", - "Algebraic simplifications (e.g. pow(RZ(pi/2),2)->RZ(pi))", - "Flatten nested control wrappers" - ], - "integration": "Runs always before other transformations via canonicalization pipeline.", - "additional": [ - "Gate definition inlining", - "Matrix unitarity validation", - "Named simplification", - "Universal expansion", - "Matrix decomposition (future)" - ] - }, - "testing": { - "primary": "googletest builder-based structural/semantic equivalence tests", - "textualTests": "Minimal parser round-trip smoke tests only", - "buildersCoverage": "All ops, modifiers, matrix unitaries, unitary definitions", - "helpers": ["assertEquivalent", "fixture utilities", "roundTrip tests"], - "avoid": ["Over-reliance on CHECK lines", "SSA name comparisons"] - }, - "pipelines": { - "baseline": [ - "-canonicalize (includes normalization)", - "-remove-dead-values", - "Named Simplification" - ], - "universalBackend": ["baseline", "Universal Expansion"], - "normalizationAlwaysFirst": true - }, - "openQuestions": [], - "decisionsChronological": [ - "Distinct ctrl and negctrl retained", - "Canonical modifier order established", - "seq has zero controls", - "Named gate set retained (no U3-only)", - "Normalization + simplification planned", - "Builder-based structural testing adopted", - "Baseline pass pipeline selected", - "Bidirectional single-qubit canonicalization decided", - "Matrix-based gates included in initial spec", - "Normalization enforced before all other transformations", - "googletest builder-based testing prioritized", - "Negative controls remain first-class (no auto-normalization)" - ], - "futureWork": [ - "Implement NormalizationPass", - "Implement named simplification & universal expansion passes", - "Structural equivalence utilities", - "Matrix-based unitary op & validation pass", - "Matrix decomposition pass (basis lowering)", - "Trait inference for composites & matrix ops", - "Basis gate registry" - ], - "metadata": { - "source": "Condensed & updated from mlir-discussions.md v3", - "intendedUse": "Automated querying / LLM grounding", - "stability": "Will evolve; track version key" - } - } -} diff --git a/docs/mlir/mlir-discussions.md b/docs/mlir/mlir-discussions.md deleted file mode 100644 index 22c7122363..0000000000 --- a/docs/mlir/mlir-discussions.md +++ /dev/null @@ -1,208 +0,0 @@ -# MLIR Quantum Dialect Discussions Digest - -Purpose: Curated, compact, duplication‑free summary of prior threaded discussions to serve as a stable knowledge base for design, implementation, and future LLM queries. - ---- - -## 1. Modifier & Control Semantics - -### 1.1 Multiple Controls in a Single Modifier - -- We allow a single `ctrl` (and `negctrl`) modifier to list multiple controls for compact IR and reduced traversal. -- Nesting remains valid but a normalization pass will flatten/merge where possible. - -### 1.2 Canonical Modifier Ordering - -Canonical order (outer → inner): - -1. `ctrl` -2. `negctrl` -3. `pow` -4. `inv` - -Rationale: - -- Groups positive and negative controls first (most structural impact on qubit usage / control sets). -- Power and inverse are algebraic refinements on the underlying operation. -- Deterministic order enables straightforward structural hashing, CSE, pattern rewrites, and simplifies equality queries. - -### 1.3 Negative Controls Representation - -Current decision: keep distinct `ctrl` and `negctrl` wrappers (cleaner semantics than a combined mixed-control list variant). - -Decision Update: Negative controls are first-class citizens; we WILL NOT automatically normalize them into positive controls in canonical pipelines. Transformations involving X gate sandwiches remain optional backend-specific rewrites, not part of normalization. - -### 1.4 Control Counting Semantics (UnitaryInterface) - -- Base (leaf) operations: 0 controls. -- `ctrl` / `negctrl`: number of (pos / neg) controls they introduce + recursively added controls of wrapped op. -- `pow`, `inv`: do not change control counts; they forward queries to the wrapped op. -- `seq` (sequence construct): always reports 0 controls (acts like a one-off composite/inline body rather than a modifier). -- User-defined unitary (definition): reports 0 controls. - -Rationale: Control counting terminates cleanly; `seq` neutrality simplifies reasoning and prevents accidental double counting when sequences are wrapped by modifiers. - -Open Question (closed): Interface exposure for enumerating `pow` / `inv` layers not needed now. - ---- - -## 2. Gate Set vs. Single Universal Gate (U3-Only Approach) - -Proposal Considered: Collapse to only a universal single-qubit gate (U3) + modifiers (OpenQASM 3 style). - -Decision: Retain a rich set of named primitive gates (X, Y, Z, H, S, T, RX, RY, RZ, P, etc.) AND provide bidirectional canonicalization passes bridging named gates and universal U3 forms. - -Rationale: - -- Readability & ergonomic authoring. -- Numerical stability: Avoids always instantiating via floating-point U3 params (reduces drift / canonicalization complexity). -- Trait inference (e.g., Hermitian, Diagonal) is straightforward on symbolic gates; harder on generic parametrized forms. -- Alignment with existing ecosystems (OpenQASM library, Qiskit standard gates) while still permitting decomposition. -- Bidirectional passes satisfy both optimization introspection (simplify to named) and backend uniformity (expand to U3) requirements. - -Committed Passes: - -1. Named Simplification Pass: Simplify generic U3/U2/U1 into simplest named gates (tolerance-based) — deterministic, part of canonicalization pipeline. -2. Universal Expansion Pass: Expand named single‑qubit gates into canonical U3 (optionally gated by a flag / backend pipeline requirement). - -Status: Dual canonicalization direction DECIDED (no longer an open question). - ---- - -## 3. Matrix-Based Gate Definitions (Arbitrary Unitaries) - -Decision: INCLUDED in initial specification (not deferred). No programmatic hard limit on qubit count; practical performance naturally constrains usage. Provide specialized fast paths and optimization focus for 1‑qubit (2×2) and 2‑qubit (4×4) matrices as part of the MVP (not postponed). - -Representation: - -- Attribute: Flat dense array (row-major) of complex numbers (candidate: MLIR DenseElementsAttr with complex element type). Potential specialized attributes may still be explored later if profiling indicates need. -- Optional metadata: dimension (inferred) and qubit arity (validation cross-check). - -Validation: - -- Unitarity Validation Pass: (Configurable) numerical tolerance check; may be skipped in trusted build modes. - -Pros: - -- Direct expression of synthesized results & externally imported unitaries. -- Unambiguous semantics and suitable anchor for decomposition passes. - -Considerations: - -- Large matrices quickly infeasible (exponential growth); accepted as user responsibility—no current heuristic advisory needed (≤3 qubits covers practical scope). -- Potential future compression (factorizations, tensor products) is out-of-scope for MVP. - ---- - -## 4. Normalization & Canonicalization Passes (Current & Planned) - -NormalizationPass (part of canonicalization pipeline; always scheduled before other transformations): - -- Enforce modifier ordering: `ctrl` → `negctrl` → `pow` → `inv`. -- Merge adjacent identical/compatible modifiers (e.g., consecutive `ctrl` merges control sets; nested `pow` multiplies exponents; nested `inv` cancels). -- Eliminate neutral identities: `pow(X, 1) → X`, `inv(inv(X)) → X`. -- Simplify algebraic forms when safe (e.g., `pow(RZ(pi/2), 2) → RZ(pi)`). -- Flatten nested control wrappers. - -Additional Passes: - -- Gate inlining pass for user-defined unitary definitions until reaching standard basis gates. -- Named Simplification Pass (Section 2). -- Universal Expansion Pass (Section 2). -- Matrix Unitarity Validation Pass (Section 3). -- Future: Decomposition of matrix-based unitaries into chosen basis (configurable backend pass). - -(Note: Negative control elimination explicitly omitted per Section 1.3 decision.) - ---- - -## 5. Testing Strategy - -Updated Direction (Stronger Commitment): - -- Primary mechanism: googletest-based infrastructure constructing IR programmatically via builder API (see spec Section 9.2) and asserting structural / semantic equivalence. -- Textual `CHECK` tests reduced to minimal smoke tests for parser/round-trip coverage only. -- Emphasis on deterministic canonicalization pre-check (run normalization + canonicalization pipeline before equivalence assertions). - -Guidelines: - -- Provide helper utilities for: module creation, gate application, modifier wrapping, matrix unitary creation, control aggregation. -- Structural Equivalence: Use MLIR IR equivalence if available; otherwise implement recursive op/attribute/operand/result comparison normalized by canonical pass. -- Avoid reliance on incidental attribute ordering or SSA naming. - -Action Items (refined): - -1. Ensure comprehensive builder coverage (all ops, modifiers, matrix unitaries, unitary definitions). -2. Implement `assertEquivalent(afterPasses, expected)` helper. -3. Provide fixture functions for common gate patterns & modifier compositions. -4. Add round-trip (print→parse) smoke tests for representative IR forms (including matrix unitaries & modifiers nesting). - ---- - -## 6. Default / Recommended Pass Pipelines - -Baseline (tests & examples): - -- `-canonicalize` (includes NormalizationPass via registration hook) -- `-remove-dead-values` -- Named Simplification Pass (enabled by default unless backend requests universal form) - -Backend Universalization Pipeline (example): - -- Baseline passes -- Universal Expansion Pass (if backend requires uniform U3) - -Decision: Normalization (modifier ordering & merging) ALWAYS runs before any other transformation stages; integrated as early canonicalization pattern population. - ---- - -## 7. Open Questions (Unresolved Summary) - -Currently none. All previously tracked questions have been resolved for the MVP scope. New questions will re-open this section when they arise. - ---- - -## 8. Decision Log (Chronological Core Decisions) - -1. Keep distinct `ctrl` and `negctrl` modifiers (no combined mixed-control wrapper for now). -2. Establish canonical modifier ordering: `ctrl`, `negctrl`, `pow`, `inv`. -3. Represent sequences (`seq`) as control-neutral (0 controls) composites. -4. Retain named gate set; do not reduce to U3-only model. -5. Plan normalization & simplification passes mirroring legacy MQT Core canonicalization behavior. -6. Adopt builder-based structural testing strategy; textual tests become supplementary. -7. Recommend baseline pipeline: `-canonicalize` + `-remove-dead-values` (expand later). -8. Decide on bidirectional single-qubit canonicalization (Named Simplification & Universal Expansion passes). -9. Include matrix-based gate definitions in initial spec (flat dense array attribute; no hard size cap; unitarity validation pass). -10. Enforce normalization (modifier ordering + merging) always-before transformations via canonicalization integration. -11. Elevate googletest builder-based testing as primary; textual tests minimized. - ---- - -## 9. Future Work (Backlog Candidates) - -- Implement NormalizationPass (ordering, merging, identity elimination). -- Implement Named Simplification Pass & Universal Expansion Pass. -- Structural equivalence utilities & test harness helpers. -- Matrix-based unitary op implementation + unitarity validation pass. -- Performance specialization for 2×2 / 4×4 matrix attributes (fast paths). -- Gate definition inlining + matrix-to-basis decomposition passes. -- Trait inference for composites & matrix-based ops (Hermitian, Diagonal detection heuristics / numerical tolerance). -- Basis gate registry with canonical decomposition recipes. - -(Removed: negative-control rewriting pass, apply_gate exploration.) - ---- - -## 10. References to Legacy Logic - -Legacy MQT Core IR already performs simplification of U3/U2/U1 to simpler gates (see former `StandardOperation::checkUgate` logic). New dialect passes replicate this within MLIR canonicalization infrastructure (Named Simplification Pass). - ---- - -## 11. (Optional) Raw Discussion Log - -The original threaded conversation has been condensed into this digest. Retrieve the prior version from version control if verbatim context is ever required. - ---- - -End of digest. diff --git a/docs/mlir/plan-sync-agent-prompt.md b/docs/mlir/plan-sync-agent-prompt.md deleted file mode 100644 index 92fddc140a..0000000000 --- a/docs/mlir/plan-sync-agent-prompt.md +++ /dev/null @@ -1,195 +0,0 @@ -# GPT-5 Agent Prompt: Integrate Discussions Summary (v3) into `quantum-dialect-revamp-plan.md` - -## Mission - -Synchronize the existing dialect plan (`quantum-dialect-revamp-plan.md`) with the authoritative design decisions encoded in `mlir-discussions-summary.json` (version 3), while _respecting and reusing_ all still-relevant material from the existing plan. Produce a fully revised, internally consistent markdown document that becomes the new single source of truth. - -You MUST: - -- Use BOTH the existing plan AND the JSON spec as inputs. -- Treat the JSON (version 3) as authoritative where conflicts arise. -- Preserve valuable explanatory prose from the current plan unless it contradicts decisions. -- Update, relocate, or condense sections for clarity—but do not silently drop useful context. -- Ensure EVERY MLIR code example is syntactically valid, semantically coherent, and consistent with the described semantics (no stale modifier order, no missing yields in value semantics, no invalid types). -- Fail the task (i.e., do not produce a final doc) if you cannot ensure MLIR validity; instead, explicitly list corrections needed. - ---- - -## Authoritative Decisions (From JSON v3 — Must Be Reflected) - -1. Canonical modifier nesting order (outer → inner): `ctrl`, `negctrl`, `pow`, `inv`. -2. Negative controls (`negctrl`) are first-class; DO NOT auto-normalize them into positive controls in canonical passes. -3. Named single-qubit gates are retained; implement _bidirectional_ canonicalization: - - Named Simplification: `U3/U2/U1 → simplest named gate` (tolerance-based). - - Universal Expansion: `named gate → canonical U3` (backend-optional). -4. Matrix-based gates ARE part of the MVP (no deferral); practical usage up to 3 qubits; optimize 2×2 and 4×4 cases. -5. Matrix unitary attribute: flat, row-major dense array of complex numbers. -6. Normalization (modifier ordering, merging, identity elimination) ALWAYS runs before other transformations (integrated into canonicalization). -7. Testing strategy: googletest builder-based structural/semantic equivalence is PRIMARY; textual FileCheck / lit tests limited to parser/printer smoke cases. -8. Sequences are control-neutral (always report 0 controls). -9. Interface currently does NOT expose explicit enumeration for pow/inv layers (deferred need). -10. No negative-control rewriting pass in MVP. -11. Open questions list is EMPTY for MVP—new ambiguities must be clearly introduced if discovered. - ---- - -## Required Output - -Produce TWO artifacts in a single response: - -1. The **fully revised** `quantum-dialect-revamp-plan.md` (complete file content; no partial diff). -2. A trailing JSON change summary object: - ```json - { - "decisionSyncVersion": 3, - "addedSections": [...], - "modifiedSections": [...], - "removedSections": [...], - "notes": [...], - "mlirExampleAudit": { - "checkedExamples": N, - "invalidExamplesFound": 0 - } - } - ``` - -If any MLIR example cannot be validated conceptually (e.g., invalid region signature, missing yield, wrong result count), set `"invalidExamplesFound" > 0` and DO NOT produce the revised document—produce instead a diagnostic section listing every problematic snippet and how to fix it. - ---- - -## Transformation Tasks (Execute in Order) - -1. **Parse Inputs** - - Load and internalize the JSON spec (v3). - - Read the existing markdown plan fully. - - Build an internal map of sections: Overview, Architecture, Types, Base Gates, Modifiers, Sequences, Interface, User-Defined Gates, Builders, Canonicalization/Passes, Testing, Conversions, Conclusion. - -2. **Conflict Audit** - Identify and correct in the existing plan: - - Outdated canonical order (`ctrl → pow → inv` must become `ctrl → negctrl → pow → inv`). - - Any implication that matrix-based gates are "future" or "optional". - - Any claim or example suggesting negative controls are normalized away. - - Missing mention of bidirectional canonicalization. - - Over-reliance on FileCheck in testing rationale (must be re-scoped). - - Redundant or conflicting descriptions of modifiers. - -3. **Structural Enhancements** - Add / modify sections: - - "Design Decisions (Synchronized with Discussions Digest v3)" near the top (concise bullet list referencing decisions above). - - "Pass Inventory" with a table: - | Pass | Purpose | Phase | Idempotent | Mandatory | Notes | - Include: NormalizationPass, Named Simplification Pass, Universal Expansion Pass, Matrix Unitarity Validation Pass. - Add a "Deferred" subsection listing: Matrix decomposition, Basis gate registry, Extended trait inference. - - Integrate matrix-unitary support into user-defined gates AND core architecture (not just an isolated advanced feature). - -4. **Modifiers Section Rewrite** - - Ensure examples reflect canonical nesting order. - - Provide at least one nested example: - ```mlir - mqtref.ctrl %c0, %c1 { - mqtref.negctrl %c2 { - mqtref.pow {exponent = 2.0 : f64} { - mqtref.inv { - mqtref.x %t - } - } - } - } - ``` - - Provide corresponding value-semantics form if appropriate, ensuring `yield` operands/results match region signatures. - -5. **Matrix Gates Integration** - - Document attribute form with a valid example: - ```mlir - // 2x2 example (Pauli-Y) - %u = mqtref.matrix %q0 { - matrix = dense<[[0.0+0.0i, 0.0-1.0i], - [0.0+1.0i, 0.0+0.0i]]> : tensor<2x2xcomplex> - } - ``` - Adjust syntax to match your dialect conventions (if a different op naming scheme like `mqtref.unitary` is used, align accordingly). - - Show 4×4 example (two-qubit) with shape validation. - - Clarify unitarity validation pass (tolerance-based; optional strict mode). - -6. **Interface Section Adjustments** - - Remove or annotate any obsolete pow/inv enumeration ambitions. - - Clarify control counting (wrappers aggregate; seq = 0; matrix + composite definitions = 0 controls themselves unless wrapped). - -7. **Canonicalization Section Update** - - Split into: - - "Normalization (Early Canonical Form)" - - "Named Simplification" - - "Universal Expansion" - - "Matrix Validation" - - (Deferred) "Matrix Decomposition" - - Remove obsolete examples (e.g., automatic negctrl erasure). - - Ensure algebraic simplification examples only include decisions currently endorsed (e.g., `inv(inv(X))`, `pow(RZ(pi/2), 2)`). - -8. **Testing Strategy Rewrite** - Must include: - - Structural equivalence assertion workflow: build → run canonical pipeline → compare IR structurally (ignore SSA names). - - Idempotence test pattern (run Normalization twice). - - Round-trip (parser → printer → parser) smoke tests for: base gates, modifiers, matrix gate, nested modifiers. - -9. **MLIR Example Validation** - For every example: - - Ensure region form is valid: block arguments present if operands thread through (value semantics). - - Ensure all yields match enclosing op result arity/types. - - Ensure attributes syntactically valid (e.g., `exponent = 0.5 : f64`). - - Ensure tensor element types use `complex` where required. - - Ensure no stray `%q0_new` without use or yield in SSA form examples. - -10. **Conclusion Update** - - Reflect finalized scope: MVP includes matrix ops, dual canonicalization, early normalization, builder-first strategy. - - Remove speculative phrasing about deferring matrix support. - -11. **Change Summary JSON** - - Populate arrays precisely (section titles or approximate headings). - - Notes should list any semantic clarifications (e.g., replacing previous canonical order, introducing Pass Inventory). - ---- - -## Style & Consistency Rules - -- Prefer concise declarative sentences in decision summary sections. -- Avoid re-defining the same rationale in multiple places—link or reference earlier sections. -- Use consistent dialect namespace prefixes (`mqtref.` / `mqtopt.`). If any example mixes them incorrectly, fix it. -- Use fenced code blocks with `mlir` language tag for MLIR IR. -- Do not include TODOs for MVP features—only for explicitly deferred future items under a "Deferred" heading. - ---- - -## Explicit Prohibitions - -DO NOT: - -- Invent new passes or features not sanctioned by JSON v3. -- Remove valuable context silently—if removed for redundancy, capture that in the change summary's `removedSections` or `notes`. -- Produce partial file content. -- Output explanations after the final JSON summary. - ---- - -## Validation Checklist (Self-Check Before Emitting Output) - -Mark each internally (do not include the checklist in final output): - -- All examples MLIR-valid. -- No contradictions about negative controls. -- Matrix gates clearly part of MVP. -- Pass inventory present & accurate. -- Testing section prioritizes builder-based structural tests. -- Canonical modifier order consistent across narrative + examples. -- No obsolete canonicalization rules remain. -- JSON summary structurally valid. - ---- - -## Final Output Format - -1. Full revised markdown (beginning at first line with title—retain or improve existing heading). -2. JSON change summary object (standalone; no extra prose after it). - -If ANY blocking inconsistency remains → output ONLY a diagnostic section titled `BLOCKED` with a bullet list of unresolved issues (no partial plan). - -BEGIN. diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index 5b89bc5c6d..f6c9303c5e 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -1,588 +1,483 @@ -## 1. Overview and Goals - -### 1.1 Current State - -We have two quantum dialects: - -- `mqtref`: Reference semantics (side-effect based) -- `mqtopt`: Value semantics (SSA based) - -Both currently provide basic gates and quantum operations but suffer from: - -- Verbose / inconsistent builder ergonomics (especially for controlled forms) -- Modifier semantics encoded inconsistently (implicit attributes, ad‑hoc controls) -- Fragmented interfaces for querying structural properties -- Limited composability for combined modifiers (e.g., controlled powers of inverses) - -### 1.2 Proposed Direction +# Quantum Dialect Revamp RFC -This revamp establishes a uniform, compositional IR that: +## 1. Overview and Goals -1. Treats all gate modifiers (controls, negative controls, powers, inverses) as explicit wrapper ops with regions. -2. Provides a single structural interface (`UnitaryOpInterface`) across both dialects. -3. Normalizes modifier ordering, merging, and identity elimination early and deterministically. -4. Supports both a rich named gate set AND matrix-based unitaries. -5. Emphasizes builder-based structural/semantic testing over brittle text-based checks. +This RFC proposes a comprehensive revamp of the quantum MLIR dialect(s) to unify unitary representations, improve expressiveness, and enable robust transformations. ---- +Goals: -## 2. Architecture Overview +- Introduce a single abstraction: **UnitaryExpr** (symbolic unitary expression) replacing prior matrix vs expression split. +- Provide a coherent interface for all operations that apply or produce a unitary (base gates, user-defined gates, modifier-wrapped constructs, sequences). +- Support both reference semantics (in-place: `mqtref`) and value semantics (SSA threading: `mqtopt`). +- Add inversion, powering, negative and positive multi-controls, custom gate definitions (matrix & composite), and composition. +- Unify parameter handling (static + dynamic) with consistent ordering and interface queries. +- Embed canonicalization rules directly at each operation definition. +- Establish a normalized modifier nesting order: `negctrl → ctrl → pow → inv`. +- Enable static matrix extraction where possible and symbolic composition otherwise. +- Prepare foundations for advanced transformations without speculative overreach. -### 2.1 Dialect Structure +## 2. Current State and Limitations -``` -Common/ -├── Interfaces (UnitaryOpInterface, etc.) -├── Traits (Hermitian, Diagonal, SingleTarget, etc.) -└── Support (UnitaryExpr / UnitaryMatrix utilities) - -mqtref/ -├── Types (qubit) -├── Base Gates (x, rx, cx, u1/u2/u3, etc.) -├── Modifiers (ctrl, negctrl, pow, inv) -├── Sequences (seq) -└── Resources (alloc, measure, ...) - -mqtopt/ -└── Parallel set with value semantics (results thread through) -``` +Current issues the revamp addresses: -### 2.2 Principles +- Only a rudimentary control modifier exists; missing negative controls, power, inversion. +- No unified interface for extracting/composing unitaries—leading to ad hoc logic. +- Matrix vs expression forms diverge; lost optimization opportunities. +- No user-defined gate (matrix/composite) constructs. +- Inconsistent parameter model and missing static/dynamic integration. +- Lack of dual semantics for optimization vs generation workflows. +- Absent canonicalization strategy for modifier order, parameter folding, gate specialization. -1. Base gate ops contain only their target operands and parameters (no embedded controls / modifiers). -2. Modifiers are explicit region wrapper ops enabling arbitrary nesting in canonical order. -3. Interface / traits provide uniform structural queries (control counts, target counts, matrix form when available). -4. Reference vs value semantics differences are localized to operation signatures & result threading. +## 3. Dialect Structure and Categories -**Rationale:** Predictable structural form enables simpler canonicalization, hashing, CSE, and semantic equivalence checks. +Two parallel dialects: ---- +- `mqtref`: Reference semantics (in-place mutation of qubits; no new results). +- `mqtopt`: Value semantics (operations consume qubits and yield new qubit results). -## 3. Types and Memory Model +Categories: -### 3.1 Quantum Types +1. Resource Operations +2. Measurement and Reset +3. UnitaryExpr Concept (applies everywhere for unitaries) -- `!mqtref.qubit`: Stateful reference (in-place mutation semantics). -- `!mqtopt.qubit`: SSA value (must be threaded; operations consume & produce qubits). +### 3.1 Resource Operations -### 3.2 Register Handling +Purpose: Manage qubit lifetime and references. -Standard MLIR types (e.g., `memref`) manage collections: +Examples (reference semantics): -```mlir -%qreg = memref.alloc() : memref<2x!mqtref.qubit> -%q0 = memref.load %qreg[%c0] : memref<2x!mqtref.qubit> -%creg = memref.alloc() : memref<2xi1> -%bit = mqtref.measure %q0 : i1 -memref.store %bit, %creg[%c0] : memref<2xi1> ``` - ---- - -## 4. Base Gate Operations - -### 4.1 Philosophy - -Minimal, parameterized primitives; all composition via wrappers. - -### 4.2 Examples (Reference Semantics) - -```mlir -mqtref.x %q0 -mqtref.rx %q0 {angle = 1.57 : f64} -mqtref.cx %q0, %q1 -mqtref.u3 %q0 {theta = 0.0 : f64, phi = 0.0 : f64, lambda = 3.14159 : f64} +%q = mqtref.alloc : mqtref.Qubit +mqtref.dealloc %q : mqtref.Qubit +%q_fixed = mqtref.static_qubit @q0 : mqtref.Qubit ``` -### 4.3 Examples (Value Semantics) +Value semantics: -```mlir -%q0_out = mqtopt.x %q0_in : !mqtopt.qubit -%q0_out = mqtopt.rx %q0_in {angle = 1.57 : f64} : !mqtopt.qubit -%q0_out, %q1_out = mqtopt.cx %q0_in, %q1_in : !mqtopt.qubit, !mqtopt.qubit ``` - -### 4.4 Parameterization Strategy - -```mlir -mqtref.rx %q0 {angle = 1.57 : f64} // static -%theta = arith.constant 1.57 : f64 -mqtref.rx %q0, %theta : f64 // dynamic -%dyn_theta = arith.constant 0.5 : f64 -mqtref.u3 %q0, %dyn_theta {phi = 0.0 : f64, lambda = 3.14159 : f64, - static_mask = [false, true, true]} +%q0 = mqtopt.alloc : mqtopt.Qubit +mqtopt.dealloc %q0 : mqtopt.Qubit +%q_hw = mqtopt.static_qubit @q7 : mqtopt.Qubit ``` ---- +Canonicalization (patterns / folds): -## 5. Modifier Operations +- Remove unused `alloc` (DCE). +- Elide `dealloc` proven by lifetime analysis. +- Merge duplicate `static_qubit` references if semantics allow. -Modifiers are single-region wrappers; canonical nesting: `ctrl` → `negctrl` → `pow` → `inv`. +### 3.2 Measurement and Reset -### 5.1 Controls (`ctrl`) & Negative Controls (`negctrl`) +Non-unitary (do not implement Unitary interface). -Reference semantics: +Reference: -```mlir -mqtref.ctrl %c0 { - mqtref.x %t0 // CNOT -} - -mqtref.ctrl %c0, %c1 { - mqtref.x %t0 // Toffoli (CCX) -} - -mqtref.negctrl %c0 { - mqtref.x %t0 // Fires when %c0 is |0> -} +``` +%c = mqtref.measure %q : mqtref.Qubit -> i1 +mqtref.reset %q : mqtref.Qubit ``` -Value semantics (control + target operands; region blocks thread values explicitly): +Value: -```mlir -%c_out, %t_out = mqtopt.ctrl %c_in, %t_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { -^entry(%c: !mqtopt.qubit, %t: !mqtopt.qubit): - %t_new = mqtopt.x %t : !mqtopt.qubit - mqtopt.yield %c, %t_new : !mqtopt.qubit, !mqtopt.qubit -} +``` +%c = mqtopt.measure %qin : mqtopt.Qubit -> i1 +%qout = mqtopt.reset %qin : mqtopt.Qubit ``` -Negative controls are NOT rewritten into positive controls (no implicit X-sandwich canonicalization). +Canonicalization: -### 5.2 Power (`pow`) +- `reset` immediately after `alloc` → remove `reset`. +- Consecutive `reset` on same qubit (reference semantics) → single instance. -```mlir -mqtref.pow {exponent = 0.5 : f64} { - mqtref.x %q0 // sqrt(X) -} +### 3.3 UnitaryExpr Concept -%exp = arith.constant 0.25 : f64 -mqtref.pow %exp { - mqtref.ry %q0 {angle = 3.14159 : f64} -} -``` +`UnitaryExpr` is a conceptual abstraction representing a (possibly symbolic) unitary over n targets with optional parameters and controls. -Value semantics single-qubit power: +- Static if all parameters are static and analytic matrix is available. +- Symbolic otherwise (composition, parameterized nodes, modifiers). +- Provides inversion, powering, and control extension without immediate matrix materialization. -```mlir -%q_out = mqtopt.pow {exponent = 0.5 : f64} %q_in : (!mqtopt.qubit) -> !mqtopt.qubit { -^entry(%q: !mqtopt.qubit): - %q_x = mqtopt.x %q : !mqtopt.qubit - mqtopt.yield %q_x : !mqtopt.qubit -} -``` +## 4. Unified Unitary Interface Design -### 5.3 Inverse (`inv`) +All unitary-applying operations implement a common interface (applies to base gates, modifiers, sequences, and user-defined applies). -```mlir -mqtref.inv { - mqtref.s %q0 // S† -} +Interface methods (conceptual API): -%q0_out = mqtopt.inv %q0_in : (!mqtopt.qubit) -> !mqtopt.qubit { -^entry(%q0: !mqtopt.qubit): - %q0_s = mqtopt.s %q0 : !mqtopt.qubit - mqtopt.yield %q0_s : !mqtopt.qubit -} -``` +- `getNumTargets() -> unsigned` +- `getNumPosControls() -> unsigned` +- `getNumNegControls() -> unsigned` +- `getNumParams() -> unsigned` +- `getParameter(i) -> ParameterDescriptor` + - `ParameterDescriptor`: `isStatic()`, `getConstantValue()?`, `getValueOperand()` +- `getInput(i)` / `getOutput(i)` (value semantics distinct; reference semantics output = input) +- `mapOutputToInput(i) -> i` (pure unitaries) +- `hasStaticUnitary() -> bool` +- `getOrBuildUnitaryExpr(builder) -> UnitaryExpr` +- `tryGetStaticMatrix() -> Optional` (2D tensor with shape (2^n, 2^n) and element type `complex`; written concretely for fixed n as e.g. `tensor<4x4xcomplex>`) +- `isInverted() -> bool` +- `getPower() -> Optional` +- `withAddedControls(pos, neg) -> UnitaryExpr` +- `composeRight(other) -> UnitaryExpr` +- `getPrincipalLog() -> Optional` -### 5.4 Nested Modifier Example (Canonical Order) +Identification & Descriptor Tuple: +`(baseSymbol, orderedParams, posControls, negControls, powerExponent, invertedFlag)` allows canonical equality tests. -Reference semantics nested chain: +Parameter Model: -```mlir -mqtref.ctrl %c0, %c1 { - mqtref.negctrl %c2 { - mqtref.pow {exponent = 2.0 : f64} { - mqtref.inv { - mqtref.x %t0 - } - } - } -} -``` +- Parameters appear in parentheses immediately after mnemonic. +- Mixed static (attributes) and dynamic (operands) preserve original order. +- Enumeration returns flattened ordered list; inspect each for static/dynamic. -Value semantics counterpart (illustrative; block arguments explicit at each nesting level): - -```mlir -%c_out, %t_out = mqtopt.ctrl %c_in, %t_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { -^entry(%c: !mqtopt.qubit, %t: !mqtopt.qubit): - %c_neg_out, %t_neg = mqtopt.negctrl %c, %t : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { - ^entry(%cn: !mqtopt.qubit, %tn: !mqtopt.qubit): - %t_pow = mqtopt.pow {exponent = 2.0 : f64} %tn : (!mqtopt.qubit) -> !mqtopt.qubit { - ^entry(%tp: !mqtopt.qubit): - %t_inv = mqtopt.inv %tp : (!mqtopt.qubit) -> !mqtopt.qubit { - ^entry(%ti: !mqtopt.qubit): - %t_x = mqtopt.x %ti : !mqtopt.qubit - mqtopt.yield %t_x : !mqtopt.qubit - } - mqtopt.yield %t_inv : !mqtopt.qubit - } - mqtopt.yield %cn, %t_pow : !mqtopt.qubit, !mqtopt.qubit - } - mqtopt.yield %c_neg_out, %t_neg : !mqtopt.qubit, !mqtopt.qubit -} -``` +Static Matrix Extraction: -(Assumption: Each wrapper result list mirrors its operand list order for pass-through + transformed targets.) +- Provided if gate analytic and all parameters static, or for matrix-defined user gates. +- For sequences/composites of static subunits under size threshold, compose matrices. -### 5.5 Modifier Semantics Summary +Inversion & Power Interaction: -- Control count = sum of outer wrappers + inner (flattened by normalization). -- `pow` & `inv` forward control counts unchanged. -- No negctrl → ctrl auto-normalization. +- `inv` introduces `invertedFlag` (final canonical position). +- `pow` stores exponent; negative exponent canonicalized to `inv(pow(+exp))` then reordered. ---- +Control Extension: -## 6. Sequence Operations (`seq`) +- `ctrl` / `negctrl` wrappers extend control sets; interface aggregates flattened sets. -Sequences group subcircuits; they are control-neutral (always 0 controls) by decision. +## 5. Base Gate Operations -Reference semantics: +### 5.1 Philosophy -```mlir -mqtref.seq { - mqtref.h %q0 - mqtref.ctrl %q0 { - mqtref.x %q1 - } - mqtref.h %q0 -} -``` +- Named base gates define analytic unitaries with fixed target arity and parameter arity traits. +- Provide static matrix when parameters static; symbolic otherwise. +- Avoid embedding modifier semantics directly—wrappers handle extension. -Value semantics: +### 5.2 Gate List (Single-Qubit) -```mlir -%q0_out, %q1_out = mqtopt.seq %q0_in, %q1_in : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { -^entry(%q0: !mqtopt.qubit, %q1: !mqtopt.qubit): - %q0_h = mqtopt.h %q0 : !mqtopt.qubit - %q0_cx, %q1_cx = mqtopt.cx %q0_h, %q1 : !mqtopt.qubit, !mqtopt.qubit - %q0_final = mqtopt.h %q0_cx : !mqtopt.qubit - mqtopt.yield %q0_final, %q1_cx : !mqtopt.qubit, !mqtopt.qubit -} -``` +No-parameter: `x, y, z, h, s, sdg, t, tdg, id` +Parameterized: `rx(%theta), ry(%theta), rz(%theta), phase(%lambda), u(%theta, %phi, %lambda)` ---- - -## 7. Unified Interface Design - -### 7.1 `UnitaryOpInterface` (Conceptual Extract) - -```cpp -class UnitaryOpInterface { -public: - StringRef getIdentifier(); - size_t getNumTargets(); - size_t getNumQubits(); - size_t getNumPosControls(); - size_t getNumNegControls(); - size_t getNumControls(); // pos + neg (seq = 0) - OperandRange getTargetOperands(); - OperandRange getPosControlOperands(); - OperandRange getNegControlOperands(); - OperandRange getControlOperands(); // concatenated - OperandRange getQubitOperands(); - bool hasResults(); - ResultRange getTargetResults(); - // Control results present in value semantics wrappers - UnitaryMatrix getUnitaryMatrix(); // Static or computed - bool hasStaticUnitary(); - size_t getNumParams(); - ArrayAttr getStaticParameters(); - OperandRange getDynamicParameters(); -}; -``` +Multi-qubit illustrative: `rzz(%theta)` (two targets), not using `cx` (introduced only via sugar as controlled `x`). -### 7.2 Notes +#### 5.2.1 Base Gate Specification Template -- No explicit enumeration for pow/inv stack layers (decision: unnecessary for MVP). -- Sequences report 0 controls even if containing controlled ops. -- Matrix or composite definitions report 0 intrinsic controls; wrappers add controls. +For every named base gate op G: ---- +- Purpose: Apply the analytic unitary for gate G to its target qubit(s). +- Signature (Reference): `mqtref.G(param_list?) %q[,...] : ` (no results) +- Signature (Value): `%out_targets = mqtopt.G(param_list?) %in_targets : () -> ` +- Assembly Format: `G() `; params in parentheses; qubits as trailing operands. +- Builder Variants: + - `build(builder, loc, resultTypes, paramOperands, qubitOperands)` (value) + - `build(builder, loc, qubitOperands, paramAttrs)` (reference) + - Convenience: static param overloads generate attribute parameters. +- Interface Implementation Notes: + - `getNumTargets()` fixed by trait. + - Parameters enumerated in declared order; static vs dynamic via attribute vs operand. + - `hasStaticUnitary()` true iff all parameters static. + - `mapOutputToInput(i) = i`. +- Canonicalization Rules: See 5.6 plus gate-specific (e.g., identity elimination, specialization). +- Examples (Static): `mqtref.rz(3.14159) %q`; `%q2 = mqtopt.rx(0.785398) %q1`. +- Examples (Dynamic): `%q2 = mqtopt.rx(%theta) %q1`; `mqtref.u(%t,%p,%l) %q`. +- Conversion (ref↔value): Reference variant lowers to value variant with SSA replacement; reverse drops result. -## 8. Matrix & User-Defined Gates +#### 5.2.2 Example: rx Gate -Matrix-based unitary expression is part of MVP (NOT deferred). +- Purpose: Single-qubit rotation about X by angle θ. +- Signatures: + - Ref: `mqtref.rx(%theta) %q : f64, mqtref.Qubit` + - Value: `%q_out = mqtopt.rx(%theta) %q_in : (f64, mqtopt.Qubit) -> mqtopt.Qubit` +- Static Example: `%q_out = mqtopt.rx(1.57079632679) %q_in` +- Dynamic Example: `%q_out = mqtopt.rx(%theta) %q_in` +- Canonicalization: `rx(0) → id`; two consecutive `rx(a); rx(b)` NOT folded (axis change would require Baker-Campbell-Hausdorff? skip); `inv rx(θ)` handled by modifier → `rx(-θ)`. +- Static Matrix Available: Yes if θ constant. -### 8.1 Matrix Unitary Operation +#### 5.2.3 Example: rzz Gate -Attribute: flat row‑major dense tensor `tensor<(2^(n)*2^(n)) x complex>`. +- Purpose: Two-qubit entangling gate `exp(-i θ/2 Z⊗Z)`. +- Signatures: + - Ref: `mqtref.rzz(%theta) %q0, %q1 : f64, mqtref.Qubit, mqtref.Qubit` + - Value: `%q0_out, %q1_out = mqtopt.rzz(%theta) %q0_in, %q1_in : (f64, mqtopt.Qubit, mqtopt.Qubit) -> (mqtopt.Qubit, mqtopt.Qubit)` +- Static Example: `%a1, %b1 = mqtopt.rzz(3.14159) %a0, %b0` +- Dynamic Example: `%a1, %b1 = mqtopt.rzz(%theta) %a0, %b0` +- Canonicalization: `rzz(0) → id`; `inv rzz(θ) → rzz(-θ)`. +- Static Matrix Available: Yes if θ constant. + +### 6. Modifier Operations + +### 6.1 Overview + +Modifiers wrap unitaries, extending functionality or altering semantics. + +- Semantics-preserving (e.g., `ctrl`, `negctrl`): canonicalized order, flattened. +- Transformative (e.g., `pow`, `inv`): applied last, may alter static matrix extraction. + +### 6.2 negctrl -2×2 example (Pauli-Y): +Purpose: Add negative controls. -```mlir -// Row-major: [0 -i ; i 0] -mqtref.unitary %q0 { matrix = dense<[0.0+0.0i, 0.0-1.0i, 0.0+1.0i, 0.0+0.0i]> : tensor<4xcomplex> } -``` +Operation Specification: -Value semantics: +- Purpose: Wrap a unitary adding negative (0-state) control qubits. +- Signatures: + - Ref: `mqtref.negctrl %negControls { }` + - Value: `%res_targets = mqtopt.negctrl %negControls { } -> ` +- Assembly: `negctrl { ... }`. +- Builder Variants: + - `build(builder, loc, resultTypes, negControlOperands, bodyBuilderFn)` (value) + - Reference variant omits results. +- Interface Notes: Aggregates controls into `getNumNegControls()`; targets delegated to child. +- Canonicalization: Flatten nested, remove empty, reorder relative to other modifiers to canonical chain `negctrl → ctrl → pow → inv`. +- Examples: + - Ref: `mqtref.negctrl %n0 { mqtref.h %t }` + - Value: `%t_out = mqtopt.negctrl %n0 { %t1 = mqtopt.rx(%theta) %t_in } -> mqtopt.Qubit` +- Conversion: Region body value results threaded / dropped analogously to other wrappers. + +### 6.3 ctrl + +Operation Specification: + +- Purpose: Add positive (1-state) controls. +- Signatures: + - Ref: `mqtref.ctrl %posControls { }` + - Value: `%res_targets = mqtopt.ctrl %posControls { } -> ` +- Builder: Similar to `negctrl` with positive control list. +- Interface: `getNumPosControls()` sums flattened list. +- Canonicalization: Merge nested, remove empty, optionally distribute over `seq`, enforce order after `negctrl`. +- Examples: + - Ref: `mqtref.ctrl %c { mqtref.rzz(%φ) %q0, %q1 }` + - Value: `%t_out = mqtopt.ctrl %c { %t1 = mqtopt.rz(%φ) %t_in } -> mqtopt.Qubit` +- Conversion: As for `negctrl`. + +### 6.4 pow + +Operation Specification: + +- Purpose: Exponentiation of a unitary body. +- Signatures: + - Ref: `mqtref.pow(expAttrOrOperand) { }` + - Value: `%res_targets = mqtopt.pow(expAttrOrOperand) { } -> ` +- Assembly: `pow() { ... }`. +- Builder Variants: integer attribute exponent; float attribute; dynamic f64 operand. +- Interface: `getPower()` returns rational/float wrapper; static detection when attribute. +- Canonicalization: Negative -> `inv(pow(abs))`; combine nested powers; remove exponent 1; exponent 0 -> identity passthrough; reorder with other modifiers. +- Examples: + - `%q2 = mqtopt.pow(2) { %q1 = mqtopt.rx(%theta) %q0 }` + - `%q2 = mqtopt.pow(%k) { %q1 = mqtopt.rz(%φ) %q0 }` +- Conversion: Same region adaptation logic. + +### 6.5 inv + +Operation Specification: + +- Purpose: Adjoint of unitary body. +- Signatures: + - Ref: `mqtref.inv { }` + - Value: `%res_targets = mqtopt.inv { } -> ` +- Builder: Provide body builder lambda. +- Interface: `isInverted()` true; nested inversion removed in canonicalization. +- Canonicalization: Double inversion removal; self-adjoint detection; distribute over `pow` forms (placing `inv` innermost after ordering); axis negation for parameterized rotations. +- Examples: `%t_out = mqtopt.inv { %t1 = mqtopt.u(%theta,%phi,%lambda) %t_in }` +- Conversion: Same as other wrappers. + +### 6.6 Nested Example + +Original value form (non-canonical): -```mlir -%q0_out = mqtopt.unitary %q0_in { matrix = dense<[0.0+0.0i, 0.0-1.0i, 0.0+1.0i, 0.0+0.0i]> : tensor<4xcomplex> } : !mqtopt.qubit +``` +%out = mqtopt.inv { %a = mqtopt.ctrl %c { %b = mqtopt.negctrl %n { %g = mqtopt.rx(%theta) %in } } } -> mqtopt.Qubit ``` -4×4 example (identity on two qubits): +Canonical extraction: negctrl(%n), ctrl(%c), inv. +Reordered canonical: -```mlir -mqtref.unitary %q0, %q1 { matrix = dense<[ - 1.0+0.0i, 0.0+0.0i, 0.0+0.0i, 0.0+0.0i, - 0.0+0.0i, 1.0+0.0i, 0.0+0.0i, 0.0+0.0i, - 0.0+0.0i, 0.0+0.0i, 1.0+0.0i, 0.0+0.0i, - 0.0+0.0i, 0.0+0.0i, 0.0+0.0i, 1.0+0.0i]> : tensor<16xcomplex> } +``` +%out = mqtopt.negctrl %n { + %t1 = mqtopt.ctrl %c { + %t2 = mqtopt.inv { %t3 = mqtopt.rx(%theta) %in } -> mqtopt.Qubit + } -> mqtopt.Qubit +} -> mqtopt.Qubit ``` -### 8.2 Gate Definitions (Symbolic / Composite) - -```mlir -// Composite definition -mqtref.gate_def @bell_prep %a : !mqtref.qubit, %b : !mqtref.qubit { - mqtref.h %a - mqtref.cx %a, %b -} +After folding `inv rx(%theta)` → `rx(-%theta)`: -// Application -mqtref.apply_gate @bell_prep %q0, %q1 +``` +%out = mqtopt.negctrl %n { + %t1 = mqtopt.ctrl %c { %t2 = mqtopt.rx(-%theta) %in } -> mqtopt.Qubit +} -> mqtopt.Qubit ``` -### 8.3 Parameterized Definitions +Reference nested example (explicit): -```mlir -// Parameters modeled as additional operands (value semantics for numeric params) -mqtref.gate_def @custom_rotation(%q: !mqtref.qubit, %theta: f64, %phi: f64) { - mqtref.rz %q, %phi : f64 - mqtref.ry %q, %theta : f64 - mqtref.rz %q, %phi : f64 +``` +mqtref.negctrl %n { + mqtref.ctrl %c { + mqtref.inv { mqtref.rx(%theta) %q } + } } - -%theta = arith.constant 1.57 : f64 -%phi = arith.constant 0.78 : f64 -mqtref.apply_gate @custom_rotation %q0, %theta, %phi : (!mqtref.qubit, f64, f64) ``` -Definitions themselves are control-neutral; wrapping ops add controls. - -### 8.4 Unitarity Validation +After folding: `mqtref.negctrl %n { mqtref.ctrl %c { mqtref.rx(-%theta) %q } }` -A dedicated validation pass (Matrix Unitary Validation) verifies numerical unitarity within tolerance; strict mode (optional) can enforce tighter bounds. +## 7. Sequence Operation (`seq`) ---- +Purpose: Ordered composition of unitary operations over region block arguments. -## 9. Parser Sugar & Builder APIs +Operation Specification: -### 9.1 Parser Sugar Examples +- Purpose: Represent composite product U = U_n … U_2 U_1 in region order. +- Signatures: + - Ref: `mqtref.seq (%args: mqtref.Qubit, ...) { mqtref.seq.yield }` + - Value: `%results = mqtopt.seq (%args: mqtopt.Qubit, ...) -> (mqtopt.Qubit, ...) { mqtopt.seq.yield %newArgs }` +- Assembly: `seq (arg_list) -> (result_types)? { ... }` +- Builders: Provide argument list + body builder capturing yields; value builder generates result types from argument types. +- Interface: Targets = block argument count; parameters aggregated from children (none directly); `hasStaticUnitary()` if all child ops static and compose cost acceptable. +- Canonicalization: Flatten nested, remove empty, inline single op, distribute controls from wrappers if beneficial, inversion reversal & child inversion patterns. +- Examples: -```mlir -// Sugar → canonical expansion -mqtref.cx %c, %t // expands to: mqtref.ctrl %c { mqtref.x %t } -mqtref.cz %c, %t // expands to: mqtref.ctrl %c { mqtref.z %t } -mqtref.ccx %c0, %c1, %t // expands to: mqtref.ctrl %c0, %c1 { mqtref.x %t } ``` - -### 9.2 C++ Builder (Sketch) - -```cpp -class QuantumCircuitBuilder { -public: - QuantumCircuitBuilder &x(Value q); - QuantumCircuitBuilder &h(Value q); - QuantumCircuitBuilder &cx(Value c, Value t); - QuantumCircuitBuilder &ctrl(ValueRange controls, function_ref body); - QuantumCircuitBuilder &negctrl(ValueRange controls, function_ref body); - QuantumCircuitBuilder &pow(double exponent, function_ref body); - QuantumCircuitBuilder &inv(function_ref body); -}; - -builder.h(q0) - .ctrl({c0}, [&](){ builder.x(q1); }) - .ctrl({c0, c1}, [&](){ builder.x(q2); }); +%q0_out, %q1_out = mqtopt.seq (%q0_in: mqtopt.Qubit, %q1_in: mqtopt.Qubit) + -> (mqtopt.Qubit, mqtopt.Qubit) { + %a0 = mqtopt.h %q0_in + %b0, %b1 = mqtopt.rzz(%θ) %a0, %q1_in + %c0 = mqtopt.rz(%φ) %b0 + mqtopt.seq.yield %c0, %b1 +} ``` ---- - -## 10. Canonicalization & Transformation Stages - -### 10.1 Normalization (Early Canonical Form) - -Responsibilities (always first): - -- Enforce modifier order `ctrl` → `negctrl` → `pow` → `inv`. -- Flatten nested same-kind control wrappers (aggregate control lists) while preserving neg/pos distinction. -- Merge adjacent compatible `pow` / eliminate identities (`pow(exp=1)`, `inv(inv(X))`). -- Algebraic simplifications (e.g., `pow(RZ(pi/2), 2) → RZ(pi)`). -- Remove dead identity operations (e.g., `pow(exp=0)` → (implicit identity) if allowed by semantics). -- Prepare IR for subsequent named gate passes. - -### 10.2 Named Simplification - -- Convert `U3/U2/U1` parameter sets to simplest named gate when within numeric tolerance. -- Improves readability & hashing stability. - -### 10.3 Universal Expansion - -- Expand named single-qubit gates to canonical `U3` (backend / pipeline selectable). -- Typically applied only in universal backends or downstream lowering flows. - -### 10.4 Matrix Validation - -- Verify matrix attribute size matches `2^(n)*2^(n)`. -- Check numerical unitarity within tolerance (fast path for 2×2 & 4×4). - -### 10.5 (Deferred) Matrix Decomposition +- Conversion: Reference ↔ value via region argument threading and yields. -- Future: Decompose large / arbitrary matrix unitaries into basis gates (NOT in MVP). +## 8. User Defined Gates & Matrix / Composite Definitions -**No pass rewrites `negctrl` into positive controls.** +### 8.1 Matrix Gate Definitions ---- +Define symbol with matrix attribute (dimension 2^n × 2^n): -## 11. Pass Inventory - -| Pass | Purpose | Phase | Idempotent | Mandatory | Notes | -| --------------------------- | ------------------------------------------------------------------------------------ | ------------------------ | ------------------------ | --------------------------- | ------------------------------------- | -| NormalizationPass | Enforce modifier order; merge & simplify; flatten controls; early algebraic cleanups | Canonicalization (first) | Yes | Yes | Integrated with canonicalize pipeline | -| NamedSimplificationPass | Replace `U3/U2/U1` with simplest named gate | Simplification | Yes (post-normalization) | Baseline (recommended) | Tolerance-based | -| UniversalExpansionPass | Expand named gate → `U3` | Lowering / Backend Prep | Yes | Optional | Backend / pipeline controlled | -| MatrixUnitaryValidationPass | Verify matrix size + unitarity | Verification / Early | Yes | Yes (if matrix ops present) | Fast paths 2×2 & 4×4 | - -### Deferred (Not MVP) - -- Matrix decomposition pass -- Basis gate registry -- Extended trait inference for composites & matrix ops -- Advanced symbolic parameter algebra - ---- - -## 12. Dialect Conversions - -### 12.1 `mqtref` → `mqtopt` - -```cpp -// Side-effect → value threading -mqtref.x %q → %q_new = mqtopt.x %q : !mqtopt.qubit - -// Controlled example -mqtref.ctrl %c { mqtref.x %t } → - %c_out, %t_out = mqtopt.ctrl %c, %t { - %t_new = mqtopt.x %t : !mqtopt.qubit - mqtopt.yield %t_new : !mqtopt.qubit - } : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) ``` - -### 12.2 `mqtopt` → `mqtref` - -```cpp -%q_out = mqtopt.x %q_in : !mqtopt.qubit → mqtref.x %q_in -// Drop result, preserve ordering & effects +mqt.gatedef.matrix @myPhase(%lambda: f64) targets(1) + attr_matrix = #mqt.matrix<2x2>( ... symbolic in %lambda ... ) ``` -**Challenges:** Ensuring correct dominance & preserving semantic ordering during un-nesting / region translation. - ---- - -## 13. Testing Strategy +Matrix may embed symbolic expressions referencing parameters (internal representation detail). -Priority shift: structural & semantic equivalence via builders (googletest) > textual pattern checks. +### 8.2 Composite Gate Definitions -### 13.1 Structural / Semantic Tests (Primary) +Sequence-based body yields outputs (value semantics): -- Use IR builders to construct original & expected forms, run normalization + optional passes, then compare via: - - Shape / op sequence equivalence (ignoring SSA names). - - Control & target counts via `UnitaryOpInterface`. - - Optional unitary matrix equivalence (numerical tolerance) for small ops. -- Idempotence: Run NormalizationPass twice; assert no further changes. +``` +mqt.gatedef.composite @entang(%theta: f64) + ( %a: mqtopt.Qubit, %b: mqtopt.Qubit ) -> (mqtopt.Qubit, mqtopt.Qubit) { + %a1 = mqtopt.h %a + %a2, %b1 = mqtopt.rzz(%theta) %a1, %b + mqtopt.seq.yield %a2, %b1 +} +``` -### 13.2 Parser / Printer Smoke (Minimal Textual Tests) +Reference semantics variant has same region arguments but no gate results. -- Round-trip for: base gates, each modifier, nested modifier chain, matrix unitary, composite definition, sequence. -- FileCheck limited to presence/absence of key ops (avoid brittle SSA checks). +### 8.3 Unified Apply Operation -Example smoke test snippet: +Value: -```mlir -// CHECK-LABEL: func @nested_mods -func.func @nested_mods(%c: !mqtref.qubit, %n: !mqtref.qubit, %t: !mqtref.qubit) { - mqtref.ctrl %c { - mqtref.negctrl %n { - mqtref.pow {exponent = 2.0 : f64} { - mqtref.inv { mqtref.x %t } - } - } - } - return -} +``` +%a_out, %b_out = mqtopt.apply @entang(%theta) %a_in, %b_in +%q1 = mqtopt.apply @myPhase(%lambda) %q0 ``` -### 13.3 Utility Helpers (C++) - -- `assertEquivalent(Op a, Op b, EquivalenceOptions opts)` -- `buildNestedModifiers(builder, patternSpec)` -- Matrix validation harness (inject near-unitary perturbations and assert failures). +Reference: -### 13.4 Negative Tests +``` +mqtref.apply @entang(%theta) %a, %b +mqtref.apply @myPhase(3.14159) %q +``` -- Malformed matrix size -- Non-unitary matrix beyond tolerance -- Disallowed modifier order (e.g., `inv` wrapping `ctrl` directly → should be reordered by normalization) +Operation Specification: -### 13.5 Coverage Summary +- Purpose: Apply user-defined matrix or composite gate symbol. +- Signatures: + - Ref: `mqtref.apply @symbol(param_list?) %targets` + - Value: `%results = mqtopt.apply @symbol(param_list?) %inputs` +- Assembly: `apply @name() ` +- Builder Variants: + - For matrix gate: auto infer target count from definition. + - For composite: verify arity against definition region signature. + - Parameter list builder with static attribute injection. +- Interface Notes: + - `getNumTargets()` from definition signature. + - Parameters enumerated exactly as in definition. + - `hasStaticUnitary()` true for matrix gate; composite conditional. +- Canonicalization: Identity matrix removal; trivial single-op composite inlining; repeated static collapses per algebraic rules. +- Examples: + - Static parameter: `%q1 = mqtopt.apply @myPhase(3.14159) %q0` + - Dynamic parameter: `%q1 = mqtopt.apply @myPhase(%lambda) %q0` +- Conversion: Reference ↔ value semantics by adding/removing results and adjusting uses. -All ops (base, modifiers, sequences, unitary/matrix, composite definitions) must have: +## 9. Parser & Builder Sugar -- Builder creation tests -- Normalization idempotence tests -- Interface query sanity tests +### 9.1 Sugar Expansions ---- +- `cx %c, %t` → `mqtopt.ctrl %c { %t_out = mqtopt.x %t }` (value) +- `cz %c, %t` → `ctrl %c { z %t }` +- `ccx %c0, %c1, %t` → `ctrl %c0, %c1 { x %t }` + Parser lowers sugar to canonical IR; printer may re-sugar canonical forms that match patterns. -## 14. Analysis & Optimization Infrastructure +### 9.2 Fluent Builder API (Conceptual) -### 14.1 `UnitaryMatrix` Utility (Sketch) +Examples: -```cpp -class UnitaryMatrix { - // Variant representations: small fixed (2x2, 4x4), symbolic, lazy product -public: - UnitaryMatrix compose(const UnitaryMatrix &rhs) const; - UnitaryMatrix adjoint() const; - UnitaryMatrix power(double exponent) const; - UnitaryMatrix control(unsigned numPos, unsigned numNeg) const; - DenseElementsAttr toDenseElements(MLIRContext* ctx) const; - bool isIdentity() const; - bool isUnitary(double tol = 1e-10) const; -}; +``` +withControls({c1,c2}).gate("x").on(t) +withNegControls({n}).gate("rz").param(phi).on(t) +withPow(3).gate("h").on(q) +withInv().gate("u").params(a,b,c).on(q) +sequence({}) RAII style for `seq` +defineMatrixGate("myPhase").params({lambda}).targets(1).matrix(attr) +defineCompositeGate("entang").params({theta}).targets(2).body([]{...}) +apply("entang").params(theta).on(q0,q1) ``` -Fast paths for 2×2 & 4×4 feed both transformation heuristics and validation. - ---- +Chaining order auto-normalized to canonical modifier order. -## 15. Canonical Identities & Algebraic Rules (Non-Exhaustive) +## 10. Testing Strategy -Guaranteed (within normalization) where semantics preserved: +- Structural: verify canonical modifier order and flattening. +- Matrix correctness: `U†U = I` for static extractions. +- Interface conformance: each op's counts (targets, controls, params) correct; mapping output→input identity for pure unitaries. +- Canonicalization idempotence: run pass twice, IR stable. +- Sugar round-trip: parse sugar → canonical → print (optionally sugar) with equivalence. +- Folding tests: `rx(0)`, `pow(1){}`, `pow(0){}`, `inv(inv(U))`. +- Negative exponent normalization tests. +- Sequence inversion correctness via static matrix comparison for small systems. +- Apply inlining & identity elimination tests. +- Negative tests: arity mismatch, invalid matrix dimension, non-unitary matrix, duplicate symbol definition, parameter count mismatch. -- `inv(inv(X)) → X` -- `pow(X, 1) → X` -- `pow(X, 0) → identity` (subject to representation policy; may drop operation) -- `pow(inv(X), k) ↔ inv(pow(X, k))` (normalized placement enforces `pow` outside `inv` → `pow(inv(X), k)` canonicalizes to `pow` wrapping `inv` only if order rule maintained) -- Consecutive `pow` merges: `pow(pow(X, a), b) → pow(X, a*b)` -- Control aggregation: nested `ctrl` of `ctrl` flattens; same for nested `negctrl`; mixed pos/neg preserve order lists separately. +## 11. Integrated Canonicalization Rules Summary -No rule rewrites `negctrl` into a positive control sandwich. +(Definitions live inline above; this section aggregates references.) ---- +- Base Gates: parameter folds, identity elimination, specialization to named gates. +- `negctrl`: flatten & remove empty. +- `ctrl`: flatten, remove empty, distribute over `seq` when beneficial. +- `pow`: normalize negatives, remove trivial exponents, combine nested powers. +- `inv`: double-inversion removal, specialize to inverses/self-adjoint forms. +- Modifier Ordering: reorder to `negctrl → ctrl → pow → inv`. +- `seq`: flatten, remove empty, inline single-op. +- `apply`: inline trivial composite, fold identities, merge repeated static applies. +- Sugar: lower to canonical, optional re-sugar on print. -## 16. Conclusion +## 12. Conclusions and Future Work -The revamp solidifies a compositional, analyzable, and canonical quantum IR: +Resolved: -- Modifier wrappers with a fixed canonical order including explicit negative controls. -- Early, mandatory normalization ensuring deterministic structure for all downstream passes. -- Rich named gate ecosystem retained alongside matrix-based unitaries (with validation) in the MVP. -- Bidirectional single-qubit canonicalization allows readability and backend flexibility. -- Testing focus shifts to robust builder-based structural equivalence; textual tests minimized. +- Unified `UnitaryExpr` abstraction with full modifier and composition support. +- Static + dynamic parameter integration and robust interface methods. +- Canonical nested modifier ordering and explicit rewrite rules. +- User-defined gates (matrix & composite) plus unified `apply` op. +- Dual semantics (`mqtref`, `mqtopt`) standardized. -This plan supersedes earlier notions that matrix gates or negative controls might be deferred or normalized away. The resulting infrastructure provides a stable foundation for future extensions (matrix decomposition, basis registries, advanced trait inference) without compromising current clarity or performance. +Future Work (concise): -No open questions remain for the MVP scope defined here. +- Basis decomposition (e.g., KAK, ZX-assisted) using `UnitaryExpr` graphs. +- Shared control extraction & factoring. +- Symbolic algebra simplifications (parameter expression normalization). +- Hardware mapping leveraging `static_qubit` references. diff --git a/docs/mlir/quantum-dialect-revamp-tracking-issue.md b/docs/mlir/quantum-dialect-revamp-tracking-issue.md deleted file mode 100644 index 760453fed9..0000000000 --- a/docs/mlir/quantum-dialect-revamp-tracking-issue.md +++ /dev/null @@ -1,265 +0,0 @@ -# Tracking: Quantum Dialect Revamp (Self‑Contained) - -This issue tracks the design and implementation of a revamped MLIR quantum dialect stack for MQT. It is self‑contained and does not assume any other local files exist. - -## Context and Goals - -We maintain two quantum dialects: - -- mqtref — reference/memory semantics -- mqtopt — value semantics - -Problems with the current setup: - -- Modifiers (controls, inverse/adjoint, power) are baked into each gate op. -- Composition is clumsy; interfaces are not uniform across dialects. -- Builders are verbose; parser/printer sugar is limited. - -High‑level goals: - -- Make composition first‑class: explicit IR ops for modifiers that can wrap gates or sequences and lazily defer evaluation. -- Provide a single, idiomatic UnitaryOpInterface for analysis across both dialects. -- Dramatically improve ergonomics via a generic builder template and parser/printer sugar (e.g., `cx`, `ccx`, `mcx`, `mcp(theta)`). -- Keep dialects minimal and uniform; push optional features into wrappers/regions. -- Prefer MLIR‑native infrastructure (traits, interfaces, canonicalization, folds, symbol tables). -- QubitRegisters have been replaced by memrefs; only Qubit is used here. - -## Architecture Overview - -- Common layer - - Traits and utilities shared by both dialects (TargetArity, ParameterArity, NoControl, Hermitian, Diagonal). - - A unified UnitaryOpInterface with dialect adapters (exposes both operands and results for value semantics). - - Parser/printer utilities for modifiers, parameters, sequences, and sugar. - - A lightweight UnitaryExpr library is planned for transformation/conversion passes (no external dependencies, fast 2×2/4×4 paths). Its concrete implementation is deferred to execution of the plan. -- mqtref dialect (reference semantics) - - BaseGate ops with only targets and parameters (no embedded modifiers/controls). - - Resource ops (alloc/dealloc, static qubit), reset, measure. - - Sequence and Modifier wrapper ops. -- mqtopt dialect (value semantics) - - Mirrors mqtref base set but with value results for targets. - - Sequence and Modifier wrapper ops with linear threading of all qubits, including controls. - -## Marker Traits (Common) - -- Hermitian — self‑inverse gates (e.g., I, X, Y, Z, H). Canonicalization short‑circuit: `inv(G) -> G`. -- Diagonal — diagonal in computational basis (e.g., I, Z, S, Sdg, T, Tdg, Rz, Phase, RZZ). Enables commutation/aggregation analyses. - -## Types - -- Qubit is the only type used by gates in this revamp. -- QubitRegister is assumed absent and will be handled in a separate PR. - -## Base Gate Ops (Both Dialects) - -- Minimal, uniform base gates with fixed target arity (TargetArity) and parameter counts (ParameterArity). No control operands or modifier attributes. -- Parameters prefer static attributes; constant folding turns dynamic constant operands into static attributes. No mask for `pow`. -- Each base gate provides its UnitaryExpr, and can materialize a DenseElementsAttr when fully static (no arity limit by design). -- Base-gate placement decision: keep base gates per dialect (mqtref and mqtopt); do not introduce a shared executable base-gate dialect. Rationale: semantics mismatch (operands-only vs operands+results) would force optional results/wrappers and complicate verifiers/canonicalizations; duplication is minimized via shared traits/interfaces, a Common TableGen catalog, and generated builders. For custom gates, use symbol-based unitary.def/apply. - -## Modifiers - -Explicit wrapper ops that accept a single unitary op (gate or sequence) in a region with implicit terminator: - -- inv — adjoint of the child unitary -- ctrl / negctrl — positive/negative controls (variadic) -- pow(r) — real exponent; prefer static attribute - -Canonicalization and folds: - -- inv(inv(X)) -> X -- inv(pow(X,k)) -> pow(inv(X), k) -- inv(ctrl(X)) -> ctrl(inv(X)) -- pow(X,1) -> X; pow(X,0) -> identity -- pow(pow(X,a), b) -> pow(X, a\*b) -- pow(X, -k) -> pow(inv(X), k) -- inv(baseGate) -> baseGateInverse (each base gate defines its inverse; parameters updated) -- Controls are outermost modifiers; inverse is innermost. Rewriters normalize order. -- Controls are not deduplicated or reordered by canonicalization; duplicates are rejected by verifiers. -- In mqtopt, controls are linear SSA values and must be consumed/produced (not forwarded unchanged). - -## Sequence Op - -- `seq` is a region (single block, implicit terminator) containing only UnitaryOpInterface ops (including modifiers and nested sequences). -- Canonicalization: inline nested sequences; remove empty sequences; fuse adjacent sequences; move `inv` across sequences by reversing order and adjointing. -- Querying: `getAllOperandQubits()` and `getAllResultQubits()` (mqtopt) expose the overall set of qubits the sequence acts on. `seq` itself does not distinguish controls vs targets. -- The UnitaryExpr of a `seq` is the product of child unitaries in program order (M_n … M_2 M_1). - -## Unified UnitaryOpInterface (Common) - -Idiomatic methods (dialect‑adaptive): - -- Identification/meta: `getIdentifier()`, `getNumTargets()`, `getNumControls()`, `hasControls()`, `isSingleQubit()`, `isTwoQubit()`, `hasParams()`, `hasDynamicParams()`, `isOnlyStaticParams()`. -- Operands/results: `getTargetOperands()`, `getPosControlOperands()`, `getNegControlOperands()`, `hasTargetResults()`, `getTargetResults()`, `getPosControlResults()`, `getNegControlResults()`. -- Aggregates: `getAllOperandQubits()`, `getAllResultQubits()`. -- Unitary: `getUnitaryExpr()`. -- Value‑semantics verification ensures in/out segment sizes match for targets and controls. - -## Arbitrary Unitaries and Gate Definitions - -Not all operations are covered by base ops. The IR supports user‑defined unitaries in two complementary ways. Definitions are declared once (as symbols) and instantiated many times. - -- Matrix‑defined unitary (feasible for small n): supply a 2^n×2^n complex matrix attribute. Target arity n is inferred from the matrix shape. Parameters may be modeled via attributes; prefer static folding. No arity limit on materialization. -- Composite‑defined unitary: supply a region with a `seq` of known gates and modifiers; formal parameters (f64) and formal qubit arguments are allowed. This covers parameterized gates universally. - -IR surface (per dialect; names are idiomatic to MLIR): - -- `mqtref.unitary.def` (SymbolOpInterface, optional matrix attr, optional body region; body contains `mqtref.seq`). -- `mqtref.unitary.apply @sym (params?)` on target qubits; no results (reference semantics). Modifiers can wrap apply. -- `mqtopt.unitary.def` (mirror using value‑semantic ops; body must thread values linearly). -- `mqtopt.unitary.apply @sym (params?)` on target qubit values; yields updated target values. - -Verification: - -- Matrix shape must be square and power‑of‑two; infer n. If both matrix and region are provided, check consistency when possible. -- Regions must contain only UnitaryOpInterface ops and correct argument/result counts. - -Canonicalization: - -- Inline small composite definitions at call sites when profitable to expose further simplifications. -- Materialize matrix‑defined unitaries to UnitaryExpr on demand. - -## Builders and Parser/Printer Sugar - -- Generic builder template that accepts targets as Span and parameters as a ParamSet, dispatching based on trait arities; layered overloads provide ergonomic APIs: `x(q)`, `rx(q, theta)`, `cx(c, t)`, `ccx(c0, c1, t)`, `mcx(ctrls, t)`, `mcp(ctrls, t, theta)`. -- Parser sugar for: `cx`, `cz`, `ccx`, `ccz`, `mcx`, `mcz`, `mcp(theta)`; nested forms like `ctrl(%c) inv rx(%q) (pi/2)` print and round‑trip. - -## Testing Strategy and Robustness - -- TDD first: write failing tests (LIT and C++) before implementation; keep them small and focused. -- Prefer round‑trip `mlir-opt` tests; normalize with `-canonicalize -cse` when textual formatting is not the target of the test. -- Use `-split-input-file` to group many small tests; use `-verify-diagnostics` for negative cases with `// expected-error`. -- Avoid brittle SSA numbering in FileCheck; anchor on op names/attributes and use `CHECK-LABEL`, `CHECK-SAME`, `CHECK-DAG` appropriately. Use `-mlir-print-op-generic` where necessary for stability. -- Add C++ unit tests that parse/build IR programmatically and verify via Operation::verify() and pass runs; this catches checkstring mistakes by asserting structural invariants. - -## Verification and Canonicalization Summary - -- Base gates: enforce TargetArity and ParameterArity; static parameter preference and folding. -- Modifiers: enforce single child unitary; linear control threading in mqtopt; pow has no mask. -- Sequence: implicit terminator; only unitary ops allowed. -- Normal form: controls outermost; inverse innermost; merges of adjacent like‑modifiers; empty controls dropped; nested sequences flattened. -- Hermitian/Diagonal traits guide short‑circuits and analyses. - -## Passes and Conversions - -- Normalization, ControlPushdown, AdjointPropagation, ParamConstFold passes. -- mqtref → mqtopt conversion: map base gates/modifiers; `seq` with implicit terminator; thread controls linearly and produce fresh SSA results (including for controls). -- mqtopt → mqtref conversion: erase value results; controls become operands only; maintain modifier/sequence structure. - -## Milestones and Tasks - -Use this checklist to plan and track progress. - -M1 — Foundations (Common) - -- [ ] Unified UnitaryOpInterface (Common). -- [ ] Parser/Printer utilities for params, modifiers, sequences, sugar. -- [ ] Marker traits: Hermitian, Diagonal. -- [ ] UnitaryExpr library skeleton planned (implementation deferred to execution of the plan). - -M2 — mqtref Base + Modifiers + Sequence - -- [ ] Redefine base UnitaryOp (no embedded controls). -- [ ] Implement modifiers: ctrl/negctrl/inv/pow with implicit terminators. -- [ ] Implement seq with implicit terminator; inliner + canonicalization. -- [ ] Implement unitary.def (symbol) and unitary.apply (instantiation). -- [ ] Update std gate defs and assembly formats; add sugar (cx/cz/ccx/ccz/mcx/mcz/mcp). -- [ ] LIT tests: ops, parsers, canonicalization, unitary.def/apply. - -M3 — mqtopt Mirror - -- [ ] Redefine base gate ops with value results for targets. -- [ ] Modifiers with linear control threading; seq with implicit terminator. -- [ ] Implement unitary.def (symbol) and unitary.apply (instantiation) with value semantics. -- [ ] Interface/verifier updates for in/out segment size equality. -- [ ] LIT tests mirroring mqtref, including unitary.def/apply. - -M4 — Builders and Ergonomics - -- [ ] Generate CircuitBuilder helper API (generic template + overloads). -- [ ] Parser/printer sugar for compact modifier nesting and controlled variants. -- [ ] C++ smoke tests for builders. - -M5 — Passes and Conversions - -- [ ] Implement Normalization, ControlPushdown, AdjointPropagation, ParamConstFold. -- [ ] Update conversions mqtref↔mqtopt for modifiers/sequences and linear controls. -- [ ] Tests for conversions and passes. - -M6 — Documentation and Polishing - -- [ ] Ensure all ODS docstrings have fenced MLIR examples. -- [ ] Update user docs and examples; final test stabilization. - -## Acceptance Criteria - -- [ ] Dialects compile; LIT tests cover base gates, modifiers, sequences, unitary.def/unitary.apply, and sugar. -- [ ] Unified UnitaryOpInterface used across unitary‑capable ops, exposing operand/result views as appropriate. -- [ ] Arbitrary unitary support: symbol‑based unitary.def with matrix‑ and composite‑defined forms; unitary.apply in both dialects; modifiers wrap applications. -- [ ] Builder API (template + overloads) with sugar for cx, cz, ccx/ccz, mcx/mcz/mcp. -- [ ] Canonicalization suite maintains normal form; mqtopt controls are linearly threaded and not forwarded unchanged. -- [ ] Conversions mqtref↔mqtopt handle modifiers/sequences and control threading. - -## Examples (MLIR) - -mqtref, modifiers and sugar: - -```mlir -// Single‑controlled rotation -mqtref.crx %c, %t (1.234) - -// Double‑controlled Toffoli -mqtref.ccx %c0, %c1 %t - -// Multi‑controlled X and P(theta) -mqtref.mcx(%c0, %c1, %c2) %t -mqtref.mcp(1.234)(%c0, %c1) %t - -// Nested modifiers -mqtref.ctrl(%c) mqtref.inv mqtref.rx(%q) (3.14159) -``` - -mqtopt, sequence and linear controls: - -```mlir -%q0 = mqtopt.allocQubit -%c = mqtopt.allocQubit -mqtopt.seq { - %q0_1 = mqtopt.h %q0 - // ctrl threads control linearly: consumes %c, yields %c_1 - %q0_2, %c_1 = mqtopt.ctrl(%c) { mqtopt.x %q0_1 } -} -``` - -User‑defined unitaries: - -```mlir -// mqtref: define via region and apply -mqtref.unitary.def @H2 { - ^entry(%q: !mqtref.Qubit): - mqtref.seq { - mqtref.rx(%q) (1.5707963267948966) - mqtref.z %q - } -} -mqtref.unitary.apply @H2 %q0 -mqtref.ctrl(%c) { mqtref.unitary.apply @H2 %q1 } - -// mqtopt: parameterized composite definition and application -mqtopt.unitary.def @CRZ(%theta: f64) { - ^entry(%q0: !mqtopt.Qubit, %q1: !mqtopt.Qubit): - mqtopt.seq { - %q1_1 = mqtopt.rz(%q1) (%theta/2.0) - %q0_1, %q1_2 = mqtopt.cnot %q0, %q1_1 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) - %q1_3 = mqtopt.rz(%q1_2) (-%theta/2.0) - %q0_2, %q1_4 = mqtopt.cnot %q0_1, %q1_3 : (!mqtopt.Qubit, !mqtopt.Qubit) -> (!mqtopt.Qubit, !mqtopt.Qubit) - } -} -%q0_1, %q1_1 = mqtopt.unitary.apply @CRZ(%q0, %q1) (3.14159) -%q0_2, %q1_2 = mqtopt.ctrl(%c) { mqtopt.unitary.apply @CRZ(%q0_1, %q1_1) (0.25) } -``` - -## Notes - -- Controls must be outermost modifiers; inverse innermost; power in between. Rewriters enforce normalized order. -- Controls are not deduplicated by canonicalization; verifiers reject duplicates. -- QubitRegister is assumed absent in this revamp; only Qubit is used. A separate PR will replace any remaining register usage with memref. diff --git a/mlir-quantum-dialect.md b/mlir-quantum-dialect.md deleted file mode 100644 index b1cec37499..0000000000 --- a/mlir-quantum-dialect.md +++ /dev/null @@ -1,38 +0,0 @@ -What I want to be able to describe: - -- A `Qubit` type including operations to dynamically `allocate` and `deallocate` qubits as well as a static `qubit` operation that yields a qubit reference from an index -- A `reset` operation that acts on a qubit -- A `measure` operation that takes a qubit and produces an `i1` classical measurement result -- A way to describe unitary operations that drive the quantum computation. - -The last part is the most important and critical part as it forms the center of the quantum dialect. -These operations (also referred to as quantum gates or simply gates) have the following properties - -- they are unitary, which is an essential trait to match to -- each type of gate has a compile time fixed number of target qubits (typically one or two). This is regulated by a target arity trait at the moment, which doesn't seem perfectly feasible, but maybe it is. -- each type of gate may have a compile time fixed number of parameters. This is currently indicated similarly to the target arity by a parameter arity trait. Parameters may either be defined statically or dynamically through values. Mixtures are possible. Canonicalization should ensure that parameters that can be statically defined are indeed statically defined. -- each type of gate has a unitary matrix associated with it. The size of the matrix is 2^n\*2^n with n being the target arity. For gates without parameters or with only static parameters this is compile-time fixed and known. For gates with dynamic parameters, this description is symbolic (mostly in terms of rotation angles and trigonometric functions). -- several types of modifiers may be applied to unitary gates to transform and extend them. An `inv` (or `adj`) modifier can be added to invert the gate, which corresponds to forming the adjoint of the underlying matrix. A control modifier may be used to add a variable list of qubits as control qubits to the operation. Control qubits may be positive or negative, which means they trigger on the control qubit being in state |1> or |0>, respectively. Last but not least, the `pow(r)` powering modifier can be used to compute powers of the unitary gate. For simplicity, `r` is mostly assumed to be an integer, but any real number is feasible through computing the principle logarithm. Modifiers are generally evaluated lazily, that is, they are simply tagged onto the gate and only resolved once such a resolution is necessary. There should be a way to query the effective unitary of a gate, which takes into account the modifiers. Modifiers should have canonicalization rules. They all commute. Control qubits must be unique and multiple control modifiers can be combined into one. Two inverses cancel another. Negative powers can be translated to an inverse modifier and the positive power. powers of 1 can be removed. powers of 0 can be replaced by an identity gate. - -The difference between the MQTRef and the MQTOpt dialects is that the MQTRef dialect uses reference/memory semantics for its operations, while the MQTOpt dialect uses value semantics. The MQTOpt dialect is used for optimization and transformation passes, while the MQTRef dialect is used for initial representation and code generation. - -I want to (at least) - -- query an operation for the qubits it acts on -- query an operation whether it is a single-qubit gate (single target, no controls), two-qubit, single-target, two-target, no-controls, no-parameters, only static parameters, or dynamic parameters gate. -- query an operation for its underlying unitary -- apply `inv`, `ctrl`, `negctrl` and `pow(r)` modifiers to the gates -- conveniently construct individual basis gates in C++ without much coding overhead (e.g. mqtref.swap(q0, q1) or mqtref.rx(q0, theta)); the current setup makes this increadibly hard through the builder interface. The target and parameter arity traits should provide the necessary information to generate the correct builder overloads. -- have a construct for sequences of (unitary) gates that modifiers can also be applied to. these "compound" operations or sequences may only contain unitary operations. These should be inlinable in an MLIR native way. Canonicalization should remove empty sequences and merge nested sequences as part of inlining. -- have convenient shortcuts for constructing common gates such as the CNOT (=ctrl X) or CZ (=ctrl Z) and potentially have some syntactic sugar for these. - -### Guiding principles - -- Prefer idiomatic MLIR: keep base ops minimal and uniform, push optional features into wrapper ops/regions, and express semantics via interfaces and canonicalization rather than baking everything into each gate. -- Make composition first-class: modifiers are explicit IR constructs that can wrap single gates or entire sequences, and they compose/lazily defer. -- Ergonomics: provide sugar in the parser/printer and rich C++ builders so that writing and constructing circuits is pleasant. -- Analysis-friendly: expose queries through a single interface so passes never need to pattern-match concrete ops. -- If possible, there shouldn't be too much duplication between both dialects as they essentially share a lot of structure. -- Make use of MLIR-native traits and interfaces wherever they are useful. For example, `AttrSizedOperandSegments` or `InferTypeOpInterface`. -- Provide canonicalization where they make sense to keep the IR normalized and simple. -- Provide verifiers for IR constructs diff --git a/prompt.md b/prompt.md deleted file mode 100644 index c836816bc2..0000000000 --- a/prompt.md +++ /dev/null @@ -1,54 +0,0 @@ -I am working on an MLIR dialect hierarchy for hybrid classical quantum computing. I have a working version of the two quantum dialects, but I am not happy because it is cumbersome to use and lack a lot of features. - -The relevant files are contained solely in the `/Users/burgholzer/CLionProjects/mqt-core/mlir` directory. - -The mlir-quantum-dialects.md file contains a detailed sketch of requirements for the envisioned architecture. - -Take the existing setup as inspiration and think very hard and deep on how to replace it with a setup that facilitates the above requirements. -Nothing in the existing setup is set in stone. -There needs to be no migration path or backwards compatibility. -A fundamental rework is possible. - -Do not (yet) perform any changes on the MLIR code itself. Rather provide an extensive implementation plan in a separate Markdown document. - ---- - -I'd like to do one more iteration on the project plan before starting any edits. Address the following points - -- QubitRegisters will be entirely replaced by using `memref` in a separate PR. The respective code here should not be touched as part of this refactor and the project plan might as well assume that QubitRegisters do not exist in both dialects -- I am unsure how uniform the UnitaryInterface can really be given how the dialects use different semantics and there would need to be a way to query input und output operands of the MQTOpt operations -- I like `UnitaryExpr` more than `UnitarySpec`. This description should really be as idiomatic as possible. I do not want to reinvent the wheel here. At the same time, I want to avoid further external dependencies. Most matrices will be 2*2 or 4*4 here and I need to be able to multiply them. It is very important that this is performant but lightweight. These matrices are truly only to be used for transformation or conversion passes. -- Instead of inv(pow(X, k)) -> pow(X, -k), perform inv(pow(X, k)) -> pow(inv(X), k) as a canonicalization -- Control SSA values should not be simply yielded as unchanged. Qubits should generally be treated as linear types wherever feasible -- The power modifier does not need a mask -- Perform pow(X, -k) -> pow(inv(X), k) as canonicalization -- Controls should be the outermost modifiers, inverse the innermost. -- Inverse modifiers can be canonicalized with base gates by changing the gate to the inverse. Each base gate has its inverse defined as a base gate (which might be the same gate but with different parameters) -- Allow even more syntactic sugar, e.g. mqtref.ccx %c0, %c1 %q -- Terminators of regions should be implicit and need not be explicit. -- The notion of controls and targets does not really make sense for `seq` operations. However, it makes sense to query the overall qubits a seq acts on. -- The UnitaryExpr of a `seq` operation is the product of the `UnitaryExpr` of the child operations. -- try to pick idiomatic names for the interface functions with regard to MLIR, C++ and Quantum Computing -- "Alternatively, keep a generic template that accepts targets as Span and parameters as ParamSet and dispatches based on trait arities; then layer nicer overloads on top." i like this solution -- make sure to always include examples in fenced mlir code blocks in the to be generated docstrings -- use canonicalization and folds as much as possible and idiomatic for MLIR. The goal should be to use as much of MLIR's native infrastructure as possible. -- there is no need to provide a temporary helper conversion. All code is expected to be rewritten and fixed - ---- - -One last round of revisions and additions: - -- I want to add an additional trait for "Hermitian" gates, i.e., gates that are self-inverse. These may be used as a short-circuit in inverse canonicalization. Popular hermitian operations are: I, X, Y, Z, H -- I want to add an additional trait for diagonal gates, i.e., gates whose underlying matrix is diagonal in the computational basis. Prominent examples include: I, Z, S, Sdg, T, Tdg, Rz, Phase (or P), RZZ -- parameters of base gate ops should be preferred to be static, constant folding (or another appropriate MLIR concpet) should be responsible for transforming dynamic operands that are constants to static attributes. -- do not put a limit on the arity for the constant DenseElementsAttr for the unitary. This will hold in practice by construction -- controls do not need deduplication as they must not be duplicated from the beginning, as also ensured by a verifier -- getQubitSet is redundant if `getAllOperandQubits()` and `getAllResultQubits()` are implemented properly. Qubits that are not acted on by a seq should not be part of the operands/results of the seq. - ---- - -A couple of further requirements to factor into the plan: - -- There should be extensive documentation on all the relevant design decisions that went into this project plan. This documentation should be present in the doxygen comments that will be included in the overall project documentation build. In addition, more elaborate rationale; especially on aspects that do not end up in the doxygen comments, should be added to the mlir docs infrastructrure under `docs/mlir`. The result should be a gentle introduction to all the necessary concepts (also including the ones not necessarily modified in this plan). The writing should be of a quality that could go into a (software focused) scientific journal. Always try to include examples in the documentation. -- Check the project plan for consistency and correct any ambiguity. -- I want to post the project plan as a high-level tracking issue on GitHub. Prepare it for that, e.g., replace all local paths with relative paths. From f7ba8209fc9cfc2b173aaeb78e62b27cc2cfbaaa Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 11:42:23 +0200 Subject: [PATCH 008/419] =?UTF-8?q?=F0=9F=9A=A7=20iteration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 854 ++++++++++++++++------- 1 file changed, 602 insertions(+), 252 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index f6c9303c5e..dd1490d24b 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -6,7 +6,6 @@ This RFC proposes a comprehensive revamp of the quantum MLIR dialect(s) to unify Goals: -- Introduce a single abstraction: **UnitaryExpr** (symbolic unitary expression) replacing prior matrix vs expression split. - Provide a coherent interface for all operations that apply or produce a unitary (base gates, user-defined gates, modifier-wrapped constructs, sequences). - Support both reference semantics (in-place: `mqtref`) and value semantics (SSA threading: `mqtopt`). - Add inversion, powering, negative and positive multi-controls, custom gate definitions (matrix & composite), and composition. @@ -20,13 +19,13 @@ Goals: Current issues the revamp addresses: -- Only a rudimentary control modifier exists; missing negative controls, power, inversion. +- Only a control modifier exists and is directly embedded in the unitary operations (interface); missing power, inversion. - No unified interface for extracting/composing unitaries—leading to ad hoc logic. -- Matrix vs expression forms diverge; lost optimization opportunities. +- No way to obtain matrix representations for gates. - No user-defined gate (matrix/composite) constructs. -- Inconsistent parameter model and missing static/dynamic integration. -- Lack of dual semantics for optimization vs generation workflows. - Absent canonicalization strategy for modifier order, parameter folding, gate specialization. +- Mostly FileCheck-based testing that is cumbersome and error prone to write. +- No convenient builders for programs at the moment. ## 3. Dialect Structure and Categories @@ -39,7 +38,7 @@ Categories: 1. Resource Operations 2. Measurement and Reset -3. UnitaryExpr Concept (applies everywhere for unitaries) +3. UnitaryInterface Operations ### 3.1 Resource Operations @@ -50,22 +49,22 @@ Examples (reference semantics): ``` %q = mqtref.alloc : mqtref.Qubit mqtref.dealloc %q : mqtref.Qubit -%q_fixed = mqtref.static_qubit @q0 : mqtref.Qubit +%q0 = mqtref.qubit 0 : mqtref.Qubit ``` Value semantics: ``` -%q0 = mqtopt.alloc : mqtopt.Qubit -mqtopt.dealloc %q0 : mqtopt.Qubit -%q_hw = mqtopt.static_qubit @q7 : mqtopt.Qubit +%q = mqtopt.alloc : mqtopt.Qubit +mqtopt.dealloc %q : mqtopt.Qubit +%q0 = mqtopt.qubit 0 : mqtopt.Qubit ``` Canonicalization (patterns / folds): - Remove unused `alloc` (DCE). -- Elide `dealloc` proven by lifetime analysis. -- Merge duplicate `static_qubit` references if semantics allow. +- Elide `dealloc` proven by lifetime analysis. // TODO +- Merge duplicate `qubit` references if semantics allow. // TODO ### 3.2 Measurement and Reset @@ -81,7 +80,7 @@ mqtref.reset %q : mqtref.Qubit Value: ``` -%c = mqtopt.measure %qin : mqtopt.Qubit -> i1 +%qout, %c = mqtopt.measure %qin : mqtopt.Qubit -> (mqtopt.Qubit, i1) %qout = mqtopt.reset %qin : mqtopt.Qubit ``` @@ -90,36 +89,24 @@ Canonicalization: - `reset` immediately after `alloc` → remove `reset`. - Consecutive `reset` on same qubit (reference semantics) → single instance. -### 3.3 UnitaryExpr Concept +### 3.3 Unified Unitary Interface Design -`UnitaryExpr` is a conceptual abstraction representing a (possibly symbolic) unitary over n targets with optional parameters and controls. - -- Static if all parameters are static and analytic matrix is available. -- Symbolic otherwise (composition, parameterized nodes, modifiers). -- Provides inversion, powering, and control extension without immediate matrix materialization. - -## 4. Unified Unitary Interface Design - -All unitary-applying operations implement a common interface (applies to base gates, modifiers, sequences, and user-defined applies). +All unitary-applying operations implement a common interface (applies to base gates, modifiers, sequences, and user-defined operations). Interface methods (conceptual API): -- `getNumTargets() -> unsigned` -- `getNumPosControls() -> unsigned` -- `getNumNegControls() -> unsigned` -- `getNumParams() -> unsigned` +- `getNumTargets() -> size_t` +- `getNumPosControls() -> size_t` +- `getNumNegControls() -> size_t` +- `getNumParams() -> size_t` - `getParameter(i) -> ParameterDescriptor` - `ParameterDescriptor`: `isStatic()`, `getConstantValue()?`, `getValueOperand()` - `getInput(i)` / `getOutput(i)` (value semantics distinct; reference semantics output = input) -- `mapOutputToInput(i) -> i` (pure unitaries) +- `mapOutputToInput(i) -> i` (pure unitaries) // TODO: should map mlir::Value to mlir::Value. should include getters for targets and controls. - `hasStaticUnitary() -> bool` -- `getOrBuildUnitaryExpr(builder) -> UnitaryExpr` - `tryGetStaticMatrix() -> Optional` (2D tensor with shape (2^n, 2^n) and element type `complex`; written concretely for fixed n as e.g. `tensor<4x4xcomplex>`) - `isInverted() -> bool` - `getPower() -> Optional` -- `withAddedControls(pos, neg) -> UnitaryExpr` -- `composeRight(other) -> UnitaryExpr` -- `getPrincipalLog() -> Optional` Identification & Descriptor Tuple: `(baseSymbol, orderedParams, posControls, negControls, powerExponent, invertedFlag)` allows canonical equality tests. @@ -132,8 +119,8 @@ Parameter Model: Static Matrix Extraction: -- Provided if gate analytic and all parameters static, or for matrix-defined user gates. -- For sequences/composites of static subunits under size threshold, compose matrices. +- Provided if gate is analytic, all parameters are static, or for matrix-defined user gates. +- For sequences/composites of static subunits, compose matrices. Inversion & Power Interaction: @@ -144,30 +131,25 @@ Control Extension: - `ctrl` / `negctrl` wrappers extend control sets; interface aggregates flattened sets. -## 5. Base Gate Operations +## 4. Base Gate Operations -### 5.1 Philosophy +### 4.1 Philosophy -- Named base gates define analytic unitaries with fixed target arity and parameter arity traits. -- Provide static matrix when parameters static; symbolic otherwise. +- Named base gates define unitaries with fixed target arity and parameter arity traits. +- Provide static matrix when parameters static or no parameters; symbolic otherwise. - Avoid embedding modifier semantics directly—wrappers handle extension. -### 5.2 Gate List (Single-Qubit) - -No-parameter: `x, y, z, h, s, sdg, t, tdg, id` -Parameterized: `rx(%theta), ry(%theta), rz(%theta), phase(%lambda), u(%theta, %phi, %lambda)` - -Multi-qubit illustrative: `rzz(%theta)` (two targets), not using `cx` (introduced only via sugar as controlled `x`). - -#### 5.2.1 Base Gate Specification Template +### 4.2 Base Gate Specification Template For every named base gate op G: -- Purpose: Apply the analytic unitary for gate G to its target qubit(s). +// TODO: mixed dynamic and static parameters should be clearly explained and demonstrated here. example from existing code: `mqtref.u(%c0_f64 static [1.00000e-01, 2.00000e-01] mask [true, false, true]) %q0` + +- Purpose: Apply the unitary for gate G to its target qubit(s). - Signature (Reference): `mqtref.G(param_list?) %q[,...] : ` (no results) - Signature (Value): `%out_targets = mqtopt.G(param_list?) %in_targets : () -> ` - Assembly Format: `G() `; params in parentheses; qubits as trailing operands. -- Builder Variants: +- Builder Variants: // TODO: types should be inferred automatically based on `InferTypeOpInterface` - `build(builder, loc, resultTypes, paramOperands, qubitOperands)` (value) - `build(builder, loc, qubitOperands, paramAttrs)` (reference) - Convenience: static param overloads generate attribute parameters. @@ -175,242 +157,610 @@ For every named base gate op G: - `getNumTargets()` fixed by trait. - Parameters enumerated in declared order; static vs dynamic via attribute vs operand. - `hasStaticUnitary()` true iff all parameters static. - - `mapOutputToInput(i) = i`. -- Canonicalization Rules: See 5.6 plus gate-specific (e.g., identity elimination, specialization). -- Examples (Static): `mqtref.rz(3.14159) %q`; `%q2 = mqtopt.rx(0.785398) %q1`. -- Examples (Dynamic): `%q2 = mqtopt.rx(%theta) %q1`; `mqtref.u(%t,%p,%l) %q`. + - `mapOutputToInput(i) = i`. // TODO - Conversion (ref↔value): Reference variant lowers to value variant with SSA replacement; reverse drops result. -#### 5.2.2 Example: rx Gate - -- Purpose: Single-qubit rotation about X by angle θ. -- Signatures: - - Ref: `mqtref.rx(%theta) %q : f64, mqtref.Qubit` - - Value: `%q_out = mqtopt.rx(%theta) %q_in : (f64, mqtopt.Qubit) -> mqtopt.Qubit` -- Static Example: `%q_out = mqtopt.rx(1.57079632679) %q_in` -- Dynamic Example: `%q_out = mqtopt.rx(%theta) %q_in` -- Canonicalization: `rx(0) → id`; two consecutive `rx(a); rx(b)` NOT folded (axis change would require Baker-Campbell-Hausdorff? skip); `inv rx(θ)` handled by modifier → `rx(-θ)`. -- Static Matrix Available: Yes if θ constant. - -#### 5.2.3 Example: rzz Gate +### 4.3 Gate List -- Purpose: Two-qubit entangling gate `exp(-i θ/2 Z⊗Z)`. -- Signatures: - - Ref: `mqtref.rzz(%theta) %q0, %q1 : f64, mqtref.Qubit, mqtref.Qubit` - - Value: `%q0_out, %q1_out = mqtopt.rzz(%theta) %q0_in, %q1_in : (f64, mqtopt.Qubit, mqtopt.Qubit) -> (mqtopt.Qubit, mqtopt.Qubit)` -- Static Example: `%a1, %b1 = mqtopt.rzz(3.14159) %a0, %b0` -- Dynamic Example: `%a1, %b1 = mqtopt.rzz(%theta) %a0, %b0` -- Canonicalization: `rzz(0) → id`; `inv rzz(θ) → rzz(-θ)`. -- Static Matrix Available: Yes if θ constant. +Overview: -### 6. Modifier Operations +Zero-qubit gates: +Parametrized: `gphase(%theta)` -### 6.1 Overview +Single-qubit gates: +No-parameter: `id, x, y, z, h, s, sdg, t, tdg, sx, sxdg` +Parameterized: `rx(%theta), ry(%theta), rz(%theta), p(%lambda), r(theta, %phi), u(%theta, %phi, %lambda), u2(%phi, %lambda)` -Modifiers wrap unitaries, extending functionality or altering semantics. +Two-qubit gates: +No-parameter: `swap, iswap, dcx, ecr` +Parameterized: `rxx(%theta), ryy(%theta), rzz(%theta), rzx(%theta), xx_minus_yy(%theta, %beta), xx_plus_yy(%theta, %beta)` -- Semantics-preserving (e.g., `ctrl`, `negctrl`): canonicalized order, flattened. -- Transformative (e.g., `pow`, `inv`): applied last, may alter static matrix extraction. +Variable qubit gates: +No-parameter: `barrier` -### 6.2 negctrl +General canonicalization based on traits (not repeated for individual gates): -Purpose: Add negative controls. +- Hermitian: `inv G → G` +- Hermitian: `pow(n: int) G => if n % 2 == 0 then id else G` +- Hermitian: `G %q; G %q => cancel` -Operation Specification: +#### 4.3.1 `gphase` Gate -- Purpose: Wrap a unitary adding negative (0-state) control qubits. +- Purpose: global phase `exp(i θ)`. +- Traits: NoTarget, OneParameter - Signatures: - - Ref: `mqtref.negctrl %negControls { }` - - Value: `%res_targets = mqtopt.negctrl %negControls { } -> ` -- Assembly: `negctrl { ... }`. -- Builder Variants: - - `build(builder, loc, resultTypes, negControlOperands, bodyBuilderFn)` (value) - - Reference variant omits results. -- Interface Notes: Aggregates controls into `getNumNegControls()`; targets delegated to child. -- Canonicalization: Flatten nested, remove empty, reorder relative to other modifiers to canonical chain `negctrl → ctrl → pow → inv`. -- Examples: - - Ref: `mqtref.negctrl %n0 { mqtref.h %t }` - - Value: `%t_out = mqtopt.negctrl %n0 { %t1 = mqtopt.rx(%theta) %t_in } -> mqtopt.Qubit` -- Conversion: Region body value results threaded / dropped analogously to other wrappers. - -### 6.3 ctrl - -Operation Specification: - -- Purpose: Add positive (1-state) controls. + - Ref: `mqtref.gphase(%theta)` + - Value: `mqtopt.gphase(%theta)` +- Static Example: `mqtref.gphase(3.14159)` +- Dynamic Example: `mqtref.gphase(%theta)` +- Canonicalization: + - `gphase(0) → remove` + - `inv gphase(θ) → gphase(-θ)` + - Two consecutive `gphase(a); gphase(b)` folded by adding angles. + - `ctrl(%q0) { gphase(θ) } → p(θ) %q0` (specialization to `p` gate). + - `negctrl(%q0) { gphase(θ) } → gphase(pi); p(θ) %q0` (specialization for negative control) + - `pow(n) { gphase(θ) } → gphase(n*θ)` +- Matrix (dynamic): `[exp(i θ)]` (1x1 matrix). Static if θ constant. +- To be figured out: These gates have no users per definition as they have no targets. It is unclear how they should be merged and how they are included in traversals. + +### 4.3.2 `id` Gate + +- Purpose: Identity gate. +- Traits: OneTarget, NoParameter, Hermitian, Diagonal - Signatures: - - Ref: `mqtref.ctrl %posControls { }` - - Value: `%res_targets = mqtopt.ctrl %posControls { } -> ` -- Builder: Similar to `negctrl` with positive control list. -- Interface: `getNumPosControls()` sums flattened list. -- Canonicalization: Merge nested, remove empty, optionally distribute over `seq`, enforce order after `negctrl`. -- Examples: - - Ref: `mqtref.ctrl %c { mqtref.rzz(%φ) %q0, %q1 }` - - Value: `%t_out = mqtopt.ctrl %c { %t1 = mqtopt.rz(%φ) %t_in } -> mqtopt.Qubit` -- Conversion: As for `negctrl`. - -### 6.4 pow - -Operation Specification: - -- Purpose: Exponentiation of a unitary body. + - Ref: `mqtref.id %q` + - Value: `%q_out mqtopt.id %q_in` +- Canonicalization: + - `id → remove` + - `pow(r) id => id` + - `ctrl(...) { id } => id` + - `negctrl(...) { id } => id` +- Matrix (static): `[1, 0; 0, 1]` (2x2 matrix). +- Definition in terms of `u`: `u(0, 0, 0) %q` + +#### 4.3.3 `x` Gate + +- Purpose: Pauli-X gate +- Traits: OneTarget, NoParameter, Hermitian - Signatures: - - Ref: `mqtref.pow(expAttrOrOperand) { }` - - Value: `%res_targets = mqtopt.pow(expAttrOrOperand) { } -> ` -- Assembly: `pow() { ... }`. -- Builder Variants: integer attribute exponent; float attribute; dynamic f64 operand. -- Interface: `getPower()` returns rational/float wrapper; static detection when attribute. -- Canonicalization: Negative -> `inv(pow(abs))`; combine nested powers; remove exponent 1; exponent 0 -> identity passthrough; reorder with other modifiers. -- Examples: - - `%q2 = mqtopt.pow(2) { %q1 = mqtopt.rx(%theta) %q0 }` - - `%q2 = mqtopt.pow(%k) { %q1 = mqtopt.rz(%φ) %q0 }` -- Conversion: Same region adaptation logic. - -### 6.5 inv - -Operation Specification: - -- Purpose: Adjoint of unitary body. + - Ref: `mqtref.x %q` + - Value: `%q_out mqtopt.x %q_in` +- Canonicalization: + - `pow(1/2) x => sx` + - `pow(-1/2) x => sxdg` +- Matrix (static): `[0, 1; 1, 0]` (2x2 matrix). +- Definition in terms of `u`: `u(π, 0, π) %q` +- To be figured out: `-iX == rx(pi)` (global phase difference between `rx(pi)` and `x`). `pow(r) rx(θ) => rx(r*θ)`. What does this imply for `pow(r) x`? + +#### 4.3.4 `y` Gate + +- Purpose: Pauli-Y gate +- Traits: OneTarget, NoParameter, Hermitian - Signatures: - - Ref: `mqtref.inv { }` - - Value: `%res_targets = mqtopt.inv { } -> ` -- Builder: Provide body builder lambda. -- Interface: `isInverted()` true; nested inversion removed in canonicalization. -- Canonicalization: Double inversion removal; self-adjoint detection; distribute over `pow` forms (placing `inv` innermost after ordering); axis negation for parameterized rotations. -- Examples: `%t_out = mqtopt.inv { %t1 = mqtopt.u(%theta,%phi,%lambda) %t_in }` -- Conversion: Same as other wrappers. - -### 6.6 Nested Example - -Original value form (non-canonical): - -``` -%out = mqtopt.inv { %a = mqtopt.ctrl %c { %b = mqtopt.negctrl %n { %g = mqtopt.rx(%theta) %in } } } -> mqtopt.Qubit -``` + - Ref: `mqtref.y %q` + - Value: `%q_out mqtopt.y %q_in` +- Matrix (static): `[0, -i; i, 0]` (2x2 matrix). +- Definition in terms of `u`: `u(π, π/2, π/2) %q` +- To be figured out: `-iY == ry(pi)` (global phase difference between `ry(pi)` and `y`). `pow(r) ry(θ) => ry(r*θ)`. What does this imply for `pow(r) y`? -Canonical extraction: negctrl(%n), ctrl(%c), inv. -Reordered canonical: +#### 4.3.5 `z` Gate -``` -%out = mqtopt.negctrl %n { - %t1 = mqtopt.ctrl %c { - %t2 = mqtopt.inv { %t3 = mqtopt.rx(%theta) %in } -> mqtopt.Qubit - } -> mqtopt.Qubit -} -> mqtopt.Qubit -``` - -After folding `inv rx(%theta)` → `rx(-%theta)`: - -``` -%out = mqtopt.negctrl %n { - %t1 = mqtopt.ctrl %c { %t2 = mqtopt.rx(-%theta) %in } -> mqtopt.Qubit -} -> mqtopt.Qubit -``` - -Reference nested example (explicit): - -``` -mqtref.negctrl %n { - mqtref.ctrl %c { - mqtref.inv { mqtref.rx(%theta) %q } - } -} -``` - -After folding: `mqtref.negctrl %n { mqtref.ctrl %c { mqtref.rx(-%theta) %q } }` - -## 7. Sequence Operation (`seq`) - -Purpose: Ordered composition of unitary operations over region block arguments. - -Operation Specification: - -- Purpose: Represent composite product U = U_n … U_2 U_1 in region order. +- Purpose: Pauli-Z gate +- Traits: OneTarget, NoParameter, Hermitian, Diagonal - Signatures: - - Ref: `mqtref.seq (%args: mqtref.Qubit, ...) { mqtref.seq.yield }` - - Value: `%results = mqtopt.seq (%args: mqtopt.Qubit, ...) -> (mqtopt.Qubit, ...) { mqtopt.seq.yield %newArgs }` -- Assembly: `seq (arg_list) -> (result_types)? { ... }` -- Builders: Provide argument list + body builder capturing yields; value builder generates result types from argument types. -- Interface: Targets = block argument count; parameters aggregated from children (none directly); `hasStaticUnitary()` if all child ops static and compose cost acceptable. -- Canonicalization: Flatten nested, remove empty, inline single op, distribute controls from wrappers if beneficial, inversion reversal & child inversion patterns. -- Examples: - -``` -%q0_out, %q1_out = mqtopt.seq (%q0_in: mqtopt.Qubit, %q1_in: mqtopt.Qubit) - -> (mqtopt.Qubit, mqtopt.Qubit) { - %a0 = mqtopt.h %q0_in - %b0, %b1 = mqtopt.rzz(%θ) %a0, %q1_in - %c0 = mqtopt.rz(%φ) %b0 - mqtopt.seq.yield %c0, %b1 -} -``` - -- Conversion: Reference ↔ value via region argument threading and yields. - -## 8. User Defined Gates & Matrix / Composite Definitions - -### 8.1 Matrix Gate Definitions + - Ref: `mqtref.z %q` + - Value: `%q_out mqtopt.z %q_in` +- Canonicalization: + - `pow(1/2) z => s` + - `pow(-1/2) z => sdg` + - `pow(1/4) z => t` + - `pow(-1/4) z => tdg` + - `pow(r) z => p(π * r)` for real r +- Matrix (static): `[1, 0; 0, -1]` (2x2 matrix). +- Definition in terms of `u`: `u(0, 0, π) %q` + +#### 4.3.6 `h` Gate + +- Purpose: Hadamard gate. +- Traits: OneTarget, NoParameter, Hermitian +- Signatures: + - Ref: `mqtref.h %q` + - Value: `%q_out mqtopt.h %q_in` +- Matrix (static): `1/sqrt(2) * [1, 1; 1, -1]` (2x2 matrix). +- Definition in terms of `u`: `u(π/2, 0, π) %q` -Define symbol with matrix attribute (dimension 2^n × 2^n): +#### 4.3.7 `s` Gate -``` -mqt.gatedef.matrix @myPhase(%lambda: f64) targets(1) - attr_matrix = #mqt.matrix<2x2>( ... symbolic in %lambda ... ) -``` +- Purpose: S gate. +- Traits: OneTarget, NoParameter, Diagonal +- Signatures: + - Ref: `mqtref.s %q` + - Value: `%q_out mqtopt.s %q_in` +- Canonicalization: + - `inv s => sdg` + - `s %q; s %q => z %q` + - `pow(n: int) s => if n % 4 == 0 then id else if n % 4 == 1 then s else if n % 4 == 2 then z else sdg` + - `pow(1/2) s => t` + - `pow(-1/2) s => tdg` + - `pow(+-2) s => z` + - `pow(r) s => p(π/2 * r)` for real r +- Matrix (static): `[1, 0; 0, i]` (2x2 matrix). +- Definition in terms of `u`: `u(0, 0, π/2) %q` + +#### 4.3.8 `sdg` Gate + +- Purpose: Sdg gate. +- Traits: OneTarget, NoParameter, Diagonal +- Signatures: + - Ref: `mqtref.sdg %q` + - Value: `%q_out mqtopt.sdg %q_in` +- Canonicalization: + - `inv sdg => s` + - `sdg %q; sdg %q => z %q` + - `pow(n: int) sdg => if n % 4 == 0 then id else if n % 4 == 1 then sdg else if n % 4 == 2 then z else s` + - `pow(1/2) sdg => tdg` + - `pow(-1/2) sdg => t` + - `pow(+-2) sdg => z` + - `pow(r) sdg => p(-π/2 * r)` for real r +- Matrix (static): `[1, 0; 0, -i]` (2x2 matrix). +- Definition in terms of `u`: `u(0, 0, -π/2) %q` + +#### 4.3.9 `t` Gate + +- Purpose: T gate. +- Traits: OneTarget, NoParameter, Diagonal +- Signatures: + - Ref: `mqtref.t %q` + - Value: `%q_out mqtopt.t %q_in` +- Canonicalization: + - `inv t => tdg` + - `t %q; t %q; => s %q` + - `pow(2) t => s` + - `pow(-2) t => sdg` + - `pow(+-4) t => z` + - `pow(r) t => p(π/4 * r)` for real r +- Matrix (static): `[1, 0; 0, exp(i π/4)]` (2x2 matrix). +- Definition in terms of `u`: `u(0, 0, π/4) %q` + +#### 4.3.10 `tdg` Gate + +- Purpose: Tdg gate. +- Traits: OneTarget, NoParameter, Diagonal +- Signatures: + - Ref: `mqtref.tdg %q` + - Value: `%q_out mqtopt.tdg %q_in` +- Canonicalization: + - `inv tdg => t` + - `tdg %q; tdg %q; => sdg %q` + - `pow(2) tdg => sdg` + - `pow(-2) tdg => s` + - `pow(+-4) tdg => z` + - `pow(r) tdg => p(-π/4 * r)` for real r +- Matrix (static): `[1, 0; 0, exp(-i π/4)]` (2x2 matrix). +- Definition in terms of `u`: `u(0, 0, -π/4) %q` + +#### 4.3.11 `sx` Gate + +- Purpose: sqrt(x) gate. +- Traits: OneTarget, NoParameter +- Signatures: + - Ref: `mqtref.sx %q` + - Value: `%q_out mqtopt.sx %q_in` +- Canonicalization: + - `inv sx => sxdg` + - `sx %q; sx %q => x %q` + - `pow(+-2) sx => x` +- Matrix (static): `1/2 * [1 + i, 1 - i; 1 - i, 1 + i]` (2x2 matrix). +- To be figured out: `e^(-i pi/4) sx == rx(pi/2)` (global phase difference between `rx(pi/2)` and `sx`). `pow(r) rx(θ) => rx(r*θ)`. What does this imply for `pow(r) sx`? + +#### 4.3.12 `sxdg` Gate + +- Purpose: sqrt(x) gate. +- Traits: OneTarget, NoParameter +- Signatures: + - Ref: `mqtref.sxdg %q` + - Value: `%q_out mqtopt.sxdg %q_in` +- Canonicalization: + - `inv sxdg => sx` + - `sxdg %q; sxdg %q => x %q` + - `pow(+-2) sxdg => x` +- Matrix (static): `1/2 * [1 - i, 1 + i; 1 + i, 1 - i]` (2x2 matrix). +- To be figured out: `exp(-i pi/4) sxdg == rx(-pi/2)` (global phase difference between `rx(-pi/2)` and `sxdg`). `pow(r) rx(θ) => rx(r*θ)`. What does this imply for `pow(r) sxdg`? + +#### 4.3.13 `rx` Gate + +- Purpose: Rotation around the x-axis. +- Traits: OneTarget, OneParameter +- Signatures: + - Ref: `mqtref.rx(%theta) %q` + - Value: `%q_out mqtopt.rx(%theta) %q_in` +- Static variant: `mqtref.rx(3.14159) %q` +- Canonicalization: + - `rx(a) %q; rx(b) %q => rx(a + b) %q` + - `inv rx(θ) => rx(-θ)` + - `pow(r) rx(θ) => rx(r * θ)` for real r +- Matrix (dynamic): `exp(-i θ X) = [cos(θ/2), -i sin(θ/2); -i sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. +- Definition in terms of `u`: `u(θ, -π/2, π/2) %q` + +#### 4.3.14 `ry` Gate + +- Purpose: Rotation around the y-axis. +- Traits: OneTarget, OneParameter +- Signatures: + - Ref: `mqtref.ry(%theta) %q` + - Value: `%q_out mqtopt.ry(%theta) %q_in` +- Static variant: `mqtref.ry(3.14159) %q` +- Canonicalization: + - `ry(a) %q; ry(b) %q => ry(a + b) %q` + - `inv ry(θ) => ry(-θ)` + - `pow(r) ry(θ) => ry(r * θ)` for real r +- Matrix (dynamic): `exp(-i θ Y) = [cos(θ/2), -sin(θ/2); sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. +- Definition in terms of `u`: `u(θ, 0, 0) %q` + +#### 4.3.15 `rz` Gate + +- Purpose: Rotation around the z-axis. +- Traits: OneTarget, OneParameter, Diagonal +- Signatures: + - Ref: `mqtref.rz(%theta) %q` + - Value: `%q_out mqtopt.rz(%theta) %q_in` +- Static variant: `mqtref.rz(3.14159) %q` +- Canonicalization: + - `rz(a) %q; rz(b) %q => rz(a + b) %q` + - `inv rz(θ) => rz(-θ)` + - `pow(r) rz(θ) => rz(r * θ)` for real r +- Matrix (dynamic): `exp(-i θ Z) = [exp(-i θ/2), 0; 0, exp(i θ/2)]` (2x2 matrix). Static if θ constant. +- To be figured out: `rz(θ) == exp(i*θ/2) * p(θ)` (global phase difference between `rz(θ)` and `p(θ)`). + +#### 4.3.16 `p` Gate + +- Purpose: Phase gate. +- Traits: OneTarget, OneParameter, Diagonal +- Signatures: + - Ref: `mqtref.p(%theta) %q` + - Value: `%q_out mqtopt.p(%theta) %q_in` +- Static variant: `mqtref.p(3.14159) %q` +- Canonicalization: + - `p(a) %q; p(b) %q => p(a + b) %q` + - `inv p(θ) => p(-θ)` + - `pow(r) p(θ) => p(r * θ)` for real r +- Matrix (dynamic): `[1, 0; 0, exp(i θ)]` (2x2 matrix). Static if θ constant. +- Definition in terms of `u`: `u(0, 0, θ) %q` + +#### 4.3.17 `r` Gate + +- Purpose: General rotation around an axis in the XY-plane. +- Traits: OneTarget, TwoParameter +- Signatures: + - Ref: `mqtref.r(%theta, %phi) %q` + - Value: `%q_out mqtopt.r(%theta, %phi) %q_in` +- Static variant: `mqtref.r(3.14159, 1.5708) %q` +- Mixed variant: `mqtref.r(%theta, 1.5708) %q` +- Canonicalization: + - `inv r(θ, φ) => r(-θ, φ)` + - `pow(real) r(θ, φ) => r(real * θ, φ)` for real `real` + - `r(θ, 0) => rx(θ)` + - `r(θ, π/2) => ry(θ)` +- Matrix (dynamic): `exp(-i θ (cos(φ) X + sin(φ) Y)) = [cos(θ/2), -i exp(-i φ) sin(θ/2); -i exp(i φ) sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ and φ constant. +- Definition in terms of `u`: `u(θ, -π/2 + φ, π/2 - φ) %q` + +#### 4.3.18 `u` Gate + +- Purpose: Universal single-qubit gate. +- Traits: OneTarget, ThreeParameter +- Signatures: + - Ref: `mqtref.u(%theta, %phi, %lambda) %q` + - Value: `%q_out mqtopt.u(%theta, %phi, %lambda) %q_in` +- Static variant: `mqtref.u(3.14159, 1.5708, 0.785398) %q` +- Mixed variant: `mqtref.u(%theta, 1.5708, 0.785398) %q` +- Canonicalization: + - `inv u(θ, φ, λ) => u(-θ, -φ, -λ)` + - `rx(θ) == u(θ, -π/2, π/2)` + - `ry(θ) == u(θ, 0, 0)` + - `p(λ) == u(0, 0, λ)` +- Matrix (dynamic): `p(φ) ry(θ) p(λ) = exp(i (φ + λ)/2) * rz(φ) ry(θ) rz(λ) = [cos(θ/2), -exp(i λ) sin(θ/2); exp(i φ) sin(θ/2), exp(i (φ + λ)) cos(θ/2)]` (2x2 matrix). Static if θ, φ, λ constant. + +#### 4.3.19 `u2` Gate + +- Purpose: Simplified universal single-qubit gate. +- Traits: OneTarget, TwoParameter +- Signatures + - Ref: `mqtref.u2(%phi, %lambda) %q` + - Value: `%q_out mqtopt.u2(%phi, %lambda) %q_in` +- Static variant: `mqtref.u2(1.5708, 0.785398) %q` +- Mixed variant: `mqtref.u2(%phi, 0.785398) %q` +- Canonicalization: + - `inv u2(φ, λ) => u2(-λ - π, -φ + π)` + - `u2(0, π) => h` + - `u2(0, 0) => ry(π/2)` + - `u2(-π/2, π/2) => rx(π/2)` +- Matrix (dynamic): `1/sqrt(2) * [1, -exp(i λ); exp(i φ), exp(i (φ + λ))]` (2x2 matrix). Static if φ, λ constant. +- Definition in terms of `u`: `u2(φ, λ) == u(π/2, φ, λ)` + +#### 4.3.20 `swap` Gate + +- Purpose: Swap two qubits. +- Traits: TwoTarget, NoParameter, Hermitian +- Signatures: + - Ref: `mqtref.swap %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.swap %q0_in, %q1_in` +- Matrix (static): `[1, 0, 0, 0; 0, 0, 1, 0; 0, 1, 0, 0; 0, 0, 0, 1]` (4x4 matrix). -Matrix may embed symbolic expressions referencing parameters (internal representation detail). +#### 4.3.21 `iswap` Gate -### 8.2 Composite Gate Definitions +- Purpose: Swap two qubits. +- Traits: TwoTarget, NoParameter +- Signatures: + - Ref: `mqtref.iswap %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.iswap %q0_in, %q1_in` +- Canonicalization: + - `pow(r) iswap => xx_plus_yy(-π * r)` +- Matrix (static): `[1, 0, 0, 0; 0, 0, 1j, 0; 0, 1j, 0, 0; 0, 0, 0, 1]` (4x4 matrix). -Sequence-based body yields outputs (value semantics): +#### 4.3.22 `dcx` Gate -``` -mqt.gatedef.composite @entang(%theta: f64) - ( %a: mqtopt.Qubit, %b: mqtopt.Qubit ) -> (mqtopt.Qubit, mqtopt.Qubit) { - %a1 = mqtopt.h %a - %a2, %b1 = mqtopt.rzz(%theta) %a1, %b - mqtopt.seq.yield %a2, %b1 -} -``` - -Reference semantics variant has same region arguments but no gate results. +- Purpose: Double CX gate. +- Traits: TwoTarget, NoParameter +- Signatures: + - Ref: `mqtref.dcx %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.dcx %q0_in, %q1_in` +- Canonicalization: + - `inv dcx %q0, q1 => dcx %q1, %q0` +- Matrix (static): `[1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]` (4x4 matrix).` -### 8.3 Unified Apply Operation +#### 4.3.23 `ecr` Gate -Value: +- Purpose: Echoed cross-resonance gate. +- Traits: TwoTarget, NoParameter, Hermitian +- Signatures: + - Ref: `mqtref.ecr %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.ecr %q0_in, %q1_in` +- Matrix (static): `1/sqrt(2) * [0, 0, 1, 1j; 0, 0, 1j, 1; 1, -1j, 0, 0; -1j, 1, 0, 0]` (4x4 matrix). -``` -%a_out, %b_out = mqtopt.apply @entang(%theta) %a_in, %b_in -%q1 = mqtopt.apply @myPhase(%lambda) %q0 -``` +#### 4.3.24 `rxx` Gate -Reference: +- Purpose: General two-qubit rotation around XX. +- Traits: TwoTarget, OneParameter +- Signatures: + - Ref: `mqtref.rxx(%theta) %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.rxx(%theta) %q0_in, %q1_in` +- Static variant: `mqtref.rxx(3.14159) %q0, %q1` +- Canonicalization: + - `inv rxx(%theta) => rxx(-%theta)` + - `pow(r) rxx(%theta) => rxx(r * %theta)` for real r + - `rxx(0) => remove` + - `rxx(a) %q0, %q1; rxx(b) %q0, %q1 => rxx(a + b) %q0, %q1` +- Matrix (dynamic): `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] - 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, 1, 0; 0, 1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. + +#### 4.3.25 `ryy` Gate + +- Purpose: General two-qubit gate around YY. +- Traits: TwoTarget, OneParameter +- Signatures: + - Ref: `mqtref.ryy(%theta) %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.ryy(%theta) %q0_in, %q1_in` +- Static variant: `mqtref.ryy(3.14159) %q0, %q1` +- Canonicalization: + - `inv ryy(%theta) => ryy(-%theta)` + - `pow(r) ryy(%theta) => ryy(r * %theta)` for real r + - `ryy(0) => remove` + - `ryy(a) %q0, %q1; ryy(b) %q0, %q1 => ryy(a + b) %q0, %q1` +- Matrix (dynamic): `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, -1, 0; 0, -1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. + +#### 4.3.26 `rzx` Gate + +- Purpose: General two-qubit gate around ZX. +- Traits: TwoTarget, OneParameter +- Signatures: + - Ref: `mqtref.rzx(%theta) %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.rzx(%theta) %q0_in, %q1_in` +- Static variant: `mqtref.rzx(3.14159) %q0, %q1` +- Canonicalization: + - `inv rzx(%theta) => rzx(-%theta)` + - `pow(r) rzx(%theta) => rzx(r * %theta)` for real r + - `rzx(0) => remove` + - `rzx(a) %q0, %q1; rzx(b) %q0, %q1 => rzx(a + b) %q0, %q1` +- Matrix (dynamic): `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, -1, 0, 0; -1, 0, 0, 0; 0, 0, 0, 1; 0, 0, 1, 0]` (4x4 matrix). Static if θ constant. + +#### 4.3.27 `rzz` Gate + +- Purpose: General two-qubit gate around ZZ. +- Traits: TwoTarget, OneParameter, Diagonal +- Signatures: + - Ref: `mqtref.rzz(%theta) %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.rzz(%theta) %q0_in, %q1_in` +- Static variant: `mqtref.rzz(3.14159) %q0, %q1` +- Canonicalization: + - `inv rzz(%theta) => rzz(-%theta)` + - `pow(r) rzz(%theta) => rzz(r * %theta)` for real r + - `rzz(0) => remove` + - `rzz(a) %q0, %q1; rzz(b) %q0, %q1 => rzz(a + b) %q0, %q1` +- Matrix (dynamic): `diag[exp(-i θ/2), exp(i θ/2), exp(i θ/2), exp(-i θ/2)]` (4x4 matrix). Static if θ constant. + +#### 4.3.28 `xx_plus_yy` Gate + +- Purpose: General two-qubit gate around XX+YY. +- Traits: TwoTarget, TwoParameter +- Signatures: + - Ref: `mqtref.xx_plus_yy(%theta, %beta) %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.xx_plus_yy(%theta, %beta) %q0_in, %q1_in` +- Static variant: `mqtref.xx_plus_yy(3.14159, 1.5708) %q0, %q1` +- Mixed variant: `mqtref.xx_plus_yy(%theta, 1.5708) %q0, %q1` +- Canonicalization: + - `inv xx_plus_yy(θ, β) => xx_plus_yy(-θ, β)` + - `pow(r) xx_plus_yy(θ, β) => xx_plus_yy(r * θ, β)` for real r + - `xx_plus_yy(θ1, β) %q0, %q1; xx_plus_yy(θ2, β) %q0, %q1 => xx_plus_yy(θ1 + θ2, β) %q0, %q1` +- Matrix (dynamic): `[1, 0, 0, 0; 0, cos(θ/2), sin(θ/2) * exp(-i β), 0; 0, -sin(θ/2) * exp(i β), cos(θ/2), 0; 0, 0, 0, 1]` (4x4 matrix). Static if θ and β constant. + +#### 4.3.29 `xx_minus_yy` Gate + +- Purpose: General two-qubit gate around XX-YY. +- Traits: TwoTarget, TwoParameter +- Signatures: + - Ref: `mqtref.xx_minus_yy(%theta, %beta) %q0, %q1` + - Value: `%q0_out, %q1_out = mqtopt.xx_minus_yy(%theta, %beta) %q0_in, %q1_in` +- Static variant: `mqtref.xx_minus_yy(3.14159, 1.5708) %q0, %q1` +- Mixed variant: `mqtref.xx_minus_yy(%theta, 1.5708) %q0, %q1` +- Canonicalization: + - `inv xx_minus_yy(θ, β) => xx_minus_yy(-θ, β)` + - `pow(r) xx_minus_yy(θ, β) => xx_minus_yy(r * θ, β)` for real r + - `xx_minus_yy(θ1, β) %q0, %q1; xx_minus_yy(θ2, β) %q0, %q1 => xx_minus_yy(θ1 + θ2, β) %q0, %q1` +- Matrix (dynamic): `[cos(θ/2), 0, 0, -sin(θ/2) * exp(i β); 0, 1, 0, 0; 0, 0, 1, 0; sin(θ/2) * exp(-i β), 0, 0, cos(θ/2)]` (4x4 matrix). Static if θ and β constant. -``` -mqtref.apply @entang(%theta) %a, %b -mqtref.apply @myPhase(3.14159) %q -``` +### 5. Modifier Operations -Operation Specification: +### 5.1 Overview -- Purpose: Apply user-defined matrix or composite gate symbol. +Modifiers wrap unitaries, extending functionality or altering semantics. +They contain exactly one region with a single block whose only operation implements the `UnitaryOpInterface`. +In the reference semantics dialect, modifiers are statements without results. +In the value semantics dialect, modifiers thread their values through region arguments and yield results. +Converting from the value semantics to the reference semantics is straightforward. +The reverse direction requires a bit of care as the SSA values of the contained unitary need to be added to the region arguments as well as the results need to be yielded. +Modifiers may be arbitrarily nested, with canonicalization rules to flatten and reorder them. + +There are three types of modifiers: + +- Control modifiers: `ctrl` and `negctrl`. These add additional (control) qubits to an operation. They extend the qubit list of the unitary operation in question. +- Inverse modifier: `inv`. This takes the adjoint of the unitary operation. Specializations for many of the basis gates exist and are defined as canonicalization rules. +- Power modifier: `pow`. This takes the power of the unitary operation. Canonicalization rules are provided to simplify common cases. + +The canonical ordering for these modifiers is (from outside to inside): `negtrcl` -> `ctrl` -> `pow` -> `inv`. + +All modifiers share a common verifier: they must have a single block with a single operation implementing the `UnitaryOpInterface`. + +### 5.2 Control Modifiers + +- Purpose: Add additional (control) qubits to an operation. Control qubits can either be positive (1-state) or negative (0-state) controls. The modifier itself holds a variadic list of qubits. +- Signatures (just shown for `ctrl` for simplicity): + - Ref: `mqtref.ctrl(%ctrls) { mqtref.unitaryOp %targets }` + - Value: + ``` + %ctrl_outs, %unitary_outs = mqtopt.ctrl(%ctrl_ins, %unitary_ins) { + %u_outs = mqtopt.unitaryOp %unitary_ins + mqtopt.yield %u_outs + } + ``` +- Builders: Provide list of qubits + body builder. +- Interface: + - Targets: targets of child unitary + - Controls: controls of modifier plus controls of child unitary + - Parameters: aggregated from child unitary (none directly) + - `hasStaticUnitary()` if child unitary static +- Canonicalization: + - Flatten nested control modifiers by merging control lists. + - Remove empty control modifiers. + - Controls applied to global phase gate +> pick one (arbitrary control) and replace the global phase gate with a (controlled) phase gate. + - Canonical modifier ordering: + - `ctrl negctrl U => negctrl ctrl U` +- Verifiers: + - Ensure control and target qubits are distinct. +- Unitary computation: Computed by expanding the unitary of the child operation to the larger space defined by the additional control qubits. + +### 5.3 Inverse Modifier + +- Purpose: Take the adjoint of the unitary operation. +- Signatures: + - Ref: `mqtref.inv { mqtref.unitaryOp %targets }` + - Value: + ``` + %unitary_outs = mqtopt.inv(%unitary_ins) { + %u_outs = mqtopt.unitaryOp %unitary_ins + mqtopt.yield %u_outs + } + ``` +- Builders: Provide body builder. +- Interface: + - Targets: targets of child unitary + - Controls: controls of child unitary + - Parameters: aggregated from child unitary (none directly) + - `hasStaticUnitary()` if child unitary static +- Canonicalization: + - Pairs of nested inverses cancel, i.e. `inv inv U => U`. + - Specializations for many basis gates exist and are defined as canonicalization rules. + - Canonical modifier ordering: + - `inv ctrl U => ctrl inv U` + - `inv negctrl U => negctrl inv U` +- Verifiers: None additional. +- Unitary computation: Computed by inverting the unitary of the child operation. Given how the underlying operation is unitary, the inverse is given by the conjugate transpose. + +### 5.4 Power Modifier + +- Purpose: Take the power of the unitary operation. +- Signatures: + - Ref: `mqtref.pow(%exponent) { mqtref.unitaryOp %targets }` + - Value: + ``` + %unitary_outs = mqtopt.pow(%exponent, %unitary_ins) { + %u_outs = mqtopt.unitaryOp %unitary_ins + mqtopt.yield %u_outs + } + ``` +- Static variant: `mqtref.pow(3) { mqtref.unitaryOp %targets }` +- Builders: Provide exponent value (or attribute) + body builder. +- Interface: + - Targets: targets of child unitary + - Controls: controls of child unitary + - Parameters: aggregated from child unitary + exponent (either counted as static or dynamic parameter) + - `hasStaticUnitary()` if child unitary static and exponent static +- Canonicalization: + - Flatten nested power modifiers by multiplying exponents. + - Remove power modifier with exponent 1. + - `pow(0) U => remove` completely removed the modifier and the operation + - Specializations for many basis gates exist and are defined as canonicalization rules. + - Constant folding and propagation of exponents, e.g., replacing constant values by attributes. + - Negative exponents are pushed into the child unitary by inverting it, e.g., `pow(-r) U => pow(r) inv(U)`. + - Canonical modifier ordering: + - `pow ctrl U => ctrl pow U` + - `pow negctrl U => negctrl pow U` +- Verifiers: None additional. +- Unitary computation: Computed by raising the unitary of the child operation to the given power. For positive integer exponents, this is simply a matrix multiplication. For real-valued exponents, this can be computed by exponentiation. + +## 6. Sequence Operation (`seq`) + +- Purpose: Ordered, unnamed composition of unitary operations over region block arguments. - Signatures: - - Ref: `mqtref.apply @symbol(param_list?) %targets` - - Value: `%results = mqtopt.apply @symbol(param_list?) %inputs` -- Assembly: `apply @name() ` -- Builder Variants: - - For matrix gate: auto infer target count from definition. - - For composite: verify arity against definition region signature. - - Parameter list builder with static attribute injection. -- Interface Notes: - - `getNumTargets()` from definition signature. - - Parameters enumerated exactly as in definition. - - `hasStaticUnitary()` true for matrix gate; composite conditional. -- Canonicalization: Identity matrix removal; trivial single-op composite inlining; repeated static collapses per algebraic rules. -- Examples: - - Static parameter: `%q1 = mqtopt.apply @myPhase(3.14159) %q0` - - Dynamic parameter: `%q1 = mqtopt.apply @myPhase(%lambda) %q0` -- Conversion: Reference ↔ value semantics by adding/removing results and adjusting uses. + - Ref: `mqtref.seq { }` + - Value: + ``` + %results = mqtopt.seq(%args) -> (%result_types) { + + mqtopt.yield %newArgs + } + ``` +- Builders: Provide body builder. +- Interface: + - Targets = Aggregated targets of child unitary ops (none directly) + - Controls = None + - Parameters = Aggregated parameters of child unitary ops (none directly) + - `hasStaticUnitary()` if all child ops static` +- Canonicalization: + - Remove empty sequence. + - Replace sequence with a single operation by inlining that operation. + - Provide inlining capabilities for flattening nested sequences. +- Verifiers: All block operations must implement the `UnitaryOpInterface`. +- Unitary computation: Computed by computing the product of the unitaries of the child operations, i.e., `U_n U_{n-1} ... U_1 U_0`. +- Conversion: + - Value semantics to reference semantics: Remove block arguments and results, replace uses of arguments with direct uses of the corresponding values. + - Reference semantics to value semantics: Add block arguments and results, replace direct use of values with uses of the corresponding arguments. + +## 7. User Defined Gates & Matrix / Composite Definitions + +In addition to the unnamed sequence operation, the dialect also provides a mechanism for defining custom (unitary) gates that produce a symbol that can be referenced in an `apply` operation. +Conceptionally, this should be very close to an actual MLIR function definition and call. + +These gates can be defined in two ways: + +- Matrix-based definition: Define a gate using a matrix representation. +- Sequence-based definition: Define a gate using a sequence of (unitary) operations. + +The matrix-based definition is very efficient for small qubit numbers, but does not scale well for larger numbers of qubits. +The sequence-based definition is more general, but requires more processing to compute the underlying functionality. +Definitions might even provide both a matrix and a sequence representation, which should be consistent. + +Matrix-based definitions may be fully static or might be based on symbolic expressions, e.g., rotation gates. +Fully static matrices may be specified as dense array attributes. +Any dynamic definitions must be (somehow) specified as symbolic expressions. ## 9. Parser & Builder Sugar From 7560440a86d4353624c6c402d9ce30f8762440e3 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 12:35:46 +0200 Subject: [PATCH 009/419] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 157 +++++++++++++---------- 1 file changed, 87 insertions(+), 70 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index dd1490d24b..adabe6690a 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -63,8 +63,6 @@ mqtopt.dealloc %q : mqtopt.Qubit Canonicalization (patterns / folds): - Remove unused `alloc` (DCE). -- Elide `dealloc` proven by lifetime analysis. // TODO -- Merge duplicate `qubit` references if semantics allow. // TODO ### 3.2 Measurement and Reset @@ -101,8 +99,9 @@ Interface methods (conceptual API): - `getNumParams() -> size_t` - `getParameter(i) -> ParameterDescriptor` - `ParameterDescriptor`: `isStatic()`, `getConstantValue()?`, `getValueOperand()` -- `getInput(i)` / `getOutput(i)` (value semantics distinct; reference semantics output = input) -- `mapOutputToInput(i) -> i` (pure unitaries) // TODO: should map mlir::Value to mlir::Value. should include getters for targets and controls. +- `getInput(i) -> mlir::Value` / `getOutput(i) -> mlir::Value` (value semantics distinct; reference semantics output = input) +- `getOutputForInput(mlir::Value) -> mlir::Value` (value semantics distinct; reference semantics output = input) +- `getInputForOutput(mlir::Value) -> mlir::Value` (value semantics distinct; reference semantics output = input) - `hasStaticUnitary() -> bool` - `tryGetStaticMatrix() -> Optional` (2D tensor with shape (2^n, 2^n) and element type `complex`; written concretely for fixed n as e.g. `tensor<4x4xcomplex>`) - `isInverted() -> bool` @@ -143,22 +142,14 @@ Control Extension: For every named base gate op G: -// TODO: mixed dynamic and static parameters should be clearly explained and demonstrated here. example from existing code: `mqtref.u(%c0_f64 static [1.00000e-01, 2.00000e-01] mask [true, false, true]) %q0` - - Purpose: Apply the unitary for gate G to its target qubit(s). - Signature (Reference): `mqtref.G(param_list?) %q[,...] : ` (no results) - Signature (Value): `%out_targets = mqtopt.G(param_list?) %in_targets : () -> ` - Assembly Format: `G() `; params in parentheses; qubits as trailing operands. -- Builder Variants: // TODO: types should be inferred automatically based on `InferTypeOpInterface` - - `build(builder, loc, resultTypes, paramOperands, qubitOperands)` (value) - - `build(builder, loc, qubitOperands, paramAttrs)` (reference) - - Convenience: static param overloads generate attribute parameters. - Interface Implementation Notes: - `getNumTargets()` fixed by trait. - Parameters enumerated in declared order; static vs dynamic via attribute vs operand. - `hasStaticUnitary()` true iff all parameters static. - - `mapOutputToInput(i) = i`. // TODO -- Conversion (ref↔value): Reference variant lowers to value variant with SSA replacement; reverse drops result. ### 4.3 Gate List @@ -762,72 +753,98 @@ Matrix-based definitions may be fully static or might be based on symbolic expre Fully static matrices may be specified as dense array attributes. Any dynamic definitions must be (somehow) specified as symbolic expressions. -## 9. Parser & Builder Sugar - -### 9.1 Sugar Expansions - -- `cx %c, %t` → `mqtopt.ctrl %c { %t_out = mqtopt.x %t }` (value) -- `cz %c, %t` → `ctrl %c { z %t }` -- `ccx %c0, %c1, %t` → `ctrl %c0, %c1 { x %t }` - Parser lowers sugar to canonical IR; printer may re-sugar canonical forms that match patterns. - -### 9.2 Fluent Builder API (Conceptual) - -Examples: - -``` -withControls({c1,c2}).gate("x").on(t) -withNegControls({n}).gate("rz").param(phi).on(t) -withPow(3).gate("h").on(q) -withInv().gate("u").params(a,b,c).on(q) -sequence({}) RAII style for `seq` -defineMatrixGate("myPhase").params({lambda}).targets(1).matrix(attr) -defineCompositeGate("entang").params({theta}).targets(2).body([]{...}) -apply("entang").params(theta).on(q0,q1) +## 8. Builder API + +To make testing easier, a Builder API shall be provided similar to the `mlir::OpBuilder`. +It should allow for easy chaining of operations including modifiers. +The following operations shall be supported: + +- dynamic qubit allocation +- static qubit allocation +- qubit register allocation +- classical (bit) register allocation +- all base gates defined above +- modifiers: `ctrl`, `negctrl`, `inv`, `pow` +- `seq` +- `apply` +- gate definitions (matrix and sequence based) + +Such a builder should be defined for both the reference semantics dialect as well as the value semantics dialect. +A draft for an API of the reference semantics dialect is given below. + +```c++ +class QuantumProgramBuilder { +public: + QuantumProgramBuilder(mlir::MLIRContext *context); + // Initialize + void initialize(); + // Memory management + mlir::Value allocateStaticQubit(size_t index); + mlir::Value allocateDynamicQubit(); + mlir::Value allocateQubitRegister(size_t size); + mlir::Value allocateBitRegister(size_t size); + // Base gates + QuantumProgramBuilder& h(mlir::Value target); + QuantumProgramBuilder& x(mlir::Value target); + QuantumProgramBuilder& y(mlir::Value target); + QuantumProgramBuilder& z(mlir::Value target); + /// ... + // Modifiers + QuantumProgramBuilder& ctrl(mlir::ValueRange controls, std::function body); + QuantumProgramBuilder& negctrl(mlir::ValueRange controls, std::function body); + QuantumProgramBuilder& inv(std::function body); + QuantumProgramBuilder& pow(mlir::Value exponent, std::function body); + // Sequence + QuantumProgramBuilder& seq(std::function body); + // Gate definitions + void defineMatrixGate(mlir::StringRef name, size_t numQubits, mlir::ArrayAttr matrix); + void defineCompositeGate(mlir::StringRef name, size_t numQubits, std::function body); + // Apply + QuantumProgramBuilder& apply(mlir::StringRef gateName, mlir::ValueRange targets, mlir::ValueRange parameters); + // Finalize + mlir::ModuleOp finalize(); +private: + mlir::OpBuilder builder; + mlir::ModuleOp module; + // ... +}; ``` -Chaining order auto-normalized to canonical modifier order. - -## 10. Testing Strategy +This API should turn out to be very similar to the existing MQT Core IR `qc::QuantumComputation` API and may, eventually, replace it. +It could be beneficial to design this as a C API to ensure portability to other languages, but this is not a priority. -- Structural: verify canonical modifier order and flattening. -- Matrix correctness: `U†U = I` for static extractions. -- Interface conformance: each op's counts (targets, controls, params) correct; mapping output→input identity for pure unitaries. -- Canonicalization idempotence: run pass twice, IR stable. -- Sugar round-trip: parse sugar → canonical → print (optionally sugar) with equivalence. -- Folding tests: `rx(0)`, `pow(1){}`, `pow(0){}`, `inv(inv(U))`. -- Negative exponent normalization tests. -- Sequence inversion correctness via static matrix comparison for small systems. -- Apply inlining & identity elimination tests. -- Negative tests: arity mismatch, invalid matrix dimension, non-unitary matrix, duplicate symbol definition, parameter count mismatch. +## 9. Testing Strategy -## 11. Integrated Canonicalization Rules Summary +### 9.1 Unit Tests -(Definitions live inline above; this section aggregates references.) +Priority shift: structural & semantic equivalence via builders (googletest) > textual pattern checks. +Use IR builders to construct original & expected forms, run normalization + optional passes, then compare via: -- Base Gates: parameter folds, identity elimination, specialization to named gates. -- `negctrl`: flatten & remove empty. -- `ctrl`: flatten, remove empty, distribute over `seq` when beneficial. -- `pow`: normalize negatives, remove trivial exponents, combine nested powers. -- `inv`: double-inversion removal, specialize to inverses/self-adjoint forms. -- Modifier Ordering: reorder to `negctrl → ctrl → pow → inv`. -- `seq`: flatten, remove empty, inline single-op. -- `apply`: inline trivial composite, fold identities, merge repeated static applies. -- Sugar: lower to canonical, optional re-sugar on print. +- Shape / op sequence equivalence (ignoring SSA names). +- Control & target counts via `UnitaryOpInterface`. +- Optional unitary matrix equivalence (numerical tolerance) for small ops. -## 12. Conclusions and Future Work +Unit tests should be written for all operations, modifiers, and gate definitions. +All canonization rules and folds should be tested. (e.g., `inv inv U => U`) +Negative tests should be written for all errors. -Resolved: +### 9.2 Parser / Printer Smoke (Minimal Textual Tests) -- Unified `UnitaryExpr` abstraction with full modifier and composition support. -- Static + dynamic parameter integration and robust interface methods. -- Canonical nested modifier ordering and explicit rewrite rules. -- User-defined gates (matrix & composite) plus unified `apply` op. -- Dual semantics (`mqtref`, `mqtopt`) standardized. +- Round-trip for: base gates, each modifier, nested modifier chain, matrix unitary, composite definition, sequence. +- FileCheck limited to presence/absence of key ops (avoid brittle SSA checks). -Future Work (concise): +Example smoke test snippet: -- Basis decomposition (e.g., KAK, ZX-assisted) using `UnitaryExpr` graphs. -- Shared control extraction & factoring. -- Symbolic algebra simplifications (parameter expression normalization). -- Hardware mapping leveraging `static_qubit` references. +```mlir +// CHECK-LABEL: func @nested_mods +func.func @nested_mods(%c: !mqtref.qubit, %n: !mqtref.qubit, %t: !mqtref.qubit) { + mqtref.ctrl %c { + mqtref.negctrl %n { + mqtref.pow {exponent = 2.0 : f64} { + mqtref.inv { mqtref.x %t } + } + } + } + return +} +``` From 1f32bb2ca4bf7b5cbf860f9e8eebc1df78cd6539 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 14:47:22 +0200 Subject: [PATCH 010/419] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 1812 ++++++++++++++-------- 1 file changed, 1173 insertions(+), 639 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index adabe6690a..eae7f070bd 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -1,850 +1,1384 @@ # Quantum Dialect Revamp RFC +## Executive Summary + +This RFC proposes a comprehensive redesign of the MQT quantum MLIR dialect to provide a unified, extensible framework for quantum circuit representation and optimization. +The revamp introduces a dual-dialect approach (`mqtref` for reference semantics, `mqtopt` for value semantics), a unified unitary interface, composable modifiers (control, inversion, power), user-defined gates, and a robust canonicalization framework. + +**Key Benefits:** + +- **Unified Interface:** All unitary operations expose consistent APIs for introspection and composition +- **Enhanced Expressiveness:** Support for arbitrary gate modifications, custom gate definitions, and symbolic parameters +- **Optimization-Ready:** Built-in canonicalization rules and transformation hooks +- **Dual Semantics:** Choose between reference semantics (hardware-like) or value semantics (SSA-based optimization) + ## 1. Overview and Goals This RFC proposes a comprehensive revamp of the quantum MLIR dialect(s) to unify unitary representations, improve expressiveness, and enable robust transformations. -Goals: +**Primary Goals:** -- Provide a coherent interface for all operations that apply or produce a unitary (base gates, user-defined gates, modifier-wrapped constructs, sequences). -- Support both reference semantics (in-place: `mqtref`) and value semantics (SSA threading: `mqtopt`). -- Add inversion, powering, negative and positive multi-controls, custom gate definitions (matrix & composite), and composition. -- Unify parameter handling (static + dynamic) with consistent ordering and interface queries. -- Embed canonicalization rules directly at each operation definition. -- Establish a normalized modifier nesting order: `negctrl → ctrl → pow → inv`. -- Enable static matrix extraction where possible and symbolic composition otherwise. -- Prepare foundations for advanced transformations without speculative overreach. +- **Unified Unitary Interface:** Provide a coherent interface for all operations that apply or produce a unitary (base gates, user-defined gates, modifier-wrapped constructs, sequences) +- **Dual Semantics Support:** Support both reference semantics (in-place: `mqtref`) and value semantics (SSA threading: `mqtopt`) +- **Rich Modifier System:** Add inversion, powering, and positive/negative multi-controls as composable modifiers +- **Custom Gate Support:** Enable user-defined gates via matrix and composite (sequence-based) definitions +- **Consistent Parameterization:** Unify parameter handling (static + dynamic) with consistent ordering and interface queries +- **Canonicalization Framework:** Embed canonicalization rules directly at each operation definition +- **Normalized Modifier Nesting:** Establish a canonical modifier nesting order: `negctrl → ctrl → pow → inv` +- **Matrix Extraction:** Enable static matrix extraction where possible and symbolic composition otherwise ## 2. Current State and Limitations -Current issues the revamp addresses: +The current implementation has several significant limitations that this revamp addresses: -- Only a control modifier exists and is directly embedded in the unitary operations (interface); missing power, inversion. -- No unified interface for extracting/composing unitaries—leading to ad hoc logic. -- No way to obtain matrix representations for gates. -- No user-defined gate (matrix/composite) constructs. -- Absent canonicalization strategy for modifier order, parameter folding, gate specialization. -- Mostly FileCheck-based testing that is cumbersome and error prone to write. -- No convenient builders for programs at the moment. +**Existing Issues:** -## 3. Dialect Structure and Categories +- **Limited Modifiers:** Only a control modifier exists, directly embedded in unitary operations; missing power and inversion modifiers +- **Missing Matrix Support:** No way to obtain matrix representations for gates +- **No Custom Gates:** No support for user-defined gates (neither matrix-based nor composite) +- **Absent Canonicalization:** No systematic canonicalization strategy for modifier order, parameter folding, or gate specialization +- **Testing Challenges:** Mostly FileCheck-based testing that is cumbersome and error-prone to write +- **Limited Builders:** No convenient programmatic builders for constructing quantum programs -Two parallel dialects: +These limitations hinder both expressiveness and optimization capabilities, motivating the comprehensive redesign proposed in this RFC. -- `mqtref`: Reference semantics (in-place mutation of qubits; no new results). -- `mqtopt`: Value semantics (operations consume qubits and yield new qubit results). +## 3. Dialect Structure and Categories -Categories: +The revamp introduces two parallel dialects with identical operation sets but different operational semantics: -1. Resource Operations -2. Measurement and Reset -3. UnitaryInterface Operations +### 3.1 Dialect Overview -### 3.1 Resource Operations +**`mqtref` (Reference Semantics):** -Purpose: Manage qubit lifetime and references. +- Operations mutate qubits in-place (similar to hardware model) +- No SSA results for qubit operations +- More natural for hardware mapping and direct circuit representation +- Example: `mqtref.h %q` applies Hadamard to qubit `%q` in-place -Examples (reference semantics): +**`mqtopt` (Value Semantics):** -``` -%q = mqtref.alloc : mqtref.Qubit -mqtref.dealloc %q : mqtref.Qubit -%q0 = mqtref.qubit 0 : mqtref.Qubit -``` +- Operations consume and produce new SSA values (functional style) +- Enables powerful SSA-based optimizations and transformations +- More natural for compiler optimization passes +- Example: `%q_out = mqtopt.h %q_in` consumes `%q_in` and produces `%q_out` -Value semantics: +Both dialects share the same operation names and semantics, differing only in their type system and SSA threading model. Conversion passes enable moving between the two dialects as needed. -``` -%q = mqtopt.alloc : mqtopt.Qubit -mqtopt.dealloc %q : mqtopt.Qubit -%q0 = mqtopt.qubit 0 : mqtopt.Qubit -``` +### 3.2 Operation Categories -Canonicalization (patterns / folds): +All operations fall into three primary categories: -- Remove unused `alloc` (DCE). +1. **Resource Operations:** Manage qubit lifetime and allocation +2. **Measurement and Reset:** Non-unitary operations that collapse or reinitialize quantum states +3. **Unitary Operations:** All operations implementing the `UnitaryOpInterface` (base gates, modifiers, sequences, custom gates) -### 3.2 Measurement and Reset +### 3.3 Resource Operations -Non-unitary (do not implement Unitary interface). +**Purpose:** Manage qubit lifetime and references. -Reference: +**Reference Semantics (`mqtref`):** -``` -%c = mqtref.measure %q : mqtref.Qubit -> i1 -mqtref.reset %q : mqtref.Qubit +```mlir +%q = mqtref.alloc : !mqtref.qubit +mqtref.dealloc %q : !mqtref.qubit +%q0 = mqtref.qubit 0 : !mqtref.qubit // Static qubit reference ``` -Value: +**Value Semantics (`mqtopt`):** -``` -%qout, %c = mqtopt.measure %qin : mqtopt.Qubit -> (mqtopt.Qubit, i1) -%qout = mqtopt.reset %qin : mqtopt.Qubit +```mlir +%q = mqtopt.alloc : !mqtopt.qubit +mqtopt.dealloc %q : !mqtopt.qubit +%q0 = mqtopt.qubit 0 : !mqtopt.qubit // Static qubit reference ``` -Canonicalization: +**Canonicalization Patterns:** -- `reset` immediately after `alloc` → remove `reset`. -- Consecutive `reset` on same qubit (reference semantics) → single instance. +- Dead allocation elimination: Remove unused `alloc` operations (DCE) +- **TODO:** Define register allocation operations for multi-qubit arrays +- **TODO:** Specify interaction with classical bit allocation -### 3.3 Unified Unitary Interface Design +### 3.4 Measurement and Reset -All unitary-applying operations implement a common interface (applies to base gates, modifiers, sequences, and user-defined operations). +Non-unitary operations that do not implement the `UnitaryOpInterface`. -Interface methods (conceptual API): +**Reference Semantics:** -- `getNumTargets() -> size_t` -- `getNumPosControls() -> size_t` -- `getNumNegControls() -> size_t` -- `getNumParams() -> size_t` -- `getParameter(i) -> ParameterDescriptor` - - `ParameterDescriptor`: `isStatic()`, `getConstantValue()?`, `getValueOperand()` -- `getInput(i) -> mlir::Value` / `getOutput(i) -> mlir::Value` (value semantics distinct; reference semantics output = input) -- `getOutputForInput(mlir::Value) -> mlir::Value` (value semantics distinct; reference semantics output = input) -- `getInputForOutput(mlir::Value) -> mlir::Value` (value semantics distinct; reference semantics output = input) -- `hasStaticUnitary() -> bool` -- `tryGetStaticMatrix() -> Optional` (2D tensor with shape (2^n, 2^n) and element type `complex`; written concretely for fixed n as e.g. `tensor<4x4xcomplex>`) -- `isInverted() -> bool` -- `getPower() -> Optional` +```mlir +%c = mqtref.measure %q : !mqtref.qubit -> i1 +mqtref.reset %q : !mqtref.qubit +``` -Identification & Descriptor Tuple: -`(baseSymbol, orderedParams, posControls, negControls, powerExponent, invertedFlag)` allows canonical equality tests. +**Value Semantics:** -Parameter Model: +```mlir +%q_out, %c = mqtopt.measure %q_in : !mqtopt.qubit -> (!mqtopt.qubit, i1) +%q_out = mqtopt.reset %q_in : !mqtopt.qubit -> !mqtopt.qubit +``` -- Parameters appear in parentheses immediately after mnemonic. -- Mixed static (attributes) and dynamic (operands) preserve original order. -- Enumeration returns flattened ordered list; inspect each for static/dynamic. +**Canonicalization Patterns:** -Static Matrix Extraction: +- `reset` immediately after `alloc` → remove `reset` (already in ground state) +- Consecutive `reset` on same qubit → single instance +- **TODO:** Specify multi-qubit measurement operations +- **TODO:** Define measurement basis specification (currently assumes computational basis) -- Provided if gate is analytic, all parameters are static, or for matrix-defined user gates. -- For sequences/composites of static subunits, compose matrices. +### 3.5 Unified Unitary Interface Design -Inversion & Power Interaction: +All unitary-applying operations implement a common `UnitaryOpInterface` that provides uniform introspection and composition capabilities. This applies to base gates, modifiers, sequences, and user-defined operations. -- `inv` introduces `invertedFlag` (final canonical position). -- `pow` stores exponent; negative exponent canonicalized to `inv(pow(+exp))` then reordered. +**Interface Methods:** -Control Extension: +```c++ +// Qubit accessors +size_t getNumTargets(); +size_t getNumPosControls(); +size_t getNumNegControls(); +Value getTarget(size_t i); +Value getPosControl(size_t i); +Value getNegControl(size_t i); + +// Value semantics threading +Value getInput(size_t i); // Combined controls + targets +Value getOutput(size_t i); // Combined controls + targets +Value getOutputForInput(Value in); // Identity in reference semantics +Value getInputForOutput(Value out); // Identity in reference semantics + +// Parameter handling +size_t getNumParams(); +ParameterDescriptor getParameter(size_t i); + +// Parameter descriptor +struct ParameterDescriptor { + bool isStatic(); // True if attribute, false if operand + Optional getConstantValue(); // If static + Value getValueOperand(); // If dynamic +}; -- `ctrl` / `negctrl` wrappers extend control sets; interface aggregates flattened sets. +// Matrix extraction +bool hasStaticUnitary(); +Optional tryGetStaticMatrix(); // tensor<2^n x 2^n x complex> -## 4. Base Gate Operations +// Modifier state +bool isInverted(); +Optional getPower(); // Returns power exponent if applicable -### 4.1 Philosophy +// Identification +std::string getBaseSymbol(); +CanonicalDescriptor getCanonicalDescriptor(); // For equivalence testing +``` -- Named base gates define unitaries with fixed target arity and parameter arity traits. -- Provide static matrix when parameters static or no parameters; symbolic otherwise. -- Avoid embedding modifier semantics directly—wrappers handle extension. +**Canonical Descriptor Tuple:** -### 4.2 Base Gate Specification Template +Each unitary can be uniquely identified by the tuple: + +``` +(baseSymbol, orderedParams, posControls, negControls, powerExponent, invertedFlag) +``` + +This enables canonical equality tests and efficient deduplication. -For every named base gate op G: +**Parameter Model:** -- Purpose: Apply the unitary for gate G to its target qubit(s). -- Signature (Reference): `mqtref.G(param_list?) %q[,...] : ` (no results) -- Signature (Value): `%out_targets = mqtopt.G(param_list?) %in_targets : () -> ` -- Assembly Format: `G() `; params in parentheses; qubits as trailing operands. -- Interface Implementation Notes: - - `getNumTargets()` fixed by trait. - - Parameters enumerated in declared order; static vs dynamic via attribute vs operand. - - `hasStaticUnitary()` true iff all parameters static. +- Parameters appear in parentheses immediately after the operation mnemonic +- Support for mixed static (attributes) and dynamic (SSA values) parameters in original order +- Enumeration returns a flattened ordered list where each parameter can be inspected for static/dynamic nature +- Example: `mqtref.u(%theta, 1.5708, %lambda) %q` has three parameters: dynamic, static, dynamic -### 4.3 Gate List +**Static Matrix Extraction:** -Overview: +- Provided when the gate is analytic and all parameters are static +- For matrix-defined user gates, returns the defined matrix +- For sequences/composites of static subunits, composes matrices via multiplication +- Returns `std::nullopt_t` for symbolic or dynamic parameterizations -Zero-qubit gates: -Parametrized: `gphase(%theta)` +**Modifier Interaction:** -Single-qubit gates: -No-parameter: `id, x, y, z, h, s, sdg, t, tdg, sx, sxdg` -Parameterized: `rx(%theta), ry(%theta), rz(%theta), p(%lambda), r(theta, %phi), u(%theta, %phi, %lambda), u2(%phi, %lambda)` +- `inv` modifier introduces `invertedFlag` in the canonical descriptor +- `pow` modifier stores exponent; negative exponents are canonicalized to `inv(pow(+exp))` then reordered +- Control modifiers (`ctrl`, `negctrl`) extend control sets; interface aggregates flattened sets across nested modifiers -Two-qubit gates: -No-parameter: `swap, iswap, dcx, ecr` -Parameterized: `rxx(%theta), ryy(%theta), rzz(%theta), rzx(%theta), xx_minus_yy(%theta, %beta), xx_plus_yy(%theta, %beta)` +## 4. Base Gate Operations + +### 4.1 Philosophy and Design Principles -Variable qubit gates: -No-parameter: `barrier` +**Core Principles:** -General canonicalization based on traits (not repeated for individual gates): +- **Named Basis Gates:** Each base gate defines a unitary with fixed target arity and parameter arity (expressed via traits) +- **Static Matrix When Possible:** Provide static matrix representations when parameters are static or absent +- **Modifier-Free Core:** Avoid embedding modifier semantics directly—use wrapper operations instead +- **Consistent Signatures:** Maintain uniform syntax across reference and value semantics -- Hermitian: `inv G → G` -- Hermitian: `pow(n: int) G => if n % 2 == 0 then id else G` -- Hermitian: `G %q; G %q => cancel` +**Benefits:** -#### 4.3.1 `gphase` Gate +- Simplifies gate definitions and verification +- Enables powerful pattern matching and rewriting +- Separates concerns between gate semantics and modifications + +### 4.2 Base Gate Specification Template -- Purpose: global phase `exp(i θ)`. -- Traits: NoTarget, OneParameter -- Signatures: +For every named base gate operation `G`: + +**Specification Elements:** + +- **Purpose:** Brief description of the unitary operation +- **Traits:** Target arity (e.g., `OneTarget`, `TwoTarget`), parameter arity (e.g., `OneParameter`), special properties (e.g., `Hermitian`, `Diagonal`) +- **Signatures:** + - Reference: `mqtref.G(param_list?) %targets : (param_types..., qubit_types...)` + - Value: `%out_targets = mqtopt.G(param_list?) %in_targets : (param_types..., qubit_types...) -> (qubit_types...)` +- **Assembly Format:** `G(params?) targets` where params are in parentheses, qubits as trailing operands +- **Interface Implementation:** + - `getNumTargets()` fixed by target arity trait + - Parameters enumerated in declared order (static via attribute, dynamic via operand) + - `hasStaticUnitary()` returns true iff all parameters are static +- **Canonicalization:** List of simplification rules and rewrites +- **Matrix:** Mathematical matrix representation (static or symbolic) +- **Equivalences:** Relationships to other gates (e.g., decomposition in terms of `u` gate) + +**General Canonicalization Patterns Based on Traits:** + +The following canonicalization patterns apply automatically to all gates with the specified traits (not repeated for individual gates): + +- **Hermitian gates:** + - `inv(G) → G` (self-adjoint) + - `G %q; G %q → remove` (cancellation) + - `pow(n: even_integer) G → id` + - `pow(n: odd_integer) G → G` +- **Diagonal gates:** + - Commute with other diagonal gates on same qubits + - Can be merged when adjacent +- **Zero-parameter gates with Hermitian trait:** + - Consecutive applications cancel + +### 4.3 Gate Catalog + +**Gate Organization:** + +- **Zero-qubit gates:** Global phase +- **Single-qubit gates:** Pauli gates, rotations, phase gates, universal gates +- **Two-qubit gates:** Entangling gates, Ising-type interactions +- **Variable-qubit gates:** Barrier and utility operations + +#### 4.3.1 `gphase` Gate (Global Phase) + +- **Purpose:** Apply global phase `exp(iθ)` to the quantum state +- **Traits:** `NoTarget`, `OneParameter` +- **Signatures:** - Ref: `mqtref.gphase(%theta)` - Value: `mqtopt.gphase(%theta)` -- Static Example: `mqtref.gphase(3.14159)` -- Dynamic Example: `mqtref.gphase(%theta)` -- Canonicalization: +- **Examples:** + - Static: `mqtref.gphase(3.14159)` + - Dynamic: `mqtref.gphase(%theta)` +- **Canonicalization:** - `gphase(0) → remove` - - `inv gphase(θ) → gphase(-θ)` - - Two consecutive `gphase(a); gphase(b)` folded by adding angles. - - `ctrl(%q0) { gphase(θ) } → p(θ) %q0` (specialization to `p` gate). - - `negctrl(%q0) { gphase(θ) } → gphase(pi); p(θ) %q0` (specialization for negative control) + - `inv(gphase(θ)) → gphase(-θ)` + - `gphase(a); gphase(b) → gphase(a + b)` (consecutive phases merge) + - `ctrl(%q) { gphase(θ) } → p(θ) %q` (controlled global phase becomes phase gate) + - `negctrl(%q) { gphase(θ) } → gphase(π); p(θ) %q` (negative control specialization) - `pow(n) { gphase(θ) } → gphase(n*θ)` -- Matrix (dynamic): `[exp(i θ)]` (1x1 matrix). Static if θ constant. -- To be figured out: These gates have no users per definition as they have no targets. It is unclear how they should be merged and how they are included in traversals. +- **Matrix:** `[exp(iθ)]` (1×1 scalar, static if θ constant) +- **Open Issues:** + - **TODO:** Global phase gates have no target qubits, making traversal and merging semantics unclear + - **TODO:** Define how global phases interact with circuit-level operations + - **TODO:** Specify whether global phases should be preserved or eliminated in certain contexts -### 4.3.2 `id` Gate +#### 4.3.2 `id` Gate (Identity) -- Purpose: Identity gate. -- Traits: OneTarget, NoParameter, Hermitian, Diagonal -- Signatures: +- **Purpose:** Identity operation (does nothing) +- **Traits:** `OneTarget`, `NoParameter`, `Hermitian`, `Diagonal` +- **Signatures:** - Ref: `mqtref.id %q` - - Value: `%q_out mqtopt.id %q_in` -- Canonicalization: - - `id → remove` - - `pow(r) id => id` - - `ctrl(...) { id } => id` - - `negctrl(...) { id } => id` -- Matrix (static): `[1, 0; 0, 1]` (2x2 matrix). -- Definition in terms of `u`: `u(0, 0, 0) %q` - -#### 4.3.3 `x` Gate - -- Purpose: Pauli-X gate -- Traits: OneTarget, NoParameter, Hermitian -- Signatures: + - Value: `%q_out = mqtopt.id %q_in` +- **Canonicalization:** + - `id → remove` (no effect) + - `pow(r) id → id` (any power is still id) + - `ctrl(...) { id } → id` (control with id is just id) + - `negctrl(...) { id } → id` +- **Matrix:** `[1, 0; 0, 1]` (2x2 identity matrix) +- **Definition in terms of `u`:** `u(0, 0, 0) %q` + +#### 4.3.3 `x` Gate (Pauli-X) + +- **Purpose:** Pauli-X gate (bit flip) +- **Traits:** `OneTarget`, `NoParameter`, `Hermitian` +- **Signatures:** - Ref: `mqtref.x %q` - - Value: `%q_out mqtopt.x %q_in` -- Canonicalization: - - `pow(1/2) x => sx` - - `pow(-1/2) x => sxdg` -- Matrix (static): `[0, 1; 1, 0]` (2x2 matrix). -- Definition in terms of `u`: `u(π, 0, π) %q` -- To be figured out: `-iX == rx(pi)` (global phase difference between `rx(pi)` and `x`). `pow(r) rx(θ) => rx(r*θ)`. What does this imply for `pow(r) x`? - -#### 4.3.4 `y` Gate - -- Purpose: Pauli-Y gate -- Traits: OneTarget, NoParameter, Hermitian -- Signatures: + - Value: `%q_out = mqtopt.x %q_in` +- **Canonicalization:** + - `pow(1/2) x → sx` (square root of x is sx) + - `pow(-1/2) x → sxdg` (inverse square root is sxdg) +- **Matrix:** `[0, 1; 1, 0]` (2x2 matrix) +- **Definition in terms of `u`:** `u(π, 0, π) %q` +- **Open Issues:** + - **TODO:** Resolve global phase relationship: `-iX == rx(π)`. What are the implications for `pow(r) x`? + - **TODO:** Define whether `pow(1/3) x` should be supported or rejected + +#### 4.3.4 `y` Gate (Pauli-Y) + +- **Purpose:** Pauli-Y gate (bit and phase flip) +- **Traits:** `OneTarget`, `NoParameter`, `Hermitian` +- **Signatures:** - Ref: `mqtref.y %q` - - Value: `%q_out mqtopt.y %q_in` -- Matrix (static): `[0, -i; i, 0]` (2x2 matrix). -- Definition in terms of `u`: `u(π, π/2, π/2) %q` -- To be figured out: `-iY == ry(pi)` (global phase difference between `ry(pi)` and `y`). `pow(r) ry(θ) => ry(r*θ)`. What does this imply for `pow(r) y`? + - Value: `%q_out = mqtopt.y %q_in` +- **Matrix:** `[0, -i; i, 0]` (2x2 matrix) +- **Definition in terms of `u`:** `u(π, π/2, π/2) %q` +- **Open Issues:** + - **TODO:** Resolve global phase relationship: `-iY == ry(π)`. What are the implications for `pow(r) y`? -#### 4.3.5 `z` Gate +#### 4.3.5 `z` Gate (Pauli-Z) -- Purpose: Pauli-Z gate -- Traits: OneTarget, NoParameter, Hermitian, Diagonal -- Signatures: +- **Purpose:** Pauli-Z gate (phase flip) +- **Traits:** `OneTarget`, `NoParameter`, `Hermitian`, `Diagonal` +- **Signatures:** - Ref: `mqtref.z %q` - - Value: `%q_out mqtopt.z %q_in` -- Canonicalization: - - `pow(1/2) z => s` - - `pow(-1/2) z => sdg` - - `pow(1/4) z => t` - - `pow(-1/4) z => tdg` - - `pow(r) z => p(π * r)` for real r -- Matrix (static): `[1, 0; 0, -1]` (2x2 matrix). -- Definition in terms of `u`: `u(0, 0, π) %q` - -#### 4.3.6 `h` Gate - -- Purpose: Hadamard gate. -- Traits: OneTarget, NoParameter, Hermitian -- Signatures: + - Value: `%q_out = mqtopt.z %q_in` +- **Canonicalization:** + - `pow(1/2) z → s` + - `pow(-1/2) z → sdg` + - `pow(1/4) z → t` + - `pow(-1/4) z → tdg` + - `pow(r) z → p(π * r)` for real r +- **Matrix:** `[1, 0; 0, -1]` (2x2 matrix) +- **Definition in terms of `u`:** `u(0, 0, π) %q` + +#### 4.3.6 `h` Gate (Hadamard) + +- **Purpose:** Hadamard gate (creates superposition) +- **Traits:** `OneTarget`, `NoParameter`, `Hermitian` +- **Signatures:** - Ref: `mqtref.h %q` - - Value: `%q_out mqtopt.h %q_in` -- Matrix (static): `1/sqrt(2) * [1, 1; 1, -1]` (2x2 matrix). -- Definition in terms of `u`: `u(π/2, 0, π) %q` + - Value: `%q_out = mqtopt.h %q_in` +- **Matrix:** `1/sqrt(2) * [1, 1; 1, -1]` (2x2 matrix) +- **Definition in terms of `u`:** `u(π/2, 0, π) %q` -#### 4.3.7 `s` Gate +#### 4.3.7 `s` Gate (S/Phase-90) -- Purpose: S gate. -- Traits: OneTarget, NoParameter, Diagonal -- Signatures: +- **Purpose:** S gate (applies a phase of π/2) +- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.s %q` - - Value: `%q_out mqtopt.s %q_in` -- Canonicalization: - - `inv s => sdg` - - `s %q; s %q => z %q` - - `pow(n: int) s => if n % 4 == 0 then id else if n % 4 == 1 then s else if n % 4 == 2 then z else sdg` - - `pow(1/2) s => t` - - `pow(-1/2) s => tdg` - - `pow(+-2) s => z` - - `pow(r) s => p(π/2 * r)` for real r -- Matrix (static): `[1, 0; 0, i]` (2x2 matrix). -- Definition in terms of `u`: `u(0, 0, π/2) %q` - -#### 4.3.8 `sdg` Gate - -- Purpose: Sdg gate. -- Traits: OneTarget, NoParameter, Diagonal -- Signatures: + - Value: `%q_out = mqtopt.s %q_in` +- **Canonicalization:** + - `inv s → sdg` + - `s %q; s %q → z %q` + - `pow(n: int) s → if n % 4 == 0 then id else if n % 4 == 1 then s else if n % 4 == 2 then z else sdg` + - `pow(1/2) s → t` + - `pow(-1/2) s → tdg` + - `pow(+-2) s → z` + - `pow(r) s → p(π/2 * r)` for real r +- **Matrix:** `[1, 0; 0, i]` (2x2 matrix) +- **Definition in terms of `u`:** `u(0, 0, π/2) %q` + +#### 4.3.8 `sdg` Gate (S-Dagger) + +- **Purpose:** Sdg gate (applies a phase of -π/2) +- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.sdg %q` - - Value: `%q_out mqtopt.sdg %q_in` -- Canonicalization: - - `inv sdg => s` - - `sdg %q; sdg %q => z %q` - - `pow(n: int) sdg => if n % 4 == 0 then id else if n % 4 == 1 then sdg else if n % 4 == 2 then z else s` - - `pow(1/2) sdg => tdg` - - `pow(-1/2) sdg => t` - - `pow(+-2) sdg => z` - - `pow(r) sdg => p(-π/2 * r)` for real r -- Matrix (static): `[1, 0; 0, -i]` (2x2 matrix). -- Definition in terms of `u`: `u(0, 0, -π/2) %q` - -#### 4.3.9 `t` Gate - -- Purpose: T gate. -- Traits: OneTarget, NoParameter, Diagonal -- Signatures: + - Value: `%q_out = mqtopt.sdg %q_in` +- **Canonicalization:** + - `inv sdg → s` + - `sdg %q; sdg %q → z %q` + - `pow(n: int) sdg → if n % 4 == 0 then id else if n % 4 == 1 then sdg else if n % 4 == 2 then z else s` + - `pow(1/2) sdg → tdg` + - `pow(-1/2) sdg → t` + - `pow(+-2) sdg → z` + - `pow(r) sdg → p(-π/2 * r)` for real r +- **Matrix:** `[1, 0; 0, -i]` (2x2 matrix) +- **Definition in terms of `u`:** `u(0, 0, -π/2) %q` + +#### 4.3.9 `t` Gate (T/π-8) + +- **Purpose:** T gate (applies a phase of π/4) +- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.t %q` - - Value: `%q_out mqtopt.t %q_in` -- Canonicalization: - - `inv t => tdg` - - `t %q; t %q; => s %q` - - `pow(2) t => s` - - `pow(-2) t => sdg` - - `pow(+-4) t => z` - - `pow(r) t => p(π/4 * r)` for real r -- Matrix (static): `[1, 0; 0, exp(i π/4)]` (2x2 matrix). -- Definition in terms of `u`: `u(0, 0, π/4) %q` - -#### 4.3.10 `tdg` Gate - -- Purpose: Tdg gate. -- Traits: OneTarget, NoParameter, Diagonal -- Signatures: + - Value: `%q_out = mqtopt.t %q_in` +- **Canonicalization:** + - `inv t → tdg` + - `t %q; t %q; → s %q` + - `pow(2) t → s` + - `pow(-2) t → sdg` + - `pow(+-4) t → z` + - `pow(r) t → p(π/4 * r)` for real r +- **Matrix:** `[1, 0; 0, exp(i π/4)]` (2x2 matrix) +- **Definition in terms of `u`:** `u(0, 0, π/4) %q` + +#### 4.3.10 `tdg` Gate (T-Dagger) + +- **Purpose:** Tdg gate (applies a phase of -π/4) +- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.tdg %q` - - Value: `%q_out mqtopt.tdg %q_in` -- Canonicalization: - - `inv tdg => t` - - `tdg %q; tdg %q; => sdg %q` - - `pow(2) tdg => sdg` - - `pow(-2) tdg => s` - - `pow(+-4) tdg => z` - - `pow(r) tdg => p(-π/4 * r)` for real r -- Matrix (static): `[1, 0; 0, exp(-i π/4)]` (2x2 matrix). -- Definition in terms of `u`: `u(0, 0, -π/4) %q` - -#### 4.3.11 `sx` Gate - -- Purpose: sqrt(x) gate. -- Traits: OneTarget, NoParameter -- Signatures: + - Value: `%q_out = mqtopt.tdg %q_in` +- **Canonicalization:** + - `inv tdg → t` + - `tdg %q; tdg %q; → sdg %q` + - `pow(2) tdg → sdg` + - `pow(-2) tdg → s` + - `pow(+-4) tdg → z` + - `pow(r) tdg → p(-π/4 * r)` for real r +- **Matrix:** `[1, 0; 0, exp(-i π/4)]` (2x2 matrix) +- **Definition in terms of `u`:** `u(0, 0, -π/4) %q` + +#### 4.3.11 `sx` Gate (√X) + +- **Purpose:** Square root of X gate +- **Traits:** `OneTarget`, `NoParameter` +- **Signatures:** - Ref: `mqtref.sx %q` - - Value: `%q_out mqtopt.sx %q_in` -- Canonicalization: - - `inv sx => sxdg` - - `sx %q; sx %q => x %q` - - `pow(+-2) sx => x` -- Matrix (static): `1/2 * [1 + i, 1 - i; 1 - i, 1 + i]` (2x2 matrix). -- To be figured out: `e^(-i pi/4) sx == rx(pi/2)` (global phase difference between `rx(pi/2)` and `sx`). `pow(r) rx(θ) => rx(r*θ)`. What does this imply for `pow(r) sx`? - -#### 4.3.12 `sxdg` Gate - -- Purpose: sqrt(x) gate. -- Traits: OneTarget, NoParameter -- Signatures: + - Value: `%q_out = mqtopt.sx %q_in` +- **Canonicalization:** + - `inv sx → sxdg` + - `sx %q; sx %q → x %q` + - `pow(+-2) sx → x` +- **Matrix:** `1/2 * [1 + i, 1 - i; 1 - i, 1 + i]` (2x2 matrix) +- **Open Issues:** + - **TODO:** Resolve global phase relationship: `exp(-iπ/4) sx == rx(π/2)`. Define power semantics. + +#### 4.3.12 `sxdg` Gate (√X-Dagger) + +- **Purpose:** Inverse of the square root of X gate +- **Traits:** `OneTarget`, `NoParameter` +- **Signatures:** - Ref: `mqtref.sxdg %q` - - Value: `%q_out mqtopt.sxdg %q_in` -- Canonicalization: - - `inv sxdg => sx` - - `sxdg %q; sxdg %q => x %q` - - `pow(+-2) sxdg => x` -- Matrix (static): `1/2 * [1 - i, 1 + i; 1 + i, 1 - i]` (2x2 matrix). -- To be figured out: `exp(-i pi/4) sxdg == rx(-pi/2)` (global phase difference between `rx(-pi/2)` and `sxdg`). `pow(r) rx(θ) => rx(r*θ)`. What does this imply for `pow(r) sxdg`? - -#### 4.3.13 `rx` Gate - -- Purpose: Rotation around the x-axis. -- Traits: OneTarget, OneParameter -- Signatures: + - Value: `%q_out = mqtopt.sxdg %q_in` +- **Canonicalization:** + - `inv sxdg → sx` + - `sxdg %q; sxdg %q → x %q` + - `pow(+-2) sxdg → x` +- **Matrix:** `1/2 * [1 - i, 1 + i; 1 + i, 1 - i]` (2x2 matrix) +- **Open Issues:** + - **TODO:** Resolve global phase relationship with `rx(-π/2)`. Define power semantics. + +#### 4.3.13 `rx` Gate (X-Rotation) + +- **Purpose:** Rotation around the X-axis by angle θ +- **Traits:** `OneTarget`, `OneParameter` +- **Signatures:** - Ref: `mqtref.rx(%theta) %q` - - Value: `%q_out mqtopt.rx(%theta) %q_in` -- Static variant: `mqtref.rx(3.14159) %q` -- Canonicalization: - - `rx(a) %q; rx(b) %q => rx(a + b) %q` - - `inv rx(θ) => rx(-θ)` - - `pow(r) rx(θ) => rx(r * θ)` for real r -- Matrix (dynamic): `exp(-i θ X) = [cos(θ/2), -i sin(θ/2); -i sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. -- Definition in terms of `u`: `u(θ, -π/2, π/2) %q` - -#### 4.3.14 `ry` Gate - -- Purpose: Rotation around the y-axis. -- Traits: OneTarget, OneParameter -- Signatures: + - Value: `%q_out = mqtopt.rx(%theta) %q_in` +- **Static variant:** `mqtref.rx(3.14159) %q` +- **Canonicalization:** + - `rx(a) %q; rx(b) %q → rx(a + b) %q` + - `inv rx(θ) → rx(-θ)` + - `pow(r) rx(θ) → rx(r * θ)` for real r +- **Matrix (dynamic):** `exp(-i θ/2 X) = [cos(θ/2), -i sin(θ/2); -i sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. +- **Definition in terms of `u`:** `u(θ, -π/2, π/2) %q` + +#### 4.3.14 `ry` Gate (Y-Rotation) + +- **Purpose:** Rotation around the Y-axis by angle θ +- **Traits:** `OneTarget`, `OneParameter` +- **Signatures:** - Ref: `mqtref.ry(%theta) %q` - - Value: `%q_out mqtopt.ry(%theta) %q_in` -- Static variant: `mqtref.ry(3.14159) %q` -- Canonicalization: - - `ry(a) %q; ry(b) %q => ry(a + b) %q` - - `inv ry(θ) => ry(-θ)` - - `pow(r) ry(θ) => ry(r * θ)` for real r -- Matrix (dynamic): `exp(-i θ Y) = [cos(θ/2), -sin(θ/2); sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. -- Definition in terms of `u`: `u(θ, 0, 0) %q` - -#### 4.3.15 `rz` Gate - -- Purpose: Rotation around the z-axis. -- Traits: OneTarget, OneParameter, Diagonal -- Signatures: + - Value: `%q_out = mqtopt.ry(%theta) %q_in` +- **Static variant:** `mqtref.ry(3.14159) %q` +- **Canonicalization:** + - `ry(a) %q; ry(b) %q → ry(a + b) %q` + - `inv ry(θ) → ry(-θ)` + - `pow(r) ry(θ) → ry(r * θ)` for real r +- **Matrix (dynamic):** `exp(-i θ/2 Y) = [cos(θ/2), -sin(θ/2); sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. +- **Definition in terms of `u`:** `u(θ, 0, 0) %q` + +#### 4.3.15 `rz` Gate (Z-Rotation) + +- **Purpose:** Rotation around the Z-axis by angle θ +- **Traits:** `OneTarget`, `OneParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.rz(%theta) %q` - - Value: `%q_out mqtopt.rz(%theta) %q_in` -- Static variant: `mqtref.rz(3.14159) %q` -- Canonicalization: - - `rz(a) %q; rz(b) %q => rz(a + b) %q` - - `inv rz(θ) => rz(-θ)` - - `pow(r) rz(θ) => rz(r * θ)` for real r -- Matrix (dynamic): `exp(-i θ Z) = [exp(-i θ/2), 0; 0, exp(i θ/2)]` (2x2 matrix). Static if θ constant. -- To be figured out: `rz(θ) == exp(i*θ/2) * p(θ)` (global phase difference between `rz(θ)` and `p(θ)`). - -#### 4.3.16 `p` Gate - -- Purpose: Phase gate. -- Traits: OneTarget, OneParameter, Diagonal -- Signatures: + - Value: `%q_out = mqtopt.rz(%theta) %q_in` +- **Static variant:** `mqtref.rz(3.14159) %q` +- **Canonicalization:** + - `rz(a) %q; rz(b) %q → rz(a + b) %q` + - `inv rz(θ) → rz(-θ)` + - `pow(r) rz(θ) → rz(r * θ)` for real r +- **Matrix (dynamic):** `exp(-i θ/2 Z) = [exp(-i θ/2), 0; 0, exp(i θ/2)]` (2x2 matrix). Static if θ constant. +- **Open Issues:** + - **TODO:** Clarify global phase relationship: `rz(θ) == exp(iθ/2) * p(θ)`. Should canonicalization convert between these? + +#### 4.3.16 `p` Gate (Phase) + +- **Purpose:** Phase gate (applies a phase of θ) +- **Traits:** `OneTarget`, `OneParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.p(%theta) %q` - - Value: `%q_out mqtopt.p(%theta) %q_in` -- Static variant: `mqtref.p(3.14159) %q` -- Canonicalization: - - `p(a) %q; p(b) %q => p(a + b) %q` - - `inv p(θ) => p(-θ)` - - `pow(r) p(θ) => p(r * θ)` for real r -- Matrix (dynamic): `[1, 0; 0, exp(i θ)]` (2x2 matrix). Static if θ constant. -- Definition in terms of `u`: `u(0, 0, θ) %q` - -#### 4.3.17 `r` Gate - -- Purpose: General rotation around an axis in the XY-plane. -- Traits: OneTarget, TwoParameter -- Signatures: + - Value: `%q_out = mqtopt.p(%theta) %q_in` +- **Static variant:** `mqtref.p(3.14159) %q` +- **Canonicalization:** + - `p(a) %q; p(b) %q → p(a + b) %q` + - `inv p(θ) → p(-θ)` + - `pow(r) p(θ) → p(r * θ)` for real r +- **Matrix (dynamic):** `[1, 0; 0, exp(i θ)]` (2x2 matrix). Static if θ constant. +- **Definition in terms of `u`:** `u(0, 0, θ) %q` + +#### 4.3.17 `r` Gate (Arbitrary Axis Rotation) + +- **Purpose:** Rotation around an arbitrary axis in the XY-plane by angles θ and φ +- **Traits:** `OneTarget`, `TwoParameter` +- **Signatures:** - Ref: `mqtref.r(%theta, %phi) %q` - - Value: `%q_out mqtopt.r(%theta, %phi) %q_in` -- Static variant: `mqtref.r(3.14159, 1.5708) %q` -- Mixed variant: `mqtref.r(%theta, 1.5708) %q` -- Canonicalization: - - `inv r(θ, φ) => r(-θ, φ)` - - `pow(real) r(θ, φ) => r(real * θ, φ)` for real `real` - - `r(θ, 0) => rx(θ)` - - `r(θ, π/2) => ry(θ)` -- Matrix (dynamic): `exp(-i θ (cos(φ) X + sin(φ) Y)) = [cos(θ/2), -i exp(-i φ) sin(θ/2); -i exp(i φ) sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ and φ constant. -- Definition in terms of `u`: `u(θ, -π/2 + φ, π/2 - φ) %q` - -#### 4.3.18 `u` Gate - -- Purpose: Universal single-qubit gate. -- Traits: OneTarget, ThreeParameter -- Signatures: + - Value: `%q_out = mqtopt.r(%theta, %phi) %q_in` +- **Static variant:** `mqtref.r(3.14159, 1.5708) %q` +- **Mixed variant:** `mqtref.r(%theta, 1.5708) %q` +- **Canonicalization:** + - `inv r(θ, φ) → r(-θ, φ)` + - `pow(real) r(θ, φ) → r(real * θ, φ)` for real `real` + - `r(θ, 0) → rx(θ)` + - `r(θ, π/2) → ry(θ)` +- **Matrix (dynamic):** `exp(-i θ (cos(φ) X + sin(φ) Y)) = [cos(θ/2), -i exp(-i φ) sin(θ/2); -i exp(i φ) sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ and φ constant. +- **Definition in terms of `u`:** `u(θ, -π/2 + φ, π/2 - φ) %q` + +#### 4.3.18 `u` Gate (Universal Single-Qubit) + +- **Purpose:** Universal single-qubit gate (can implement any single-qubit operation) +- **Traits:** `OneTarget`, `ThreeParameter` +- **Signatures:** - Ref: `mqtref.u(%theta, %phi, %lambda) %q` - - Value: `%q_out mqtopt.u(%theta, %phi, %lambda) %q_in` -- Static variant: `mqtref.u(3.14159, 1.5708, 0.785398) %q` -- Mixed variant: `mqtref.u(%theta, 1.5708, 0.785398) %q` -- Canonicalization: - - `inv u(θ, φ, λ) => u(-θ, -φ, -λ)` + - Value: `%q_out = mqtopt.u(%theta, %phi, %lambda) %q_in` +- **Static variant:** `mqtref.u(3.14159, 1.5708, 0.785398) %q` +- **Mixed variant:** `mqtref.u(%theta, 1.5708, 0.785398) %q` +- **Canonicalization:** + - `inv u(θ, φ, λ) → u(-θ, -φ, -λ)` - `rx(θ) == u(θ, -π/2, π/2)` - `ry(θ) == u(θ, 0, 0)` - `p(λ) == u(0, 0, λ)` -- Matrix (dynamic): `p(φ) ry(θ) p(λ) = exp(i (φ + λ)/2) * rz(φ) ry(θ) rz(λ) = [cos(θ/2), -exp(i λ) sin(θ/2); exp(i φ) sin(θ/2), exp(i (φ + λ)) cos(θ/2)]` (2x2 matrix). Static if θ, φ, λ constant. +- **Matrix (dynamic):** `p(φ) ry(θ) p(λ) = exp(i (φ + λ)/2) * rz(φ) ry(θ) rz(λ) = [cos(θ/2), -exp(i λ) sin(θ/2); exp(i φ) sin(θ/2), exp(i (φ + λ)) cos(θ/2)]` (2x2 matrix). Static if θ, φ, λ constant. -#### 4.3.19 `u2` Gate +#### 4.3.19 `u2` Gate (Simplified Universal) -- Purpose: Simplified universal single-qubit gate. -- Traits: OneTarget, TwoParameter -- Signatures +- **Purpose:** Simplified universal single-qubit gate (special case of `u` gate) +- **Traits:** `OneTarget`, `TwoParameter` +- \*\*Signatures - Ref: `mqtref.u2(%phi, %lambda) %q` - - Value: `%q_out mqtopt.u2(%phi, %lambda) %q_in` -- Static variant: `mqtref.u2(1.5708, 0.785398) %q` -- Mixed variant: `mqtref.u2(%phi, 0.785398) %q` -- Canonicalization: - - `inv u2(φ, λ) => u2(-λ - π, -φ + π)` - - `u2(0, π) => h` - - `u2(0, 0) => ry(π/2)` - - `u2(-π/2, π/2) => rx(π/2)` -- Matrix (dynamic): `1/sqrt(2) * [1, -exp(i λ); exp(i φ), exp(i (φ + λ))]` (2x2 matrix). Static if φ, λ constant. -- Definition in terms of `u`: `u2(φ, λ) == u(π/2, φ, λ)` + - Value: `%q_out = mqtopt.u2(%phi, %lambda) %q_in` +- **Static variant:** `mqtref.u2(1.5708, 0.785398) %q` +- **Mixed variant:** `mqtref.u2(%phi, 0.785398) %q` +- **Canonicalization:** + - `inv u2(φ, λ) → u2(-λ - π, -φ + π)` + - `u2(0, π) → h` + - `u2(0, 0) → ry(π/2)` + - `u2(-π/2, π/2) → rx(π/2)` +- **Matrix (dynamic):** `1/sqrt(2) * [1, -exp(i λ); exp(i φ), exp(i (φ + λ))]` (2x2 matrix). Static if φ, λ constant. +- **Definition in terms of `u`:** `u2(φ, λ) == u(π/2, φ, λ)` #### 4.3.20 `swap` Gate -- Purpose: Swap two qubits. -- Traits: TwoTarget, NoParameter, Hermitian -- Signatures: +- **Purpose:** Swap two qubits +- **Traits:** `TwoTarget`, `NoParameter`, `Hermitian` +- **Signatures:** - Ref: `mqtref.swap %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.swap %q0_in, %q1_in` -- Matrix (static): `[1, 0, 0, 0; 0, 0, 1, 0; 0, 1, 0, 0; 0, 0, 0, 1]` (4x4 matrix). +- **Matrix:** `[1, 0, 0, 0; 0, 0, 1, 0; 0, 1, 0, 0; 0, 0, 0, 1]` (4x4 matrix) #### 4.3.21 `iswap` Gate -- Purpose: Swap two qubits. -- Traits: TwoTarget, NoParameter -- Signatures: +- **Purpose:** Swap two qubits with imaginary coefficient +- **Traits:** `TwoTarget`, `NoParameter` +- **Signatures:** - Ref: `mqtref.iswap %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.iswap %q0_in, %q1_in` -- Canonicalization: - - `pow(r) iswap => xx_plus_yy(-π * r)` -- Matrix (static): `[1, 0, 0, 0; 0, 0, 1j, 0; 0, 1j, 0, 0; 0, 0, 0, 1]` (4x4 matrix). +- **Canonicalization:** + - `pow(r) iswap → xx_plus_yy(-π * r)` +- **Matrix:** `[1, 0, 0, 0; 0, 0, 1j, 0; 0, 1j, 0, 0; 0, 0, 0, 1]` (4x4 matrix) -#### 4.3.22 `dcx` Gate +#### 4.3.22 `dcx` Gate (Double CNOT) -- Purpose: Double CX gate. -- Traits: TwoTarget, NoParameter -- Signatures: +- **Purpose:** Double control-NOT gate +- **Traits:** `TwoTarget`, `NoParameter` +- **Signatures:** - Ref: `mqtref.dcx %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.dcx %q0_in, %q1_in` -- Canonicalization: +- **Canonicalization:** - `inv dcx %q0, q1 => dcx %q1, %q0` -- Matrix (static): `[1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]` (4x4 matrix).` +- **Matrix:** `[1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]` (4x4 matrix) -#### 4.3.23 `ecr` Gate +#### 4.3.23 `ecr` Gate (Echoed Cross-Resonance) -- Purpose: Echoed cross-resonance gate. -- Traits: TwoTarget, NoParameter, Hermitian -- Signatures: +- **Purpose:** Cross-resonance gate with echo +- **Traits:** `TwoTarget`, `NoParameter`, `Hermitian` +- **Signatures:** - Ref: `mqtref.ecr %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.ecr %q0_in, %q1_in` -- Matrix (static): `1/sqrt(2) * [0, 0, 1, 1j; 0, 0, 1j, 1; 1, -1j, 0, 0; -1j, 1, 0, 0]` (4x4 matrix). +- **Matrix:** `1/sqrt(2) * [0, 0, 1, 1j; 0, 0, 1j, 1; 1, -1j, 0, 0; -1j, 1, 0, 0]` (4x4 matrix) -#### 4.3.24 `rxx` Gate +#### 4.3.24 `rxx` Gate (XX-Rotation) -- Purpose: General two-qubit rotation around XX. -- Traits: TwoTarget, OneParameter -- Signatures: +- **Purpose:** General two-qubit rotation around XX. +- **Traits:** `TwoTarget`, `OneParameter` +- **Signatures:** - Ref: `mqtref.rxx(%theta) %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.rxx(%theta) %q0_in, %q1_in` -- Static variant: `mqtref.rxx(3.14159) %q0, %q1` -- Canonicalization: +- **Static variant:** `mqtref.rxx(3.14159) %q0, %q1` +- **Canonicalization:** - `inv rxx(%theta) => rxx(-%theta)` - `pow(r) rxx(%theta) => rxx(r * %theta)` for real r - `rxx(0) => remove` - `rxx(a) %q0, %q1; rxx(b) %q0, %q1 => rxx(a + b) %q0, %q1` -- Matrix (dynamic): `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] - 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, 1, 0; 0, 1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. +- **Matrix (dynamic):** `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] - 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, 1, 0; 0, 1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. -#### 4.3.25 `ryy` Gate +#### 4.3.25 `ryy` Gate (YY-Rotation) -- Purpose: General two-qubit gate around YY. -- Traits: TwoTarget, OneParameter -- Signatures: +- **Purpose:** General two-qubit gate around YY. +- **Traits:** `TwoTarget`, `OneParameter` +- **Signatures:** - Ref: `mqtref.ryy(%theta) %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.ryy(%theta) %q0_in, %q1_in` -- Static variant: `mqtref.ryy(3.14159) %q0, %q1` -- Canonicalization: +- **Static variant:** `mqtref.ryy(3.14159) %q0, %q1` +- **Canonicalization:** - `inv ryy(%theta) => ryy(-%theta)` - `pow(r) ryy(%theta) => ryy(r * %theta)` for real r - `ryy(0) => remove` - `ryy(a) %q0, %q1; ryy(b) %q0, %q1 => ryy(a + b) %q0, %q1` -- Matrix (dynamic): `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, -1, 0; 0, -1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. +- **Matrix (dynamic):** `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, -1, 0; 0, -1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. -#### 4.3.26 `rzx` Gate +#### 4.3.26 `rzx` Gate (ZX-Rotation) -- Purpose: General two-qubit gate around ZX. -- Traits: TwoTarget, OneParameter -- Signatures: +- **Purpose:** General two-qubit gate around ZX. +- **Traits:** `TwoTarget`, `OneParameter` +- **Signatures:** - Ref: `mqtref.rzx(%theta) %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.rzx(%theta) %q0_in, %q1_in` -- Static variant: `mqtref.rzx(3.14159) %q0, %q1` -- Canonicalization: +- **Static variant:** `mqtref.rzx(3.14159) %q0, %q1` +- **Canonicalization:** - `inv rzx(%theta) => rzx(-%theta)` - `pow(r) rzx(%theta) => rzx(r * %theta)` for real r - `rzx(0) => remove` - `rzx(a) %q0, %q1; rzx(b) %q0, %q1 => rzx(a + b) %q0, %q1` -- Matrix (dynamic): `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, -1, 0, 0; -1, 0, 0, 0; 0, 0, 0, 1; 0, 0, 1, 0]` (4x4 matrix). Static if θ constant. +- **Matrix (dynamic):** `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, -1, 0, 0; -1, 0, 0, 0; 0, 0, 0, 1; 0, 0, 1, 0]` (4x4 matrix). Static if θ constant. -#### 4.3.27 `rzz` Gate +#### 4.3.27 `rzz` Gate (ZZ-Rotation) -- Purpose: General two-qubit gate around ZZ. -- Traits: TwoTarget, OneParameter, Diagonal -- Signatures: +- **Purpose:** General two-qubit gate around ZZ. +- **Traits:** `TwoTarget`, `OneParameter`, `Diagonal` +- **Signatures:** - Ref: `mqtref.rzz(%theta) %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.rzz(%theta) %q0_in, %q1_in` -- Static variant: `mqtref.rzz(3.14159) %q0, %q1` -- Canonicalization: +- **Static variant:** `mqtref.rzz(3.14159) %q0, %q1` +- **Canonicalization:** - `inv rzz(%theta) => rzz(-%theta)` - `pow(r) rzz(%theta) => rzz(r * %theta)` for real r - `rzz(0) => remove` - `rzz(a) %q0, %q1; rzz(b) %q0, %q1 => rzz(a + b) %q0, %q1` -- Matrix (dynamic): `diag[exp(-i θ/2), exp(i θ/2), exp(i θ/2), exp(-i θ/2)]` (4x4 matrix). Static if θ constant. +- **Matrix (dynamic):** `diag[exp(-i θ/2), exp(i θ/2), exp(i θ/2), exp(-i θ/2)]` (4x4 matrix). Static if θ constant. #### 4.3.28 `xx_plus_yy` Gate -- Purpose: General two-qubit gate around XX+YY. -- Traits: TwoTarget, TwoParameter -- Signatures: +- **Purpose:** General two-qubit gate around XX+YY. +- **Traits:** `TwoTarget`, `TwoParameter` +- \*\*Signatures: - Ref: `mqtref.xx_plus_yy(%theta, %beta) %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.xx_plus_yy(%theta, %beta) %q0_in, %q1_in` -- Static variant: `mqtref.xx_plus_yy(3.14159, 1.5708) %q0, %q1` -- Mixed variant: `mqtref.xx_plus_yy(%theta, 1.5708) %q0, %q1` -- Canonicalization: +- **Static variant:** `mqtref.xx_plus_yy(3.14159, 1.5708) %q0, %q1` +- **Mixed variant:** `mqtref.xx_plus_yy(%theta, 1.5708) %q0, %q1` +- **Canonicalization:** - `inv xx_plus_yy(θ, β) => xx_plus_yy(-θ, β)` - `pow(r) xx_plus_yy(θ, β) => xx_plus_yy(r * θ, β)` for real r - `xx_plus_yy(θ1, β) %q0, %q1; xx_plus_yy(θ2, β) %q0, %q1 => xx_plus_yy(θ1 + θ2, β) %q0, %q1` -- Matrix (dynamic): `[1, 0, 0, 0; 0, cos(θ/2), sin(θ/2) * exp(-i β), 0; 0, -sin(θ/2) * exp(i β), cos(θ/2), 0; 0, 0, 0, 1]` (4x4 matrix). Static if θ and β constant. +- **Matrix (dynamic):** `[1, 0, 0, 0; 0, cos(θ/2), sin(θ/2) * exp(-i β), 0; 0, -sin(θ/2) * exp(i β), cos(θ/2), 0; 0, 0, 0, 1]` (4x4 matrix). Static if θ and β constant. #### 4.3.29 `xx_minus_yy` Gate -- Purpose: General two-qubit gate around XX-YY. -- Traits: TwoTarget, TwoParameter -- Signatures: +- **Purpose:** General two-qubit gate around XX-YY. +- **Traits:** `TwoTarget`, `TwoParameter` +- \*\*Signatures: - Ref: `mqtref.xx_minus_yy(%theta, %beta) %q0, %q1` - Value: `%q0_out, %q1_out = mqtopt.xx_minus_yy(%theta, %beta) %q0_in, %q1_in` -- Static variant: `mqtref.xx_minus_yy(3.14159, 1.5708) %q0, %q1` -- Mixed variant: `mqtref.xx_minus_yy(%theta, 1.5708) %q0, %q1` -- Canonicalization: +- **Static variant:** `mqtref.xx_minus_yy(3.14159, 1.5708) %q0, %q1` +- **Mixed variant:** `mqtref.xx_minus_yy(%theta, 1.5708) %q0, %q1` +- **Canonicalization:** - `inv xx_minus_yy(θ, β) => xx_minus_yy(-θ, β)` - `pow(r) xx_minus_yy(θ, β) => xx_minus_yy(r * θ, β)` for real r - `xx_minus_yy(θ1, β) %q0, %q1; xx_minus_yy(θ2, β) %q0, %q1 => xx_minus_yy(θ1 + θ2, β) %q0, %q1` -- Matrix (dynamic): `[cos(θ/2), 0, 0, -sin(θ/2) * exp(i β); 0, 1, 0, 0; 0, 0, 1, 0; sin(θ/2) * exp(-i β), 0, 0, cos(θ/2)]` (4x4 matrix). Static if θ and β constant. +- **Matrix (dynamic):** `[cos(θ/2), 0, 0, -sin(θ/2) * exp(i β); 0, 1, 0, 0; 0, 0, 1, 0; sin(θ/2) * exp(-i β), 0, 0, cos(θ/2)]` (4x4 matrix). Static if θ and β constant. -### 5. Modifier Operations +#### 4.3.30 `barrier` Gate -### 5.1 Overview +- **Purpose:** Prevents optimization passes from reordering operations across the barrier +- **Traits:** `VariadicTarget`, `NoParameter` +- **Signatures:** + - Ref: `mqtref.barrier %q0, %q1, ...` + - Value: `%q0_out, %q1_out, ... = mqtopt.barrier %q0_in, %q1_in, ...` +- **Canonicalization:** + - Barriers with no qubits can be removed + - **TODO:** Define interaction with other optimization passes +- **Matrix:** Not applicable (compiler directive, not a unitary operation) +- **Open Issues:** + - **TODO:** Should barrier implement `UnitaryOpInterface`? (It's effectively identity but has scheduling semantics) + - **TODO:** Define semantics for nested barriers within modifiers -Modifiers wrap unitaries, extending functionality or altering semantics. -They contain exactly one region with a single block whose only operation implements the `UnitaryOpInterface`. -In the reference semantics dialect, modifiers are statements without results. -In the value semantics dialect, modifiers thread their values through region arguments and yield results. -Converting from the value semantics to the reference semantics is straightforward. -The reverse direction requires a bit of care as the SSA values of the contained unitary need to be added to the region arguments as well as the results need to be yielded. -Modifiers may be arbitrarily nested, with canonicalization rules to flatten and reorder them. +## 5. Modifier Operations -There are three types of modifiers: +### 5.1 Overview and Philosophy -- Control modifiers: `ctrl` and `negctrl`. These add additional (control) qubits to an operation. They extend the qubit list of the unitary operation in question. -- Inverse modifier: `inv`. This takes the adjoint of the unitary operation. Specializations for many of the basis gates exist and are defined as canonicalization rules. -- Power modifier: `pow`. This takes the power of the unitary operation. Canonicalization rules are provided to simplify common cases. +**What Are Modifiers?** -The canonical ordering for these modifiers is (from outside to inside): `negtrcl` -> `ctrl` -> `pow` -> `inv`. +Modifiers are wrapper operations that transform or extend unitary operations without modifying the base gate definitions. They provide a composable mechanism for: -All modifiers share a common verifier: they must have a single block with a single operation implementing the `UnitaryOpInterface`. +- Adding control qubits (positive or negative control) +- Inverting (taking the adjoint of) operations +- Raising operations to powers -### 5.2 Control Modifiers +**Key Design Principles:** -- Purpose: Add additional (control) qubits to an operation. Control qubits can either be positive (1-state) or negative (0-state) controls. The modifier itself holds a variadic list of qubits. -- Signatures (just shown for `ctrl` for simplicity): - - Ref: `mqtref.ctrl(%ctrls) { mqtref.unitaryOp %targets }` - - Value: - ``` - %ctrl_outs, %unitary_outs = mqtopt.ctrl(%ctrl_ins, %unitary_ins) { - %u_outs = mqtopt.unitaryOp %unitary_ins - mqtopt.yield %u_outs - } - ``` -- Builders: Provide list of qubits + body builder. -- Interface: - - Targets: targets of child unitary - - Controls: controls of modifier plus controls of child unitary - - Parameters: aggregated from child unitary (none directly) - - `hasStaticUnitary()` if child unitary static -- Canonicalization: - - Flatten nested control modifiers by merging control lists. - - Remove empty control modifiers. - - Controls applied to global phase gate +> pick one (arbitrary control) and replace the global phase gate with a (controlled) phase gate. - - Canonical modifier ordering: - - `ctrl negctrl U => negctrl ctrl U` -- Verifiers: - - Ensure control and target qubits are distinct. -- Unitary computation: Computed by expanding the unitary of the child operation to the larger space defined by the additional control qubits. - -### 5.3 Inverse Modifier - -- Purpose: Take the adjoint of the unitary operation. -- Signatures: - - Ref: `mqtref.inv { mqtref.unitaryOp %targets }` - - Value: - ``` - %unitary_outs = mqtopt.inv(%unitary_ins) { - %u_outs = mqtopt.unitaryOp %unitary_ins - mqtopt.yield %u_outs - } - ``` -- Builders: Provide body builder. -- Interface: - - Targets: targets of child unitary - - Controls: controls of child unitary - - Parameters: aggregated from child unitary (none directly) - - `hasStaticUnitary()` if child unitary static -- Canonicalization: - - Pairs of nested inverses cancel, i.e. `inv inv U => U`. - - Specializations for many basis gates exist and are defined as canonicalization rules. - - Canonical modifier ordering: - - `inv ctrl U => ctrl inv U` - - `inv negctrl U => negctrl inv U` -- Verifiers: None additional. -- Unitary computation: Computed by inverting the unitary of the child operation. Given how the underlying operation is unitary, the inverse is given by the conjugate transpose. - -### 5.4 Power Modifier - -- Purpose: Take the power of the unitary operation. -- Signatures: - - Ref: `mqtref.pow(%exponent) { mqtref.unitaryOp %targets }` - - Value: - ``` - %unitary_outs = mqtopt.pow(%exponent, %unitary_ins) { - %u_outs = mqtopt.unitaryOp %unitary_ins - mqtopt.yield %u_outs - } - ``` -- Static variant: `mqtref.pow(3) { mqtref.unitaryOp %targets }` -- Builders: Provide exponent value (or attribute) + body builder. -- Interface: - - Targets: targets of child unitary - - Controls: controls of child unitary - - Parameters: aggregated from child unitary + exponent (either counted as static or dynamic parameter) - - `hasStaticUnitary()` if child unitary static and exponent static -- Canonicalization: - - Flatten nested power modifiers by multiplying exponents. - - Remove power modifier with exponent 1. - - `pow(0) U => remove` completely removed the modifier and the operation - - Specializations for many basis gates exist and are defined as canonicalization rules. - - Constant folding and propagation of exponents, e.g., replacing constant values by attributes. - - Negative exponents are pushed into the child unitary by inverting it, e.g., `pow(-r) U => pow(r) inv(U)`. - - Canonical modifier ordering: - - `pow ctrl U => ctrl pow U` - - `pow negctrl U => negctrl pow U` -- Verifiers: None additional. -- Unitary computation: Computed by raising the unitary of the child operation to the given power. For positive integer exponents, this is simply a matrix multiplication. For real-valued exponents, this can be computed by exponentiation. +- **Single-Operation Regions:** Each modifier contains exactly one region with a single block whose only operation implements `UnitaryOpInterface` +- **Arbitrary Nesting:** Modifiers may be arbitrarily nested +- **Canonical Ordering:** Canonicalization rules flatten and reorder modifiers to a standard form: `negctrl → ctrl → pow → inv` +- **Dialect Consistency:** Both `mqtref` and `mqtopt` variants with corresponding semantics + +**Value vs. Reference Semantics:** + +- **Reference:** Modifiers are statements without results; wrapped operation mutates qubits in-place +- **Value:** Modifiers thread SSA values through region arguments and yield results +- **Conversion:** `mqtopt → mqtref` is straightforward; `mqtref → mqtopt` requires adding SSA values to region arguments and yields + +### 5.2 Control Modifiers (`ctrl` and `negctrl`) + +**Purpose:** Add additional control qubits to an operation. Control qubits can be positive (1-state control via `ctrl`) or negative (0-state control via `negctrl`). + +**Signatures (shown for `ctrl`; `negctrl` is analogous):** + +- **Reference:** + + ```mlir + mqtref.ctrl(%ctrl0, %ctrl1, ...) { + mqtref.unitaryOp %target0, %target1, ... + } + ``` + +- **Value:** + ```mlir + %ctrl_outs, %target_outs = mqtopt.ctrl(%ctrl_ins, %target_ins) { + %new_targets = mqtopt.unitaryOp %target_ins + mqtopt.yield %new_targets + } + ``` + +**Interface Implementation:** + +- **Targets:** Aggregated from child unitary +- **Controls:** Control qubits from this modifier plus any controls from child unitary (flattened) +- **Parameters:** Aggregated from child unitary (none directly from modifier) +- **Static Unitary:** Available if child unitary is static + +**Canonicalization:** + +- Flatten nested control modifiers: `ctrl(%c1) { ctrl(%c2) { U } } → ctrl(%c1, %c2) { U }` +- Remove empty controls: `ctrl() { U } → U` +- Controlled global phase specialization: `ctrl(%c) { gphase(θ) } → p(θ) %c` +- Canonical ordering: `ctrl { negctrl { U } } → negctrl { ctrl { U } }` +- **TODO:** Define behavior when same qubit appears as both positive and negative control + +**Verifiers:** + +- Control and target qubits must be distinct (no qubit can be both control and target) +- All control qubits must be distinct from each other +- **TODO:** Specify verification for nested modifiers with overlapping qubit sets + +**Unitary Computation:** + +The unitary is computed by expanding the child operation's unitary to the larger Hilbert space defined by the additional control qubits: + +``` +U_controlled = |0⟩⟨0| ⊗ I_{target_space} + |1⟩⟨1| ⊗ U_{target_space} +``` + +For negative controls, `|1⟩⟨1|` is replaced with `|0⟩⟨0|`. + +### 5.3 Inverse Modifier (`inv`) + +**Purpose:** Take the adjoint (conjugate transpose) of a unitary operation. + +**Signatures:** + +- **Reference:** + + ```mlir + mqtref.inv { + mqtref.unitaryOp %targets + } + ``` + +- **Value:** + ```mlir + %targets_out = mqtopt.inv(%targets_in) { + %new_targets = mqtopt.unitaryOp %targets_in + mqtopt.yield %new_targets + } + ``` + +**Interface Implementation:** + +- **Targets:** Aggregated from child unitary +- **Controls:** Aggregated from child unitary +- **Parameters:** Aggregated from child unitary (none directly from modifier) +- **Static Unitary:** Available if child unitary is static +- **Is Inverted:** Returns `true` + +**Canonicalization:** + +- Double inverse cancellation: `inv { inv { U } } → U` +- Hermitian gate simplification: `inv { G } → G` (for Hermitian gates) +- Parametric rotation inversion: `inv { rx(θ) } → rx(-θ)` (and similar for ry, rz, p, etc.) +- Canonical ordering: `inv { ctrl { U } } → ctrl { inv { U } }` + +**Unitary Computation:** + +Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugate transpose). + +### 5.4 Power Modifier (`pow`) + +**Purpose:** Raise a unitary operation to a given power (exponent can be integer, rational, or real). + +**Signatures:** + +- **Reference:** + + ```mlir + mqtref.pow(%exponent) { + mqtref.unitaryOp %targets + } + ``` + + Static variant: `mqtref.pow {exponent = 2.0 : f64} { ... }` + +- **Value:** + ```mlir + %targets_out = mqtopt.pow(%exponent, %targets_in) { + %new_targets = mqtopt.unitaryOp %targets_in + mqtopt.yield %new_targets + } + ``` + +**Interface Implementation:** + +- **Targets:** Aggregated from child unitary +- **Controls:** Aggregated from child unitary +- **Parameters:** Aggregated from child unitary plus the exponent parameter +- **Static Unitary:** Available if child unitary and exponent are both static +- **Get Power:** Returns the exponent value + +**Canonicalization:** + +- Nested power flattening: `pow(a) { pow(b) { U } } → pow(a*b) { U }` +- Identity power: `pow(1) { U } → U` +- Zero power: `pow(0) { U } → remove` (becomes identity, then eliminated) +- Negative power: `pow(-r) { U } → pow(r) { inv { U } }` +- Parametric gate power folding: `pow(r) { rx(θ) } → rx(r*θ)` (for rotation gates) +- Integer power specialization: For specific gates and integer powers (e.g., `pow(2) { sx } → x`) +- Canonical ordering: `pow { ctrl { U } } → ctrl { pow { U } }` + +**Unitary Computation:** + +- **Integer exponents:** Matrix multiplication `U^n = U · U · ... · U` (n times) +- **Real/rational exponents:** Matrix exponentiation via eigendecomposition: `U^r = V · D^r · V†` where `U = V · D · V†` is the eigendecomposition + +**Open Issues:** + +- **TODO:** Define behavior for non-integer powers of non-diagonalizable gates +- **TODO:** Specify numerical precision requirements for matrix exponentiation +- **TODO:** Define semantics for complex exponents (currently not supported) ## 6. Sequence Operation (`seq`) -- Purpose: Ordered, unnamed composition of unitary operations over region block arguments. -- Signatures: - - Ref: `mqtref.seq { }` - - Value: - ``` - %results = mqtopt.seq(%args) -> (%result_types) { - - mqtopt.yield %newArgs - } - ``` -- Builders: Provide body builder. -- Interface: - - Targets = Aggregated targets of child unitary ops (none directly) - - Controls = None - - Parameters = Aggregated parameters of child unitary ops (none directly) - - `hasStaticUnitary()` if all child ops static` -- Canonicalization: - - Remove empty sequence. - - Replace sequence with a single operation by inlining that operation. - - Provide inlining capabilities for flattening nested sequences. -- Verifiers: All block operations must implement the `UnitaryOpInterface`. -- Unitary computation: Computed by computing the product of the unitaries of the child operations, i.e., `U_n U_{n-1} ... U_1 U_0`. -- Conversion: - - Value semantics to reference semantics: Remove block arguments and results, replace uses of arguments with direct uses of the corresponding values. - - Reference semantics to value semantics: Add block arguments and results, replace direct use of values with uses of the corresponding arguments. - -## 7. User Defined Gates & Matrix / Composite Definitions - -In addition to the unnamed sequence operation, the dialect also provides a mechanism for defining custom (unitary) gates that produce a symbol that can be referenced in an `apply` operation. -Conceptionally, this should be very close to an actual MLIR function definition and call. - -These gates can be defined in two ways: - -- Matrix-based definition: Define a gate using a matrix representation. -- Sequence-based definition: Define a gate using a sequence of (unitary) operations. - -The matrix-based definition is very efficient for small qubit numbers, but does not scale well for larger numbers of qubits. -The sequence-based definition is more general, but requires more processing to compute the underlying functionality. -Definitions might even provide both a matrix and a sequence representation, which should be consistent. - -Matrix-based definitions may be fully static or might be based on symbolic expressions, e.g., rotation gates. -Fully static matrices may be specified as dense array attributes. -Any dynamic definitions must be (somehow) specified as symbolic expressions. +**Purpose:** Ordered, unnamed composition of unitary operations. Represents the application of multiple operations. + +**Signatures:** + +- **Reference:** + + ```mlir + mqtref.seq { + mqtref.h %q0 + mqtref.cx %q0, %q1 + mqtref.rz(1.57) %q1 + } + ``` + +- **Value:** + ```mlir + %q0_out, %q1_out = mqtopt.seq(%q0_in, %q1_in) : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { + %q0_1 = mqtopt.h %q0_in + %q0_2, %q1_1 = mqtopt.cx %q0_1, %q1_in + %q1_2 = mqtopt.rz(1.57) %q1_1 + mqtopt.yield %q0_2, %q1_2 + } + ``` + +**Interface Implementation:** + +- **Targets:** Aggregated from all child unitary operations (union of all targets) +- **Controls:** None (controls must be within individual operations or modifiers) +- **Parameters:** Aggregated from all child operations +- **Static Unitary:** Available if all child operations have static unitaries + +**Canonicalization:** + +- Empty sequence elimination: `seq { } → remove` +- Single-operation inlining: `seq { U } → U` +- Nested sequence flattening: `seq { seq { U; V }; W } → seq { U; V; W }` + +**Verifiers:** + +- All operations in the block must implement `UnitaryOpInterface` +- Value semantics: All yielded values must be defined within the block + +**Unitary Computation:** + +The composite unitary is computed as the product of child unitaries in reverse order (right-to-left multiplication, since operations apply left-to-right): + +``` +U_seq = U_n · U_{n-1} · ... · U_1 · U_0 +``` + +**Conversion Between Dialects:** + +- **`mqtopt → mqtref`:** Remove block arguments and results; replace argument uses with direct value references +- **`mqtref → mqtopt`:** Add block arguments for all used qubits; thread SSA values through operations; add yield with final values + +## 7. User-Defined Gates & Matrix/Composite Definitions + +**Purpose:** Enable users to define custom gates that can be referenced and instantiated throughout the program, similar to function definitions and calls. + +### 7.1 Overview + +User-defined gates provide two definition mechanisms: + +1. **Matrix-based definitions:** Define a gate via its unitary matrix (efficient for small qubit counts) +2. **Sequence-based definitions:** Define a gate as a composition of existing operations (more general, better for larger circuits) + +Definitions may optionally provide both representations for the same gate, which should be consistent. + +**Symbol Management:** + +- Gate definitions create symbols (similar to `func.func`) +- Symbols are referenced by `apply` operations (similar to `func.call`) +- Symbols have module-level scope and must be unique within a module + +### 7.2 Matrix-Based Gate Definitions + +**Purpose:** Define a custom gate using its unitary matrix representation. + +**Signature:** + +```mlir +mqt.define_matrix_gate @my_gate(%param0: f64, %param1: f64) : (2 qubits) { + matrix = dense<[[complex_values]]> : tensor<4x4xcomplex> + // Or symbolic expression attribute +} +``` + +**Open Issues:** + +- **TODO:** Define complete syntax for matrix-based definitions +- **TODO:** Specify format for dynamic (parameterized) matrices using symbolic expressions +- **TODO:** Define verification rules for matrix unitarity +- **TODO:** Specify maximum practical qubit count (matrices scale as 2^n × 2^n) + +### 7.3 Sequence-Based Gate Definitions + +**Purpose:** Define a custom gate as a composition of existing unitary operations. + +**Signature (draft):** + +```mlir +mqt.define_composite_gate @my_gate(%param0: f64) : (2 qubits) { +^bb0(%q0: !mqt.qubit, %q1: !mqt.qubit): + %q0_1 = mqt.ry(%param0) %q0 + %q0_2, %q1_1 = mqt.cx %q0_1, %q1 + mqt.return %q0_2, %q1_1 +} +``` + +**Open Issues:** + +- **TODO:** Define complete syntax with proper dialect prefix +- **TODO:** Specify parameter binding mechanism +- **TODO:** Define qubit argument conventions +- **TODO:** Clarify relationship with `seq` operation +- **TODO:** Specify whether recursive definitions are allowed + +### 7.4 Gate Application (`apply`) + +**Purpose:** Instantiate a user-defined gate by referencing its symbol. + +**Signature (draft):** + +```mlir +mqtref.apply @my_gate(%runtime_param) %q0, %q1 +``` + +**Open Issues:** + +- **TODO:** Complete specification of `apply` operation +- **TODO:** Define parameter passing mechanisms (static vs. dynamic) +- **TODO:** Specify how `apply` implements `UnitaryOpInterface` +- **TODO:** Define inlining behavior and thresholds +- **TODO:** Specify linking semantics for multi-module programs + +### 7.5 Design Considerations + +**Matrix vs. Sequence Trade-offs:** + +- **Matrix definitions:** + - Pros: Direct representation, efficient evaluation for small gates, exact + - Cons: Exponential space complexity, no structure for optimization + +- **Sequence definitions:** + - Pros: Scalable, exposes structure for optimization, composable + - Cons: May require complex unitary extraction, potential overhead + +**Consistency Requirements:** + +- Gates providing both matrix and sequence must be verified for consistency +- **TODO:** Define tolerance for numerical verification of consistency +- **TODO:** Specify which representation takes precedence in different contexts ## 8. Builder API -To make testing easier, a Builder API shall be provided similar to the `mlir::OpBuilder`. -It should allow for easy chaining of operations including modifiers. -The following operations shall be supported: +**Purpose:** Provide a programmatic API for constructing quantum programs, replacing FileCheck-based test construction with type-safe builders. -- dynamic qubit allocation -- static qubit allocation -- qubit register allocation -- classical (bit) register allocation -- all base gates defined above -- modifiers: `ctrl`, `negctrl`, `inv`, `pow` -- `seq` -- `apply` -- gate definitions (matrix and sequence based) +### 8.1 Design Goals -Such a builder should be defined for both the reference semantics dialect as well as the value semantics dialect. -A draft for an API of the reference semantics dialect is given below. +- **Ergonomic:** Easy chaining and nesting of operations +- **Type-safe:** Leverage C++ type system to catch errors at compile time +- **Dialect-aware:** Separate builders for `mqtref` and `mqtopt` +- **Testable:** Enable structural comparison of circuits in unit tests + +### 8.2 Reference Semantics Builder (Draft API) ```c++ -class QuantumProgramBuilder { +class RefQuantumProgramBuilder { public: - QuantumProgramBuilder(mlir::MLIRContext *context); - // Initialize + RefQuantumProgramBuilder(mlir::MLIRContext *context); + + // Initialization void initialize(); - // Memory management - mlir::Value allocateStaticQubit(size_t index); - mlir::Value allocateDynamicQubit(); - mlir::Value allocateQubitRegister(size_t size); - mlir::Value allocateBitRegister(size_t size); - // Base gates - QuantumProgramBuilder& h(mlir::Value target); - QuantumProgramBuilder& x(mlir::Value target); - QuantumProgramBuilder& y(mlir::Value target); - QuantumProgramBuilder& z(mlir::Value target); - /// ... - // Modifiers - QuantumProgramBuilder& ctrl(mlir::ValueRange controls, std::function body); - QuantumProgramBuilder& negctrl(mlir::ValueRange controls, std::function body); - QuantumProgramBuilder& inv(std::function body); - QuantumProgramBuilder& pow(mlir::Value exponent, std::function body); + + // Resource management + mlir::Value allocQubit(); // Dynamic allocation + mlir::Value qubit(size_t index); // Static qubit reference + mlir::Value allocQubits(size_t count); // Register allocation + mlir::Value allocBits(size_t count); // Classical register + + // Single-qubit gates (return *this for chaining) + RefQuantumProgramBuilder& h(mlir::Value q); + RefQuantumProgramBuilder& x(mlir::Value q); + RefQuantumProgramBuilder& y(mlir::Value q); + RefQuantumProgramBuilder& z(mlir::Value q); + RefQuantumProgramBuilder& s(mlir::Value q); + RefQuantumProgramBuilder& sdg(mlir::Value q); + RefQuantumProgramBuilder& t(mlir::Value q); + RefQuantumProgramBuilder& tdg(mlir::Value q); + RefQuantumProgramBuilder& sx(mlir::Value q); + RefQuantumProgramBuilder& sxdg(mlir::Value q); + + // Parametric single-qubit gates + RefQuantumProgramBuilder& rx(double theta, mlir::Value q); + RefQuantumProgramBuilder& rx(mlir::Value theta, mlir::Value q); // Dynamic + RefQuantumProgramBuilder& ry(double theta, mlir::Value q); + RefQuantumProgramBuilder& ry(mlir::Value theta, mlir::Value q); + RefQuantumProgramBuilder& rz(double theta, mlir::Value q); + RefQuantumProgramBuilder& rz(mlir::Value theta, mlir::Value q); + RefQuantumProgramBuilder& p(double lambda, mlir::Value q); + RefQuantumProgramBuilder& p(mlir::Value lambda, mlir::Value q); + + // Two-qubit gates + RefQuantumProgramBuilder& cx(mlir::Value ctrl, mlir::Value target); + RefQuantumProgramBuilder& swap(mlir::Value q0, mlir::Value q1); + // ... other two-qubit gates + + // Modifiers (take lambdas for body construction) + RefQuantumProgramBuilder& ctrl(mlir::ValueRange ctrls, + std::function body); + RefQuantumProgramBuilder& negctrl(mlir::ValueRange ctrls, + std::function body); + RefQuantumProgramBuilder& inv(std::function body); + RefQuantumProgramBuilder& pow(double exponent, + std::function body); + RefQuantumProgramBuilder& pow(mlir::Value exponent, + std::function body); + // Sequence - QuantumProgramBuilder& seq(std::function body); + RefQuantumProgramBuilder& seq(std::function body); + // Gate definitions - void defineMatrixGate(mlir::StringRef name, size_t numQubits, mlir::ArrayAttr matrix); - void defineCompositeGate(mlir::StringRef name, size_t numQubits, std::function body); - // Apply - QuantumProgramBuilder& apply(mlir::StringRef gateName, mlir::ValueRange targets, mlir::ValueRange parameters); - // Finalize + void defineMatrixGate(mlir::StringRef name, size_t numQubits, + mlir::DenseElementsAttr matrix); + void defineCompositeGate(mlir::StringRef name, size_t numQubits, + mlir::ArrayRef paramTypes, + std::function body); + + // Apply custom gate + RefQuantumProgramBuilder& apply(mlir::StringRef gateName, + mlir::ValueRange targets, + mlir::ValueRange params = {}); + + // Measurement and reset + mlir::Value measure(mlir::Value q); + RefQuantumProgramBuilder& reset(mlir::Value q); + + // Finalization mlir::ModuleOp finalize(); + mlir::ModuleOp getModule() const; + private: mlir::OpBuilder builder; mlir::ModuleOp module; - // ... + mlir::Location loc; + // ... internal state }; ``` -This API should turn out to be very similar to the existing MQT Core IR `qc::QuantumComputation` API and may, eventually, replace it. -It could be beneficial to design this as a C API to ensure portability to other languages, but this is not a priority. +### 8.3 Value Semantics Builder + +**TODO:** Define `OptQuantumProgramBuilder` with appropriate SSA threading semantics. + +Key differences from reference builder: + +- Operations return new SSA values instead of modifying in-place +- Region construction must handle argument threading +- Yield operations must be inserted appropriately + +### 8.4 Usage Example + +```c++ +// Example: Build Bell state preparation +RefQuantumProgramBuilder builder(context); +builder.initialize(); + +auto q0 = builder.qubit(0); +auto q1 = builder.qubit(1); + +builder.h(q0) + .ctrl({q0}, [&](auto& b) { + b.x(q1); + }); + +auto module = builder.finalize(); +``` + +### 8.5 C API Considerations + +**TODO:** Evaluate feasibility of C API wrapper for language interoperability (Python, Rust, etc.). + +Benefits: + +- Enables bindings to other languages +- May eventually replace `qc::QuantumComputation` API + +Challenges: + +- Lambda-based modifier API needs translation +- Memory management across language boundaries ## 9. Testing Strategy -### 9.1 Unit Tests +### 9.1 Philosophy + +**Priority:** Structural and semantic testing > textual pattern matching -Priority shift: structural & semantic equivalence via builders (googletest) > textual pattern checks. -Use IR builders to construct original & expected forms, run normalization + optional passes, then compare via: +Move away from brittle FileCheck tests toward robust programmatic testing: -- Shape / op sequence equivalence (ignoring SSA names). -- Control & target counts via `UnitaryOpInterface`. -- Optional unitary matrix equivalence (numerical tolerance) for small ops. +- **Builder-based construction:** Use builder APIs to construct circuits +- **Structural equivalence:** Compare IR structure, not SSA names +- **Interface-based verification:** Use `UnitaryOpInterface` for semantic checks +- **Numerical validation:** Compare unitary matrices with appropriate tolerances -Unit tests should be written for all operations, modifiers, and gate definitions. -All canonization rules and folds should be tested. (e.g., `inv inv U => U`) -Negative tests should be written for all errors. +### 9.2 Unit Testing Framework (GoogleTest) + +**Test Categories:** + +1. **Operation Construction:** Verify each operation constructs correctly +2. **Canonicalization:** Test each canonicalization pattern +3. **Interface Implementation:** Verify `UnitaryOpInterface` methods +4. **Matrix Extraction:** Validate static matrix computation +5. **Modifier Composition:** Test nested modifier behavior +6. **Conversion:** Test dialect conversion passes + +**Example Test Structure:** + +```c++ +TEST(QuantumDialectTest, InverseInverseCanonicalizes) { + MLIRContext context; + RefQuantumProgramBuilder builder(&context); + + auto q = builder.qubit(0); + builder.inv([&](auto& b) { + b.inv([&](auto& b2) { + b2.x(q); + }); + }); + + auto module = builder.finalize(); + + // Apply canonicalization + pm.addPass(createCanonicalizerPass()); + pm.run(module); + + // Verify structure: should just be single x gate + auto func = module.lookupSymbol("main"); + ASSERT_EQ(countOpsOfType(func), 1); + ASSERT_EQ(countOpsOfType(func), 0); +} +``` -### 9.2 Parser / Printer Smoke (Minimal Textual Tests) +**Coverage Requirements:** -- Round-trip for: base gates, each modifier, nested modifier chain, matrix unitary, composite definition, sequence. -- FileCheck limited to presence/absence of key ops (avoid brittle SSA checks). +- All base gates: construction, canonicalization, matrix extraction +- All modifiers: nesting, flattening, canonical ordering +- All canonicalization rules explicitly listed in this RFC +- Negative tests for verification failures -Example smoke test snippet: +### 9.3 Parser/Printer Round-Trip Tests (Minimal FileCheck) + +**Purpose:** Ensure textual representation is stable and parseable. + +**Scope:** Limited to basic round-trip validation, avoiding brittle SSA name checks. + +**Example:** ```mlir -// CHECK-LABEL: func @nested_mods -func.func @nested_mods(%c: !mqtref.qubit, %n: !mqtref.qubit, %t: !mqtref.qubit) { +// RUN: mqt-opt %s | mqt-opt | FileCheck %s + +// CHECK-LABEL: func @nested_modifiers +func.func @nested_modifiers(%q: !mqtref.qubit) { + // CHECK: mqtref.ctrl + // CHECK-NEXT: mqtref.pow + // CHECK-NEXT: mqtref.inv + // CHECK-NEXT: mqtref.x mqtref.ctrl %c { - mqtref.negctrl %n { - mqtref.pow {exponent = 2.0 : f64} { - mqtref.inv { mqtref.x %t } + mqtref.pow {exponent = 2.0 : f64} { + mqtref.inv { + mqtref.x %q } } } return } ``` + +**Principles:** + +- Check for presence/absence of operations, not exact formatting +- Avoid checking SSA value names (they may change) +- Focus on structural properties +- Keep tests small and focused + +### 9.4 Integration Tests + +**TODO:** Define integration testing strategy covering: + +- Multi-pass optimization pipelines +- Dialect conversion sequences +- End-to-end compilation scenarios +- Performance regression testing + +### 9.5 Test Coverage Metrics + +**TODO:** Establish coverage targets: + +- Line coverage goal: >90% +- Branch coverage goal: >85% +- Canonicalization pattern coverage: 100% +- Interface method coverage: 100% + +## 10. Implementation Roadmap + +**TODO:** Define phased implementation plan with milestones. + +Suggested phases: + +### Phase 1: Foundation (Weeks 1-3) + +- Define core type system (`mqtref.qubit`, `mqtopt.qubit`) +- Implement `UnitaryOpInterface` +- Create basic builder infrastructure +- Establish testing framework + +### Phase 2: Base Gates (Weeks 4-6) + +- Implement all single-qubit gates +- Implement all two-qubit gates +- Add basic canonicalization patterns +- Comprehensive unit tests for each gate + +### Phase 3: Modifiers (Weeks 7-9) + +- Implement `ctrl` and `negctrl` +- Implement `inv` modifier +- Implement `pow` modifier +- Test nested modifier combinations + +### Phase 4: Composition (Weeks 10-12) + +- Implement `seq` operation +- Add modifier flattening and ordering +- Implement gate merging canonicalization + +### Phase 5: Custom Gates (Weeks 13-15) + +- Design and implement gate definition operations +- Implement `apply` operation +- Add symbol table management +- Test custom gate integration + +### Phase 6: Optimization (Weeks 16-18) + +- Implement advanced canonicalization patterns +- Add dialect conversion passes +- Performance optimization +- Documentation + +### Phase 7: Validation (Weeks 19-20) + +- Comprehensive integration testing +- Performance benchmarking +- Documentation review +- Migration guide for existing code + +## 11. Open Questions and Future Work + +### 11.1 Critical Open Questions (Require Resolution Before Implementation) + +1. **Global Phase Semantics:** + - How should global phase gates be merged and optimized? + - Should they implement `UnitaryOpInterface`? + - How do they interact with circuit traversal? + +2. **Global Phase Relationships:** + - Resolve global phase differences between equivalent gates (e.g., `x` vs `rx(π)`) + - Define whether these should be considered equivalent or distinct + - Specify canonicalization behavior + +3. **Power Semantics for Non-Diagonalizable Gates:** + - Define behavior for fractional powers of Pauli gates + - Specify whether `pow(1/3) x` is valid or should be rejected + - Define numerical methods for general matrix exponentiation + +4. **Custom Gate Symbol Resolution:** + - Define complete syntax for gate definitions and applications + - Specify linking semantics for multi-module programs + - Define inlining heuristics + +5. **Barrier Semantics:** + - Should `barrier` implement `UnitaryOpInterface`? + - How does it interact with modifiers? + - Define its role in optimization passes + +6. **Control Qubit Conflicts:** + - What happens if the same qubit is both positive and negative control? + - Should this be an error or have defined semantics? + +--- + +## Document Metadata + +- **Version:** 0.2 (Draft) +- **Last Updated:** 2025-10-06 +- **Status:** Request for Comments +- **Authors:** Lukas Burgholzer (@burgholzer) +- **Reviewers:** Damian Rovara (@Drovara), Yannick Stade (@ystade), Patrick Hopf (@flowerthrower), Daniel Haag (@denialhaag), Matthias Reumann (@MatthiasReumann), Tamino Bauknecht (@taminob) +- **Target MLIR Version:** [TODO: Specify] From 9b947bc71d5ee016ab8b64f3d63cc505c50a4c9b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 16:20:07 +0200 Subject: [PATCH 011/419] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 465 ++++++++++++++++------- 1 file changed, 334 insertions(+), 131 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index eae7f070bd..53011c9259 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -76,6 +76,41 @@ All operations fall into three primary categories: **Purpose:** Manage qubit lifetime and references. +**Qubit and Register Allocation:** + +In MQT's MLIR dialects, quantum and classical registers are represented by MLIR-native `memref` operations rather than custom types. This design choice offers several advantages: + +- **MLIR Integration:** Seamless compatibility with existing MLIR infrastructure, enabling reuse of memory handling patterns and optimization passes +- **Implementation Efficiency:** No need to define and maintain custom register operations, significantly reducing implementation complexity +- **Enhanced Interoperability:** Easier integration with other MLIR dialects and passes, allowing for more flexible compilation pipelines +- **Sustainable Evolution:** Standard memory operations can handle transformations while allowing new features without changing the fundamental register model + +**Quantum Register Representation:** + +A quantum register is represented by a `memref` of type `!mqtref.Qubit` or `!mqtopt.Qubit`: + +```mlir +// A quantum register with 2 qubits +%qreg = memref.alloc() : memref<2x!mqtref.Qubit> + +// Load qubits from the register +%q0 = memref.load %qreg[%i0] : memref<2x!mqtref.Qubit> +%q1 = memref.load %qreg[%i1] : memref<2x!mqtref.Qubit> +``` + +**Classical Register Representation:** + +Classical registers follow the same pattern but use the `i1` type for boolean measurement results: + +```mlir +// A classical register with 1 bit +%creg = memref.alloc() : memref<1xi1> + +// Store measurement result +%c = mqtref.measure %q +memref.store %c, %creg[%i0] : memref<1xi1> +``` + **Reference Semantics (`mqtref`):** ```mlir @@ -95,13 +130,15 @@ mqtopt.dealloc %q : !mqtopt.qubit **Canonicalization Patterns:** - Dead allocation elimination: Remove unused `alloc` operations (DCE) -- **TODO:** Define register allocation operations for multi-qubit arrays -- **TODO:** Specify interaction with classical bit allocation ### 3.4 Measurement and Reset Non-unitary operations that do not implement the `UnitaryOpInterface`. +**Measurement Basis:** All measurements are performed in the **computational basis** (Z-basis). Measurements in other bases must be implemented by applying appropriate basis-change gates before measurement. + +**Single-Qubit Measurements Only:** Multi-qubit measurements are explicitly **not supported**. Joint measurements must be decomposed into individual single-qubit measurements. + **Reference Semantics:** ```mlir @@ -119,9 +156,7 @@ mqtref.reset %q : !mqtref.qubit **Canonicalization Patterns:** - `reset` immediately after `alloc` → remove `reset` (already in ground state) -- Consecutive `reset` on same qubit → single instance -- **TODO:** Specify multi-qubit measurement operations -- **TODO:** Define measurement basis specification (currently assumes computational basis) +- Consecutive `reset` on same qubit (reference semantics) → single instance ### 3.5 Unified Unitary Interface Design @@ -272,15 +307,12 @@ The following canonicalization patterns apply automatically to all gates with th - **Canonicalization:** - `gphase(0) → remove` - `inv(gphase(θ)) → gphase(-θ)` - - `gphase(a); gphase(b) → gphase(a + b)` (consecutive phases merge) + - `gphase(a); gphase(b) → gphase(a + b)` (consecutive phases merge within same scope) - `ctrl(%q) { gphase(θ) } → p(θ) %q` (controlled global phase becomes phase gate) - `negctrl(%q) { gphase(θ) } → gphase(π); p(θ) %q` (negative control specialization) - `pow(n) { gphase(θ) } → gphase(n*θ)` - **Matrix:** `[exp(iθ)]` (1×1 scalar, static if θ constant) -- **Open Issues:** - - **TODO:** Global phase gates have no target qubits, making traversal and merging semantics unclear - - **TODO:** Define how global phases interact with circuit-level operations - - **TODO:** Specify whether global phases should be preserved or eliminated in certain contexts +- **Global Phase Preservation:** Global phase gates are **preserved by default** and should only be eliminated by dedicated optimization passes. They are bound to a scope (e.g., function, box) at which they are aggregated and canonicalized. Operations within the same scope may merge adjacent global phases, but global phases should not be arbitrarily removed during general canonicalization. #### 4.3.2 `id` Gate (Identity) @@ -305,13 +337,12 @@ The following canonicalization patterns apply automatically to all gates with th - Ref: `mqtref.x %q` - Value: `%q_out = mqtopt.x %q_in` - **Canonicalization:** - - `pow(1/2) x → sx` (square root of x is sx) - - `pow(-1/2) x → sxdg` (inverse square root is sxdg) + - `pow(1/2) x → sx` + - `pow(-1/2) x → sxdg` + - `pow(r) x → gphase(-r*π/2); rx(r*π)` (general power translates to rotation with global phase) - **Matrix:** `[0, 1; 1, 0]` (2x2 matrix) - **Definition in terms of `u`:** `u(π, 0, π) %q` -- **Open Issues:** - - **TODO:** Resolve global phase relationship: `-iX == rx(π)`. What are the implications for `pow(r) x`? - - **TODO:** Define whether `pow(1/3) x` should be supported or rejected +- **Global Phase Relationship:** The Pauli-X gate and `rx(π)` differ by a global phase: `X = -i·exp(-iπ/2·X) = -i·rx(π)`. When raising X to fractional powers, the result is expressed as a rotation with an appropriate global phase factor. #### 4.3.4 `y` Gate (Pauli-Y) @@ -320,10 +351,11 @@ The following canonicalization patterns apply automatically to all gates with th - **Signatures:** - Ref: `mqtref.y %q` - Value: `%q_out = mqtopt.y %q_in` +- **Canonicalization:** + - `pow(r) y → gphase(-r*π/2); ry(r*π)` (general power translates to rotation with global phase) - **Matrix:** `[0, -i; i, 0]` (2x2 matrix) - **Definition in terms of `u`:** `u(π, π/2, π/2) %q` -- **Open Issues:** - - **TODO:** Resolve global phase relationship: `-iY == ry(π)`. What are the implications for `pow(r) y`? +- **Global Phase Relationship:** The Pauli-Y gate and `ry(π)` differ by a global phase: `Y = -i·exp(-iπ/2·Y) = -i·ry(π)`. #### 4.3.5 `z` Gate (Pauli-Z) @@ -432,13 +464,13 @@ The following canonicalization patterns apply automatically to all gates with th - `inv sx → sxdg` - `sx %q; sx %q → x %q` - `pow(+-2) sx → x` + - `pow(r) sx → gphase(-r*π/4); rx(r*π/2)` (power translates to rotation with global phase) - **Matrix:** `1/2 * [1 + i, 1 - i; 1 - i, 1 + i]` (2x2 matrix) -- **Open Issues:** - - **TODO:** Resolve global phase relationship: `exp(-iπ/4) sx == rx(π/2)`. Define power semantics. +- **Global Phase Relationship:** `sx = exp(-iπ/4)·rx(π/2)`. Powers are handled by translating to the rotation form with appropriate global phase correction. #### 4.3.12 `sxdg` Gate (√X-Dagger) -- **Purpose:** Inverse of the square root of X gate +- **Purpose:** Square root of X-Dagger gate - **Traits:** `OneTarget`, `NoParameter` - **Signatures:** - Ref: `mqtref.sxdg %q` @@ -447,9 +479,9 @@ The following canonicalization patterns apply automatically to all gates with th - `inv sxdg → sx` - `sxdg %q; sxdg %q → x %q` - `pow(+-2) sxdg → x` + - `pow(r) sxdg → gphase(r*π/4); rx(-r*π/2)` (power translates to rotation with global phase) - **Matrix:** `1/2 * [1 - i, 1 + i; 1 + i, 1 - i]` (2x2 matrix) -- **Open Issues:** - - **TODO:** Resolve global phase relationship with `rx(-π/2)`. Define power semantics. +- **Global Phase Relationship:** `sxdg = exp(iπ/4)·rx(-π/2)`. Powers are handled by translating to the rotation form with appropriate global phase correction. #### 4.3.13 `rx` Gate (X-Rotation) @@ -494,8 +526,7 @@ The following canonicalization patterns apply automatically to all gates with th - `inv rz(θ) → rz(-θ)` - `pow(r) rz(θ) → rz(r * θ)` for real r - **Matrix (dynamic):** `exp(-i θ/2 Z) = [exp(-i θ/2), 0; 0, exp(i θ/2)]` (2x2 matrix). Static if θ constant. -- **Open Issues:** - - **TODO:** Clarify global phase relationship: `rz(θ) == exp(iθ/2) * p(θ)`. Should canonicalization convert between these? +- **Relationship with `p` gate:** The `rz` and `p` gates differ by a global phase: `rz(θ) = exp(iθ/2)·p(θ)`. However, **`rz` and `p` should remain separate** and are **not canonicalized** into each other, as they represent different conventions that may be important for hardware backends or algorithm implementations. #### 4.3.16 `p` Gate (Phase) @@ -695,17 +726,20 @@ The following canonicalization patterns apply automatically to all gates with th #### 4.3.30 `barrier` Gate - **Purpose:** Prevents optimization passes from reordering operations across the barrier -- **Traits:** `VariadicTarget`, `NoParameter` +- **Traits:** `NoParameter` - **Signatures:** - Ref: `mqtref.barrier %q0, %q1, ...` - Value: `%q0_out, %q1_out, ... = mqtopt.barrier %q0_in, %q1_in, ...` +- **Semantics:** The `barrier` operation implements the `UnitaryOpInterface` and is treated similarly to the identity gate from a unitary perspective. However, it serves as a compiler directive that constrains optimization: operations cannot be moved across a barrier boundary. - **Canonicalization:** - Barriers with no qubits can be removed - - **TODO:** Define interaction with other optimization passes -- **Matrix:** Not applicable (compiler directive, not a unitary operation) -- **Open Issues:** - - **TODO:** Should barrier implement `UnitaryOpInterface`? (It's effectively identity but has scheduling semantics) - - **TODO:** Define semantics for nested barriers within modifiers + - `barrier; barrier` on same qubits → single `barrier` (adjacent barriers merge) + - `inv { barrier } → barrier` (barrier is self-adjoint) + - `pow(r) { barrier } → barrier` (any power of barrier is still barrier) + - `ctrl(%c) { barrier } → barrier` (controlled barrier is still barrier) + - `negctrl(%c) { barrier } → barrier` (negatively controlled barrier is still barrier) +- **Matrix:** Identity matrix of appropriate dimension (2^n × 2^n for n qubits) +- **UnitaryOpInterface Implementation:** Returns identity matrix, no parameters, no controls, targets are the specified qubits ## 5. Modifier Operations @@ -773,7 +807,7 @@ Modifiers are wrapper operations that transform or extend unitary operations wit - Control and target qubits must be distinct (no qubit can be both control and target) - All control qubits must be distinct from each other -- **TODO:** Specify verification for nested modifiers with overlapping qubit sets +- **Control Modifier Exclusivity:** A qubit must **not** appear in both positive (`ctrl`) and negative (`negctrl`) control modifiers. The sets of positive and negative controls must be **disjoint** with no overlap. If a qubit appears in both, the operation is invalid and must be rejected during verification. **Unitary Computation:** @@ -873,13 +907,13 @@ Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugat - **Integer exponents:** Matrix multiplication `U^n = U · U · ... · U` (n times) - **Real/rational exponents:** Matrix exponentiation via eigendecomposition: `U^r = V · D^r · V†` where `U = V · D · V†` is the eigendecomposition -**Open Issues:** +**Well-Definedness:** Since all quantum gates are unitary matrices, they are always diagonalizable (unitaries are normal operators). Therefore, **non-integer powers are always well-defined** through eigendecomposition, regardless of the specific gate. -- **TODO:** Define behavior for non-integer powers of non-diagonalizable gates -- **TODO:** Specify numerical precision requirements for matrix exponentiation -- **TODO:** Define semantics for complex exponents (currently not supported) +**Numerical Precision:** Matrix exponentiation should be computed to **machine precision** (typically double precision floating point, ~15-16 decimal digits). Implementations should use numerically stable algorithms for eigendecomposition. -## 6. Sequence Operation (`seq`) +**Complex Exponents:** Complex exponents are **not supported** and will **not be supported** in the future, as they do not have meaningful physical interpretations for quantum gates. Only real-valued exponents are permitted. + +## 6. Box Operation (`box`) **Purpose:** Ordered, unnamed composition of unitary operations. Represents the application of multiple operations. @@ -1031,8 +1065,8 @@ mqtref.apply @my_gate(%runtime_param) %q0, %q1 **Consistency Requirements:** - Gates providing both matrix and sequence must be verified for consistency -- **TODO:** Define tolerance for numerical verification of consistency -- **TODO:** Specify which representation takes precedence in different contexts +- **Numerical Tolerance:** Consistency verification should use a tolerance close to machine precision (e.g., `1e-14` for double precision) +- **Precedence:** When both matrix and sequence representations are provided, the **matrix representation takes precedence** for unitary extraction. The sequence is treated as a suggestion for decomposition but the matrix defines the ground truth semantics. ## 8. Builder API @@ -1058,8 +1092,8 @@ public: // Resource management mlir::Value allocQubit(); // Dynamic allocation mlir::Value qubit(size_t index); // Static qubit reference - mlir::Value allocQubits(size_t count); // Register allocation - mlir::Value allocBits(size_t count); // Classical register + mlir::Value allocQubits(size_t count); // Register allocation (returns memref) + mlir::Value allocBits(size_t count); // Classical register (returns memref) // Single-qubit gates (return *this for chaining) RefQuantumProgramBuilder& h(mlir::Value q); @@ -1084,10 +1118,28 @@ public: RefQuantumProgramBuilder& p(mlir::Value lambda, mlir::Value q); // Two-qubit gates - RefQuantumProgramBuilder& cx(mlir::Value ctrl, mlir::Value target); RefQuantumProgramBuilder& swap(mlir::Value q0, mlir::Value q1); + RefQuantumProgramBuilder& iswap(mlir::Value q0, mlir::Value q1); + RefQuantumProgramBuilder& dcx(mlir::Value q0, mlir::Value q1); + RefQuantumProgramBuilder& ecr(mlir::Value q0, mlir::Value q1); // ... other two-qubit gates + // Convenience gates (inspired by qc::QuantumComputation API) + // Standard controlled gates + RefQuantumProgramBuilder& cx(mlir::Value ctrl, mlir::Value target); // CNOT + RefQuantumProgramBuilder& cy(mlir::Value ctrl, mlir::Value target); + RefQuantumProgramBuilder& cz(mlir::Value ctrl, mlir::Value target); + RefQuantumProgramBuilder& ch(mlir::Value ctrl, mlir::Value target); + RefQuantumProgramBuilder& crx(double theta, mlir::Value ctrl, mlir::Value target); + RefQuantumProgramBuilder& cry(double theta, mlir::Value ctrl, mlir::Value target); + RefQuantumProgramBuilder& crz(double theta, mlir::Value ctrl, mlir::Value target); + + // Multi-controlled gates (arbitrary number of controls) + RefQuantumProgramBuilder& mcx(mlir::ValueRange ctrls, mlir::Value target); // Toffoli, etc. + RefQuantumProgramBuilder& mcy(mlir::ValueRange ctrls, mlir::Value target); + RefQuantumProgramBuilder& mcz(mlir::ValueRange ctrls, mlir::Value target); + RefQuantumProgramBuilder& mch(mlir::ValueRange ctrls, mlir::Value target); + // Modifiers (take lambdas for body construction) RefQuantumProgramBuilder& ctrl(mlir::ValueRange ctrls, std::function body); @@ -1134,24 +1186,57 @@ private: ### 8.3 Value Semantics Builder -**TODO:** Define `OptQuantumProgramBuilder` with appropriate SSA threading semantics. +The `OptQuantumProgramBuilder` follows the same design principles as the reference semantics builder but with SSA value threading: + +**Key Differences:** + +- **Return Values:** All gate operations return new SSA values representing the output qubits +- **Threading:** Operations consume input qubits and produce output qubits +- **Region Construction:** Modifiers and box operations handle proper argument threading and yield insertion +- **Type Signatures:** Uses `!mqtopt.qubit` instead of `!mqtref.qubit` + +**Example API Sketch:** + +```c++ +class OptQuantumProgramBuilder { +public: + OptQuantumProgramBuilder(mlir::MLIRContext *context); + + // Single-qubit gates return new qubit values + mlir::Value h(mlir::Value q_in); + mlir::Value x(mlir::Value q_in); + mlir::Value rx(double theta, mlir::Value q_in); + + // Two-qubit gates return tuple of output qubits + std::pair cx(mlir::Value ctrl_in, mlir::Value target_in); + std::pair swap(mlir::Value q0_in, mlir::Value q1_in); -Key differences from reference builder: + // Convenience multi-controlled gates + mlir::ValueRange mcx(mlir::ValueRange ctrl_ins, mlir::Value target_in); -- Operations return new SSA values instead of modifying in-place -- Region construction must handle argument threading -- Yield operations must be inserted appropriately + // Modifiers handle value threading automatically + mlir::ValueRange ctrl(mlir::ValueRange ctrl_ins, mlir::ValueRange target_ins, + std::function body); + + // ... similar methods to RefQuantumProgramBuilder +}; +``` ### 8.4 Usage Example ```c++ -// Example: Build Bell state preparation +// Example: Build Bell state preparation with reference semantics RefQuantumProgramBuilder builder(context); builder.initialize(); auto q0 = builder.qubit(0); auto q1 = builder.qubit(1); +// Using convenience method +builder.h(q0).cx(q0, q1); + +// Equivalent using modifier builder.h(q0) .ctrl({q0}, [&](auto& b) { b.x(q1); @@ -1160,21 +1245,25 @@ builder.h(q0) auto module = builder.finalize(); ``` -### 8.5 C API Considerations - -**TODO:** Evaluate feasibility of C API wrapper for language interoperability (Python, Rust, etc.). - -Benefits: +```c++ +// Example: Build Toffoli gate with multi-controlled convenience +RefQuantumProgramBuilder builder(context); +builder.initialize(); -- Enables bindings to other languages -- May eventually replace `qc::QuantumComputation` API +auto q0 = builder.qubit(0); +auto q1 = builder.qubit(1); +auto q2 = builder.qubit(2); -Challenges: +// Toffoli using convenience method +builder.mcx({q0, q1}, q2); -- Lambda-based modifier API needs translation -- Memory management across language boundaries +// Equivalent using modifier +builder.ctrl({q0, q1}, [&](auto& b) { + b.x(q2); +}); +``` -## 9. Testing Strategy +### 9. Testing Strategy ### 9.1 Philosophy @@ -1269,116 +1358,230 @@ func.func @nested_modifiers(%q: !mqtref.qubit) { ### 9.4 Integration Tests -**TODO:** Define integration testing strategy covering: +Integration tests should cover end-to-end compilation scenarios, focusing on real-world usage patterns and interoperability with existing quantum software ecosystems. -- Multi-pass optimization pipelines -- Dialect conversion sequences -- End-to-end compilation scenarios -- Performance regression testing +**Test Coverage:** -### 9.5 Test Coverage Metrics +1. **Frontend Translation:** + - **Qiskit → MLIR:** Convert Qiskit `QuantumCircuit` objects to MQTRef/MQTOpt dialect + - **OpenQASM 3.0 → MLIR:** Parse OpenQASM 3.0 source and lower to MLIR + - **`qc::QuantumComputation` → MLIR:** Migrate from existing MQT Core IR to MLIR representation -**TODO:** Establish coverage targets: +2. **Compiler Pipeline Integration:** + - Execute default optimization pass pipeline on translated circuits + - Verify correctness through unitary equivalence checks + - Test dialect conversions (MQTRef ↔ MQTOpt) within the pipeline + - Validate canonicalization and optimization passes -- Line coverage goal: >90% -- Branch coverage goal: >85% -- Canonicalization pattern coverage: 100% -- Interface method coverage: 100% +3. **Backend Translation:** + - **MLIR → QIR:** Lower optimized MLIR to Quantum Intermediate Representation (QIR) + - Verify QIR output is valid and executable + - Round-trip testing where applicable + +4. **End-to-End Scenarios:** + - Common quantum algorithms (Bell state, GHZ, QPE, VQE ansätze) + - Parameterized circuits with classical optimization loops + - Circuits with measurements and classical control flow + - Large-scale circuits (100+ qubits, 1000+ gates) ## 10. Implementation Roadmap -**TODO:** Define phased implementation plan with milestones. +### Phase 1: Foundation (Weeks 1-2) + +**Goal:** Establish core infrastructure + +- **Week 1:** + - Define core type system (`mqtref.qubit`, `mqtopt.qubit`) + - Implement `UnitaryOpInterface` definition + - Set up basic CMake integration and build system + - Establish GoogleTest framework for unit testing + +- **Week 2:** + - Create basic builder infrastructure (RefQuantumProgramBuilder skeleton) + - Implement resource operations (alloc, dealloc, qubit reference) + - Add measurement and reset operations + - Basic parser/printer for qubit types + +**Deliverable:** Compiling infrastructure with basic qubit operations + +### Phase 2: Base Gates - Core Set (Weeks 2-3) + +**Goal:** Implement essential gates for basic circuits -Suggested phases: +- **Week 2-3:** + - Implement Pauli gates (x, y, z, id) + - Implement Hadamard (h) + - Implement phase gates (s, sdg, t, tdg, p) + - Implement rotation gates (rx, ry, rz) + - Implement universal gates (u, u2) + - Implement convenience gates (sx, sxdg) + - Basic canonicalization patterns for each gate + - Unit tests for all implemented gates -### Phase 1: Foundation (Weeks 1-3) +**Deliverable:** Core single-qubit gate set with tests -- Define core type system (`mqtref.qubit`, `mqtopt.qubit`) -- Implement `UnitaryOpInterface` -- Create basic builder infrastructure -- Establish testing framework +### Phase 3: Base Gates - Two-Qubit Set (Week 4) -### Phase 2: Base Gates (Weeks 4-6) +**Goal:** Complete two-qubit gate library -- Implement all single-qubit gates -- Implement all two-qubit gates -- Add basic canonicalization patterns -- Comprehensive unit tests for each gate +- Implement swap, iswap, dcx, ecr +- Implement parametric two-qubit gates (rxx, ryy, rzz, rzx, xx_plus_yy, xx_minus_yy) +- Implement barrier operation +- Implement global phase gate (gphase) +- Advanced canonicalization patterns +- Comprehensive unit tests -### Phase 3: Modifiers (Weeks 7-9) +**Deliverable:** Complete base gate set -- Implement `ctrl` and `negctrl` +### Phase 4: Modifiers (Week 5) + +**Goal:** Implement composable modifier system + +- Implement `ctrl` modifier (positive controls) +- Implement `negctrl` modifier (negative controls) - Implement `inv` modifier - Implement `pow` modifier -- Test nested modifier combinations +- Nested modifier verification and canonicalization +- Canonical ordering implementation (negctrl → ctrl → pow → inv) +- Modifier flattening and optimization +- Extensive unit tests for modifier combinations + +**Deliverable:** Working modifier system with canonicalization + +### Phase 5: Composition & Box (Week 6) + +**Goal:** Enable circuit composition -### Phase 4: Composition (Weeks 10-12) +- Implement `box` operation (replaces `seq`) +- Add box-level canonicalization +- Implement optimization constraint enforcement +- Builder API for box operations +- Integration with modifiers +- Unit tests for box semantics -- Implement `seq` operation -- Add modifier flattening and ordering -- Implement gate merging canonicalization +**Deliverable:** Box operation with proper scoping -### Phase 5: Custom Gates (Weeks 13-15) +### Phase 6: Custom Gates (Week 7) -- Design and implement gate definition operations +**Goal:** User-defined gate support + +- Design and implement gate definition operations (matrix-based) +- Design and implement gate definition operations (sequence-based) - Implement `apply` operation -- Add symbol table management -- Test custom gate integration +- Symbol table management and resolution +- Consistency verification for dual representations +- Inlining infrastructure +- Unit tests for custom gates -### Phase 6: Optimization (Weeks 16-18) +**Deliverable:** Custom gate definition and application -- Implement advanced canonicalization patterns -- Add dialect conversion passes -- Performance optimization -- Documentation +### Phase 7: Builder API & Convenience (Week 8) + +**Goal:** Ergonomic programmatic construction + +- Complete RefQuantumProgramBuilder implementation +- Complete OptQuantumProgramBuilder implementation +- Add convenience methods (cx, cz, ccx, mcx, mcy, mcz, mch, etc.) +- Builder unit tests +- Example programs using builders -### Phase 7: Validation (Weeks 19-20) +**Deliverable:** Production-ready builder APIs -- Comprehensive integration testing -- Performance benchmarking -- Documentation review +### Phase 8: Dialect Conversion (Week 9) + +**Goal:** Enable transformations between dialects + +- Implement MQTRef → MQTOpt conversion pass +- Implement MQTOpt → MQTRef conversion pass +- Handle modifier and box conversions +- SSA value threading logic +- Conversion unit tests + +**Deliverable:** Working dialect conversion infrastructure + +### Phase 9: Frontend Integration (Week 10) + +**Goal:** Connect to existing quantum software + +- Qiskit → MLIR translation +- OpenQASM 3.0 → MLIR translation +- `qc::QuantumComputation` → MLIR translation +- Parser infrastructure for OpenQASM +- Integration tests for each frontend + +**Deliverable:** Multiple input format support + +### Phase 10: Optimization Passes (Week 11) + +**Goal:** Implement optimization infrastructure + +- Advanced canonicalization patterns +- Gate fusion passes +- Circuit optimization passes +- Dead code elimination +- Constant folding and propagation +- Pass pipeline configuration +- Performance benchmarks + +**Deliverable:** Optimization pass suite + +### Phase 11: Backend Integration (Week 12) + +**Goal:** QIR lowering + +- MLIR → QIR lowering pass +- QIR emission and verification +- End-to-end integration tests (Qiskit → MLIR → QIR) +- Documentation for QIR mapping + +**Deliverable:** Complete compilation pipeline + +### Phase 12: Testing & Documentation (Week 13) + +**Goal:** Production readiness + +- Comprehensive integration test suite +- Parser/printer round-trip tests +- Performance regression tests +- API documentation +- Tutorial examples - Migration guide for existing code -## 11. Open Questions and Future Work +**Deliverable:** Production-ready system with documentation -### 11.1 Critical Open Questions (Require Resolution Before Implementation) +### Phase 13: Polish & Release (Week 14) -1. **Global Phase Semantics:** - - How should global phase gates be merged and optimized? - - Should they implement `UnitaryOpInterface`? - - How do they interact with circuit traversal? +**Goal:** Release preparation -2. **Global Phase Relationships:** - - Resolve global phase differences between equivalent gates (e.g., `x` vs `rx(π)`) - - Define whether these should be considered equivalent or distinct - - Specify canonicalization behavior +- Bug fixes from testing +- Performance optimization +- Code review and cleanup +- Release notes +- Final documentation review +- Announce and deploy -3. **Power Semantics for Non-Diagonalizable Gates:** - - Define behavior for fractional powers of Pauli gates - - Specify whether `pow(1/3) x` is valid or should be rejected - - Define numerical methods for general matrix exponentiation +**Deliverable:** Released dialect system -4. **Custom Gate Symbol Resolution:** - - Define complete syntax for gate definitions and applications - - Specify linking semantics for multi-module programs - - Define inlining heuristics +**Parallelization Strategy:** -5. **Barrier Semantics:** - - Should `barrier` implement `UnitaryOpInterface`? - - How does it interact with modifiers? - - Define its role in optimization passes +- Base gate implementation (Phases 2-3) can partially overlap +- Builder API (Phase 7) can be developed in parallel with custom gates (Phase 6) +- Frontend integration (Phase 9) can start once base operations are stable (after Phase 5) +- Testing and documentation (Phase 12) should be ongoing throughout -6. **Control Qubit Conflicts:** - - What happens if the same qubit is both positive and negative control? - - Should this be an error or have defined semantics? +**Risk Mitigation:** ---- +- **Critical Path:** Phases 1-4 are sequential and critical +- **Buffer Time:** Phase 13 provides 1-week buffer for unforeseen issues +- **Early Testing:** Integration tests should begin in Phase 9 to catch issues early +- **Incremental Delivery:** Each phase produces a working subset to enable testing ## Document Metadata - **Version:** 0.2 (Draft) -- **Last Updated:** 2025-10-06 +- **Last Updated:** [Current date will be filled automatically] - **Status:** Request for Comments -- **Authors:** Lukas Burgholzer (@burgholzer) -- **Reviewers:** Damian Rovara (@Drovara), Yannick Stade (@ystade), Patrick Hopf (@flowerthrower), Daniel Haag (@denialhaag), Matthias Reumann (@MatthiasReumann), Tamino Bauknecht (@taminob) -- **Target MLIR Version:** [TODO: Specify] +- **Authors:** [TODO: Add authors] +- **Reviewers:** [TODO: Add reviewers] +- **Target MLIR Version:** LLVM 21.0+ +- **Target Completion:** 14 weeks from start date From ccc24740499fa44b8ec45e14d17d5b7271bca477 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 16:56:26 +0200 Subject: [PATCH 012/419] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/quantum-dialect-revamp-plan.md | 750 +++++++++++------------ 1 file changed, 366 insertions(+), 384 deletions(-) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/quantum-dialect-revamp-plan.md index 53011c9259..10a3a6e02b 100644 --- a/docs/mlir/quantum-dialect-revamp-plan.md +++ b/docs/mlir/quantum-dialect-revamp-plan.md @@ -1,68 +1,157 @@ -# Quantum Dialect Revamp RFC +# MQT MLIR Compilation Infrastructure: Technical Concept ## Executive Summary -This RFC proposes a comprehensive redesign of the MQT quantum MLIR dialect to provide a unified, extensible framework for quantum circuit representation and optimization. -The revamp introduces a dual-dialect approach (`mqtref` for reference semantics, `mqtopt` for value semantics), a unified unitary interface, composable modifiers (control, inversion, power), user-defined gates, and a robust canonicalization framework. +This document describes the technical design of the MQT's MLIR-based compilation infrastructure for quantum computing. +The infrastructure provides a unified, extensible framework for quantum circuit representation, optimization, and compilation through a multi-dialect architecture built on MLIR (Multi-Level Intermediate Representation). -**Key Benefits:** +**Architecture Overview:** -- **Unified Interface:** All unitary operations expose consistent APIs for introspection and composition +The MQT MLIR infrastructure consists of two complementary dialects that work together to enable flexible quantum program representation and optimization: + +- **Quartz Dialect** (`quartz`): Uses reference semantics with in-place qubit mutation, optimized for direct hardware mapping and straightforward translation to/from existing quantum programming languages. +- **Flux Dialect** (`flux`): Uses SSA value semantics with functional-style transformations, designed for powerful compiler optimizations and circuit transformations. + +Both dialects implement a unified unitary interface, composable gate modifiers, and comprehensive canonicalization frameworks that enable seamless interoperability and progressive optimization strategies. + +**Key Features:** + +- **Unified Interface:** All unitary operations expose consistent APIs for introspection and composition across dialects - **Enhanced Expressiveness:** Support for arbitrary gate modifications, custom gate definitions, and symbolic parameters -- **Optimization-Ready:** Built-in canonicalization rules and transformation hooks -- **Dual Semantics:** Choose between reference semantics (hardware-like) or value semantics (SSA-based optimization) +- **Optimization-Ready:** Built-in canonicalization rules and transformation hooks throughout the representation +- **Dual Semantics:** Choose the appropriate dialect for each compilation stage—reference semantics for I/O boundaries, value semantics for optimization +- **Extensible Architecture:** Designed to accommodate additional dialects and abstraction levels as the quantum computing field evolves + +**Design Rationale:** + +The dual-dialect architecture reflects a fundamental insight: different stages of quantum compilation benefit from different representations. +Reference semantics (Quartz) provides a natural bridge to hardware and existing quantum programming ecosystems, while value semantics (Flux) unlocks the full power of compiler analysis and optimization. +Conversion passes between dialects enable compilation strategies that leverage the strengths of each representation at the appropriate stage. + +## 1. Overview and Design Philosophy + +The MQT MLIR compilation infrastructure represents a comprehensive approach to quantum program compilation, building on MLIR's proven multi-level intermediate representation framework. +This design enables quantum computing to benefit from decades of classical compiler research while addressing the unique challenges of quantum circuits. + +**Design Goals:** + +- **Unified Unitary Interface:** Provide a coherent interface for all operations that apply or produce a unitary transformation (base gates, user-defined gates, modified operations, compositions) +- **Multi-Level Representation:** Support both reference semantics (Quartz) for hardware-oriented representations and value semantics (Flux) for optimization-oriented transformations +- **Composable Modifiers:** Enable arbitrary combinations of inversion, powering, and positive/negative control modifications through a clean modifier system +- **Custom Gate Support:** Allow users to define gates via matrix representations or compositional sequences, enabling domain-specific abstractions +- **Consistent Parameterization:** Unify handling of static and dynamic parameters with consistent ordering and introspection capabilities +- **Systematic Canonicalization:** Embed optimization rules directly at operation definitions for predictable and composable transformations +- **Normalized Representations:** Establish canonical forms (e.g., modifier ordering: `negctrl → ctrl → pow → inv`) to simplify pattern matching and optimization +- **Matrix Extraction:** Enable static matrix computation where possible while supporting symbolic composition for dynamic cases -## 1. Overview and Goals +**Architectural Principles:** -This RFC proposes a comprehensive revamp of the quantum MLIR dialect(s) to unify unitary representations, improve expressiveness, and enable robust transformations. +1. **Separation of Concerns:** Base gates are modifier-free; extensions are applied via wrapper operations +2. **Progressive Refinement:** Compilation proceeds through multiple passes, each operating at appropriate abstraction levels +3. **Semantic Preservation:** All transformations maintain unitary equivalence (or explicitly document approximations) +4. **Extensibility:** The architecture accommodates future dialects for pulse-level control, error correction, or domain-specific optimizations -**Primary Goals:** +## 2. Motivation and Context -- **Unified Unitary Interface:** Provide a coherent interface for all operations that apply or produce a unitary (base gates, user-defined gates, modifier-wrapped constructs, sequences) -- **Dual Semantics Support:** Support both reference semantics (in-place: `mqtref`) and value semantics (SSA threading: `mqtopt`) -- **Rich Modifier System:** Add inversion, powering, and positive/negative multi-controls as composable modifiers -- **Custom Gate Support:** Enable user-defined gates via matrix and composite (sequence-based) definitions -- **Consistent Parameterization:** Unify parameter handling (static + dynamic) with consistent ordering and interface queries -- **Canonicalization Framework:** Embed canonicalization rules directly at each operation definition -- **Normalized Modifier Nesting:** Establish a canonical modifier nesting order: `negctrl → ctrl → pow → inv` -- **Matrix Extraction:** Enable static matrix extraction where possible and symbolic composition otherwise +**The MLIR Opportunity:** -## 2. Current State and Limitations +MLIR provides a proven framework for building progressive compilation pipelines with multiple levels of abstraction. +By building quantum compilation infrastructure on MLIR, we gain: -The current implementation has several significant limitations that this revamp addresses: +- Mature ecosystem of transformation passes and analysis frameworks +- Standard infrastructure for dialect definition, conversion, and optimization +- Established patterns for SSA-based transformations and rewrites +- Interoperability with classical compilation infrastructure for hybrid quantum-classical systems -**Existing Issues:** +**Why Dual Dialects?** -- **Limited Modifiers:** Only a control modifier exists, directly embedded in unitary operations; missing power and inversion modifiers -- **Missing Matrix Support:** No way to obtain matrix representations for gates -- **No Custom Gates:** No support for user-defined gates (neither matrix-based nor composite) -- **Absent Canonicalization:** No systematic canonicalization strategy for modifier order, parameter folding, or gate specialization -- **Testing Challenges:** Mostly FileCheck-based testing that is cumbersome and error-prone to write -- **Limited Builders:** No convenient programmatic builders for constructing quantum programs +The Quartz/Flux dual-dialect architecture reflects a key insight: quantum compilation benefits from different semantic models at different stages: -These limitations hinder both expressiveness and optimization capabilities, motivating the comprehensive redesign proposed in this RFC. +- **Quartz (Reference Semantics):** Provides an intuitive, hardware-like model where gates modify qubits in place. These semantics align naturally with: + - Physical quantum hardware models + - Existing quantum programming languages (OpenQASM, Qiskit, etc.) + - Direct circuit representations + - Backend code generation -## 3. Dialect Structure and Categories +- **Flux (Value Semantics):** Provides a functional, SSA-based model where operations produce new quantum values. These semantics enable: + - Powerful dataflow analysis + - Safe parallelization and reordering + - Sophisticated optimization passes + - Clear dependency tracking -The revamp introduces two parallel dialects with identical operation sets but different operational semantics: +Conversion passes between dialects allow compilation strategies to use the right representation at the right time. + +## 3. Dialect Architecture + +The MQT MLIR infrastructure consists of two parallel dialects with identical operation sets but different operational semantics. +This section describes the architectural design shared across both dialects. ### 3.1 Dialect Overview -**`mqtref` (Reference Semantics):** +**Quartz Dialect (`quartz`):** + +Quartz uses **reference semantics** where quantum operations modify qubits in place, similar to how hardware physically transforms quantum states. +This model provides: + +- Natural mapping to hardware execution models +- Intuitive representation for circuit descriptions +- Direct compatibility with imperative quantum programming languages +- Straightforward backend code generation + +The name "Quartz" reflects the crystalline, structured nature of hardware-oriented representations—operations have fixed positions and transform states in place, like atoms in a crystal lattice. + +**Example:** + +```mlir +quartz.h %q // Applies Hadamard to qubit %q in place +quartz.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets +``` + +**Flux Dialect (`flux`):** + +Flux uses **value semantics** where quantum operations consume input qubits and produce new output values, following the functional programming and SSA paradigm. +This model enables: + +- Powerful compiler optimizations through clear dataflow +- Safe reordering and parallelization analysis +- Advanced transformation passes +- Explicit dependency tracking + +The name "Flux" captures the flowing, transformative nature of value-based representations—quantum states flow through operations, each transformation producing new values like a river flowing through a landscape. + +**Example:** + +```mlir +%q_out = flux.h %q_in // Consumes %q_in, produces %q_out +%q0_out, %q1_out = flux.swap %q0_in, %q1_in // Consumes inputs, produces outputs +``` + +**Dialect Interoperability:** -- Operations mutate qubits in-place (similar to hardware model) -- No SSA results for qubit operations -- More natural for hardware mapping and direct circuit representation -- Example: `mqtref.h %q` applies Hadamard to qubit `%q` in-place +Both dialects share operation names and core semantics, differing only in their type systems and value threading models. Bidirectional conversion passes enable flexible compilation strategies: -**`mqtopt` (Value Semantics):** +``` +Frontend (Qiskit, OpenQASM3, ...) → Quartz → Flux → Optimizations → Flux → Quartz → Backend (QIR, OpenQASM 3, ...) + ↑_____(conversion passes)_____↑ + ↑______________(translation passes)_____________↑ +``` + +This architecture allows: + +- Input from quantum programming languages via Quartz +- Optimization in Flux using SSA transformations +- Output to hardware backends via Quartz + +**Future Extensibility:** -- Operations consume and produce new SSA values (functional style) -- Enables powerful SSA-based optimizations and transformations -- More natural for compiler optimization passes -- Example: `%q_out = mqtopt.h %q_in` consumes `%q_in` and produces `%q_out` +The architecture anticipates additional dialects for: -Both dialects share the same operation names and semantics, differing only in their type system and SSA threading model. Conversion passes enable moving between the two dialects as needed. +- Pulse-level control representations +- Error-corrected logical circuits +- Tensor network representations (e.g., ZX calculus) +- Domain-specific optimizations + +Each dialect can target specific optimization goals while maintaining interoperability through conversion passes. ### 3.2 Operation Categories @@ -78,7 +167,7 @@ All operations fall into three primary categories: **Qubit and Register Allocation:** -In MQT's MLIR dialects, quantum and classical registers are represented by MLIR-native `memref` operations rather than custom types. This design choice offers several advantages: +The MQT MLIR dialects represent quantum and classical registers using MLIR-native `memref` operations rather than custom types. This design choice offers several advantages: - **MLIR Integration:** Seamless compatibility with existing MLIR infrastructure, enabling reuse of memory handling patterns and optimization passes - **Implementation Efficiency:** No need to define and maintain custom register operations, significantly reducing implementation complexity @@ -87,44 +176,44 @@ In MQT's MLIR dialects, quantum and classical registers are represented by MLIR- **Quantum Register Representation:** -A quantum register is represented by a `memref` of type `!mqtref.Qubit` or `!mqtopt.Qubit`: +A quantum register is represented by a `memref` of type `!quartz.Qubit` or `!flux.Qubit`: ```mlir // A quantum register with 2 qubits -%qreg = memref.alloc() : memref<2x!mqtref.Qubit> +%qreg = memref.alloc() : memref<2x!quartz.Qubit> // Load qubits from the register -%q0 = memref.load %qreg[%i0] : memref<2x!mqtref.Qubit> -%q1 = memref.load %qreg[%i1] : memref<2x!mqtref.Qubit> +%q0 = memref.load %qreg[%i0] : memref<2x!quartz.Qubit> +%q1 = memref.load %qreg[%i1] : memref<2x!quartz.Qubit> ``` **Classical Register Representation:** -Classical registers follow the same pattern but use the `i1` type for boolean measurement results: +Classical registers follow the same pattern but use the `i1` type for Boolean measurement results: ```mlir // A classical register with 1 bit %creg = memref.alloc() : memref<1xi1> // Store measurement result -%c = mqtref.measure %q +%c = quartz.measure %q memref.store %c, %creg[%i0] : memref<1xi1> ``` -**Reference Semantics (`mqtref`):** +**Quartz Dialect (Reference Semantics):** ```mlir -%q = mqtref.alloc : !mqtref.qubit -mqtref.dealloc %q : !mqtref.qubit -%q0 = mqtref.qubit 0 : !mqtref.qubit // Static qubit reference +%q = quartz.alloc : !quartz.qubit +quartz.dealloc %q : !quartz.qubit +%q0 = quartz.qubit 0 : !quartz.qubit // Static qubit reference ``` -**Value Semantics (`mqtopt`):** +**Flux Dialect (Value Semantics):** ```mlir -%q = mqtopt.alloc : !mqtopt.qubit -mqtopt.dealloc %q : !mqtopt.qubit -%q0 = mqtopt.qubit 0 : !mqtopt.qubit // Static qubit reference +%q = flux.alloc : !flux.qubit +flux.dealloc %q : !flux.qubit +%q0 = flux.qubit 0 : !flux.qubit // Static qubit reference ``` **Canonicalization Patterns:** @@ -139,18 +228,18 @@ Non-unitary operations that do not implement the `UnitaryOpInterface`. **Single-Qubit Measurements Only:** Multi-qubit measurements are explicitly **not supported**. Joint measurements must be decomposed into individual single-qubit measurements. -**Reference Semantics:** +**Quartz Dialect (Reference Semantics):** ```mlir -%c = mqtref.measure %q : !mqtref.qubit -> i1 -mqtref.reset %q : !mqtref.qubit +%c = quartz.measure %q : !quartz.qubit -> i1 +quartz.reset %q : !quartz.qubit ``` -**Value Semantics:** +**Flux Dialect (Value Semantics):** ```mlir -%q_out, %c = mqtopt.measure %q_in : !mqtopt.qubit -> (!mqtopt.qubit, i1) -%q_out = mqtopt.reset %q_in : !mqtopt.qubit -> !mqtopt.qubit +%q_out, %c = flux.measure %q_in : !flux.qubit -> (!flux.qubit, i1) +%q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit ``` **Canonicalization Patterns:** @@ -218,7 +307,7 @@ This enables canonical equality tests and efficient deduplication. - Parameters appear in parentheses immediately after the operation mnemonic - Support for mixed static (attributes) and dynamic (SSA values) parameters in original order - Enumeration returns a flattened ordered list where each parameter can be inspected for static/dynamic nature -- Example: `mqtref.u(%theta, 1.5708, %lambda) %q` has three parameters: dynamic, static, dynamic +- Example: `quartz.u(%theta, 1.5708, %lambda) %q` has three parameters: dynamic, static, dynamic **Static Matrix Extraction:** @@ -242,7 +331,7 @@ This enables canonical equality tests and efficient deduplication. - **Named Basis Gates:** Each base gate defines a unitary with fixed target arity and parameter arity (expressed via traits) - **Static Matrix When Possible:** Provide static matrix representations when parameters are static or absent - **Modifier-Free Core:** Avoid embedding modifier semantics directly—use wrapper operations instead -- **Consistent Signatures:** Maintain uniform syntax across reference and value semantics +- **Consistent Signatures:** Maintain uniform syntax across Quartz and Flux dialects **Benefits:** @@ -259,8 +348,8 @@ For every named base gate operation `G`: - **Purpose:** Brief description of the unitary operation - **Traits:** Target arity (e.g., `OneTarget`, `TwoTarget`), parameter arity (e.g., `OneParameter`), special properties (e.g., `Hermitian`, `Diagonal`) - **Signatures:** - - Reference: `mqtref.G(param_list?) %targets : (param_types..., qubit_types...)` - - Value: `%out_targets = mqtopt.G(param_list?) %in_targets : (param_types..., qubit_types...) -> (qubit_types...)` + - Quartz: `quartz.G(param_list?) %targets : (param_types..., qubit_types...)` + - Flux: `%out_targets = flux.G(param_list?) %in_targets : (param_types..., qubit_types...) -> (qubit_types...)` - **Assembly Format:** `G(params?) targets` where params are in parentheses, qubits as trailing operands - **Interface Implementation:** - `getNumTargets()` fixed by target arity trait @@ -299,11 +388,11 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Apply global phase `exp(iθ)` to the quantum state - **Traits:** `NoTarget`, `OneParameter` - **Signatures:** - - Ref: `mqtref.gphase(%theta)` - - Value: `mqtopt.gphase(%theta)` + - Quartz: `quartz.gphase(%theta)` + - Flux: `flux.gphase(%theta)` - **Examples:** - - Static: `mqtref.gphase(3.14159)` - - Dynamic: `mqtref.gphase(%theta)` + - Static: `quartz.gphase(3.14159)` + - Dynamic: `quartz.gphase(%theta)` - **Canonicalization:** - `gphase(0) → remove` - `inv(gphase(θ)) → gphase(-θ)` @@ -319,8 +408,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Identity operation (does nothing) - **Traits:** `OneTarget`, `NoParameter`, `Hermitian`, `Diagonal` - **Signatures:** - - Ref: `mqtref.id %q` - - Value: `%q_out = mqtopt.id %q_in` + - Quartz: `quartz.id %q` + - Flux: `%q_out = flux.id %q_in` - **Canonicalization:** - `id → remove` (no effect) - `pow(r) id → id` (any power is still id) @@ -334,11 +423,11 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Pauli-X gate (bit flip) - **Traits:** `OneTarget`, `NoParameter`, `Hermitian` - **Signatures:** - - Ref: `mqtref.x %q` - - Value: `%q_out = mqtopt.x %q_in` + - Quartz: `quartz.x %q` + - Flux: `%q_out = flux.x %q_in` - **Canonicalization:** - - `pow(1/2) x → sx` - - `pow(-1/2) x → sxdg` + - `pow(1/2) x → gphase(-π/4); sx` (square root with global phase correction) + - `pow(-1/2) x → gphase(π/4); sxdg` - `pow(r) x → gphase(-r*π/2); rx(r*π)` (general power translates to rotation with global phase) - **Matrix:** `[0, 1; 1, 0]` (2x2 matrix) - **Definition in terms of `u`:** `u(π, 0, π) %q` @@ -349,8 +438,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Pauli-Y gate (bit and phase flip) - **Traits:** `OneTarget`, `NoParameter`, `Hermitian` - **Signatures:** - - Ref: `mqtref.y %q` - - Value: `%q_out = mqtopt.y %q_in` + - Quartz: `quartz.y %q` + - Flux: `%q_out = flux.y %q_in` - **Canonicalization:** - `pow(r) y → gphase(-r*π/2); ry(r*π)` (general power translates to rotation with global phase) - **Matrix:** `[0, -i; i, 0]` (2x2 matrix) @@ -362,8 +451,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Pauli-Z gate (phase flip) - **Traits:** `OneTarget`, `NoParameter`, `Hermitian`, `Diagonal` - **Signatures:** - - Ref: `mqtref.z %q` - - Value: `%q_out = mqtopt.z %q_in` + - Quartz: `quartz.z %q` + - Flux: `%q_out = flux.z %q_in` - **Canonicalization:** - `pow(1/2) z → s` - `pow(-1/2) z → sdg` @@ -378,8 +467,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Hadamard gate (creates superposition) - **Traits:** `OneTarget`, `NoParameter`, `Hermitian` - **Signatures:** - - Ref: `mqtref.h %q` - - Value: `%q_out = mqtopt.h %q_in` + - Quartz: `quartz.h %q` + - Flux: `%q_out = flux.h %q_in` - **Matrix:** `1/sqrt(2) * [1, 1; 1, -1]` (2x2 matrix) - **Definition in terms of `u`:** `u(π/2, 0, π) %q` @@ -388,8 +477,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** S gate (applies a phase of π/2) - **Traits:** `OneTarget`, `NoParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.s %q` - - Value: `%q_out = mqtopt.s %q_in` + - Quartz: `quartz.s %q` + - Flux: `%q_out = flux.s %q_in` - **Canonicalization:** - `inv s → sdg` - `s %q; s %q → z %q` @@ -406,8 +495,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Sdg gate (applies a phase of -π/2) - **Traits:** `OneTarget`, `NoParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.sdg %q` - - Value: `%q_out = mqtopt.sdg %q_in` + - Quartz: `quartz.sdg %q` + - Flux: `%q_out = flux.sdg %q_in` - **Canonicalization:** - `inv sdg → s` - `sdg %q; sdg %q → z %q` @@ -424,8 +513,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** T gate (applies a phase of π/4) - **Traits:** `OneTarget`, `NoParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.t %q` - - Value: `%q_out = mqtopt.t %q_in` + - Quartz: `quartz.t %q` + - Flux: `%q_out = flux.t %q_in` - **Canonicalization:** - `inv t → tdg` - `t %q; t %q; → s %q` @@ -441,8 +530,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Tdg gate (applies a phase of -π/4) - **Traits:** `OneTarget`, `NoParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.tdg %q` - - Value: `%q_out = mqtopt.tdg %q_in` + - Quartz: `quartz.tdg %q` + - Flux: `%q_out = flux.tdg %q_in` - **Canonicalization:** - `inv tdg → t` - `tdg %q; tdg %q; → sdg %q` @@ -458,8 +547,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Square root of X gate - **Traits:** `OneTarget`, `NoParameter` - **Signatures:** - - Ref: `mqtref.sx %q` - - Value: `%q_out = mqtopt.sx %q_in` + - Quartz: `quartz.sx %q` + - Flux: `%q_out = flux.sx %q_in` - **Canonicalization:** - `inv sx → sxdg` - `sx %q; sx %q → x %q` @@ -473,8 +562,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Square root of X-Dagger gate - **Traits:** `OneTarget`, `NoParameter` - **Signatures:** - - Ref: `mqtref.sxdg %q` - - Value: `%q_out = mqtopt.sxdg %q_in` + - Quartz: `quartz.sxdg %q` + - Flux: `%q_out = flux.sxdg %q_in` - **Canonicalization:** - `inv sxdg → sx` - `sxdg %q; sxdg %q → x %q` @@ -488,9 +577,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Rotation around the X-axis by angle θ - **Traits:** `OneTarget`, `OneParameter` - **Signatures:** - - Ref: `mqtref.rx(%theta) %q` - - Value: `%q_out = mqtopt.rx(%theta) %q_in` -- **Static variant:** `mqtref.rx(3.14159) %q` + - Quartz: `quartz.rx(%theta) %q` + - Flux: `%q_out = flux.rx(%theta) %q_in` +- **Static variant:** `quartz.rx(3.14159) %q` - **Canonicalization:** - `rx(a) %q; rx(b) %q → rx(a + b) %q` - `inv rx(θ) → rx(-θ)` @@ -503,9 +592,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Rotation around the Y-axis by angle θ - **Traits:** `OneTarget`, `OneParameter` - **Signatures:** - - Ref: `mqtref.ry(%theta) %q` - - Value: `%q_out = mqtopt.ry(%theta) %q_in` -- **Static variant:** `mqtref.ry(3.14159) %q` + - Quartz: `quartz.ry(%theta) %q` + - Flux: `%q_out = flux.ry(%theta) %q_in` +- **Static variant:** `quartz.ry(3.14159) %q` - **Canonicalization:** - `ry(a) %q; ry(b) %q → ry(a + b) %q` - `inv ry(θ) → ry(-θ)` @@ -518,9 +607,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Rotation around the Z-axis by angle θ - **Traits:** `OneTarget`, `OneParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.rz(%theta) %q` - - Value: `%q_out = mqtopt.rz(%theta) %q_in` -- **Static variant:** `mqtref.rz(3.14159) %q` + - Quartz: `quartz.rz(%theta) %q` + - Flux: `%q_out = flux.rz(%theta) %q_in` +- **Static variant:** `quartz.rz(3.14159) %q` - **Canonicalization:** - `rz(a) %q; rz(b) %q → rz(a + b) %q` - `inv rz(θ) → rz(-θ)` @@ -533,9 +622,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Phase gate (applies a phase of θ) - **Traits:** `OneTarget`, `OneParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.p(%theta) %q` - - Value: `%q_out = mqtopt.p(%theta) %q_in` -- **Static variant:** `mqtref.p(3.14159) %q` + - Quartz: `quartz.p(%theta) %q` + - Flux: `%q_out = flux.p(%theta) %q_in` +- **Static variant:** `quartz.p(3.14159) %q` - **Canonicalization:** - `p(a) %q; p(b) %q → p(a + b) %q` - `inv p(θ) → p(-θ)` @@ -548,10 +637,10 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Rotation around an arbitrary axis in the XY-plane by angles θ and φ - **Traits:** `OneTarget`, `TwoParameter` - **Signatures:** - - Ref: `mqtref.r(%theta, %phi) %q` - - Value: `%q_out = mqtopt.r(%theta, %phi) %q_in` -- **Static variant:** `mqtref.r(3.14159, 1.5708) %q` -- **Mixed variant:** `mqtref.r(%theta, 1.5708) %q` + - Quartz: `quartz.r(%theta, %phi) %q` + - Flux: `%q_out = flux.r(%theta, %phi) %q_in` +- **Static variant:** `quartz.r(3.14159, 1.5708) %q` +- **Mixed variant:** `quartz.r(%theta, 1.5708) %q` - **Canonicalization:** - `inv r(θ, φ) → r(-θ, φ)` - `pow(real) r(θ, φ) → r(real * θ, φ)` for real `real` @@ -565,10 +654,10 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Universal single-qubit gate (can implement any single-qubit operation) - **Traits:** `OneTarget`, `ThreeParameter` - **Signatures:** - - Ref: `mqtref.u(%theta, %phi, %lambda) %q` - - Value: `%q_out = mqtopt.u(%theta, %phi, %lambda) %q_in` -- **Static variant:** `mqtref.u(3.14159, 1.5708, 0.785398) %q` -- **Mixed variant:** `mqtref.u(%theta, 1.5708, 0.785398) %q` + - Quartz: `quartz.u(%theta, %phi, %lambda) %q` + - Flux: `%q_out = flux.u(%theta, %phi, %lambda) %q_in` +- **Static variant:** `quartz.u(3.14159, 1.5708, 0.785398) %q` +- **Mixed variant:** `quartz.u(%theta, 1.5708, 0.785398) %q` - **Canonicalization:** - `inv u(θ, φ, λ) → u(-θ, -φ, -λ)` - `rx(θ) == u(θ, -π/2, π/2)` @@ -581,10 +670,10 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Simplified universal single-qubit gate (special case of `u` gate) - **Traits:** `OneTarget`, `TwoParameter` - \*\*Signatures - - Ref: `mqtref.u2(%phi, %lambda) %q` - - Value: `%q_out = mqtopt.u2(%phi, %lambda) %q_in` -- **Static variant:** `mqtref.u2(1.5708, 0.785398) %q` -- **Mixed variant:** `mqtref.u2(%phi, 0.785398) %q` + - Quartz: `quartz.u2(%phi, %lambda) %q` + - Flux: `%q_out = flux.u2(%phi, %lambda) %q_in` +- **Static variant:** `quartz.u2(1.5708, 0.785398) %q` +- **Mixed variant:** `quartz.u2(%phi, 0.785398) %q` - **Canonicalization:** - `inv u2(φ, λ) → u2(-λ - π, -φ + π)` - `u2(0, π) → h` @@ -598,8 +687,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Swap two qubits - **Traits:** `TwoTarget`, `NoParameter`, `Hermitian` - **Signatures:** - - Ref: `mqtref.swap %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.swap %q0_in, %q1_in` + - Quartz: `quartz.swap %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.swap %q0_in, %q1_in` - **Matrix:** `[1, 0, 0, 0; 0, 0, 1, 0; 0, 1, 0, 0; 0, 0, 0, 1]` (4x4 matrix) #### 4.3.21 `iswap` Gate @@ -607,8 +696,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Swap two qubits with imaginary coefficient - **Traits:** `TwoTarget`, `NoParameter` - **Signatures:** - - Ref: `mqtref.iswap %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.iswap %q0_in, %q1_in` + - Quartz: `quartz.iswap %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.iswap %q0_in, %q1_in` - **Canonicalization:** - `pow(r) iswap → xx_plus_yy(-π * r)` - **Matrix:** `[1, 0, 0, 0; 0, 0, 1j, 0; 0, 1j, 0, 0; 0, 0, 0, 1]` (4x4 matrix) @@ -618,8 +707,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Double control-NOT gate - **Traits:** `TwoTarget`, `NoParameter` - **Signatures:** - - Ref: `mqtref.dcx %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.dcx %q0_in, %q1_in` + - Quartz: `quartz.dcx %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.dcx %q0_in, %q1_in` - **Canonicalization:** - `inv dcx %q0, q1 => dcx %q1, %q0` - **Matrix:** `[1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]` (4x4 matrix) @@ -629,8 +718,8 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** Cross-resonance gate with echo - **Traits:** `TwoTarget`, `NoParameter`, `Hermitian` - **Signatures:** - - Ref: `mqtref.ecr %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.ecr %q0_in, %q1_in` + - Quartz: `quartz.ecr %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.ecr %q0_in, %q1_in` - **Matrix:** `1/sqrt(2) * [0, 0, 1, 1j; 0, 0, 1j, 1; 1, -1j, 0, 0; -1j, 1, 0, 0]` (4x4 matrix) #### 4.3.24 `rxx` Gate (XX-Rotation) @@ -638,9 +727,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** General two-qubit rotation around XX. - **Traits:** `TwoTarget`, `OneParameter` - **Signatures:** - - Ref: `mqtref.rxx(%theta) %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.rxx(%theta) %q0_in, %q1_in` -- **Static variant:** `mqtref.rxx(3.14159) %q0, %q1` + - Quartz: `quartz.rxx(%theta) %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.rxx(%theta) %q0_in, %q1_in` +- **Static variant:** `quartz.rxx(3.14159) %q0, %q1` - **Canonicalization:** - `inv rxx(%theta) => rxx(-%theta)` - `pow(r) rxx(%theta) => rxx(r * %theta)` for real r @@ -653,9 +742,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** General two-qubit gate around YY. - **Traits:** `TwoTarget`, `OneParameter` - **Signatures:** - - Ref: `mqtref.ryy(%theta) %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.ryy(%theta) %q0_in, %q1_in` -- **Static variant:** `mqtref.ryy(3.14159) %q0, %q1` + - Quartz: `quartz.ryy(%theta) %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.ryy(%theta) %q0_in, %q1_in` +- **Static variant:** `quartz.ryy(3.14159) %q0, %q1` - **Canonicalization:** - `inv ryy(%theta) => ryy(-%theta)` - `pow(r) ryy(%theta) => ryy(r * %theta)` for real r @@ -668,9 +757,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** General two-qubit gate around ZX. - **Traits:** `TwoTarget`, `OneParameter` - **Signatures:** - - Ref: `mqtref.rzx(%theta) %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.rzx(%theta) %q0_in, %q1_in` -- **Static variant:** `mqtref.rzx(3.14159) %q0, %q1` + - Quartz: `quartz.rzx(%theta) %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.rzx(%theta) %q0_in, %q1_in` +- **Static variant:** `quartz.rzx(3.14159) %q0, %q1` - **Canonicalization:** - `inv rzx(%theta) => rzx(-%theta)` - `pow(r) rzx(%theta) => rzx(r * %theta)` for real r @@ -683,9 +772,9 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** General two-qubit gate around ZZ. - **Traits:** `TwoTarget`, `OneParameter`, `Diagonal` - **Signatures:** - - Ref: `mqtref.rzz(%theta) %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.rzz(%theta) %q0_in, %q1_in` -- **Static variant:** `mqtref.rzz(3.14159) %q0, %q1` + - Quartz: `quartz.rzz(%theta) %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.rzz(%theta) %q0_in, %q1_in` +- **Static variant:** `quartz.rzz(3.14159) %q0, %q1` - **Canonicalization:** - `inv rzz(%theta) => rzz(-%theta)` - `pow(r) rzz(%theta) => rzz(r * %theta)` for real r @@ -698,10 +787,10 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** General two-qubit gate around XX+YY. - **Traits:** `TwoTarget`, `TwoParameter` - \*\*Signatures: - - Ref: `mqtref.xx_plus_yy(%theta, %beta) %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.xx_plus_yy(%theta, %beta) %q0_in, %q1_in` -- **Static variant:** `mqtref.xx_plus_yy(3.14159, 1.5708) %q0, %q1` -- **Mixed variant:** `mqtref.xx_plus_yy(%theta, 1.5708) %q0, %q1` + - Quartz: `quartz.xx_plus_yy(%theta, %beta) %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.xx_plus_yy(%theta, %beta) %q0_in, %q1_in` +- **Static variant:** `quartz.xx_plus_yy(3.14159, 1.5708) %q0, %q1` +- **Mixed variant:** `quartz.xx_plus_yy(%theta, 1.5708) %q0, %q1` - **Canonicalization:** - `inv xx_plus_yy(θ, β) => xx_plus_yy(-θ, β)` - `pow(r) xx_plus_yy(θ, β) => xx_plus_yy(r * θ, β)` for real r @@ -713,10 +802,10 @@ The following canonicalization patterns apply automatically to all gates with th - **Purpose:** General two-qubit gate around XX-YY. - **Traits:** `TwoTarget`, `TwoParameter` - \*\*Signatures: - - Ref: `mqtref.xx_minus_yy(%theta, %beta) %q0, %q1` - - Value: `%q0_out, %q1_out = mqtopt.xx_minus_yy(%theta, %beta) %q0_in, %q1_in` -- **Static variant:** `mqtref.xx_minus_yy(3.14159, 1.5708) %q0, %q1` -- **Mixed variant:** `mqtref.xx_minus_yy(%theta, 1.5708) %q0, %q1` + - Quartz: `quartz.xx_minus_yy(%theta, %beta) %q0, %q1` + - Flux: `%q0_out, %q1_out = flux.xx_minus_yy(%theta, %beta) %q0_in, %q1_in` +- **Static variant:** `quartz.xx_minus_yy(3.14159, 1.5708) %q0, %q1` +- **Mixed variant:** `quartz.xx_minus_yy(%theta, 1.5708) %q0, %q1` - **Canonicalization:** - `inv xx_minus_yy(θ, β) => xx_minus_yy(-θ, β)` - `pow(r) xx_minus_yy(θ, β) => xx_minus_yy(r * θ, β)` for real r @@ -726,18 +815,16 @@ The following canonicalization patterns apply automatically to all gates with th #### 4.3.30 `barrier` Gate - **Purpose:** Prevents optimization passes from reordering operations across the barrier -- **Traits:** `NoParameter` +- **Traits:** `OneTarget`, `TwoTarget`, `NoParameter` (overloaded for different qubit counts) - **Signatures:** - - Ref: `mqtref.barrier %q0, %q1, ...` - - Value: `%q0_out, %q1_out, ... = mqtopt.barrier %q0_in, %q1_in, ...` + - Quartz: `quartz.barrier %q0, %q1, ...` + - Flux: `%q0_out, %q1_out, ... = flux.barrier %q0_in, %q1_in, ...` - **Semantics:** The `barrier` operation implements the `UnitaryOpInterface` and is treated similarly to the identity gate from a unitary perspective. However, it serves as a compiler directive that constrains optimization: operations cannot be moved across a barrier boundary. - **Canonicalization:** - Barriers with no qubits can be removed - `barrier; barrier` on same qubits → single `barrier` (adjacent barriers merge) - `inv { barrier } → barrier` (barrier is self-adjoint) - `pow(r) { barrier } → barrier` (any power of barrier is still barrier) - - `ctrl(%c) { barrier } → barrier` (controlled barrier is still barrier) - - `negctrl(%c) { barrier } → barrier` (negatively controlled barrier is still barrier) - **Matrix:** Identity matrix of appropriate dimension (2^n × 2^n for n qubits) - **UnitaryOpInterface Implementation:** Returns identity matrix, no parameters, no controls, targets are the specified qubits @@ -758,13 +845,13 @@ Modifiers are wrapper operations that transform or extend unitary operations wit - **Single-Operation Regions:** Each modifier contains exactly one region with a single block whose only operation implements `UnitaryOpInterface` - **Arbitrary Nesting:** Modifiers may be arbitrarily nested - **Canonical Ordering:** Canonicalization rules flatten and reorder modifiers to a standard form: `negctrl → ctrl → pow → inv` -- **Dialect Consistency:** Both `mqtref` and `mqtopt` variants with corresponding semantics +- **Dialect Consistency:** Both `quartz` and `flux` variants with corresponding semantics **Value vs. Reference Semantics:** -- **Reference:** Modifiers are statements without results; wrapped operation mutates qubits in-place -- **Value:** Modifiers thread SSA values through region arguments and yield results -- **Conversion:** `mqtopt → mqtref` is straightforward; `mqtref → mqtopt` requires adding SSA values to region arguments and yields +- **Quartz (Reference):** Modifiers are statements without results; wrapped operation mutates qubits in-place +- **Flux (Value):** Modifiers thread SSA values through region arguments and yield results +- **Conversion:** `flux → quartz` is straightforward; `quartz → flux` requires adding SSA values to region arguments and yields ### 5.2 Control Modifiers (`ctrl` and `negctrl`) @@ -772,19 +859,19 @@ Modifiers are wrapper operations that transform or extend unitary operations wit **Signatures (shown for `ctrl`; `negctrl` is analogous):** -- **Reference:** +- **Quartz (Reference Semantics):** ```mlir - mqtref.ctrl(%ctrl0, %ctrl1, ...) { - mqtref.unitaryOp %target0, %target1, ... + quartz.ctrl(%ctrl0, %ctrl1, ...) { + quartz.unitaryOp %target0, %target1, ... } ``` -- **Value:** +- **Flux (Value Semantics):** ```mlir - %ctrl_outs, %target_outs = mqtopt.ctrl(%ctrl_ins, %target_ins) { - %new_targets = mqtopt.unitaryOp %target_ins - mqtopt.yield %new_targets + %ctrl_outs, %target_outs = flux.ctrl(%ctrl_ins, %target_ins) { + %new_targets = flux.unitaryOp %target_ins + flux.yield %new_targets } ``` @@ -825,19 +912,19 @@ For negative controls, `|1⟩⟨1|` is replaced with `|0⟩⟨0|`. **Signatures:** -- **Reference:** +- **Quartz (Reference Semantics):** ```mlir - mqtref.inv { - mqtref.unitaryOp %targets + quartz.inv { + quartz.unitaryOp %targets } ``` -- **Value:** +- **Flux (Value Semantics):** ```mlir - %targets_out = mqtopt.inv(%targets_in) { - %new_targets = mqtopt.unitaryOp %targets_in - mqtopt.yield %new_targets + %targets_out = flux.inv(%targets_in) { + %new_targets = flux.unitaryOp %targets_in + flux.yield %new_targets } ``` @@ -866,21 +953,21 @@ Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugat **Signatures:** -- **Reference:** +- **Quartz (Reference Semantics):** ```mlir - mqtref.pow(%exponent) { - mqtref.unitaryOp %targets + quartz.pow(%exponent) { + quartz.unitaryOp %targets } ``` - Static variant: `mqtref.pow {exponent = 2.0 : f64} { ... }` + Static variant: `quartz.pow {exponent = 2.0 : f64} { ... }` -- **Value:** +- **Flux (Value Semantics):** ```mlir - %targets_out = mqtopt.pow(%exponent, %targets_in) { - %new_targets = mqtopt.unitaryOp %targets_in - mqtopt.yield %new_targets + %targets_out = flux.pow(%exponent, %targets_in) { + %new_targets = flux.unitaryOp %targets_in + flux.yield %new_targets } ``` @@ -915,27 +1002,27 @@ Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugat ## 6. Box Operation (`box`) -**Purpose:** Ordered, unnamed composition of unitary operations. Represents the application of multiple operations. +**Purpose:** Scoped composition of unitary operations with timing and optimization constraints. Inspired by OpenQASM 3.0's `box` statement, this operation encapsulates a sequence of operations while constraining how optimizations may interact with them. **Signatures:** -- **Reference:** +- **Quartz (Reference Semantics):** ```mlir - mqtref.seq { - mqtref.h %q0 - mqtref.cx %q0, %q1 - mqtref.rz(1.57) %q1 + quartz.box { + quartz.h %q0 + quartz.cx %q0, %q1 + quartz.rz(1.57) %q1 } ``` -- **Value:** +- **Flux (Value Semantics):** ```mlir - %q0_out, %q1_out = mqtopt.seq(%q0_in, %q1_in) : (!mqtopt.qubit, !mqtopt.qubit) -> (!mqtopt.qubit, !mqtopt.qubit) { - %q0_1 = mqtopt.h %q0_in - %q0_2, %q1_1 = mqtopt.cx %q0_1, %q1_in - %q1_2 = mqtopt.rz(1.57) %q1_1 - mqtopt.yield %q0_2, %q1_2 + %q0_out, %q1_out = flux.box(%q0_in, %q1_in) : (!flux.qubit, !flux.qubit) -> (!flux.qubit, !flux.qubit) { + %q0_1 = flux.h %q0_in + %q0_2, %q1_1 = flux.cx %q0_1, %q1_in + %q1_2 = flux.rz(1.57) %q1_1 + flux.yield %q0_2, %q1_2 } ``` @@ -948,9 +1035,9 @@ Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugat **Canonicalization:** -- Empty sequence elimination: `seq { } → remove` -- Single-operation inlining: `seq { U } → U` -- Nested sequence flattening: `seq { seq { U; V }; W } → seq { U; V; W }` +- Empty sequence elimination: `box { } → remove` +- Single-operation inlining: `box { U } → U` +- Nested sequence flattening: `box { box { U; V }; W } → box { U; V; W }` **Verifiers:** @@ -962,129 +1049,31 @@ Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugat The composite unitary is computed as the product of child unitaries in reverse order (right-to-left multiplication, since operations apply left-to-right): ``` -U_seq = U_n · U_{n-1} · ... · U_1 · U_0 +U_box = U_n · U_{n-1} · ... · U_1 · U_0 ``` **Conversion Between Dialects:** -- **`mqtopt → mqtref`:** Remove block arguments and results; replace argument uses with direct value references -- **`mqtref → mqtopt`:** Add block arguments for all used qubits; thread SSA values through operations; add yield with final values - -## 7. User-Defined Gates & Matrix/Composite Definitions - -**Purpose:** Enable users to define custom gates that can be referenced and instantiated throughout the program, similar to function definitions and calls. - -### 7.1 Overview - -User-defined gates provide two definition mechanisms: - -1. **Matrix-based definitions:** Define a gate via its unitary matrix (efficient for small qubit counts) -2. **Sequence-based definitions:** Define a gate as a composition of existing operations (more general, better for larger circuits) - -Definitions may optionally provide both representations for the same gate, which should be consistent. - -**Symbol Management:** - -- Gate definitions create symbols (similar to `func.func`) -- Symbols are referenced by `apply` operations (similar to `func.call`) -- Symbols have module-level scope and must be unique within a module - -### 7.2 Matrix-Based Gate Definitions - -**Purpose:** Define a custom gate using its unitary matrix representation. - -**Signature:** - -```mlir -mqt.define_matrix_gate @my_gate(%param0: f64, %param1: f64) : (2 qubits) { - matrix = dense<[[complex_values]]> : tensor<4x4xcomplex> - // Or symbolic expression attribute -} -``` - -**Open Issues:** - -- **TODO:** Define complete syntax for matrix-based definitions -- **TODO:** Specify format for dynamic (parameterized) matrices using symbolic expressions -- **TODO:** Define verification rules for matrix unitarity -- **TODO:** Specify maximum practical qubit count (matrices scale as 2^n × 2^n) - -### 7.3 Sequence-Based Gate Definitions - -**Purpose:** Define a custom gate as a composition of existing unitary operations. - -**Signature (draft):** - -```mlir -mqt.define_composite_gate @my_gate(%param0: f64) : (2 qubits) { -^bb0(%q0: !mqt.qubit, %q1: !mqt.qubit): - %q0_1 = mqt.ry(%param0) %q0 - %q0_2, %q1_1 = mqt.cx %q0_1, %q1 - mqt.return %q0_2, %q1_1 -} -``` - -**Open Issues:** - -- **TODO:** Define complete syntax with proper dialect prefix -- **TODO:** Specify parameter binding mechanism -- **TODO:** Define qubit argument conventions -- **TODO:** Clarify relationship with `seq` operation -- **TODO:** Specify whether recursive definitions are allowed - -### 7.4 Gate Application (`apply`) - -**Purpose:** Instantiate a user-defined gate by referencing its symbol. - -**Signature (draft):** - -```mlir -mqtref.apply @my_gate(%runtime_param) %q0, %q1 -``` - -**Open Issues:** - -- **TODO:** Complete specification of `apply` operation -- **TODO:** Define parameter passing mechanisms (static vs. dynamic) -- **TODO:** Specify how `apply` implements `UnitaryOpInterface` -- **TODO:** Define inlining behavior and thresholds -- **TODO:** Specify linking semantics for multi-module programs - -### 7.5 Design Considerations - -**Matrix vs. Sequence Trade-offs:** - -- **Matrix definitions:** - - Pros: Direct representation, efficient evaluation for small gates, exact - - Cons: Exponential space complexity, no structure for optimization - -- **Sequence definitions:** - - Pros: Scalable, exposes structure for optimization, composable - - Cons: May require complex unitary extraction, potential overhead - -**Consistency Requirements:** - -- Gates providing both matrix and sequence must be verified for consistency -- **Numerical Tolerance:** Consistency verification should use a tolerance close to machine precision (e.g., `1e-14` for double precision) -- **Precedence:** When both matrix and sequence representations are provided, the **matrix representation takes precedence** for unitary extraction. The sequence is treated as a suggestion for decomposition but the matrix defines the ground truth semantics. +- **`flux → quartz`:** Remove block arguments and results; replace argument uses with direct value references +- **`quartz → flux`:** Add block arguments for all used qubits; thread SSA values through operations; add yield with final values ## 8. Builder API -**Purpose:** Provide a programmatic API for constructing quantum programs, replacing FileCheck-based test construction with type-safe builders. +**Purpose:** Provide a programmatic API for constructing quantum programs within the MQT MLIR infrastructure. The builder APIs offer type-safe, ergonomic interfaces for both Quartz and Flux dialects. ### 8.1 Design Goals - **Ergonomic:** Easy chaining and nesting of operations - **Type-safe:** Leverage C++ type system to catch errors at compile time -- **Dialect-aware:** Separate builders for `mqtref` and `mqtopt` +- **Dialect-aware:** Separate builders for Quartz and Flux - **Testable:** Enable structural comparison of circuits in unit tests -### 8.2 Reference Semantics Builder (Draft API) +### 8.2 Quartz Builder (Reference Semantics) ```c++ -class RefQuantumProgramBuilder { +class QuartzProgramBuilder { public: - RefQuantumProgramBuilder(mlir::MLIRContext *context); + QuartzProgramBuilder(mlir::MLIRContext *context); // Initialization void initialize(); @@ -1096,81 +1085,81 @@ public: mlir::Value allocBits(size_t count); // Classical register (returns memref) // Single-qubit gates (return *this for chaining) - RefQuantumProgramBuilder& h(mlir::Value q); - RefQuantumProgramBuilder& x(mlir::Value q); - RefQuantumProgramBuilder& y(mlir::Value q); - RefQuantumProgramBuilder& z(mlir::Value q); - RefQuantumProgramBuilder& s(mlir::Value q); - RefQuantumProgramBuilder& sdg(mlir::Value q); - RefQuantumProgramBuilder& t(mlir::Value q); - RefQuantumProgramBuilder& tdg(mlir::Value q); - RefQuantumProgramBuilder& sx(mlir::Value q); - RefQuantumProgramBuilder& sxdg(mlir::Value q); + QuartzProgramBuilder& h(mlir::Value q); + QuartzProgramBuilder& x(mlir::Value q); + QuartzProgramBuilder& y(mlir::Value q); + QuartzProgramBuilder& z(mlir::Value q); + QuartzProgramBuilder& s(mlir::Value q); + QuartzProgramBuilder& sdg(mlir::Value q); + QuartzProgramBuilder& t(mlir::Value q); + QuartzProgramBuilder& tdg(mlir::Value q); + QuartzProgramBuilder& sx(mlir::Value q); + QuartzProgramBuilder& sxdg(mlir::Value q); // Parametric single-qubit gates - RefQuantumProgramBuilder& rx(double theta, mlir::Value q); - RefQuantumProgramBuilder& rx(mlir::Value theta, mlir::Value q); // Dynamic - RefQuantumProgramBuilder& ry(double theta, mlir::Value q); - RefQuantumProgramBuilder& ry(mlir::Value theta, mlir::Value q); - RefQuantumProgramBuilder& rz(double theta, mlir::Value q); - RefQuantumProgramBuilder& rz(mlir::Value theta, mlir::Value q); - RefQuantumProgramBuilder& p(double lambda, mlir::Value q); - RefQuantumProgramBuilder& p(mlir::Value lambda, mlir::Value q); + QuartzProgramBuilder& rx(double theta, mlir::Value q); + QuartzProgramBuilder& rx(mlir::Value theta, mlir::Value q); // Dynamic + QuartzProgramBuilder& ry(double theta, mlir::Value q); + QuartzProgramBuilder& ry(mlir::Value theta, mlir::Value q); + QuartzProgramBuilder& rz(double theta, mlir::Value q); + QuartzProgramBuilder& rz(mlir::Value theta, mlir::Value q); + QuartzProgramBuilder& p(double lambda, mlir::Value q); + QuartzProgramBuilder& p(mlir::Value lambda, mlir::Value q); // Two-qubit gates - RefQuantumProgramBuilder& swap(mlir::Value q0, mlir::Value q1); - RefQuantumProgramBuilder& iswap(mlir::Value q0, mlir::Value q1); - RefQuantumProgramBuilder& dcx(mlir::Value q0, mlir::Value q1); - RefQuantumProgramBuilder& ecr(mlir::Value q0, mlir::Value q1); + QuartzProgramBuilder& swap(mlir::Value q0, mlir::Value q1); + QuartzProgramBuilder& iswap(mlir::Value q0, mlir::Value q1); + QuartzProgramBuilder& dcx(mlir::Value q0, mlir::Value q1); + QuartzProgramBuilder& ecr(mlir::Value q0, mlir::Value q1); // ... other two-qubit gates // Convenience gates (inspired by qc::QuantumComputation API) // Standard controlled gates - RefQuantumProgramBuilder& cx(mlir::Value ctrl, mlir::Value target); // CNOT - RefQuantumProgramBuilder& cy(mlir::Value ctrl, mlir::Value target); - RefQuantumProgramBuilder& cz(mlir::Value ctrl, mlir::Value target); - RefQuantumProgramBuilder& ch(mlir::Value ctrl, mlir::Value target); - RefQuantumProgramBuilder& crx(double theta, mlir::Value ctrl, mlir::Value target); - RefQuantumProgramBuilder& cry(double theta, mlir::Value ctrl, mlir::Value target); - RefQuantumProgramBuilder& crz(double theta, mlir::Value ctrl, mlir::Value target); + QuartzProgramBuilder& cx(mlir::Value ctrl, mlir::Value target); // CNOT + QuartzProgramBuilder& cy(mlir::Value ctrl, mlir::Value target); + QuartzProgramBuilder& cz(mlir::Value ctrl, mlir::Value target); + QuartzProgramBuilder& ch(mlir::Value ctrl, mlir::Value target); + QuartzProgramBuilder& crx(double theta, mlir::Value ctrl, mlir::Value target); + QuartzProgramBuilder& cry(double theta, mlir::Value ctrl, mlir::Value target); + QuartzProgramBuilder& crz(double theta, mlir::Value ctrl, mlir::Value target); // Multi-controlled gates (arbitrary number of controls) - RefQuantumProgramBuilder& mcx(mlir::ValueRange ctrls, mlir::Value target); // Toffoli, etc. - RefQuantumProgramBuilder& mcy(mlir::ValueRange ctrls, mlir::Value target); - RefQuantumProgramBuilder& mcz(mlir::ValueRange ctrls, mlir::Value target); - RefQuantumProgramBuilder& mch(mlir::ValueRange ctrls, mlir::Value target); + QuartzProgramBuilder& mcx(mlir::ValueRange ctrls, mlir::Value target); // Toffoli, etc. + QuartzProgramBuilder& mcy(mlir::ValueRange ctrls, mlir::Value target); + QuartzProgramBuilder& mcz(mlir::ValueRange ctrls, mlir::Value target); + QuartzProgramBuilder& mch(mlir::ValueRange ctrls, mlir::Value target); // Modifiers (take lambdas for body construction) - RefQuantumProgramBuilder& ctrl(mlir::ValueRange ctrls, - std::function body); - RefQuantumProgramBuilder& negctrl(mlir::ValueRange ctrls, - std::function body); - RefQuantumProgramBuilder& inv(std::function body); - RefQuantumProgramBuilder& pow(double exponent, - std::function body); - RefQuantumProgramBuilder& pow(mlir::Value exponent, - std::function body); - - // Sequence - RefQuantumProgramBuilder& seq(std::function body); + QuartzProgramBuilder& ctrl(mlir::ValueRange ctrls, + std::function body); + QuartzProgramBuilder& negctrl(mlir::ValueRange ctrls, + std::function body); + QuartzProgramBuilder& inv(std::function body); + QuartzProgramBuilder& pow(double exponent, + std::function body); + QuartzProgramBuilder& pow(mlir::Value exponent, + std::function body); + + // Box (scoped sequence with optimization constraints) + QuartzProgramBuilder& box(std::function body); // Gate definitions void defineMatrixGate(mlir::StringRef name, size_t numQubits, mlir::DenseElementsAttr matrix); void defineCompositeGate(mlir::StringRef name, size_t numQubits, mlir::ArrayRef paramTypes, - std::function body); // Apply custom gate - RefQuantumProgramBuilder& apply(mlir::StringRef gateName, + QuartzProgramBuilder& apply(mlir::StringRef gateName, mlir::ValueRange targets, mlir::ValueRange params = {}); // Measurement and reset mlir::Value measure(mlir::Value q); - RefQuantumProgramBuilder& reset(mlir::Value q); + QuartzProgramBuilder& reset(mlir::Value q); // Finalization mlir::ModuleOp finalize(); @@ -1184,23 +1173,23 @@ private: }; ``` -### 8.3 Value Semantics Builder +### 8.3 Flux Builder (Value Semantics) -The `OptQuantumProgramBuilder` follows the same design principles as the reference semantics builder but with SSA value threading: +The `FluxProgramBuilder` follows the same design principles as the Quartz builder but with SSA value threading: **Key Differences:** - **Return Values:** All gate operations return new SSA values representing the output qubits - **Threading:** Operations consume input qubits and produce output qubits - **Region Construction:** Modifiers and box operations handle proper argument threading and yield insertion -- **Type Signatures:** Uses `!mqtopt.qubit` instead of `!mqtref.qubit` +- **Type Signatures:** Uses `!flux.qubit` instead of `!quartz.qubit` **Example API Sketch:** ```c++ -class OptQuantumProgramBuilder { +class FluxProgramBuilder { public: - OptQuantumProgramBuilder(mlir::MLIRContext *context); + FluxProgramBuilder(mlir::MLIRContext *context); // Single-qubit gates return new qubit values mlir::Value h(mlir::Value q_in); @@ -1212,22 +1201,23 @@ public: std::pair swap(mlir::Value q0_in, mlir::Value q1_in); // Convenience multi-controlled gates - mlir::ValueRange mcx(mlir::ValueRange ctrl_ins, mlir::Value target_in); + mlir::Value mcx(mlir::ValueRange ctrl_ins, mlir::Value target_in, + mlir::ValueRange& ctrl_outs); // Modifiers handle value threading automatically mlir::ValueRange ctrl(mlir::ValueRange ctrl_ins, mlir::ValueRange target_ins, - std::function body); - // ... similar methods to RefQuantumProgramBuilder + // ... similar methods to QuartzProgramBuilder }; ``` -### 8.4 Usage Example +### 8.4 Usage Examples ```c++ -// Example: Build Bell state preparation with reference semantics -RefQuantumProgramBuilder builder(context); +// Example: Build Bell state preparation with Quartz dialect +QuartzProgramBuilder builder(context); builder.initialize(); auto q0 = builder.qubit(0); @@ -1247,7 +1237,7 @@ auto module = builder.finalize(); ```c++ // Example: Build Toffoli gate with multi-controlled convenience -RefQuantumProgramBuilder builder(context); +QuartzProgramBuilder builder(context); builder.initialize(); auto q0 = builder.qubit(0); @@ -1263,7 +1253,7 @@ builder.ctrl({q0, q1}, [&](auto& b) { }); ``` -### 9. Testing Strategy +## 9. Testing Strategy ### 9.1 Philosophy @@ -1333,15 +1323,15 @@ TEST(QuantumDialectTest, InverseInverseCanonicalizes) { // RUN: mqt-opt %s | mqt-opt | FileCheck %s // CHECK-LABEL: func @nested_modifiers -func.func @nested_modifiers(%q: !mqtref.qubit) { - // CHECK: mqtref.ctrl - // CHECK-NEXT: mqtref.pow - // CHECK-NEXT: mqtref.inv - // CHECK-NEXT: mqtref.x - mqtref.ctrl %c { - mqtref.pow {exponent = 2.0 : f64} { - mqtref.inv { - mqtref.x %q +func.func @nested_modifiers(%q: !quartz.qubit) { + // CHECK: quartz.ctrl + // CHECK-NEXT: quartz.pow + // CHECK-NEXT: quartz.inv + // CHECK-NEXT: quartz.x + quartz.ctrl %c { + quartz.pow {exponent = 2.0 : f64} { + quartz.inv { + quartz.x %q } } } @@ -1358,19 +1348,19 @@ func.func @nested_modifiers(%q: !mqtref.qubit) { ### 9.4 Integration Tests -Integration tests should cover end-to-end compilation scenarios, focusing on real-world usage patterns and interoperability with existing quantum software ecosystems. +Integration tests validate the complete MQT MLIR compilation infrastructure, covering end-to-end scenarios from frontend ingestion through optimization to backend emission. **Test Coverage:** 1. **Frontend Translation:** - - **Qiskit → MLIR:** Convert Qiskit `QuantumCircuit` objects to MQTRef/MQTOpt dialect + - **Qiskit → MLIR:** Convert Qiskit `QuantumCircuit` objects to Quartz/Flux dialects - **OpenQASM 3.0 → MLIR:** Parse OpenQASM 3.0 source and lower to MLIR - - **`qc::QuantumComputation` → MLIR:** Migrate from existing MQT Core IR to MLIR representation + - **`qc::QuantumComputation` → MLIR:** Translate from existing MQT Core IR to MLIR representation 2. **Compiler Pipeline Integration:** - Execute default optimization pass pipeline on translated circuits - Verify correctness through unitary equivalence checks - - Test dialect conversions (MQTRef ↔ MQTOpt) within the pipeline + - Test dialect conversions (Quartz ↔ Flux) within the pipeline - Validate canonicalization and optimization passes 3. **Backend Translation:** @@ -1386,18 +1376,20 @@ Integration tests should cover end-to-end compilation scenarios, focusing on rea ## 10. Implementation Roadmap +**Timeline:** 14-week implementation plan to complete the MQT MLIR compilation infrastructure. + ### Phase 1: Foundation (Weeks 1-2) **Goal:** Establish core infrastructure - **Week 1:** - - Define core type system (`mqtref.qubit`, `mqtopt.qubit`) + - Define core type system (`quartz.qubit`, `flux.qubit`) - Implement `UnitaryOpInterface` definition - Set up basic CMake integration and build system - Establish GoogleTest framework for unit testing - **Week 2:** - - Create basic builder infrastructure (RefQuantumProgramBuilder skeleton) + - Create basic builder infrastructure (QuartzProgramBuilder skeleton) - Implement resource operations (alloc, dealloc, qubit reference) - Add measurement and reset operations - Basic parser/printer for qubit types @@ -1575,13 +1567,3 @@ Integration tests should cover end-to-end compilation scenarios, focusing on rea - **Buffer Time:** Phase 13 provides 1-week buffer for unforeseen issues - **Early Testing:** Integration tests should begin in Phase 9 to catch issues early - **Incremental Delivery:** Each phase produces a working subset to enable testing - -## Document Metadata - -- **Version:** 0.2 (Draft) -- **Last Updated:** [Current date will be filled automatically] -- **Status:** Request for Comments -- **Authors:** [TODO: Add authors] -- **Reviewers:** [TODO: Add reviewers] -- **Target MLIR Version:** LLVM 21.0+ -- **Target Completion:** 14 weeks from start date From 2ac573a21cbe91705362d9eedf671c47ccfaa060 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 18:00:50 +0200 Subject: [PATCH 013/419] =?UTF-8?q?=F0=9F=9A=A7=20work=20in=20progress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/UnitaryExprDesign.md | 909 +++++++++++++++++++++++++++++++++ 1 file changed, 909 insertions(+) create mode 100644 docs/mlir/UnitaryExprDesign.md diff --git a/docs/mlir/UnitaryExprDesign.md b/docs/mlir/UnitaryExprDesign.md new file mode 100644 index 0000000000..3909456cc2 --- /dev/null +++ b/docs/mlir/UnitaryExprDesign.md @@ -0,0 +1,909 @@ +# Unitary Expression Support Library + +**Purpose:** Provide efficient unitary matrix computation and symbolic reasoning for transformation and optimization passes in the MQT MLIR compilation infrastructure. + +## Overview + +The `UnitaryExpr` support library is a lightweight C++20 framework that enables transformation passes to efficiently reason about, compose, and manipulate quantum gate unitaries. +It serves as the computational backbone for operations like static matrix extraction, gate fusion, equivalence checking, and optimization within the MQT MLIR dialects. + +This library integrates seamlessly with the unified `UnitaryOpInterface` defined in the quantum dialect architecture, providing the underlying computational machinery for extracting and manipulating unitary matrices from quantum operations. + +## Design Philosophy + +The library balances performance, expressiveness, and integration with MLIR infrastructure through the following principles: + +### Core Principles + +- **Performance-Critical:** Optimized for small matrices common in quantum computing (2×2 single-qubit, 4×4 two-qubit gates) +- **MLIR-Native:** Leverages MLIR/LLVM support types (`mlir::ArrayRef`, `mlir::SmallVector`, `llvm::APFloat`) over standard library equivalents where appropriate +- **Allocation-Aware:** Stack-based storage for fixed-size matrices; minimal heap allocations for dynamic cases +- **Inline-Friendly:** Header-only implementations for critical paths to enable aggressive compiler optimization +- **Type-Safe:** Exploits C++20 concepts and the type system to prevent dimension mismatches at compile time +- **Symbolic When Needed:** Supports both concrete materialized matrices and symbolic expression trees for deferred evaluation +- **Zero External Dependencies:** Relies exclusively on LLVM/MLIR infrastructure—no Eigen, BLAS, or quantum simulation libraries + +### Design Goals + +The library addresses three key use cases in quantum compilation: + +1. **Static Analysis:** Enable passes to query matrix properties (hermiticity, diagonality, unitarity) without full materialization +2. **Gate Fusion:** Efficiently compose sequences of gates to identify fusion opportunities +3. **Equivalence Checking:** Compare unitary operations for semantic equivalence during optimization + +## Qubit Ordering and Endianness + +A fundamental challenge in multi-qubit gate composition is correctly handling qubit ordering and endianness conventions. The matrix representation of a quantum gate depends on which physical qubits it operates on and in what order they appear in the tensor product basis. + +### The Problem + +Consider a controlled-X gate applied to qubits `q0` and `q1`: + +```mlir +quartz.ctrl(%q0) { quartz.x %q1 } // q0 controls, q1 is target +``` + +versus + +```mlir +quartz.ctrl(%q1) { quartz.x %q0 } // q1 controls, q0 is target +``` + +Even though both operations have the same SSA values, their matrix representations differ because the computational basis ordering changes: + +- First case: basis ordered as `|q0⟩ ⊗ |q1⟩` → states `|00⟩, |01⟩, |10⟩, |11⟩` +- Second case: basis ordered as `|q1⟩ ⊗ |q0⟩` → states `|00⟩, |01⟩, |10⟩, |11⟩` (but q1 is now the high-order bit) + +This becomes especially critical when composing sequences of multi-qubit gates where qubits appear in different positions. + +### Endianness Convention + +The library adopts **big-endian** qubit ordering convention, which aligns with standard quantum computing literature and notation: + +- **Qubit position 0** (first in the list) corresponds to the **most significant bit** in the basis state +- **Qubit position n-1** (last in the list) corresponds to the **least significant bit** in the basis state +- For n qubits at positions [q₀, q₁, ..., qₙ₋₁], the basis state index is: 2^(n-1)·q₀ + 2^(n-2)·q₁ + ... + 2^0·qₙ₋₁ +- Basis states are enumerated in standard binary order: `|00⟩, |01⟩, |10⟩, |11⟩` for two qubits + +**Example:** For `ctrl(%q0) { x %q1 }` where the qubit order is `[q0, q1]`: + +- `q0` at position 0 (MSB) → controls on bit 1 (high-order bit) +- `q1` at position 1 (LSB) → target is bit 0 (low-order bit) +- Tensor product: `|q0⟩ ⊗ |q1⟩` +- Matrix acts as: `|00⟩→|00⟩, |01⟩→|01⟩, |10⟩→|11⟩, |11⟩→|10⟩` (standard CNOT with q0 control, q1 target) + +**Rationale:** Big-endian ordering is the standard convention in quantum computing papers and textbooks, where multi-qubit states are written left-to-right with the first qubit as the leftmost tensor factor: `|ψ⟩ = |q₀⟩ ⊗ |q₁⟩ ⊗ ... ⊗ |qₙ₋₁⟩`. This convention ensures that the library's matrix representations match published results and established quantum algorithms. + +### Design Principles + +1. **Explicit Qubit Ordering:** All multi-qubit operations must explicitly track which qubits they operate on and in what order +2. **Permutation Tracking:** When composing gates with different qubit orderings, explicit permutation matrices handle reordering +3. **Canonical Forms:** Operations on the same qubits in the same order can be directly composed; otherwise, reordering is required +4. **Efficient Common Case:** Same-order composition (the common case) has zero overhead; reordering only when necessary + +### Qubit Order Representation + +```cpp +namespace mqt::unitary { + +/// Represents an ordered list of qubits for a multi-qubit operation +/// Uses MLIR Value to represent SSA values (for Flux) or qubit references (for Quartz) +using QubitOrder = mlir::SmallVector; + +/// Check if two qubit orders are identical (same qubits in same positions) +[[nodiscard]] inline bool isSameOrder(const QubitOrder& a, const QubitOrder& b) { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin()); +} + +/// Check if two qubit orders operate on the same qubits (possibly different order) +[[nodiscard]] bool isSameQubits(const QubitOrder& a, const QubitOrder& b); + +/// Compute permutation mapping from order 'from' to order 'to' +/// Returns std::nullopt if they don't contain the same qubits +[[nodiscard]] std::optional> + computePermutation(const QubitOrder& from, const QubitOrder& to); + +} // namespace mqt::unitary +``` + +## Core Data Structures + +### Fixed-Size Matrix Types + +The library provides specialized matrix types for the most common quantum operations, leveraging stack allocation and cache-friendly memory layouts. + +#### Mat2: Single-Qubit Matrices + +The `Mat2` class represents 2×2 complex matrices for single-qubit gates: + +```cpp +namespace mqt::unitary { + +/// 2×2 complex matrix for single-qubit gates (stack-allocated, 64 bytes) +class Mat2 { +public: + using Complex = std::complex; + + /// Stack storage: 4 complex values = 8 doubles = 64 bytes + /// Fits in a single CPU cache line + std::array data; + + /// Constructors + constexpr Mat2() = default; + constexpr Mat2(Complex a00, Complex a01, Complex a10, Complex a11); + + /// Static factory methods for common gates + static constexpr Mat2 identity() noexcept; + static constexpr Mat2 pauliX() noexcept; + static constexpr Mat2 pauliY() noexcept; + static constexpr Mat2 pauliZ() noexcept; + static constexpr Mat2 hadamard() noexcept; + static constexpr Mat2 phaseS() noexcept; + static constexpr Mat2 phaseT() noexcept; + + /// Element access (row-major ordering) + constexpr Complex& operator()(size_t row, size_t col) noexcept; + constexpr const Complex& operator()(size_t row, size_t col) const noexcept; + + /// Matrix operations + [[nodiscard]] Mat2 operator*(const Mat2& other) const noexcept; + [[nodiscard]] Mat2 adjoint() const noexcept; + [[nodiscard]] Mat2 power(double exponent) const; + + /// Numerical queries + [[nodiscard]] bool isApproxEqual(const Mat2& other, + double tolerance = 1e-14) const noexcept; + [[nodiscard]] bool isUnitary(double tolerance = 1e-14) const noexcept; + [[nodiscard]] bool isHermitian(double tolerance = 1e-14) const noexcept; + [[nodiscard]] bool isDiagonal(double tolerance = 1e-14) const noexcept; +}; + +} // namespace mqt::unitary +``` + +**Design Rationale:** + +- **Cache-Friendly:** 64-byte alignment fits exactly in one cache line on most architectures +- **constexpr Support:** Common gates can be computed at compile time, eliminating runtime overhead +- **Row-Major Storage:** Matches typical access patterns in matrix operations +- **Numerical Tolerance:** Machine precision (≈10⁻¹⁴) used for floating-point comparisons +- **Qubit-Order Independent:** Single-qubit gates have no ordering ambiguity + +#### Mat4: Two-Qubit Matrices + +The `Mat4` class represents 4×4 complex matrices for two-qubit gates: + +```cpp +namespace mqt::unitary { + +/// 4×4 complex matrix for two-qubit gates (stack-allocated, 256 bytes) +/// +/// Qubit Ordering Convention: +/// - Qubits are ordered as [q0, q1] where q0 is the LSB (position 0) +/// - Basis states: |00⟩, |01⟩, |10⟩, |11⟩ (binary: 0, 1, 2, 3) +/// - Matrix element M[i][j] represents amplitude from basis state j to state i +class Mat4 { +public: + using Complex = std::complex; + + /// Stack storage: 16 complex values = 32 doubles = 256 bytes + /// Fits in four cache lines + std::array data; + + /// Qubit order for this matrix (which qubits, in what positions) + /// Empty for anonymous matrices; populated for gate-derived matrices + QubitOrder qubits; + + /// Constructors + constexpr Mat4() = default; + explicit constexpr Mat4(std::array values); + explicit Mat4(std::array values, QubitOrder order); + + /// Static factory methods for common gates + /// These assume canonical two-qubit ordering [q0, q1] + static constexpr Mat4 identity() noexcept; + static constexpr Mat4 cnot() noexcept; // Control on q0, target on q1 + static constexpr Mat4 cz() noexcept; // Control on q0, target on q1 + static constexpr Mat4 swap() noexcept; // Symmetric: swaps q0 and q1 + + /// Element access (row-major ordering) + constexpr Complex& operator()(size_t row, size_t col) noexcept; + constexpr const Complex& operator()(size_t row, size_t col) const noexcept; + + /// Matrix operations + [[nodiscard]] Mat4 operator*(const Mat4& other) const; + [[nodiscard]] Mat4 adjoint() const noexcept; + [[nodiscard]] Mat4 power(double exponent) const; + + /// Construction from smaller matrices + [[nodiscard]] static Mat4 tensorProduct(const Mat2& left, const Mat2& right); + [[nodiscard]] static Mat4 controlled(const Mat2& target, + bool positiveControl = true); + + /// Qubit reordering + /// Permute the qubit order of this matrix to match a target ordering + /// Example: if this matrix is for [q0, q1] and target is [q1, q0], + /// returns a new matrix with basis reordered accordingly + [[nodiscard]] Mat4 permuteQubits(const QubitOrder& targetOrder) const; + + /// Apply a permutation directly (permutation[i] = position of target qubit i in source) + [[nodiscard]] Mat4 applyPermutation(mlir::ArrayRef permutation) const; + + /// Numerical queries + [[nodiscard]] bool isApproxEqual(const Mat4& other, + double tolerance = 1e-14) const noexcept; + [[nodiscard]] bool isUnitary(double tolerance = 1e-14) const noexcept; + [[nodiscard]] bool isHermitian(double tolerance = 1e-14) const noexcept; + [[nodiscard]] bool isDiagonal(double tolerance = 1e-14) const noexcept; +}; + +} // namespace mqt::unitary +``` + +**Design Rationale:** + +- **Controlled Gate Construction:** Efficient expansion from single-qubit gates to controlled two-qubit gates +- **Tensor Product Support:** Enables construction of product states and parallel gate application +- **Consistent Interface:** Mirrors `Mat2` API for uniform usage patterns +- **Explicit Qubit Tracking:** `qubits` field maintains ordering information for composition correctness +- **Permutation Support:** Efficient basis reordering when composing gates with different qubit orders + +#### Permutation Algorithm + +The basis permutation algorithm reorders matrix elements according to the qubit permutation: + +```cpp +namespace mqt::unitary { + +/// Apply a qubit permutation to a 2^n × 2^n matrix +/// permutation[i] specifies the source position of target qubit i +/// +/// Algorithm: For each basis state |b⟩: +/// 1. Interpret b as n-bit number with qubits in target order +/// 2. Remap bits according to permutation to get source state |b'⟩ +/// 3. Copy M[b'][c'] → M_out[b][c] for all b, c +/// +/// Time complexity: O(4^n) for n-qubit gates +/// Space complexity: O(4^n) for output matrix (input unchanged) +template +[[nodiscard]] std::array, N * N> + permuteMatrixBasis(const std::array, N * N>& matrix, + mlir::ArrayRef permutation, + size_t numQubits); + +} // namespace mqt::unitary +``` + +**Example:** Consider `CNOT[q0, q1]` (control q0, target q1) and we want `CNOT[q1, q0]`: + +- Permutation: `[1, 0]` (target position 0 gets source position 1; target position 1 gets source position 0) +- Basis mapping: `|00⟩→|00⟩, |01⟩→|10⟩, |10⟩→|01⟩, |11⟩→|11⟩` +- Result: CNOT with swapped control/target roles + +### Symbolic Expression Framework + +For gates with dynamic parameters, complex compositions, or deferred evaluation, the library provides a symbolic expression tree system. + +#### Base Expression Interface + +```cpp +namespace mqt::unitary { + +/// Base class for symbolic unitary expressions +class UnitaryExpr { +public: + virtual ~UnitaryExpr() = default; + + /// Query expression structure + [[nodiscard]] virtual size_t getNumQubits() const = 0; + [[nodiscard]] virtual bool canMaterialize() const = 0; + [[nodiscard]] virtual bool isFullyStatic() const = 0; + + /// Get the qubits this expression operates on (in order) + [[nodiscard]] virtual QubitOrder getQubitOrder() const = 0; + + /// Materialization (returns std::nullopt if not possible) + [[nodiscard]] virtual std::optional materializeMat2() const; + [[nodiscard]] virtual std::optional materializeMat4() const; + [[nodiscard]] virtual mlir::DenseElementsAttr + materializeDense(mlir::MLIRContext* ctx) const; + + /// Symbolic property queries + [[nodiscard]] virtual bool isHermitian() const = 0; + [[nodiscard]] virtual bool isDiagonal() const = 0; + + /// Smart pointer for ownership + using Ptr = std::unique_ptr; +}; + +} // namespace mqt::unitary +``` + +**Design Philosophy:** + +- **Lazy Evaluation:** Build expression trees without immediate computation +- **Partial Information:** Query properties without full materialization +- **Optimization Opportunities:** Simplify expressions symbolically before materializing + +#### Expression Node Types + +##### ConstExpr: Concrete Matrices + +```cpp +/// Fully materialized matrix expression +class ConstExpr : public UnitaryExpr { + std::variant matrix; + QubitOrder qubits; + +public: + explicit ConstExpr(Mat2 m); + explicit ConstExpr(Mat4 m); + explicit ConstExpr(Mat2 m, QubitOrder order); + explicit ConstExpr(Mat4 m, QubitOrder order); + + [[nodiscard]] size_t getNumQubits() const override; + [[nodiscard]] QubitOrder getQubitOrder() const override { return qubits; } + [[nodiscard]] bool canMaterialize() const override { return true; } + [[nodiscard]] bool isFullyStatic() const override { return true; } + + [[nodiscard]] std::optional materializeMat2() const override; + [[nodiscard]] std::optional materializeMat4() const override; + + [[nodiscard]] bool isHermitian() const override; + [[nodiscard]] bool isDiagonal() const override; +}; +``` + +##### MulExpr: Matrix Composition with Qubit Reordering + +```cpp +/// Matrix multiplication (composition of unitaries) +/// Handles automatic qubit reordering when composing gates with different orderings +class MulExpr : public UnitaryExpr { + UnitaryExpr::Ptr left; + UnitaryExpr::Ptr right; + QubitOrder resultOrder; // Cached result qubit order + +public: + MulExpr(UnitaryExpr::Ptr l, UnitaryExpr::Ptr r); + + [[nodiscard]] size_t getNumQubits() const override; + [[nodiscard]] QubitOrder getQubitOrder() const override { return resultOrder; } + [[nodiscard]] bool canMaterialize() const override; + [[nodiscard]] bool isFullyStatic() const override; + + [[nodiscard]] std::optional materializeMat2() const override; + [[nodiscard]] std::optional materializeMat4() const override; + + [[nodiscard]] bool isHermitian() const override; + [[nodiscard]] bool isDiagonal() const override; + +private: + /// Compute the result qubit order from left and right expressions + /// Uses left's ordering as the canonical target + static QubitOrder computeResultOrder(const UnitaryExpr& left, + const UnitaryExpr& right); +}; +``` + +**Materialization Strategy with Reordering:** + +When materializing a `MulExpr`: + +1. Check if both children have the same qubit order → direct composition (fast path) +2. If orders differ, align them: + - Use `left`'s qubit order as canonical + - Permute `right`'s matrix to match `left`'s order + - Compose the aligned matrices +3. Result inherits `left`'s qubit order + +**Example:** + +```cpp +// Left: CNOT with qubits [q0, q1] +// Right: CZ with qubits [q1, q0] (opposite order) +// +// Composition process: +// 1. Identify qubit order mismatch +// 2. Permute right's matrix: CZ[q1,q0] → CZ[q0,q1] +// 3. Compose: CNOT[q0,q1] * CZ[q0,q1] +// 4. Result has order [q0, q1] +``` + +##### AdjExpr: Adjoint (Inverse) + +```cpp +/// Adjoint (conjugate transpose) of a unitary +class AdjExpr : public UnitaryExpr { + UnitaryExpr::Ptr child; + +public: + explicit AdjExpr(UnitaryExpr::Ptr c); + + [[nodiscard]] size_t getNumQubits() const override; + [[nodiscard]] bool canMaterialize() const override; + [[nodiscard]] bool isFullyStatic() const override; + + [[nodiscard]] std::optional materializeMat2() const override; + [[nodiscard]] std::optional materializeMat4() const override; + + [[nodiscard]] bool isHermitian() const override; + [[nodiscard]] bool isDiagonal() const override; +}; +``` + +**Property Propagation:** If child is Hermitian, adjoint equals child; diagonal property preserved. + +##### PowExpr: Matrix Exponentiation + +```cpp +/// Power of a unitary (U^exponent) +class PowExpr : public UnitaryExpr { + UnitaryExpr::Ptr child; + double exponent; + +public: + PowExpr(UnitaryExpr::Ptr c, double exp); + + [[nodiscard]] size_t getNumQubits() const override; + [[nodiscard]] bool canMaterialize() const override; + [[nodiscard]] bool isFullyStatic() const override; + + [[nodiscard]] std::optional materializeMat2() const override; + [[nodiscard]] std::optional materializeMat4() const override; + + [[nodiscard]] bool isHermitian() const override; + [[nodiscard]] bool isDiagonal() const override; +}; +``` + +**Exponentiation Strategy:** + +- **Integer powers:** Repeated multiplication +- **Fractional powers:** Eigendecomposition and diagonal exponentiation +- **Special cases:** Identity for exponent 0, adjoint for exponent -1 + +##### CtrlExpr: Control Expansion + +```cpp +/// Controlled unitary expansion +class CtrlExpr : public UnitaryExpr { + UnitaryExpr::Ptr target; + mlir::SmallVector posControls; + mlir::SmallVector negControls; + QubitOrder resultOrder; // Control qubits followed by target qubits + +public: + CtrlExpr(UnitaryExpr::Ptr t, + mlir::ArrayRef posCtrl, + mlir::ArrayRef negCtrl); + + [[nodiscard]] size_t getNumQubits() const override; + [[nodiscard]] QubitOrder getQubitOrder() const override { return resultOrder; } + [[nodiscard]] bool canMaterialize() const override; + [[nodiscard]] bool isFullyStatic() const override; + + [[nodiscard]] mlir::DenseElementsAttr + materializeDense(mlir::MLIRContext* ctx) const override; + + [[nodiscard]] bool isHermitian() const override; + [[nodiscard]] bool isDiagonal() const override; + +private: + /// Compute canonical qubit ordering: controls first, then targets + static QubitOrder computeResultOrder(mlir::ArrayRef posCtrl, + mlir::ArrayRef negCtrl, + const UnitaryExpr& target); +}; +``` + +**Qubit Ordering for Controlled Gates:** + +- Control qubits appear first in the qubit order (positive controls, then negative controls) +- Target qubits follow controls +- Example: `ctrl(%q2, %q3) { gate %q0, %q1 }` → order is `[q2, q3, q0, q1]` + +##### ParamExpr: Parametrized Gates + +```cpp +/// Parametrized expression with symbolic parameters +class ParamExpr : public UnitaryExpr { + mlir::SmallVector paramIndices; + std::function)> generator; + +public: + ParamExpr(mlir::ArrayRef indices, + std::function)> gen); + + [[nodiscard]] size_t getNumQubits() const override { return 1; } + [[nodiscard]] bool canMaterialize() const override { return false; } + [[nodiscard]] bool isFullyStatic() const override { return false; } + + /// Partial evaluation: given parameter values, materialize + [[nodiscard]] Mat2 evaluate(mlir::ArrayRef paramValues) const; + + [[nodiscard]] bool isHermitian() const override; + [[nodiscard]] bool isDiagonal() const override; +}; +``` + +**Design Notes:** + +- **Multiple Parameters:** Supports gates with arbitrary parameter counts (e.g., `u(θ, φ, λ)` has three parameters) +- **Parameter Tracking:** Maintains indices for parameter identification across expressions +- **Generator Function:** Callable that produces the matrix given parameter values + +## Core Operations and Algorithms + +### Composition + +Matrix composition represents sequential application of quantum gates (right-to-left convention): + +```cpp +namespace mqt::unitary { + +/// Compose two unitaries (matrix multiplication: b then a) +/// Automatically handles qubit reordering if necessary +[[nodiscard]] UnitaryExpr::Ptr compose(UnitaryExpr::Ptr a, UnitaryExpr::Ptr b); + +/// Fast path for static 2×2 composition (no qubit ordering issues) +[[nodiscard]] Mat2 compose(const Mat2& a, const Mat2& b) noexcept; + +/// Fast path for static 4×4 composition with explicit qubit ordering +/// If qubit orders match, performs direct multiplication +/// If orders differ, permutes b's matrix before multiplication +[[nodiscard]] Mat4 compose(const Mat4& a, const Mat4& b); + +/// Compose sequence of unitaries (applied left-to-right) +/// Maintains consistent qubit ordering throughout composition +[[nodiscard]] UnitaryExpr::Ptr + composeSequence(mlir::ArrayRef unitaries); + +} // namespace mqt::unitary +``` + +**Optimization Strategy:** + +- **Same-order fast path:** When gates operate on the same qubits in the same order, skip permutation logic entirely +- **Reordering path:** When orders differ, apply permutation then compose +- **Single-qubit gates:** No ordering issues; always use fast path +- **Symbolic expressions:** Build `MulExpr` nodes that defer reordering until materialization + +#### Composition Algorithm Details + +```cpp +namespace mqt::unitary { + +/// Detailed composition algorithm for two 4×4 matrices +/// +/// Given: matrices A (qubits [qa0, qa1]) and B (qubits [qb0, qb1]) +/// Goal: Compute A * B correctly accounting for qubit ordering +/// +/// Cases: +/// 1. Same order ([qa0, qa1] == [qb0, qb1]): Direct multiplication +/// 2. Different order: +/// a. Check if qubits overlap: must have same qubit set +/// b. Compute permutation: π such that qb[π[i]] = qa[i] +/// c. Apply permutation to B: B' = permute(B, π) +/// d. Multiply: A * B' +/// e. Result has A's qubit order +/// +[[nodiscard]] Mat4 composeWithReordering(const Mat4& a, const Mat4& b); + +} // namespace mqt::unitary +``` + +**Example Scenario:** + +```cpp +// Gate sequence: H⊗H on [q0,q1], then CNOT on [q1,q0], then CZ on [q0,q1] + +// Step 1: H⊗H with order [q0, q1] +Mat2 h = Mat2::hadamard(); +Mat4 hh = Mat4::tensorProduct(h, h); +hh.qubits = {q0, q1}; + +// Step 2: CNOT with order [q1, q0] (control=q1, target=q0) +Mat4 cnot = Mat4::cnot(); // Assumes [control, target] +cnot.qubits = {q1, q0}; + +// Step 3: Compose HH with CNOT +// - HH has order [q0, q1] +// - CNOT has order [q1, q0] +// - Permutation: [1, 0] (swap qubits) +// - Permute CNOT: CNOT' with order [q0, q1] +// - Result: HH * CNOT' with order [q0, q1] +Mat4 intermediate = compose(hh, cnot); + +// Step 4: CZ with order [q0, q1] +Mat4 cz = Mat4::cz(); +cz.qubits = {q0, q1}; + +// Step 5: Compose with CZ +// - intermediate has order [q0, q1] +// - CZ has order [q0, q1] +// - Same order → direct multiplication (fast path) +Mat4 result = compose(intermediate, cz); +``` + +### Mixed Single-Qubit and Multi-Qubit Composition + +A particularly important case is composing sequences where single-qubit and two-qubit gates are interleaved: + +```cpp +namespace mqt::unitary { + +/// Compose a single-qubit gate into a two-qubit context +/// +/// Given: 2×2 matrix G for single-qubit gate on qubit q +/// Qubit context [q0, q1] for the two-qubit space +/// Result: 4×4 matrix representing G ⊗ I or I ⊗ G depending on which qubit +/// +/// Example: X gate on q1 in context [q0, q1] → I ⊗ X +/// X gate on q0 in context [q0, q1] → X ⊗ I +/// +[[nodiscard]] Mat4 embedSingleQubit(const Mat2& gate, + mlir::Value targetQubit, + const QubitOrder& context); + +/// Compose a sequence with mixed single and two-qubit gates +/// Automatically expands single-qubit gates to the appropriate two-qubit space +[[nodiscard]] UnitaryExpr::Ptr + composeMixedSequence(mlir::ArrayRef gates, + const QubitOrder& targetContext); + +} // namespace mqt::unitary +``` + +**Example:** + +```mlir +// Sequence: CNOT %q0, %q1; H %q0; CZ %q0, %q1 +// Context: [q0, q1] (two-qubit space [q0, q1]) +// +// Step 1: CNOT on [q0, q1] → 4×4 matrix +// Step 2: H on q0 → expand to (H ⊗ I) in context [q0, q1] → 4×4 matrix +// Step 3: CZ on [q0, q1] → 4×4 matrix +// Step 4: Compose all three 4×4 matrices with consistent [q0, q1] ordering +``` + +### Example 6: Symbolic Expression with Reordering + +Build symbolic expression trees that track qubit ordering: + +```cpp +using namespace mqt::unitary; + +// Build expression: CNOT[q0,q1] · (CZ[q1,q0])† + +// CNOT with order [q0, q1] +Mat4 cnot = Mat4::cnot(); +cnot.qubits = {q0, q1}; +auto cnotExpr = std::make_unique(cnot, QubitOrder{q0, q1}); + +// CZ with order [q1, q0] +Mat4 cz = Mat4::cz(); +cz.qubits = {q1, q0}; +auto czExpr = std::make_unique(cz, QubitOrder{q1, q0}); + +// Adjoint of CZ +auto czAdj = std::make_unique(std::move(czExpr)); + +// Compose (handles reordering automatically) +auto composed = std::make_unique(std::move(cnotExpr), + std::move(czAdj)); + +// Query result order (inherits from left operand) +QubitOrder resultOrder = composed->getQubitOrder(); +assert(resultOrder[0] == q0 && resultOrder[1] == q1); + +// Materialize when needed +if (auto result = composed->materializeMat4()) { + // Matrix is correctly computed with [q0, q1] ordering + assert(result->qubits[0] == q0); + assert(result->qubits[1] == q1); +} +``` + +### Example 7: Parametrized Gates + +Handle gates with multiple parameters: + +```cpp +using namespace mqt::unitary; + +// Generator for U gate with three parameters: u(θ, φ, λ) +auto uGenerator = [](mlir::ArrayRef params) -> Mat2 { + assert(params.size() == 3); + double theta = params[0]; + double phi = params[1]; + double lambda = params[2]; + + // Matrix for u(θ, φ, λ) + // [[cos(θ/2), -exp(iλ)sin(θ/2)], + // [exp(iφ)sin(θ/2), exp(i(φ+λ))cos(θ/2)]] + // ... implementation ... +}; + +// Create parametrized expression +mlir::SmallVector paramIndices = {0, 1, 2}; +auto uExpr = std::make_unique(paramIndices, uGenerator); + +// Evaluate with concrete values +mlir::SmallVector values = {M_PI / 2, 0.0, M_PI}; +Mat2 concrete = uExpr->evaluate(values); +``` + +## Testing Strategy + +### Unit Tests + +**Correctness Tests:** + +- Mathematical properties: unitarity (U†U = I), adjoint correctness, power consistency +- Gate identities: Pauli relations, Hadamard properties, rotation periodicity +- Composition associativity: (A·B)·C = A·(B·C) +- Control expansion: verify controlled-unitary structure +- **Qubit ordering:** Verify different qubit orderings produce different matrices +- **Reordering correctness:** Compose A·B with explicit reordering matches permuted composition +- **Mixed sequences:** Single-qubit gates embedded correctly in multi-qubit contexts + +**Precision Tests:** + +- Machine precision validation: ≈10⁻¹⁴ relative error +- Numerical stability: condition number analysis for matrix operations +- Edge cases: identity matrices, diagonal matrices, Hermitian matrices +- **Permutation accuracy:** Basis reordering preserves matrix unitarity and determinant + +### Performance Tests + +**Microbenchmarks:** + +- Individual operation latencies (multiplication, adjoint, power) +- **Same-order vs different-order composition:** Measure fast-path effectiveness +- **Qubit order detection:** Measure comparison overhead +- **Permutation cost:** Measure basis reordering overhead +- Comparison against reference implementations +- Regression detection across compiler versions + +**Allocation Tracking:** + +- Verify zero heap allocations for fixed-size operations +- Profile expression tree memory usage +- Detect allocation regressions + +**Cache Behavior:** + +- Profile L1/L2/L3 cache hit rates +- Measure memory bandwidth utilization +- Optimize for cache line alignment + +### Integration Tests + +**MLIR Interoperability:** + +- Round-trip conversion: Mat2 ↔ DenseElementsAttr +- Attribute preservation across transformations +- Context lifetime management +- **Qubit order preservation:** Verify SSA values maintained through conversions + +**Interface Usage:** + +- `UnitaryOpInterface` implementation correctness +- Modifier chain materialization with qubit tracking +- Pass infrastructure integration +- **Gate fusion:** Verify qubit order compatibility checks in fusion passes + +## Implementation Structure + +### Dependencies + +The library relies exclusively on LLVM/MLIR infrastructure and C++20 standard library: + +- **C++20 Standard Library:** + - ``: Complex number arithmetic + - ``: Fixed-size containers + - ``: Optional return types + - ``: Type-safe unions + - ``: Function objects + - ``: Type constraints + - ``: Standard algorithms (std::equal, std::find) + +- **LLVM ADT:** + - `llvm/ADT/ArrayRef.h`: Non-owning array reference + - `llvm/ADT/SmallVector.h`: Small-size optimized vector + - `llvm/ADT/APFloat.h`: Arbitrary precision floating point + +- **MLIR Infrastructure:** + - `mlir/IR/Attributes.h`: MLIR attribute system + - `mlir/IR/Builders.h`: Attribute construction utilities + - `mlir/IR/BuiltinTypes.h`: Tensor and complex types + - `mlir/IR/MLIRContext.h`: MLIR context management + +### Header Organization + +``` +include/mqt/unitary/ + Mat2.h // 2×2 matrix class + Mat4.h // 4×4 matrix class + QubitOrder.h // Qubit ordering utilities + Permutation.h // Basis permutation algorithms + UnitaryExpr.h // Symbolic expression framework + Operations.h // Composition, adjoint, power, control + Conversion.h // MLIR attribute interop + Numerical.h // Eigendecomposition, exponentiation + Utils.h // Numerical utilities, tolerances +``` + +### Namespace Structure + +```cpp +namespace mqt::unitary { + // Core matrix types + class Mat2; + class Mat4; + + // Qubit ordering + using QubitOrder = mlir::SmallVector; + [[nodiscard]] bool isSameOrder(const QubitOrder&, const QubitOrder&); + [[nodiscard]] bool isSameQubits(const QubitOrder&, const QubitOrder&); + [[nodiscard]] std::optional> + computePermutation(const QubitOrder&, const QubitOrder&); + + // Symbolic expressions + class UnitaryExpr; + class ConstExpr; + class MulExpr; + class AdjExpr; + class PowExpr; + class CtrlExpr; + class ParamExpr; + + // Operations + [[nodiscard]] UnitaryExpr::Ptr compose(UnitaryExpr::Ptr, UnitaryExpr::Ptr); + [[nodiscard]] UnitaryExpr::Ptr adjoint(UnitaryExpr::Ptr); + [[nodiscard]] UnitaryExpr::Ptr power(UnitaryExpr::Ptr, double); + [[nodiscard]] Mat4 controlled(const Mat2&, mlir::Value, mlir::Value, bool); + + // Mixed composition + [[nodiscard]] Mat4 embedSingleQubit(const Mat2&, mlir::Value, const QubitOrder&); + [[nodiscard]] UnitaryExpr::Ptr + composeMixedSequence(mlir::ArrayRef, const QubitOrder&); + + // Materialization + [[nodiscard]] bool canMaterialize(const UnitaryExpr&); + [[nodiscard]] std::optional materializeMat2(const UnitaryExpr&); + [[nodiscard]] std::optional materializeMat4(const UnitaryExpr&); + [[nodiscard]] mlir::DenseElementsAttr materializeAttr(const UnitaryExpr&, + mlir::MLIRContext*); + + // MLIR conversion + [[nodiscard]] mlir::DenseElementsAttr toAttr(const Mat2&, mlir::MLIRContext*); + [[nodiscard]] mlir::DenseElementsAttr toAttr(const Mat4&, mlir::MLIRContext*); + [[nodiscard]] std::optional fromAttr(mlir::DenseElementsAttr); + [[nodiscard]] std::optional fromAttr(mlir::DenseElementsAttr); +} +``` + +## Relationship to Quantum Dialect + +The `UnitaryExpr` library provides the computational substrate for the unified unitary interface defined in the MQT quantum dialects (Quartz and Flux): + +**Integration Points:** + +1. **Matrix Extraction:** Operations implementing `UnitaryOpInterface::tryGetStaticMatrix()` use this library to compute concrete matrices with correct qubit ordering +2. **Symbolic Composition:** Modifier operations (`ctrl`, `inv`, `pow`) build `UnitaryExpr` trees that track qubit ordering through transformations +3. **Equivalence Checking:** Optimization passes use matrix comparison to identify equivalent gate sequences, accounting for qubit order differences +4. **Gate Fusion:** Canonicalization passes compose matrices with automatic reordering to detect fusion opportunities across different qubit orderings + +**Design Separation:** + +The library maintains clean separation from MLIR dialect definitions: + +- **No dialect dependencies:** Can be used standalone for matrix computations +- **Generic interface:** Not tied to specific operation types +- **Reusable infrastructure:** Applicable to future dialects or external tools +- **Qubit-order agnostic core:** Matrix operations work with any ordering convention + +This architectural choice enables the library to serve as a foundational component across the entire MQT MLIR infrastructure while maintaining modularity and testability. The explicit handling of qubit ordering ensures correctness when composing gates from different sources or with different qubit arrangements, which is essential for robust optimization passes. From 6b5d4dabffe34a378c06a89416ffb9f4f418501b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 18:35:18 +0200 Subject: [PATCH 014/419] =?UTF-8?q?=F0=9F=8D=B1=20demo=20logos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/flux-logo.svg | 106 ++++++++++++++++++++++++++++++++ docs/mlir/quartz-logo.svg | 126 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 docs/mlir/flux-logo.svg create mode 100644 docs/mlir/quartz-logo.svg diff --git a/docs/mlir/flux-logo.svg b/docs/mlir/flux-logo.svg new file mode 100644 index 0000000000..4ce4aa3ecb --- /dev/null +++ b/docs/mlir/flux-logo.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FLUX + + + + + diff --git a/docs/mlir/quartz-logo.svg b/docs/mlir/quartz-logo.svg new file mode 100644 index 0000000000..7f10281550 --- /dev/null +++ b/docs/mlir/quartz-logo.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + QUARTZ + + + + + From 06216665c5ad6cd1c3cbcf5ab5f1363eb49f4604 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 18:35:45 +0200 Subject: [PATCH 015/419] =?UTF-8?q?=F0=9F=94=A5=20remove=20temporary=20fil?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/QuantumDialectRevamp-Rationale.md | 197 ---------------- docs/mlir/feedback-prompt.md | 238 -------------------- 2 files changed, 435 deletions(-) delete mode 100644 docs/mlir/QuantumDialectRevamp-Rationale.md delete mode 100644 docs/mlir/feedback-prompt.md diff --git a/docs/mlir/QuantumDialectRevamp-Rationale.md b/docs/mlir/QuantumDialectRevamp-Rationale.md deleted file mode 100644 index 0224759ce5..0000000000 --- a/docs/mlir/QuantumDialectRevamp-Rationale.md +++ /dev/null @@ -1,197 +0,0 @@ -# Quantum Dialect Revamp — Design Rationale and Background - -Author: Junie (auto-generated) -Date: 2025-09-23 -Status: Supplementary design rationale to the implementation plan -Scope: Rationale for the revamped MLIR quantum dialect architecture under mlir/ - -## Abstract - -This document motivates and explains the major design decisions in the planned revamp of the MQTRef and MQTOpt MLIR dialects for hybrid classical–quantum computing. The goals are to improve composability, analysis, and ergonomics by introducing first-class modifier ops (controls, inverse, power), a sequence op, a unified UnitaryOpInterface, and a lightweight UnitaryExpr support library. The rationale emphasizes alignment with idiomatic MLIR, predictable normalization via canonicalization, and performance-conscious design for common 2×2 and 4×4 unitaries used in compiler transformations. - -## 1. Background and Objectives - -Current challenges: - -- Controls and other modifiers are embedded into each unitary gate, complicating analysis and normalization. -- Builder ergonomics are cumbersome; textual IR lacks compact sugar for controlled variants. -- Two dialects (reference semantics vs value semantics) diverge where interfaces should be uniform. - -Primary objectives: - -- Treat composition as a first-class concern (explicit modifier and sequence ops). -- Unify analysis via a single interface (UnitaryOpInterface) with dialect-adaptive in/out views. -- Preserve performance for frequent 2×2/4×4 unitaries without adding heavy dependencies. -- Keep the IR normalized and ergonomic through canonicalization and parser/printer sugar. - -## 2. Why Explicit Modifiers and Sequences? - -### 2.1. Explicit inv/ctrl/negctrl/pow as wrapper ops - -- Composability: Modifiers become uniform constructs that can wrap either single gates or entire sequences. -- Laziness: Evaluation is deferred; transformations can rewrite at the IR level without materializing matrices. -- Normal forms: Canonicalization can enforce a stable modifier order (inverse innermost, controls outermost), ensuring predictable patterns and avoiding rewrite loops. - -Illustration (MLIR): - -```mlir -// Before (implicit modifiers baked into gates) -// mqtref.x %q ctrl(%c) - -// After (explicit wrappers) -mqtref.ctrl(%c) { mqtref.x %q } -``` - -### 2.2. Sequence op - -- Expressive power: A seq region contains only unitary operations (including wrapped sequences). It captures common subcircuits as first-class objects. -- Implicit terminator: Smoother user experience and more idiomatic MLIR text. -- Analysis: The UnitaryExpr for a sequence is the product of child unitaries in program order. - -Example: - -```mlir -mqtref.seq { - mqtref.h %q0 - mqtref.ctrl(%c) { mqtref.x %q0 } -} -``` - -## 3. Interface Unification Across Dialects - -- MQTRef (reference semantics) and MQTOpt (value semantics) share the same high-level queries via UnitaryOpInterface. -- Dialect-adaptive accessors: - - getTargetOperands(), getPosControlOperands(), getNegControlOperands() — always available. - - getTargetResults(), getPosControlResults(), getNegControlResults() — meaningful for MQTOpt; empty for MQTRef. - - Aggregates: getAllOperandQubits() and getAllResultQubits() (the latter empty in MQTRef). -- Consistency: Clients (passes, analyses) no longer branch on the concrete dialect; they check hasTargetResults(). - -## 4. Linear Controls in Value Semantics - -- In MQTOpt, all qubits—including controls—are value-semantics and should be treated linearly. -- Modifiers and sequences consume and produce fresh SSA values for controls even if the control action is semantically identity. -- Benefits: Enforces single-use constraints, simplifies dataflow reasoning, and aligns with classical SSA-style optimizations. - -Example: - -```mlir -// Controls are threaded linearly in mqtopt -%q1, %c1 = mqtopt.ctrl(%c0) { mqtopt.x %q0 } -``` - -## 5. Hermitian and Diagonal Traits - -- Hermitian (self-inverse) gates: I, X, Y, Z, H (and possibly others after validation). Canonicalization shortcut: inv(G) -> G. -- Diagonal gates (in computational basis): I, Z, S, Sdg, T, Tdg, Rz, P/Phase, RZZ. These enable commuting analyses and targeted lowers. -- Both are marker traits; they carry no runtime cost and inform canonicalization and analysis. - -## 6. Canonicalization Strategy and Modifier Ordering - -- Ordering: inverse (innermost) — power — controls (outermost). Rewriters maintain this normal form. -- Selected canonicalization: - - inv(inv(X)) -> X - - inv(pow(X, k)) -> pow(inv(X), k) - - pow(X, -k) -> pow(inv(X), k) - - inv(ctrl(X)) -> ctrl(inv(X)) - - inv(baseGate) -> baseGateInverse (parameters adjusted) - - ctrl/negctrl with empty lists are removed; adjacent modifiers of the same kind are merged (no dedup or reorder of controls). - -Examples: - -```mlir -// inv(pow(X, k)) -> pow(inv(X), k) -mqtref.inv mqtref.pow(3) { mqtref.x %q } -// -> mqtref.pow(3) { mqtref.inv mqtref.x %q } - -// pow(X, -k) -> pow(inv(X), k) -mqtref.pow(-2) { mqtref.h %q } -// -> mqtref.pow(2) { mqtref.inv mqtref.h %q } -``` - -## 7. Parameters: Prefer Static, Fold Constants - -- Base-gate parameters should be static attributes whenever possible. -- If a dynamic operand is defined by a constant, fold it into static_params and adjust params_mask (or drop mask if all static). -- Benefits: Enables constant UnitaryExpr materialization and more effective downstream rewrites. - -Examples: - -```mlir -// Before: dynamic parameter -%pi2 = arith.constant 1.5707963267948966 : f64 -mqtref.rx(%q) (%pi2) - -// After: static attribute preferred -mqtref.rx(%q) (1.5707963267948966) -``` - -## 8. UnitaryExpr: Lightweight and Performant - -- Tiny fixed-size complex matrices (2×2, 4×4) plus a compact expression graph (Mul, Adj, Pow, Control, Const, Param, Trig, Exp, Embed). -- No external dependencies; leverages LLVM/MLIR support and std::complex. -- Materialization to DenseElementsAttr for any arity (no artificial limit), with fast paths for 2×2/4×4. -- Intended usage: transformation and conversion passes; not a runtime simulator. - -Example (C++ sketch): - -```cpp -UnitaryExpr u = UnitaryExpr::Const(Mat2::X()); -UnitaryExpr v = UnitaryExpr::Pow(u, 3); -UnitaryExpr w = UnitaryExpr::Adj(v); -auto maybeDense = w.materializeIfStatic(ctx); // DenseElementsAttr when all params static -``` - -## 9. Sequence Unitary and Footprint - -- The UnitaryExpr of a seq is the product of child UnitaryExpr in program order (M_n … M_2 M_1). -- Sequences expose their overall qubit footprint only via getAllOperandQubits()/getAllResultQubits(). The notions of "controls vs targets" are not meaningful for seq as a whole. - -Example: - -```mlir -// The sequence's unitary is the product of the inner unitaries in program order -mqtref.seq { - mqtref.h %q - mqtref.rz(%q) (0.5) -} -``` - -## 10. Ergonomics: Builders and Sugar - -- Generic builder template that dispatches using trait arities; layered with generated overloads per base gate. -- Parser/printer sugar provides concise forms like cx/cz/ccx/ccz and mcx/mcz/mcp(theta). - -Examples: - -```mlir -// Sugar for controlled gates -mqtref.ccx %c0, %c1 %t -mqtref.mcp(1.234)(%c0, %c1) %t - -// Nested modifiers, implicit terminators -mqtref.ctrl(%c) { mqtref.inv mqtref.rx(%q) (3.14159) } -``` - -## 11. Alternatives Considered - -- Single shared base-gate dialect vs per-dialect mirrored base gates: A shared dialect reduces op duplication but clashes with semantics (mqtref operands-only vs mqtopt operands+results), forcing optional results or wrappers and complicating verifiers/canonicalizations; modifiers/seq still require dialect-specific threading. Decision: keep per-dialect base gates; minimize duplication via shared traits/interfaces, TableGen catalogs, and generated builders; use symbol-based unitary.def/apply for custom gates. -- Embedding modifiers into every base gate: rejected due to poor composability and normalization challenges. -- Heavy linear-algebra backends for unitary composition: rejected to avoid dependencies and preserve compile-time performance. -- Treating controls in mqtopt as pass-through results: rejected; linear threading yields better dataflow properties and SSA adherence. - -## 12. Scope Boundaries - -- QubitRegister handling is out of scope; a separate PR will replace it with memref. All new design elements operate solely on Qubit. -- No temporary helper conversion layers; all affected code will be updated directly. - -## 13. Expected Impact - -- More predictable, analyzable IR with explicit composition mechanisms. -- Better usability for both textual IR authors and C++ clients through sugar and builders. -- Robust foundations for optimization passes (normalization, propagation, constant folding) and dialect conversions. - -## 14. References and Further Reading - -- MLIR Interfaces and Traits (mlir.llvm.org) -- QIRO: A Static Single Assignment based Quantum Program Representation for Optimization (doi:10.1145/3491247) -- Project plan: docs/mlir/quantum-dialect-revamp-plan.md diff --git a/docs/mlir/feedback-prompt.md b/docs/mlir/feedback-prompt.md deleted file mode 100644 index 60f095f8c0..0000000000 --- a/docs/mlir/feedback-prompt.md +++ /dev/null @@ -1,238 +0,0 @@ -Design an LLM prompt for revising the RFC dialect proposal based on the following feedback: - -## 1.1 Current State - -- the existing dialects only implement the control (pos+neg) modifier, but no power or inverse modifier. That's the main drawback of the existing solution with regard to modifiers, not that they modifiers are encoded inconsistently -- The interfaces aren't really fragmented, they are just not expressive enough yet and do not cover all the use cases we would want to cover -- There is also no way currently to define custom gates, to get the unitary of a gate, or to compose multiple gates - -## 1.2 Proposed direction - -Aims to address all the drawbacks from the previous section - -## 2.1 Dialect structure - -Measurements and reset are not resource operations. They belong in their own category. -Resource operations should be the allocation (`alloc`) and deallocation (`dealloc`) of qubits as well as the definition of static qubits (`qubit`). - -UnitaryExpr and UnitaryMatrix are actually the same concept. The proposal should be entirely phrased in terms of UnitaryExpr. - -## 2.2 Principles - -This section should be fleshed out a little more based on the remaining proposal - -## 3.1 Quantum Types - -all types should start with a capital letter (e.g., mqtref.Qubit) to not conflict with the static qubit allocation - -## 3.2 Register Handling - -should be fleshed out a little bit more based on the existing (working) implementation - -## 7. Unified Interface Design - -This section should come as section 4 before describing all various unitary operations and to clearly set the stage for what to expect from every following section on the unitary operations. Each following section should indicate how it expects to implement the various interface functions. - -Combine the existing 7.1 and 7.2 into one single more elaborative proposal that includes more interface functions. -Especially functions for - -- getting the i-th input operand, getting the i-th output operand, getting the i-th parameter (somehow handling the fact that the parameter might be defined as an attribute or a dynamic value). getting the output value corresponding to an input value and vice versa, get all parameters (combining static and dynamic parameters in the right order - -## 4. Base Gate Operations - -the `cx` gate is not a base gate, but only syntactic sugar that is explained later. Do not use it as an example of a two-qubit gate in this section, but rather use the parametrized rzz(theta) gate. - -## 4.1 Philosophy - -This should be fleshed out more. The base gates should heavily use the traits for target and parameter arity to define clean operations with efficient builders and custom assembly formats. These should be defined with as little code duplication as possible. -Each base gate defines an explicit UnitaryExpr representing its matrix. Gates without parameters always have a statically defined matrix. Those gates with parameters have a statically defined matrix representation if and only if all parameters are statically specified. Otherwise, the matrix representation depends on dynamic `mlir::Value`s - -Parameters and qubit parameters should be distinguished in the text-based format similar to the existing implementation, where parameters are depicted in `(...)` directly after the gate name, with qubit arguments being listed as "regular" SSA values afterwards, e.g., `%q0_out = mqtopt.rx(%theta) %q0_in`. This should work with attributes and dynamic values. - -## 5. Modifier Operations - -Swap `negctrl` and `ctrl` in the canonical nesting order. - -All subsections should have custom assembly format that enables more compact and MLIR idiomatic text-based IR descriptions. All subsections should also include examples in the reference and the value semantics dialects. These examples should be consistent and correct. - -All modifiers should define canonicalization patterns. - -## 5.1 Controls (`ctrl`) & Negative Controls (`negctrl`) - -The unitary representation of these is the accordingly extended unitary of the underlying unitary operation. -It reports controls of the modifier plus any controls of the contained operation. -Parameters are just passed through. -Operands of the target operation are just passed through, control qubits are explicitly handled. -directly nested control modifiers are merged through canonicalization or folds -Control applied to a `seq` equals to a control applied to every gate of the sequence. -There may be an optional transformation pass that checks consecutive gates for shared controls and may extract these into a shared control modifier on a `seq`. - -## 5.2 Power (`pow`) - -Similar to the base gate parameters, `pow` modifiers should list the parameter in round brackets `(...)`. - -The unitary of the pow modifier may be explicitly computed. Integer powers are just repetitions. Floating point values are computed based on the principal logarithm. Negative powers are canonicalized to an inverse modifier and a power modifier with a positive exponent. consecutive power modifiers are merged by multiplying the factors. -Specializations of powers of base gates with known analytical forms are to be implemented (e.g., rotation gates) - -## 5.3 Inverse (`inv`) - -The unitary of base gates may be explicitly computed (conjugate adjoint). This may be specialized for when the inverse is another explicit base gate as part of canonicalization. All qubit values are simply passed through. -The inverse of a sequence is the reversed sequence with every gate inverted. Consecutive `inv` cancel as part of the canonicalization pipeline - -## 5.4 Nested Modifier Example (Canonical Order) - -The example needs to be updated. It is also not yet correct as the blocks do not have the right block arguments (the nested sequence applies two controls to a single-qubit gate, so the outside modifier needs to have three qubit arguments. - -## 5.5 - -Remove this subsection - -## 6. Sequence Operations (`seq`) - -Similar to Section 5, these operations should also have a custom assembly format that is as compact as possible and as idiomatic for MLIR as possible. - -## 8. Matrix & User-Defined Gates - -Should be written as if this was naturally part of the proposal and not being discussed on. -The matrix defined gates should also be proper definitions of gate symbols similar to section 8.2, i.e., they should contain a symbol name and it should be possible to apply them (similar to 8.2). -A unified interface for this would be appropriate so that the application operation can be reused between unitary and gate based definitions. -All of the operations should have convenient assembly formats. -These gates report no controls, expose their parameters, and their unitary is either directly available (unitary definition) or can be computed as a product of the unitaries in the definition (U_M,...U_1). - -## 8.1 Matrix Unitary Operation - -Tensors should be 2d shaped to reflect that these are matrices. these operations should also have a custom assembly format that is more compact. -Matrix definitions may also be parametrized by dynamic values. - -## 9.1 Parser Sugar Examples - -Provide more details on how these canonical expansions are actually realized in the MLIR framework. - -## 9.2 9.2 C++ Builder (Sketch) - -This builder is incredibly important for the test related changes. Ensure this is as easy to use as possible. Enrich the available interface by further methods based on the previous sections. This should not just be a sketch. - -## 10 Canonicalization & Transformation Stages - -Remove this section for now. Any canonicalization patterns should be directly described in the respective sections. They should not be dedicated passes, but integrated into the dedicated canonicalization pipeline in MLIR. -Any optional transformation passes should also be mentioned in the individual sections. - -## 11. Pass Inventory - -Similar to section 10, remove this section - -## 12. Dialect Conversions - -Remove this dedicated section but add the respective information about converting from one representation to the other to the individual sections on the operations. The individual sections should be almost self contained and contain detailed information on all relevant aspects. - -## 13. Testing Strategy - -Rephrase and iterate on this section based on feedback above - -## 14. Analysis & Optimization Infrastructure - -include the UnitaryMatrix subsection in a revised form based on feedback into a previous section that is suitable. - -## 15. Canonical Identities & Algebraic Rules (Non-Exhaustive) - -These should be part of the canonicalization subsections in previous sections and can be removed here after folding them into the previous sections. - -## 16. Conclusion - -Revamp the conclusions based on the updated proposal - ---- - -Generated prompt: -You are an expert MLIR and quantum compiler technical writer. Rewrite the RFC for the quantum dialect revamp incorporating the structured feedback below. Produce a self‑contained, updated proposal (single document) with precise MLIR operation specifications, canonicalization rules inline, and consistent terminology. - -Overall goals: -\- Clarify current limitations (only control modifiers exist; missing pow/inv; insufficient interface expressiveness; no custom gate definitions, composition, or unitary extraction). -\- Introduce a coherent UnitaryExpr abstraction used everywhere (replace any split between UnitaryMatrix and UnitaryExpr). -\- Reorder and restructure sections as specified. -\- Integrate canonicalization rules directly inside each operation/section (no separate pass inventory section). -\- Provide clear interface contracts before defining concrete ops. -\- Use capitalized types (e.g., mqtref.Qubit, mqtopt.Qubit). -\- Distinguish parameter operands (in parentheses) from qubit operands in examples: `%q_out = mqtopt.rx(%theta) %q_in`. -\- Provide both reference semantics (mqtref) and value semantics (mqtopt) examples for every unitary + modifier + sequence construct. -\- Ensure examples are correct: block arguments, result threading, modifier nesting order, etc. - -Required new document structure (renumber all sections accordingly): - -1. Overview and Goals -2. Current State and Limitations -3. Dialect Structure and Categories - 3.1 Resource Operations (alloc, dealloc, static qubit definition) - 3.2 Measurement and Reset (separate category) - 3.3 UnitaryExpr Concept (single abstraction) -4. Unified Unitary Interface Design - \- Merge old 7.1 and 7.2; expand. - \- Specify: identification, arities, controls (pos/neg), target access, parameter model (static + dynamic merged ordering), i\-th input/output access, mapping input↔output for value semantics, collecting all parameters, static vs dynamic unitary availability, matrix extraction, inversion, power, control extension hooks. - \- Define how operations report: getNumTargets, getNumPosControls, getNumNegControls, getNumParams, getParameter(i), getInput(i), getOutput(i), mapOutputToInput(i), hasStaticUnitary, getOrBuildUnitaryExpr, etc. -5. Base Gate Operations - 5.1 Philosophy (traits for target/parameter arity, minimal duplication, explicit UnitaryExpr definition; static matrix when all params static; dynamic composition otherwise). - 5.2 Gate List (single\-qubit: x, y, z, h, s, sdg, t, tdg, rx, ry, rz, u, etc.) - 5.3 Multi\-qubit illustrative example: rzz(theta) instead of cx (cx deferred to sugar section). - 5.4 Syntax and Assembly Formats (parameter parentheses + qubits). - 5.5 Builders (static param overloads, dynamic param variants, combined convenience helpers). - 5.6 Canonicalization & Identities (parameter folding, specialization to simpler named gates). - 5.7 Value vs Reference Semantics mapping. -6. Modifier Operations - 6.1 Canonical Nesting Order (negctrl → ctrl → pow → inv) [swap per feedback]. - 6.2 negctrl (semantics, unitary extension, merging/flattening). - 6.3 ctrl (same pattern; merging directly nested; seq lifting rule). - 6.4 pow (parentheses exponent, integer repetition, float principal log, negative exponent → inv+pow normalization, merge consecutive powers, analytical specializations). - 6.5 inv (adjoint computation, folding to known inverse base gates, double inverse elimination, sequence inversion reversal). - 6.6 Nested Example (correct threading, updated order, both dialects). -7. Sequence Operation (seq) - \- Custom assembly format. - \- Control lifting semantics (external control equivalence). - \- Inversion + power behavior (defer to contained unitaries). -8. User Defined Gates & Matrix / Composite Definitions - 8.1 Symbolic matrix gate definitions (2D tensor shape, optional dynamic parameters). - 8.2 Composite gate definitions (sequence body; derive unitary via ordered product). - 8.3 Unified apply operation (applies any gate symbol: matrix or composite). - 8.4 Parameter handling (static/dynamic mix; ordering rules). - 8.5 Canonicalization (inline trivial identity, fold consecutive applies of same static gate if allowed). - 8.6 Unitary extraction rules (static matrix availability or lazy composition). -9. Parser & Builder Sugar - 9.1 Sugar expansions (cx, cz, ccx) → explicit modifier wrapping; specify how parse hooks lower to canonical form. - 9.2 Detailed builder API (fluent style): gate, param, controls (pos/neg), power, inverse, sequence, defineGate, defineMatrixGate, applyGate, withControls, withNegControls, withPow, withInv. Provide ergonomic overloads and RAII region helpers. -10. Testing Strategy - \- Emphasize builder\-driven structural tests. - \- Matrix correctness tests (unitarity, dimension). - \- Interface conformance tests per op category. - \- Canonicalization idempotence (run canonicalization twice). - \- Sugar round trip tests. - \- Negative tests (bad arity, mismatched params, invalid matrix shape, non\-unitary). -11. Integrated Canonicalization Rules Summary (collected references only; full definitions live inline above). -12. Conclusions and Future Work - \- Summarize resolved limitations and extensibility (basis decomposition, advanced symbolic algebra, shared control extraction pass). - -For every operation/modifier: -\- Provide: Purpose, Signature (ref + value semantics), Assembly Format, Builder Variants, Interface Implementation Notes, Canonicalization Rules, Examples (static + dynamic params), Conversion (ref↔value). -\- Ensure consistency of naming, capitalization, and parameter ordering. - -Conventions: -\- Types capitalized (mqtref.Qubit). -\- Parameters in parentheses right after op mnemonic. -\- Use 2D tensor<2^n x 2^n> for matrices. -\- Do not reintroduce removed standalone sections (old passes, separate canonicalization inventory, etc.). -\- All canonicalization logic described inline; mention MLIR pattern categories (fold vs pattern). -\- Avoid speculative future work beyond concise Future Work subsection. - -Deliverables: - -1. Rewritten RFC text (single cohesive document). -2. No extraneous commentary; fully integrated narrative. -3. All examples syntactically valid MLIR. - -Quality checklist before finalizing: -\- Section ordering matches specification. -\- No leftover references to UnitaryMatrix separate from UnitaryExpr. -\- All modifier examples updated to new nesting order. -\- cx not used as base gate example (only as sugar). -\- Block arguments correct for region wrappers (value semantics). -\- Canonicalization rules phrased as declarative transformations. - -Now produce the full revised RFC accordingly. From e4c832c2e9cb07afc49e107b90f038a7f329d3ea Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 18:37:05 +0200 Subject: [PATCH 016/419] =?UTF-8?q?=F0=9F=9A=9A=20rename=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- ...an.md => mlir-compilation-infrastructure-technical-concept.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/mlir/{quantum-dialect-revamp-plan.md => mlir-compilation-infrastructure-technical-concept.md} (100%) diff --git a/docs/mlir/quantum-dialect-revamp-plan.md b/docs/mlir/mlir-compilation-infrastructure-technical-concept.md similarity index 100% rename from docs/mlir/quantum-dialect-revamp-plan.md rename to docs/mlir/mlir-compilation-infrastructure-technical-concept.md From 4c30f6cf11e87fde330900f8a4c74f161cb58fad Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 19:46:07 +0200 Subject: [PATCH 017/419] =?UTF-8?q?=F0=9F=9A=A7=20add=20back=20section=20o?= =?UTF-8?q?n=20custom=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- ...lation-infrastructure-technical-concept.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/docs/mlir/mlir-compilation-infrastructure-technical-concept.md b/docs/mlir/mlir-compilation-infrastructure-technical-concept.md index 10a3a6e02b..1446a42f51 100644 --- a/docs/mlir/mlir-compilation-infrastructure-technical-concept.md +++ b/docs/mlir/mlir-compilation-infrastructure-technical-concept.md @@ -1057,6 +1057,84 @@ U_box = U_n · U_{n-1} · ... · U_1 · U_0 - **`flux → quartz`:** Remove block arguments and results; replace argument uses with direct value references - **`quartz → flux`:** Add block arguments for all used qubits; thread SSA values through operations; add yield with final values +## 7. User-Defined Gates & Matrix/Composite Definitions + +**Purpose:** Enable users to define custom gates that can be referenced and instantiated throughout the program, similar to function definitions and calls. + +### 7.1 Overview + +User-defined gates follow a `func.func`-like design pattern with definitions and application sites: + +- **Gate definitions** create module-level symbols (similar to `func.func`) +- **Application operations** reference these symbols to instantiate gates (similar to `func.call`) +- All user-defined gate operations **implement the unitary interface**, enabling consistent introspection and composition + +Two definition mechanisms are provided to accommodate different use cases: + +1. **Matrix-based definitions (`define_matrix_gate`):** Define a gate via its unitary matrix representation + - Suitable for small qubit counts where the matrix is tractable (2^n × 2^n complexity) + - Provides exact representation and efficient unitary extraction + - Can be parameterized using symbolic expressions + +2. **Composite definitions (`define_composite_gate`):** Define a gate as a sequence of existing unitary operations + - Scalable to larger qubit counts + - Exposes internal structure for optimization and analysis + - Enables hierarchical abstractions + +**Separation Rationale:** The distinction between matrix and composite definitions is fundamental: + +- Matrix definitions provide a **semantic ground truth** via explicit unitary matrices +- Composite definitions provide **structural decomposition** that can be analyzed and optimized +- Gates may provide both representations, with the matrix taking precedence for unitary extraction + +### 7.2 Design Principles + +**Symbol Management:** + +- Gate definitions have module-level scope with unique symbol names +- Symbols are referenced by `apply` operations for instantiation +- Symbol resolution follows standard MLIR visibility rules + +**Parameterization:** + +- Both definition types support static and dynamic parameters +- Parameter binding follows standard calling conventions +- Parameters flow through to the unitary interface for matrix computation + +**Unitary Interface Implementation:** + +- Both `define_matrix_gate` and `define_composite_gate` implement the unitary interface +- Matrix definitions provide direct unitary extraction +- Composite definitions compute unitaries by composing constituent operations +- Application operations (`apply`) delegate to their referenced definitions + +**Consistency:** + +- When both matrix and composite representations are provided, they must represent the same unitary (within numerical tolerance) +- The matrix representation takes precedence as the semantic ground truth +- The composite representation serves as a preferred decomposition hint for optimization + +### 7.3 Integration with Modifier System + +User-defined gates integrate seamlessly with the modifier system described in Section 5: + +- `inv`, `pow`, `ctrl`, and `negctrl` can wrap `apply` operations +- Modifiers are applied to the resolved unitary from the definition +- This enables flexible composition: `inv(pow(apply(@my_gate), 2))` + +### 7.4 Future Work + +The detailed specification of user-defined gates—including concrete syntax, verification rules, inlining strategies, and builder API integration—is deferred to a subsequent design phase. Key open questions include: + +- Precise syntax for matrix attributes and symbolic parameterization +- Verification rules for matrix unitarity and consistency checking +- Inlining heuristics and thresholds for composite definitions +- Recursive definition policies +- Multi-module linking semantics +- Builder API convenience methods for gate definition + +This deferred specification allows the infrastructure to stabilize around core operations before committing to user-defined gate semantics. + ## 8. Builder API **Purpose:** Provide a programmatic API for constructing quantum programs within the MQT MLIR infrastructure. The builder APIs offer type-safe, ergonomic interfaces for both Quartz and Flux dialects. From bf525b7e36700b9866983966619c57e5ecef247f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 20:30:56 +0200 Subject: [PATCH 018/419] =?UTF-8?q?=F0=9F=9A=A7=20add=20subsection=20on=20?= =?UTF-8?q?default=20compiler=20passes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- ...lation-infrastructure-technical-concept.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/docs/mlir/mlir-compilation-infrastructure-technical-concept.md b/docs/mlir/mlir-compilation-infrastructure-technical-concept.md index 1446a42f51..d89330dfb0 100644 --- a/docs/mlir/mlir-compilation-infrastructure-technical-concept.md +++ b/docs/mlir/mlir-compilation-infrastructure-technical-concept.md @@ -1452,6 +1452,104 @@ Integration tests validate the complete MQT MLIR compilation infrastructure, cov - Circuits with measurements and classical control flow - Large-scale circuits (100+ qubits, 1000+ gates) +### 9.5 Default Compiler Passes + +**Purpose:** Define a standard set of compiler passes that should be applied by default to ensure consistent, optimized IR across the MQT MLIR infrastructure. + +**Rationale:** + +The MQT MLIR dialects define extensive canonicalization patterns for all operations (base gates, modifiers, boxes, and custom gates). +To ensure these canonicalization are consistently applied and to maintain clean IR for downstream passes, certain compiler passes should be run by default in most compilation scenarios. + +**Recommended Default Passes:** + +1. **Canonicalization Pass (`-canonicalize`):** + - **Purpose:** Apply all canonicalization patterns defined in the dialect operations + - **Benefits:** + - Simplifies IR by applying algebraic identities (e.g., `inv { inv { U } } → U`) + - Normalizes modifier ordering to canonical form (`negctrl → ctrl → pow → inv`) + - Merges adjacent compatible operations (e.g., `rx(a); rx(b) → rx(a+b)`) + - Eliminates identity operations and no-ops + - Hoists constants to the top of modules/functions + - Enables pattern matching for subsequent optimization passes + - **When to Apply:** After every major transformation pass, and as part of the default pipeline + - **Justification:** Given the rich set of canonicalization rules defined throughout this specification, this pass is essential to maintaining normalized IR + +2. **Remove Dead Values Pass (`-remove-dead-values`):** + - **Purpose:** Eliminate unused SSA values and operations with no side effects + - **Benefits:** + - Cleans up intermediate values from transformations + - Reduces IR size and complexity + - Improves readability of generated IR + - Essential for Flux dialect (SSA-based) to remove unused qubit values + - **When to Apply:** After canonicalization, as part of the default pipeline + - **Justification:** Particularly important in Flux dialect where value threading can create unused intermediate values + +**Default Pass Pipeline:** + +For most compilation scenarios, the following minimal pass pipeline should be applied: + +```bash +mqt-opt \ + --canonicalize \ + --remove-dead-values \ + input.mlir +``` + +For more aggressive optimization, this can be extended: + +```bash +mqt-opt \ + --canonicalize \ + --remove-dead-values \ + --canonicalize \ + input.mlir +``` + +**Testing Implications:** + +- **Consistent Test IR:** Running default passes ensures that constants are always defined at the top of modules in test files, improving readability and consistency +- **Normalized Forms:** Tests can rely on canonical forms (e.g., modifier ordering) rather than handling all possible equivalent representations +- **Reduced Brittleness:** Tests that check IR structure benefit from normalized representations with dead code eliminated + +**Integration with Custom Passes:** + +Custom optimization passes should be inserted into pipelines alongside the default passes: + +```bash +mqt-opt \ + --canonicalize \ + --remove-dead-values \ + --custom-optimization-pass \ + --canonicalize \ + --remove-dead-values \ + input.mlir +``` + +This pattern ensures that custom passes operate on normalized IR and that their outputs are subsequently normalized. + +**Builder API Integration:** + +The builder APIs may optionally apply these passes automatically when `finalize()` is called, controlled by a configuration flag: + +```c++ +QuartzProgramBuilder builder(context); +builder.setApplyDefaultPasses(true); // Enable automatic canonicalization +// ... build circuit ... +auto module = builder.finalize(); // Applies canonicalize + remove-dead-values +``` + +**Future Considerations:** + +As the dialect matures, additional passes may be added to the default pipeline: + +- Constant folding and propagation +- Common subexpression elimination (for parameterized gates) +- Gate fusion optimizations +- Circuit optimization passes (commutation-based reordering) + +However, the core default passes (`-canonicalize` and `-remove-dead-values`) should remain stable and universally applicable. + ## 10. Implementation Roadmap **Timeline:** 14-week implementation plan to complete the MQT MLIR compilation infrastructure. From 1771b0976c0bc29cc332ee82251aaca87c357a5f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 21:20:40 +0200 Subject: [PATCH 019/419] =?UTF-8?q?=F0=9F=9A=A7=20add=20implementation=20p?= =?UTF-8?q?rompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/implementation-prompt.md | 520 +++++++++++++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 docs/mlir/implementation-prompt.md diff --git a/docs/mlir/implementation-prompt.md b/docs/mlir/implementation-prompt.md new file mode 100644 index 0000000000..dd68f92d32 --- /dev/null +++ b/docs/mlir/implementation-prompt.md @@ -0,0 +1,520 @@ +# MLIR Quantum Compilation Infrastructure Implementation Prompt + +You are an expert MLIR compiler developer tasked with implementing a comprehensive MLIR-based quantum compilation infrastructure for the Munich Quantum Toolkit (MQT). Your implementation will follow the detailed technical specification provided in the `docs/mlir/mlir-compilation-infrastructure-technical-concept.md` document. + +## Project Context + +**Location:** The implementation lives in the top-level `mlir/` directory of the MQT Core project. + +**Approach:** You should build on top of the existing infrastructure where beneficial, but **backwards compatibility is NOT a concern**. Full refactors are preferred if they better achieve the design goals. The existing code can serve as inspiration but should not constrain the implementation. + +**Build Configuration:** + +```bash +cmake -S . -B build-mlir-copilot \ + -DLLVM_EXTERNAL_LIT=/home/lburgholzer/Documents/mqt-core/.venv/bin/lit \ + -DBUILD_MQT_CORE_MLIR=ON \ + -DMLIR_DIR=/usr/lib/llvm-21/lib/cmake/mlir \ + --config Release +``` + +**Build Targets:** + +- Build MLIR tests: `cmake --build build-mlir-copilot --target mqt-core-mlir-lit-test-build-only` +- Run MLIR lit tests: `cmake --build build-mlir-copilot --target mqt-core-mlir-lit-test` +- Build translation tests: `cmake --build build-mlir-copilot --target mqt-core-mlir-translation-test` +- Run translation tests: `./build-mlir-copilot/mlir/unittests/translation/mqt-core-mlir-translation-test` + +**Code Quality:** + +- Lint the project: `pre-commit run -a` + +## Implementation Phases + +Your implementation should proceed through the following phases, implementing the design systematically: + +### Phase 1: Core Type System and Dialect Infrastructure + +**Goal:** Establish the foundation for both Quartz and Flux dialects. + +**Tasks:** + +1. Define `!quartz.qubit` and `!flux.qubit` types in their respective dialects +2. Implement the `UnitaryOpInterface` with all methods specified in Section 3.5: + +- Qubit accessors (targets, positive/negative controls) +- Value semantics threading (inputs/outputs) +- Parameter handling with `ParameterDescriptor` +- Matrix extraction (`hasStaticUnitary()`, `tryGetStaticMatrix()`) +- Modifier state (`isInverted()`, `getPower()`) +- Identification methods (`getBaseSymbol()`, `getCanonicalDescriptor()`) + +3. Set up dialect infrastructure with proper CMake integration +4. Implement basic verification framework + +**Acceptance Criteria:** + +- Both dialects register successfully with MLIR +- `UnitaryOpInterface` compiles and can be attached to operations +- CMake build succeeds without errors + +### Phase 2: Resource and Measurement Operations + +**Goal:** Implement non-unitary operations for qubit management and measurement. + +**Tasks:** + +1. **Resource Operations (Section 3.3):** + +- `quartz.alloc` / `flux.alloc` +- `quartz.dealloc` / `flux.dealloc` +- `quartz.qubit` / `flux.qubit` (static qubit references) +- Integration with `memref` for quantum and classical registers + +2. **Measurement and Reset (Section 3.4):** + +- `quartz.measure` / `flux.measure` (computational basis only) +- `quartz.reset` / `flux.reset` +- Proper type signatures for both reference and value semantics + +3. Implement canonicalization patterns for these operations +4. Add verification rules + +**Acceptance Criteria:** + +- All resource operations parse, verify, and print correctly +- Canonicalization patterns trigger appropriately +- Unit tests validate each operation's behavior + +### Phase 3: Base Gate Operations - Part 1 (Zero/Single-Qubit Gates) + +**Goal:** Implement all single-qubit base gates with full interface support. + +**Tasks:** + +1. Implement gates from Section 4.3: + +- **Zero-qubit:** `gphase` (4.3.1) +- **Pauli gates:** `id` (4.3.2), `x` (4.3.3), `y` (4.3.4), `z` (4.3.5) +- **Fixed gates:** `h` (4.3.6), `s` (4.3.7), `sdg` (4.3.8), `t` (4.3.9), `tdg` (4.3.10), `sx` (4.3.11), `sxdg` (4.3.12) +- **Rotation gates:** `rx` (4.3.13), `ry` (4.3.14), `rz` (4.3.15), `p` (4.3.16), `r` (4.3.17) +- **Universal gates:** `u` (4.3.18), `u2` (4.3.19) + +2. For each gate: + +- Implement proper traits (`OneTarget`, `NoParameter`, `Hermitian`, etc.) +- Implement `UnitaryOpInterface` methods +- Define static matrix representation where applicable +- Implement all specified canonicalization patterns + +3. Support both static (attribute) and dynamic (SSA value) parameters +4. Add comprehensive unit tests for each gate + +**Acceptance Criteria:** + +- All single-qubit gates parse and verify correctly in both dialects +- Static matrix extraction works for all parameter-free gates +- Dynamic matrix computation works for parameterized gates with static parameters +- All canonicalization patterns trigger correctly +- Global phase preservation semantics are respected + +### Phase 4: Base Gate Operations - Part 2 (Two-Qubit Gates) + +**Goal:** Implement all two-qubit base gates. + +**Tasks:** + +1. Implement gates from Section 4.3: + +- **Entangling gates:** `swap` (4.3.20), `iswap` (4.3.21), `dcx` (4.3.22), `ecr` (4.3.23) +- **Ising-type gates:** `rxx` (4.3.24), `ryy` (4.3.25), `rzx` (4.3.26), `rzz` (4.3.27) +- **General gates:** `xx_plus_yy` (4.3.28), `xx_minus_yy` (4.3.29) +- **Utility gates:** `barrier` (4.3.30) + +2. For each gate: + +- Implement proper traits (`TwoTarget`, parameter arities) +- Implement `UnitaryOpInterface` with 4×4 matrices +- Implement canonicalization patterns + +3. Special handling for `barrier`: + +- Implements `UnitaryOpInterface` but acts as compiler directive +- Prevents optimization reordering +- Scope-based global phase aggregation + +**Acceptance Criteria:** + +- All two-qubit gates verify and print correctly +- 4×4 matrix extraction works for all gates +- Canonicalization patterns function correctly +- Barrier semantics are properly enforced + +### Phase 5: Modifier Operations + +**Goal:** Implement the complete modifier system for composable gate transformations. + +**Tasks:** + +1. **Control Modifiers (Section 5.2):** + +- Implement `quartz.ctrl` / `flux.ctrl` +- Implement `quartz.negctrl` / `flux.negctrl` +- Support arbitrary nesting and flattening +- Implement control qubit exclusivity verification +- Handle value threading in Flux dialect + +2. **Inverse Modifier (Section 5.3):** + +- Implement `quartz.inv` / `flux.inv` +- Double inverse cancellation +- Hermitian gate simplification +- Parametric rotation inversion + +3. **Power Modifier (Section 5.4):** + +- Implement `quartz.pow` / `flux.pow` +- Support static and dynamic exponents +- Handle negative powers (convert to `inv(pow(+exp))`) +- Integer and real/rational exponent handling +- Matrix exponentiation via eigendecomposition + +4. **Canonical Ordering:** + +- Implement canonicalization rules to enforce `negctrl → ctrl → pow → inv` ordering +- Nested modifier flattening + +5. **Unitary Computation:** + +- Extend matrices to control spaces +- Conjugate transpose for inversion +- Matrix exponentiation for powers + +**Acceptance Criteria:** + +- All modifiers parse, verify, and execute correctly +- Canonical ordering is enforced through canonicalization +- Nested modifiers flatten appropriately +- Control modifier exclusivity is verified (no qubit in both `ctrl` and `negctrl`) +- Matrix extraction works through modifier layers +- Unit tests cover all canonicalization patterns + +### Phase 6: Box Operation (Scoped Sequences) + +**Goal:** Implement the `box` operation for scoped unitary composition. + +**Tasks:** + +1. Implement `quartz.box` / `flux.box` (Section 6) +2. Handle value threading for Flux dialect (region arguments and yields) +3. Implement canonicalization: + +- Empty box elimination +- Single-operation inlining +- Nested box flattening + +4. Composite unitary computation (product of constituent unitaries) +5. Verification rules + +**Acceptance Criteria:** + +- Box operations construct and verify correctly in both dialects +- Flux dialect properly threads values through region +- Canonicalization patterns trigger appropriately +- Composite matrix multiplication works for static sequences + +### Phase 7: Builder APIs + +**Goal:** Provide ergonomic programmatic APIs for circuit construction. + +**Tasks:** + +1. **QuartzProgramBuilder (Section 8.2):** + +- Resource management methods +- All base gate methods (single and two-qubit) +- Convenience methods (`cx`, `mcx`, multi-controlled gates) +- Modifier methods (lambdas for body construction) +- Box scoping +- Gate definition and application placeholders +- Finalization with optional default pass application + +2. **FluxProgramBuilder (Section 8.3):** + +- Similar API but with SSA value returns +- Proper value threading +- Tuple returns for multi-qubit gates + +3. **Integration:** + +- Initialize MLIR context +- Create module and function +- Provide fluent chaining interface +- Apply default passes on finalization (optional) + +**Acceptance Criteria:** + +- Both builders construct valid MLIR modules +- Fluent chaining works correctly +- Generated IR matches hand-written IR structurally +- Example circuits (Bell state, GHZ) build successfully + +### Phase 8: Testing Infrastructure Refactor + +**Goal:** Establish robust, maintainable testing following Section 9. + +**Tasks:** + +1. **Unit Testing Framework (Section 9.2):** + +- Set up GoogleTest infrastructure +- Test categories: + - Operation construction + - Canonicalization patterns + - Interface implementation + - Matrix extraction + - Modifier composition + - Dialect conversion +- Structural equivalence checks (not SSA name matching) +- Numerical matrix comparison with tolerances + +2. **Minimal FileCheck Tests (Section 9.3):** + +- Parser/printer round-trip validation +- Focus on structural properties, not formatting +- Avoid brittle SSA name checks + +3. **Integration Tests (Section 9.4):** + +- Frontend translation tests (Qiskit, OpenQASM 3, `qc::QuantumComputation`) +- Compiler pipeline tests +- Backend translation tests (QIR) +- End-to-end algorithm scenarios + +4. **Default Pass Pipeline (Section 9.5):** + +- Integrate `-canonicalize` and `-remove-dead-values` as defaults +- Configure builder API to optionally apply on finalization +- Document testing patterns using default pipeline + +**Acceptance Criteria:** + +- Comprehensive unit test coverage for all operations +- FileCheck tests are minimal and focused +- Integration tests cover key scenarios +- All tests pass reliably +- Test execution time is reasonable (<5 minutes for full suite) + +### Phase 9: User-Defined Gates (Deferred) + +**Note:** Section 7 explicitly defers detailed specification of user-defined gates. This phase is a placeholder for future work once core infrastructure stabilizes. + +**Future Tasks:** + +- Design concrete syntax for `define_matrix_gate` and `define_composite_gate` +- Implement symbol table integration +- Define verification rules for unitarity and consistency +- Create `apply` operation for gate instantiation +- Integrate with modifier system +- Design builder API convenience methods + +## Implementation Guidelines + +### Code Organization + +``` +mlir/ +├── include/ +│ └── mqt/ +│ └── Dialect/ +│ ├── Quartz/ +│ │ ├── IR/ +│ │ │ ├── QuartzDialect.h +│ │ │ ├── QuartzOps.h +│ │ │ ├── QuartzTypes.h +│ │ │ └── QuartzInterfaces.h +│ │ └── Transforms/ +│ │ └── Passes.h +│ └── Flux/ +│ ├── IR/ +│ │ ├── FluxDialect.h +│ │ ├── FluxOps.h +│ │ ├── FluxTypes.h +│ │ └── FluxInterfaces.h +│ └── Transforms/ +│ └── Passes.h +├── lib/ +│ └── Dialect/ +│ ├── Quartz/ +│ │ ├── IR/ +│ │ │ ├── QuartzDialect.cpp +│ │ │ ├── QuartzOps.cpp +│ │ │ └── QuartzTypes.cpp +│ │ └── Transforms/ +│ │ └── Canonicalize.cpp +│ └── Flux/ +│ ├── IR/ +│ │ ├── FluxDialect.cpp +│ │ ├── FluxOps.cpp +│ │ └── FluxTypes.cpp +│ └── Transforms/ +│ └── Canonicalize.cpp +├── tools/ +│ └── mqt-opt/ +│ └── mqt-opt.cpp +├── test/ +│ ├── Dialect/ +│ │ ├── Quartz/ +│ │ └── Flux/ +│ └── Integration/ +└── unittests/ + ├── Dialect/ + │ ├── Quartz/ + │ └── Flux/ + └── Builder/ +``` + +### TableGen Usage + +- Define operations using ODS (Operation Definition Specification) +- Define interfaces using TableGen interface definitions +- Use traits to express operation properties +- Generate boilerplate code where possible + +### Naming Conventions + +- **Dialects:** `quartz`, `flux` (lowercase) +- **Operations:** `quartz.h`, `flux.ctrl`, etc. (lowercase, dialect prefix) +- **Types:** `!quartz.qubit`, `!flux.qubit` +- **C++ Classes:** `QuartzDialect`, `HadamardOp`, `UnitaryOpInterface` (PascalCase) +- **Methods:** `getNumTargets()`, `tryGetStaticMatrix()` (camelCase) + +### Verification Philosophy + +- Verify type correctness +- Verify qubit uniqueness constraints +- Verify control/target disjointness +- Verify region structure (single block, proper terminator) +- Verify interface implementation consistency +- **Do not** verify unitary correctness (too expensive, user responsibility) + +### Canonicalization Strategy + +- Implement patterns as separate classes inheriting from `OpRewritePattern` +- Register patterns in `getCanonicalizationPatterns()` +- Patterns should be confluent (order-independent) +- Use pattern benefit values to prioritize important patterns +- Document pattern purpose and examples + +### Matrix Computation + +- Use `mlir::DenseElementsAttr` for static matrices +- Store as `tensor>` where N = 2^(num_qubits) +- Use numerical libraries (Eigen, if available) for: + - Matrix multiplication + - Eigendecomposition + - Exponentiation +- Handle numerical precision: machine epsilon for comparisons +- Return `std::nullopt` for symbolic/dynamic cases + +### Testing Best Practices + +1. **Unit Tests:** + +- One test fixture per operation type +- Test construction with valid and invalid parameters +- Test each canonicalization pattern independently +- Test interface methods return correct values +- Use builders for construction, not raw IR strings + +2. **Integration Tests:** + +- Use realistic circuit examples +- Verify unitary equivalence numerically +- Test complete compilation pipelines +- Include performance benchmarks for large circuits + +3. **FileCheck Tests:** + +- Minimal use, only for round-trip parsing +- Check operation presence, not SSA names +- Use `CHECK-LABEL` for test separation +- Keep tests small and focused + +## Implementation Checklist + +For each phase, ensure: + +- [ ] Code compiles without warnings +- [ ] All operations have TableGen definitions +- [ ] All operations implement required interfaces +- [ ] Verification logic is comprehensive +- [ ] Canonicalization patterns are implemented and tested +- [ ] Unit tests achieve high coverage +- [ ] Documentation comments are complete +- [ ] Code passes `pre-commit run -a` +- [ ] CMake integration is correct +- [ ] All specified tests pass + +## Key Design Principles to Preserve + +1. **Separation of Concerns:** Base gates are modifier-free; use wrappers for extensions +2. **Canonical Forms:** Enforce consistent representation through canonicalization +3. **Interface Uniformity:** All unitary operations expose the same introspection API +4. **Semantic Clarity:** Reference semantics (Quartz) vs. value semantics (Flux) are distinct +5. **Extensibility:** Design accommodates future dialects and abstraction levels +6. **Global Phase Preservation:** Treat global phases carefully; only eliminate via explicit passes +7. **Numerical Robustness:** Use appropriate tolerances and stable algorithms + +## Success Criteria + +The implementation is successful when: + +1. All operations in Sections 3-6 are fully implemented and tested +2. Both Quartz and Flux dialects function correctly with proper semantics +3. Builder APIs enable ergonomic circuit construction +4. Testing infrastructure provides confidence through structural and semantic validation +5. Default pass pipeline normalizes IR consistently +6. Documentation is complete and accurate +7. Code quality meets project standards (linting passes) +8. All build targets succeed +9. Integration tests demonstrate end-to-end functionality + +## Getting Started + +**Step 1:** Read the complete specification in `docs/mlir/mlir-compilation-infrastructure-technical-concept.md` + +**Step 2:** Set up your build environment using the configuration command + +**Step 3:** Explore the existing `mlir/` directory to understand current structure + +**Step 4:** Begin with Phase 1, implementing core types and interfaces + +**Step 5:** Incrementally build up functionality, testing at each stage + +**Step 6:** Use the existing translation tests as inspiration for test structure + +**Step 7:** Iterate on design decisions, documenting deviations from the spec + +## Questions and Clarifications + +If you encounter ambiguities or need clarifications: + +1. Check if the specification explicitly defers the decision (e.g., user-defined gates) +2. Look for related patterns in existing MLIR dialects (e.g., `arith`, `tensor`, `scf`) +3. Choose the simplest correct implementation that satisfies the requirements +4. Document your decision for future reference + +## Deliverables + +At the end of each phase, provide: + +1. **Code:** Fully implemented operations, types, interfaces, and passes +2. **Tests:** Comprehensive unit tests and targeted integration tests +3. **Documentation:** Inline code comments and any necessary updates to the specification +4. **Build Validation:** Confirmation that all build and test targets pass +5. **Summary:** Brief description of what was implemented and any notable decisions + +--- + +**Remember:** This is a comprehensive implementation project that requires attention to detail, adherence to MLIR best practices, and systematic testing. Focus on correctness and clarity over premature optimization. The goal is to create a robust foundation for quantum compilation that can evolve with the field. From 8ddc0a03893cf982bdb8c61539cd598e445da6da Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Oct 2025 22:58:44 +0200 Subject: [PATCH 020/419] =?UTF-8?q?=F0=9F=9A=A7=20broken=20first=20LLM=20d?= =?UTF-8?q?raft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/CMakeLists.txt | 2 + mlir/include/mlir/Dialect/Flux/CMakeLists.txt | 9 ++ .../mlir/Dialect/Flux/IR/CMakeLists.txt | 15 ++ .../mlir/Dialect/Flux/IR/FluxDialect.h | 52 +++++++ .../mlir/Dialect/Flux/IR/FluxDialect.td | 54 ++++++++ .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 117 ++++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.h | 22 +++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 101 ++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h | 19 +++ .../include/mlir/Dialect/Flux/IR/FluxTypes.td | 41 ++++++ .../mlir/Dialect/Quartz/CMakeLists.txt | 9 ++ .../mlir/Dialect/Quartz/IR/CMakeLists.txt | 25 ++++ .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 17 +++ .../mlir/Dialect/Quartz/IR/QuartzDialect.td | 53 ++++++++ .../Dialect/Quartz/IR/QuartzInterfaces.td | 128 ++++++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.h | 22 +++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 100 ++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzTypes.h | 19 +++ .../mlir/Dialect/Quartz/IR/QuartzTypes.td | 42 ++++++ mlir/lib/Dialect/CMakeLists.txt | 2 + mlir/lib/Dialect/Flux/CMakeLists.txt | 9 ++ mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 21 +++ mlir/lib/Dialect/Flux/IR/FluxDialect.cpp | 36 +++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 30 ++++ mlir/lib/Dialect/Quartz/CMakeLists.txt | 9 ++ mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 21 +++ mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp | 36 +++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 30 ++++ 28 files changed, 1041 insertions(+) create mode 100644 mlir/include/mlir/Dialect/Flux/CMakeLists.txt create mode 100644 mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxOps.h create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxOps.td create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h create mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td create mode 100644 mlir/include/mlir/Dialect/Quartz/CMakeLists.txt create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h create mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td create mode 100644 mlir/lib/Dialect/Flux/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Flux/IR/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Flux/IR/FluxDialect.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/FluxOps.cpp create mode 100644 mlir/lib/Dialect/Quartz/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Quartz/IR/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp diff --git a/mlir/include/mlir/Dialect/CMakeLists.txt b/mlir/include/mlir/Dialect/CMakeLists.txt index 1a85073274..5cf34f8170 100644 --- a/mlir/include/mlir/Dialect/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/CMakeLists.txt @@ -8,3 +8,5 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) +add_subdirectory(Quartz) +add_subdirectory(Flux) diff --git a/mlir/include/mlir/Dialect/Flux/CMakeLists.txt b/mlir/include/mlir/Dialect/Flux/CMakeLists.txt new file mode 100644 index 0000000000..ace971aa36 --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) diff --git a/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt new file mode 100644 index 0000000000..d3ad9bfd11 --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(DIALECT_NAME "Flux") +set(DIALECT_NAME_UPPER "FLUX") + +add_mlir_dialect(FluxOps flux) +add_mlir_interface(FluxInterfaces) +add_mlir_doc(FluxOps MLIRFluxDialect Dialects/ -gen-dialect-doc) +add_mlir_doc(FluxInterfaces MLIRFluxInterfaces Dialects/ -gen-op-interface-docs -dialect=flux) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h new file mode 100644 index 0000000000..ef88391afc --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include + +// Suppress warnings about ambiguous reversed operators in MLIR +// (see https://github.com/llvm/llvm-project/issues/45853) +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wambiguous-reversed-operator" +#endif +#include "mlir/Interfaces/InferTypeOpInterface.h" +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#define DIALECT_NAME_FLUX "flux" + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Flux/IR/FluxOpsDialect.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOpsTypes.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOps.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td new file mode 100644 index 0000000000..74ca17ef1c --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td @@ -0,0 +1,54 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef FLUX_DIALECT +#define FLUX_DIALECT + +include "mlir/IR/DialectBase.td" + +//===----------------------------------------------------------------------===// +// Flux Dialect Definition +//===----------------------------------------------------------------------===// + +def Flux_Dialect : Dialect { + let name = "flux"; + + let summary = "The Flux (value semantics) dialect for quantum computing."; + + let description = [{ + The Flux dialect uses **value semantics** where quantum operations + consume input qubits and produce new output values, following the + functional programming and SSA paradigm. This model enables: + + - Powerful compiler optimizations through clear dataflow + - Safe reordering and parallelization analysis + - Advanced transformation passes + - Explicit dependency tracking + + The name "Flux" captures the flowing, transformative nature of + value-based representations—quantum states flow through operations, + each transformation producing new values like a river flowing through + a landscape. + + Example: + ```mlir + %q_out = flux.h %q_in // Consumes %q_in, produces %q_out + %q0_out, %q1_out = flux.swap %q0_in, %q1_in // Consumes inputs, produces outputs + ``` + }]; + + let cppNamespace = "::mlir::flux"; + + let useDefaultTypePrinterParser = 1; + + let extraClassDeclaration = [{ + void registerTypes(); + }]; +} + +#endif // FLUX_DIALECT diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td new file mode 100644 index 0000000000..e6f523a877 --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -0,0 +1,117 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef FLUX_INTERFACES +#define FLUX_INTERFACES + +include "mlir/IR/OpBase.td" + +//===----------------------------------------------------------------------===// +// UnitaryOpInterface +//===----------------------------------------------------------------------===// + +def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { + let description = [{ + This interface provides a unified API for all operations that apply or + produce a unitary transformation in the Flux dialect. This includes base + gates, user-defined gates, modified operations (control, inverse, power), + and sequences. + + The interface enables uniform introspection and composition capabilities + across all unitary operations with value semantics. + }]; + + let cppNamespace = "::mlir::flux"; + + let methods = [ + // Qubit accessors + InterfaceMethod< + "Returns the number of target qubits (excluding control qubits).", + "size_t", "getNumTargets", (ins) + >, + InterfaceMethod< + "Returns the number of positive control qubits.", + "size_t", "getNumPosControls", (ins) + >, + InterfaceMethod< + "Returns the number of negative control qubits.", + "size_t", "getNumNegControls", (ins) + >, + InterfaceMethod< + "Returns the i-th target input qubit.", + "::mlir::Value", "getTarget", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th positive control input qubit.", + "::mlir::Value", "getPosControl", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th negative control input qubit.", + "::mlir::Value", "getNegControl", (ins "size_t":$i) + >, + + // Value semantics threading + InterfaceMethod< + "Returns the i-th input qubit (targets + controls combined).", + "::mlir::Value", "getInput", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th output qubit (targets + controls combined).", + "::mlir::Value", "getOutput", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the output qubit corresponding to the given input qubit.", + "::mlir::Value", "getOutputForInput", (ins "::mlir::Value":$input) + >, + InterfaceMethod< + "Returns the input qubit corresponding to the given output qubit.", + "::mlir::Value", "getInputForOutput", (ins "::mlir::Value":$output) + >, + + // Parameter handling + InterfaceMethod< + "Returns the number of parameters.", + "size_t", "getNumParams", (ins) + >, + + // Matrix extraction + InterfaceMethod< + "Returns true if the operation has a static unitary matrix.", + "bool", "hasStaticUnitary", (ins) + >, + InterfaceMethod< + "Attempts to extract the static unitary matrix. " + "Returns std::nullopt if the operation is symbolic or dynamic.", + "std::optional<::mlir::DenseElementsAttr>", "tryGetStaticMatrix", (ins) + >, + + // Modifier state + InterfaceMethod< + "Returns true if the operation is inverted.", + "bool", "isInverted", (ins), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return false; + }] + >, + InterfaceMethod< + "Returns the power exponent if applicable, otherwise std::nullopt.", + "std::optional", "getPower", (ins), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return std::nullopt; + }] + >, + + // Identification + InterfaceMethod< + "Returns the base symbol/mnemonic of the operation.", + "::llvm::StringRef", "getBaseSymbol", (ins) + >, + ]; +} + +#endif // FLUX_INTERFACES diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.h b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.h new file mode 100644 index 0000000000..43bf126150 --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MLIR_DIALECT_FLUX_IR_FLUXOPS_H +#define MLIR_DIALECT_FLUX_IR_FLUXOPS_H + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Flux/IR/FluxTypes.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#define GET_OP_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOps.h.inc" + +#endif // MLIR_DIALECT_FLUX_IR_FLUXOPS_H diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td new file mode 100644 index 0000000000..54062392ec --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -0,0 +1,101 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef FLUX_OPS +#define FLUX_OPS + +include "mlir/Dialect/Flux/IR/FluxDialect.td" +include "mlir/Dialect/Flux/IR/FluxTypes.td" +include "mlir/Dialect/Flux/IR/FluxInterfaces.td" +include "mlir/IR/OpBase.td" +include "mlir/IR/BuiltinAttributes.td" +include "mlir/IR/BuiltinTypes.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +//===----------------------------------------------------------------------===// +// Base Operation Classes +//===----------------------------------------------------------------------===// + +class Flux_Op traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// Resource Operations +//===----------------------------------------------------------------------===// + +def Flux_AllocOp : Flux_Op<"alloc", [Pure]> { + let summary = "Allocate a qubit dynamically"; + let description = [{ + Allocates a new qubit dynamically and returns an SSA value representing it. + The qubit is initialized to the |0⟩ state. + + Example: + ```mlir + %q = flux.alloc : !flux.qubit + ``` + }]; + + let results = (outs Flux_QubitType:$result); + let assemblyFormat = "attr-dict `:` type($result)"; +} + +def Flux_DeallocOp : Flux_Op<"dealloc"> { + let summary = "Deallocate a qubit"; + let description = [{ + Deallocates a qubit, releasing its resources. + + Example: + ```mlir + flux.dealloc %q : !flux.qubit + ``` + }]; + + let arguments = (ins Flux_QubitType:$qubit); + let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset Operations +//===----------------------------------------------------------------------===// + +def Flux_MeasureOp : Flux_Op<"measure"> { + let summary = "Measure a qubit in the computational basis"; + let description = [{ + Measures a qubit in the computational (Z) basis, collapsing the state + and returning both the output qubit and a classical bit result. + + Example: + ```mlir + %q_out, %c = flux.measure %q_in : !flux.qubit + ``` + }]; + + let arguments = (ins Flux_QubitType:$qubit_in); + let results = (outs Flux_QubitType:$qubit_out, I1:$result); +} + +def Flux_ResetOp : Flux_Op<"reset"> { + let summary = "Reset a qubit to |0⟩ state"; + let description = [{ + Resets a qubit to the |0⟩ state, regardless of its current state, + and returns the reset qubit. + + Example: + ```mlir + %q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Flux_QubitType:$qubit_in); + let results = (outs Flux_QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let hasCanonicalizer = 1; +} + +#endif // FLUX_OPS diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h b/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h new file mode 100644 index 0000000000..895aed6eec --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MLIR_DIALECT_FLUX_IR_FLUXTYPES_H +#define MLIR_DIALECT_FLUX_IR_FLUXTYPES_H + +#include "mlir/IR/Types.h" + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOpsTypes.h.inc" + +#endif // MLIR_DIALECT_FLUX_IR_FLUXTYPES_H diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td b/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td new file mode 100644 index 0000000000..d39ecdf8cd --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef FLUX_TYPES +#define FLUX_TYPES + +include "mlir/Dialect/Flux/IR/FluxDialect.td" +include "mlir/IR/AttrTypeBase.td" + +//===----------------------------------------------------------------------===// +// Flux Type Definitions +//===----------------------------------------------------------------------===// + +class Flux_Type traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + +def Flux_QubitType : Flux_Type<"Qubit", "qubit"> { + let summary = "Flux qubit value type"; + let description = [{ + The `!flux.qubit` type represents an SSA value holding a quantum bit + in the Flux dialect. Operations using this type consume input qubits + and produce new output qubits following value semantics and the SSA + paradigm, enabling powerful dataflow analysis and optimization. + + Example: + ```mlir + %q0 = flux.alloc : !flux.qubit + %q1 = flux.h %q0 : !flux.qubit -> !flux.qubit + %q2 = flux.x %q1 : !flux.qubit -> !flux.qubit + ``` + }]; +} + +#endif // FLUX_TYPES diff --git a/mlir/include/mlir/Dialect/Quartz/CMakeLists.txt b/mlir/include/mlir/Dialect/Quartz/CMakeLists.txt new file mode 100644 index 0000000000..ace971aa36 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt new file mode 100644 index 0000000000..5fa3452210 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QuartzOps.td) +mlir_tablegen(QuartzOps.h.inc -gen-op-decls) +mlir_tablegen(QuartzOps.cpp.inc -gen-op-defs) +mlir_tablegen(QuartzOpsDialect.h.inc -gen-dialect-decls -dialect=quartz) +mlir_tablegen(QuartzOpsDialect.cpp.inc -gen-dialect-defs -dialect=quartz) +mlir_tablegen(QuartzOpsTypes.h.inc -gen-typedef-decls -typedefs-dialect=quartz) +mlir_tablegen(QuartzOpsTypes.cpp.inc -gen-typedef-defs -typedefs-dialect=quartz) +add_public_tablegen_target(MLIRQuartzOpsIncGen) + +add_mlir_doc(QuartzDialect QuartzDialect Dialects/ -gen-dialect-doc) +add_mlir_doc(QuartzOps QuartzOps Dialects/ -gen-op-doc) +add_mlir_doc(QuartzTypes QuartzTypes Dialects/ -gen-typedef-doc) + +set(LLVM_TARGET_DEFINITIONS QuartzInterfaces.td) +mlir_tablegen(QuartzOpsInterfaces.h.inc -gen-op-interface-decls) +mlir_tablegen(QuartzOpsInterfaces.cpp.inc -gen-op-interface-defs) +add_public_tablegen_target(MLIRQuartzInterfacesIncGen) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h new file mode 100644 index 0000000000..f43db44a06 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MLIR_DIALECT_QUARTZ_IR_QUARTZDIALECT_H +#define MLIR_DIALECT_QUARTZ_IR_QUARTZDIALECT_H + +#include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.h.inc" +#include "mlir/IR/Dialect.h" + +#endif // MLIR_DIALECT_QUARTZ_IR_QUARTZDIALECT_H diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td new file mode 100644 index 0000000000..6abddf4006 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td @@ -0,0 +1,53 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QUARTZ_DIALECT +#define QUARTZ_DIALECT + +include "mlir/IR/DialectBase.td" + +//===----------------------------------------------------------------------===// +// Quartz Dialect Definition +//===----------------------------------------------------------------------===// + +def Quartz_Dialect : Dialect { + let name = "quartz"; + + let summary = "The Quartz (reference semantics) dialect for quantum computing."; + + let description = [{ + The Quartz dialect uses **reference semantics** where quantum operations + modify qubits in place, similar to how hardware physically transforms + quantum states. This model provides: + + - Natural mapping to hardware execution models + - Intuitive representation for circuit descriptions + - Direct compatibility with imperative quantum programming languages + - Straightforward backend code generation + + The name "Quartz" reflects the crystalline, structured nature of + hardware-oriented representations—operations have fixed positions and + transform states in place, like atoms in a crystal lattice. + + Example: + ```mlir + quartz.h %q // Applies Hadamard to qubit %q in place + quartz.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets + ``` + }]; + + let cppNamespace = "::mlir::quartz"; + + let useDefaultTypePrinterParser = 1; + + let extraClassDeclaration = [{ + void registerTypes(); + }]; +} + +#endif // QUARTZ_DIALECT diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td new file mode 100644 index 0000000000..de5b7b2f37 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -0,0 +1,128 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QUARTZ_INTERFACES +#define QUARTZ_INTERFACES + +include "mlir/IR/OpBase.td" + +//===----------------------------------------------------------------------===// +// UnitaryOpInterface +//===----------------------------------------------------------------------===// + +def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { + let description = [{ + This interface provides a unified API for all operations that apply or + produce a unitary transformation. This includes base gates, user-defined + gates, modified operations (control, inverse, power), and sequences. + + The interface enables uniform introspection and composition capabilities + across all unitary operations in the Quartz dialect. + }]; + + let cppNamespace = "::mlir::quartz"; + + let methods = [ + // Qubit accessors + InterfaceMethod< + "Returns the number of target qubits (excluding control qubits).", + "size_t", "getNumTargets", (ins) + >, + InterfaceMethod< + "Returns the number of positive control qubits.", + "size_t", "getNumPosControls", (ins) + >, + InterfaceMethod< + "Returns the number of negative control qubits.", + "size_t", "getNumNegControls", (ins) + >, + InterfaceMethod< + "Returns the i-th target qubit.", + "::mlir::Value", "getTarget", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th positive control qubit.", + "::mlir::Value", "getPosControl", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th negative control qubit.", + "::mlir::Value", "getNegControl", (ins "size_t":$i) + >, + + // Value semantics threading (identity in reference semantics) + InterfaceMethod< + "Returns the i-th input qubit (targets + controls combined).", + "::mlir::Value", "getInput", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th output qubit (targets + controls combined). " + "In reference semantics, returns the same as getInput.", + "::mlir::Value", "getOutput", (ins "size_t":$i), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return $_op.getInput(i); + }] + >, + InterfaceMethod< + "Returns the output qubit corresponding to the given input qubit. " + "In reference semantics, returns the input itself.", + "::mlir::Value", "getOutputForInput", (ins "::mlir::Value":$input), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return input; + }] + >, + InterfaceMethod< + "Returns the input qubit corresponding to the given output qubit. " + "In reference semantics, returns the output itself.", + "::mlir::Value", "getInputForOutput", (ins "::mlir::Value":$output), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return output; + }] + >, + + // Parameter handling + InterfaceMethod< + "Returns the number of parameters.", + "size_t", "getNumParams", (ins) + >, + + // Matrix extraction + InterfaceMethod< + "Returns true if the operation has a static unitary matrix.", + "bool", "hasStaticUnitary", (ins) + >, + InterfaceMethod< + "Attempts to extract the static unitary matrix. " + "Returns std::nullopt if the operation is symbolic or dynamic.", + "std::optional<::mlir::DenseElementsAttr>", "tryGetStaticMatrix", (ins) + >, + + // Modifier state + InterfaceMethod< + "Returns true if the operation is inverted.", + "bool", "isInverted", (ins), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return false; + }] + >, + InterfaceMethod< + "Returns the power exponent if applicable, otherwise std::nullopt.", + "std::optional", "getPower", (ins), + /*methodBody=*/"", /*defaultImplementation=*/[{ + return std::nullopt; + }] + >, + + // Identification + InterfaceMethod< + "Returns the base symbol/mnemonic of the operation.", + "::llvm::StringRef", "getBaseSymbol", (ins) + >, + ]; +} + +#endif // QUARTZ_INTERFACES diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h new file mode 100644 index 0000000000..119458f6c3 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MLIR_DIALECT_QUARTZ_IR_QUARTZOPS_H +#define MLIR_DIALECT_QUARTZ_IR_QUARTZOPS_H + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Quartz/IR/QuartzTypes.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#define GET_OP_CLASSES +#include "mlir/Dialect/Quartz/IR/QuartzOps.h.inc" + +#endif // MLIR_DIALECT_QUARTZ_IR_QUARTZOPS_H diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td new file mode 100644 index 0000000000..44ddc68f79 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -0,0 +1,100 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QUARTZ_OPS +#define QUARTZ_OPS + +include "mlir/Dialect/Quartz/IR/QuartzDialect.td" +include "mlir/Dialect/Quartz/IR/QuartzTypes.td" +include "mlir/Dialect/Quartz/IR/QuartzInterfaces.td" +include "mlir/IR/OpBase.td" +include "mlir/IR/BuiltinAttributes.td" +include "mlir/IR/BuiltinTypes.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +//===----------------------------------------------------------------------===// +// Base Operation Classes +//===----------------------------------------------------------------------===// + +class Quartz_Op traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// Resource Operations +//===----------------------------------------------------------------------===// + +def Quartz_AllocOp : Quartz_Op<"alloc", [Pure]> { + let summary = "Allocate a qubit dynamically"; + let description = [{ + Allocates a new qubit dynamically and returns a reference to it. + The qubit is initialized to the |0⟩ state. + + Example: + ```mlir + %q = quartz.alloc : !quartz.qubit + ``` + }]; + + let results = (outs Quartz_QubitType:$result); + let assemblyFormat = "attr-dict `:` type($result)"; +} + +def Quartz_DeallocOp : Quartz_Op<"dealloc"> { + let summary = "Deallocate a qubit"; + let description = [{ + Deallocates a qubit, releasing its resources. + + Example: + ```mlir + quartz.dealloc %q : !quartz.qubit + ``` + }]; + + let arguments = (ins Quartz_QubitType:$qubit); + let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset Operations +//===----------------------------------------------------------------------===// + +def Quartz_MeasureOp : Quartz_Op<"measure"> { + let summary = "Measure a qubit in the computational basis"; + let description = [{ + Measures a qubit in the computational (Z) basis, collapsing the state + and returning a classical bit result. + + Example: + ```mlir + %c = quartz.measure %q : !quartz.qubit -> i1 + ``` + }]; + + let arguments = (ins Quartz_QubitType:$qubit); + let results = (outs I1:$result); + let assemblyFormat = "$qubit attr-dict `:` type($qubit) `->` type($result)"; +} + +def Quartz_ResetOp : Quartz_Op<"reset"> { + let summary = "Reset a qubit to |0⟩ state"; + let description = [{ + Resets a qubit to the |0⟩ state, regardless of its current state. + + Example: + ```mlir + quartz.reset %q : !quartz.qubit + ``` + }]; + + let arguments = (ins Quartz_QubitType:$qubit); + let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; + + let hasCanonicalizer = 1; +} + +#endif // QUARTZ_OPS diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h new file mode 100644 index 0000000000..23a443b224 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MLIR_DIALECT_QUARTZ_IR_QUARTZTYPES_H +#define MLIR_DIALECT_QUARTZ_IR_QUARTZTYPES_H + +#include "mlir/IR/Types.h" + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.h.inc" + +#endif // MLIR_DIALECT_QUARTZ_IR_QUARTZTYPES_H diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td new file mode 100644 index 0000000000..26450c68e3 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QUARTZ_TYPES +#define QUARTZ_TYPES + +include "mlir/Dialect/Quartz/IR/QuartzDialect.td" +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/BuiltinTypeInterfaces.td" + +//===----------------------------------------------------------------------===// +// Quartz Type Definitions +//===----------------------------------------------------------------------===// + +class Quartz_Type traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + +def Quartz_QubitType : Quartz_Type<"Qubit", "qubit", [MemRefElementTypeInterface]> { + let summary = "Quartz qubit reference type"; + let description = [{ + The `!quartz.qubit` type represents a reference to a quantum bit in the + Quartz dialect. Operations using this type modify qubits in place using + reference semantics, similar to how classical imperative languages handle + mutable references. + + This type can be used as an element type in memref to represent quantum + registers: + ```mlir + %qreg = memref.alloc() : memref<2x!quartz.qubit> + %q0 = memref.load %qreg[%c0] : memref<2x!quartz.qubit> + ``` + }]; +} + +#endif // QUARTZ_TYPES diff --git a/mlir/lib/Dialect/CMakeLists.txt b/mlir/lib/Dialect/CMakeLists.txt index 1a85073274..5cf34f8170 100644 --- a/mlir/lib/Dialect/CMakeLists.txt +++ b/mlir/lib/Dialect/CMakeLists.txt @@ -8,3 +8,5 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) +add_subdirectory(Quartz) +add_subdirectory(Flux) diff --git a/mlir/lib/Dialect/Flux/CMakeLists.txt b/mlir/lib/Dialect/Flux/CMakeLists.txt new file mode 100644 index 0000000000..ace971aa36 --- /dev/null +++ b/mlir/lib/Dialect/Flux/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt new file mode 100644 index 0000000000..f24b81ef5e --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_dialect_library( + MLIRFluxDialect + FluxDialect.cpp + FluxOps.cpp + ADDITIONAL_HEADER_DIRS + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux + DEPENDS + MLIRFluxOpsIncGen + MLIRFluxInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRIR + MLIRSideEffectInterfaces) diff --git a/mlir/lib/Dialect/Flux/IR/FluxDialect.cpp b/mlir/lib/Dialect/Flux/IR/FluxDialect.cpp new file mode 100644 index 0000000000..024015bacb --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/FluxDialect.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include "mlir/Dialect/Flux/IR/FluxOps.h" +#include "mlir/Dialect/Flux/IR/FluxTypes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" + +using namespace mlir; +using namespace mlir::flux; + +#include "mlir/Dialect/Flux/IR/FluxOpsDialect.cpp.inc" + +void FluxDialect::initialize() { + addOperations< +#define GET_OP_LIST +#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" + >(); + registerTypes(); +} + +void FluxDialect::registerTypes() { + addTypes< +#define GET_TYPEDEF_LIST +#include "mlir/Dialect/Flux/IR/FluxOpsTypes.cpp.inc" + >(); +} diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp new file mode 100644 index 0000000000..d8d5b546d2 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxOps.h" + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/PatternMatch.h" + +using namespace mlir; +using namespace mlir::flux; + +//===----------------------------------------------------------------------===// +// ResetOp Canonicalization +//===----------------------------------------------------------------------===// + +void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + // Patterns will be added in future phases +} + +#define GET_OP_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" diff --git a/mlir/lib/Dialect/Quartz/CMakeLists.txt b/mlir/lib/Dialect/Quartz/CMakeLists.txt new file mode 100644 index 0000000000..ace971aa36 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(IR) diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt new file mode 100644 index 0000000000..e7d6cc2181 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_dialect_library( + MLIRQuartzDialect + QuartzDialect.cpp + QuartzOps.cpp + ADDITIONAL_HEADER_DIRS + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz + DEPENDS + MLIRQuartzOpsIncGen + MLIRQuartzInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRIR + MLIRSideEffectInterfaces) diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp new file mode 100644 index 0000000000..68f1dc016e --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include "mlir/Dialect/Quartz/IR/QuartzOps.h" +#include "mlir/Dialect/Quartz/IR/QuartzTypes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" + +using namespace mlir; +using namespace mlir::quartz; + +#include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.cpp.inc" + +void QuartzDialect::initialize() { + addOperations< +#define GET_OP_LIST +#include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" + >(); + registerTypes(); +} + +void QuartzDialect::registerTypes() { + addTypes< +#define GET_TYPEDEF_LIST +#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" + >(); +} diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp new file mode 100644 index 0000000000..b44ac980c4 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzOps.h" + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/PatternMatch.h" + +using namespace mlir; +using namespace mlir::quartz; + +//===----------------------------------------------------------------------===// +// ResetOp Canonicalization +//===----------------------------------------------------------------------===// + +void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + // Patterns will be added in future phases +} + +#define GET_OP_CLASSES +#include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" From f4491d6ba95b5abc1d22fc8fd70f08d1e13c742a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 7 Oct 2025 09:51:15 +0200 Subject: [PATCH 021/419] =?UTF-8?q?=F0=9F=9A=A7=20first=20version=20of=20t?= =?UTF-8?q?he=20flux=20dialect=20construct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 7 +- .../mlir/Dialect/Flux/IR/FluxDialect.td | 54 -------- mlir/include/mlir/Dialect/Flux/IR/FluxOps.h | 22 ---- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 119 +++++++++++++++--- mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h | 19 --- .../include/mlir/Dialect/Flux/IR/FluxTypes.td | 41 ------ mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 23 +++- mlir/lib/Dialect/Flux/IR/FluxDialect.cpp | 36 ------ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 53 ++++++-- 9 files changed, 170 insertions(+), 204 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td delete mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxOps.h delete mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h delete mode 100644 mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td delete mode 100644 mlir/lib/Dialect/Flux/IR/FluxDialect.cpp diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index ef88391afc..7576288641 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -11,6 +11,7 @@ #pragma once #include +#include // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) @@ -18,7 +19,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wambiguous-reversed-operator" #endif -#include "mlir/Interfaces/InferTypeOpInterface.h" +#include #if defined(__clang__) #pragma clang diagnostic pop #endif @@ -29,14 +30,14 @@ // Dialect //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Flux/IR/FluxOpsDialect.h.inc" // IWYU pragma: export +#include "mlir/Dialect/Flux/IR/FluxOpsDialect.h.inc" //===----------------------------------------------------------------------===// // Types //===----------------------------------------------------------------------===// #define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOpsTypes.h.inc" // IWYU pragma: export +#include "mlir/Dialect/Flux/IR/FluxOpsTypes.h.inc" //===----------------------------------------------------------------------===// // Interfaces diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td deleted file mode 100644 index 74ca17ef1c..0000000000 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.td +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -#ifndef FLUX_DIALECT -#define FLUX_DIALECT - -include "mlir/IR/DialectBase.td" - -//===----------------------------------------------------------------------===// -// Flux Dialect Definition -//===----------------------------------------------------------------------===// - -def Flux_Dialect : Dialect { - let name = "flux"; - - let summary = "The Flux (value semantics) dialect for quantum computing."; - - let description = [{ - The Flux dialect uses **value semantics** where quantum operations - consume input qubits and produce new output values, following the - functional programming and SSA paradigm. This model enables: - - - Powerful compiler optimizations through clear dataflow - - Safe reordering and parallelization analysis - - Advanced transformation passes - - Explicit dependency tracking - - The name "Flux" captures the flowing, transformative nature of - value-based representations—quantum states flow through operations, - each transformation producing new values like a river flowing through - a landscape. - - Example: - ```mlir - %q_out = flux.h %q_in // Consumes %q_in, produces %q_out - %q0_out, %q1_out = flux.swap %q0_in, %q1_in // Consumes inputs, produces outputs - ``` - }]; - - let cppNamespace = "::mlir::flux"; - - let useDefaultTypePrinterParser = 1; - - let extraClassDeclaration = [{ - void registerTypes(); - }]; -} - -#endif // FLUX_DIALECT diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.h b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.h deleted file mode 100644 index 43bf126150..0000000000 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#ifndef MLIR_DIALECT_FLUX_IR_FLUXOPS_H -#define MLIR_DIALECT_FLUX_IR_FLUXOPS_H - -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Flux/IR/FluxTypes.h" -#include "mlir/IR/OpDefinition.h" -#include "mlir/Interfaces/SideEffectInterfaces.h" - -#define GET_OP_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOps.h.inc" - -#endif // MLIR_DIALECT_FLUX_IR_FLUXOPS_H diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 54062392ec..fb1327bfc5 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -6,29 +6,90 @@ // // Licensed under the MIT License -#ifndef FLUX_OPS -#define FLUX_OPS +#ifndef FluxOPS +#define FluxOPS -include "mlir/Dialect/Flux/IR/FluxDialect.td" -include "mlir/Dialect/Flux/IR/FluxTypes.td" -include "mlir/Dialect/Flux/IR/FluxInterfaces.td" +include "mlir/IR/BuiltinTypeInterfaces.td" +include "mlir/IR/DialectBase.td" +include "mlir/IR/EnumAttr.td" include "mlir/IR/OpBase.td" -include "mlir/IR/BuiltinAttributes.td" -include "mlir/IR/BuiltinTypes.td" +include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +def FluxDialect : Dialect { + let name = "flux"; + + let summary = "The Flux (value semantics) dialect for quantum computing."; + + let description = [{ + The Flux dialect uses **value semantics** where quantum operations + consume input qubits and produce new output values, following the + functional programming and SSA paradigm. This model enables: + + - Powerful compiler optimizations through clear dataflow + - Safe reordering and parallelization analysis + - Advanced transformation passes + - Explicit dependency tracking + + The name "Flux" captures the flowing, transformative nature of + value-based representations—quantum states flow through operations, + each transformation producing new values like a river flowing through + a landscape. + + Example: + ```mlir + %q_out = flux.h %q_in // Consumes %q_in, produces %q_out + %q0_out, %q1_out = flux.swap %q0_in, %q1_in // Consumes inputs, produces outputs + ``` + }]; + + let cppNamespace = "::mlir::flux"; + + let useDefaultTypePrinterParser = 1; +} + +//===----------------------------------------------------------------------===// +// Flux Type Definitions +//===----------------------------------------------------------------------===// + +class FluxType traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + +def QubitType : FluxType<"Qubit", "qubit", [MemRefElementTypeInterface]> { + let summary = "Flux qubit value type"; + let description = [{ + The `!flux.qubit` type represents an SSA value holding a quantum bit + in the Flux dialect. Operations using this type consume input qubits + and produce new output qubits following value semantics and the SSA + paradigm, enabling powerful dataflow analysis and optimization. + + Example: + ```mlir + %q0 = flux.alloc : !flux.qubit + %q1 = flux.h %q0 : !flux.qubit -> !flux.qubit + %q2 = flux.x %q1 : !flux.qubit -> !flux.qubit + ``` + }]; +} + //===----------------------------------------------------------------------===// // Base Operation Classes //===----------------------------------------------------------------------===// -class Flux_Op traits = []> : - Op; +class FluxOp traits = []> : + Op; //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// -def Flux_AllocOp : Flux_Op<"alloc", [Pure]> { +def AllocOp : FluxOp<"alloc", [Pure]> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns an SSA value representing it. @@ -40,11 +101,11 @@ def Flux_AllocOp : Flux_Op<"alloc", [Pure]> { ``` }]; - let results = (outs Flux_QubitType:$result); + let results = (outs QubitType:$result); let assemblyFormat = "attr-dict `:` type($result)"; } -def Flux_DeallocOp : Flux_Op<"dealloc"> { +def DeallocOp : FluxOp<"dealloc", [Pure]> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. @@ -55,15 +116,33 @@ def Flux_DeallocOp : Flux_Op<"dealloc"> { ``` }]; - let arguments = (ins Flux_QubitType:$qubit); + let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; } +def StaticOp : FluxOp<"static", [Pure]> { + let summary = "Retrieve a static qubit by index"; + let description = [{ + The `flux.static` operation produces an SSA value representing a qubit + identified by a static index. This is useful for referring to fixed + qubits in a quantum program or to hardware-mapped qubits. + + Example: + ```mlir + %q = flux.static 0 : !flux.qubit + ``` + }]; + + let arguments = (ins ConfinedAttr:$index); + let results = (outs QubitType:$qubit); + let assemblyFormat = "$index attr-dict `:` type($qubit)"; +} + //===----------------------------------------------------------------------===// // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def Flux_MeasureOp : Flux_Op<"measure"> { +def MeasureOp : FluxOp<"measure", [Pure]> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -75,11 +154,11 @@ def Flux_MeasureOp : Flux_Op<"measure"> { ``` }]; - let arguments = (ins Flux_QubitType:$qubit_in); - let results = (outs Flux_QubitType:$qubit_out, I1:$result); + let arguments = (ins QubitType:$qubit_in); + let results = (outs QubitType:$qubit_out, I1:$result); } -def Flux_ResetOp : Flux_Op<"reset"> { +def ResetOp : FluxOp<"reset", [Pure, Idempotent, SameOperandsAndResultType]> { let summary = "Reset a qubit to |0⟩ state"; let description = [{ Resets a qubit to the |0⟩ state, regardless of its current state, @@ -91,11 +170,11 @@ def Flux_ResetOp : Flux_Op<"reset"> { ``` }]; - let arguments = (ins Flux_QubitType:$qubit_in); - let results = (outs Flux_QubitType:$qubit_out); + let arguments = (ins QubitType:$qubit_in); + let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let hasCanonicalizer = 1; } -#endif // FLUX_OPS +#endif // FluxOPS diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h b/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h deleted file mode 100644 index 895aed6eec..0000000000 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#ifndef MLIR_DIALECT_FLUX_IR_FLUXTYPES_H -#define MLIR_DIALECT_FLUX_IR_FLUXTYPES_H - -#include "mlir/IR/Types.h" - -#define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOpsTypes.h.inc" - -#endif // MLIR_DIALECT_FLUX_IR_FLUXTYPES_H diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td b/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td deleted file mode 100644 index d39ecdf8cd..0000000000 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxTypes.td +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -#ifndef FLUX_TYPES -#define FLUX_TYPES - -include "mlir/Dialect/Flux/IR/FluxDialect.td" -include "mlir/IR/AttrTypeBase.td" - -//===----------------------------------------------------------------------===// -// Flux Type Definitions -//===----------------------------------------------------------------------===// - -class Flux_Type traits = []> - : TypeDef { - let mnemonic = typeMnemonic; -} - -def Flux_QubitType : Flux_Type<"Qubit", "qubit"> { - let summary = "Flux qubit value type"; - let description = [{ - The `!flux.qubit` type represents an SSA value holding a quantum bit - in the Flux dialect. Operations using this type consume input qubits - and produce new output qubits following value semantics and the SSA - paradigm, enabling powerful dataflow analysis and optimization. - - Example: - ```mlir - %q0 = flux.alloc : !flux.qubit - %q1 = flux.h %q0 : !flux.qubit -> !flux.qubit - %q2 = flux.x %q1 : !flux.qubit -> !flux.qubit - ``` - }]; -} - -#endif // FLUX_TYPES diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt index f24b81ef5e..338c7679f7 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -7,8 +7,7 @@ # Licensed under the MIT License add_mlir_dialect_library( - MLIRFluxDialect - FluxDialect.cpp + MLIRFlux FluxOps.cpp ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux @@ -19,3 +18,23 @@ add_mlir_dialect_library( PUBLIC MLIRIR MLIRSideEffectInterfaces) + +# collect header files +file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Flux/IR/*.h") +file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/Flux/IR/*.inc") + +# add public headers using file sets +target_sources( + MLIRFlux + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${IR_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${IR_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/Flux/IR/FluxDialect.cpp b/mlir/lib/Dialect/Flux/IR/FluxDialect.cpp deleted file mode 100644 index 024015bacb..0000000000 --- a/mlir/lib/Dialect/Flux/IR/FluxDialect.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Flux/IR/FluxDialect.h" - -#include "mlir/Dialect/Flux/IR/FluxOps.h" -#include "mlir/Dialect/Flux/IR/FluxTypes.h" -#include "mlir/IR/Builders.h" -#include "mlir/IR/DialectImplementation.h" - -using namespace mlir; -using namespace mlir::flux; - -#include "mlir/Dialect/Flux/IR/FluxOpsDialect.cpp.inc" - -void FluxDialect::initialize() { - addOperations< -#define GET_OP_LIST -#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" - >(); - registerTypes(); -} - -void FluxDialect::registerTypes() { - addTypes< -#define GET_TYPEDEF_LIST -#include "mlir/Dialect/Flux/IR/FluxOpsTypes.cpp.inc" - >(); -} diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index d8d5b546d2..cde29cd3a5 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -8,15 +8,57 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxOps.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" // IWYU pragma: associated -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/IR/OpImplementation.h" -#include "mlir/IR/PatternMatch.h" +// The following headers are needed for some template instantiations. +// IWYU pragma: begin_keep +#include +#include +#include +// IWYU pragma: end_keep using namespace mlir; using namespace mlir::flux; +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Flux/IR/FluxOpsDialect.cpp.inc" + +void FluxDialect::initialize() { + // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape) + addTypes< +#define GET_TYPEDEF_LIST +#include "mlir/Dialect/Flux/IR/FluxOpsTypes.cpp.inc" + >(); + + addOperations< +#define GET_OP_LIST +#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" + >(); +} + +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOpsTypes.cpp.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Flux/IR/FluxInterfaces.cpp.inc" + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" + //===----------------------------------------------------------------------===// // ResetOp Canonicalization //===----------------------------------------------------------------------===// @@ -25,6 +67,3 @@ void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { // Patterns will be added in future phases } - -#define GET_OP_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" From 905f1acc00df50b8f209e90291a2f3c46c5d662f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 7 Oct 2025 10:05:00 +0200 Subject: [PATCH 022/419] =?UTF-8?q?=F0=9F=9A=A7=20first=20version=20of=20t?= =?UTF-8?q?he=20quartz=20dialect=20construct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../mlir/Dialect/Quartz/IR/CMakeLists.txt | 22 +--- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 44 ++++++- .../mlir/Dialect/Quartz/IR/QuartzDialect.td | 53 --------- .../mlir/Dialect/Quartz/IR/QuartzOps.h | 22 ---- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 110 +++++++++++++++--- .../mlir/Dialect/Quartz/IR/QuartzTypes.h | 19 --- .../mlir/Dialect/Quartz/IR/QuartzTypes.td | 42 ------- mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 23 +++- mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp | 36 ------ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 46 ++++++-- 10 files changed, 198 insertions(+), 219 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td delete mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h delete mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h delete mode 100644 mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td delete mode 100644 mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp diff --git a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt index 5fa3452210..34065fac73 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt @@ -6,20 +6,10 @@ # # Licensed under the MIT License -set(LLVM_TARGET_DEFINITIONS QuartzOps.td) -mlir_tablegen(QuartzOps.h.inc -gen-op-decls) -mlir_tablegen(QuartzOps.cpp.inc -gen-op-defs) -mlir_tablegen(QuartzOpsDialect.h.inc -gen-dialect-decls -dialect=quartz) -mlir_tablegen(QuartzOpsDialect.cpp.inc -gen-dialect-defs -dialect=quartz) -mlir_tablegen(QuartzOpsTypes.h.inc -gen-typedef-decls -typedefs-dialect=quartz) -mlir_tablegen(QuartzOpsTypes.cpp.inc -gen-typedef-defs -typedefs-dialect=quartz) -add_public_tablegen_target(MLIRQuartzOpsIncGen) +set(DIALECT_NAME "Quartz") +set(DIALECT_NAME_UPPER "FLUX") -add_mlir_doc(QuartzDialect QuartzDialect Dialects/ -gen-dialect-doc) -add_mlir_doc(QuartzOps QuartzOps Dialects/ -gen-op-doc) -add_mlir_doc(QuartzTypes QuartzTypes Dialects/ -gen-typedef-doc) - -set(LLVM_TARGET_DEFINITIONS QuartzInterfaces.td) -mlir_tablegen(QuartzOpsInterfaces.h.inc -gen-op-interface-decls) -mlir_tablegen(QuartzOpsInterfaces.cpp.inc -gen-op-interface-defs) -add_public_tablegen_target(MLIRQuartzInterfacesIncGen) +add_mlir_dialect(QuartzOps quartz) +add_mlir_interface(QuartzInterfaces) +add_mlir_doc(QuartzOps MLIRQuartzDialect Dialects/ -gen-dialect-doc) +add_mlir_doc(QuartzInterfaces MLIRQuartzInterfaces Dialects/ -gen-op-interface-docs -dialect=quartz) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index f43db44a06..c3ecc9e972 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -8,10 +8,46 @@ * Licensed under the MIT License */ -#ifndef MLIR_DIALECT_QUARTZ_IR_QUARTZDIALECT_H -#define MLIR_DIALECT_QUARTZ_IR_QUARTZDIALECT_H +#pragma once + +#include +#include + +// Suppress warnings about ambiguous reversed operators in MLIR +// (see https://github.com/llvm/llvm-project/issues/45853) +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wambiguous-reversed-operator" +#endif +#include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#define DIALECT_NAME_FLUX "quartz" + +//===----------------------------------------------------------------------===// +// Dialect +//===----------------------------------------------------------------------===// #include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.h.inc" -#include "mlir/IR/Dialect.h" -#endif // MLIR_DIALECT_QUARTZ_IR_QUARTZDIALECT_H +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.h.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "mlir/Dialect/Quartz/IR/QuartzOps.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td deleted file mode 100644 index 6abddf4006..0000000000 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.td +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -#ifndef QUARTZ_DIALECT -#define QUARTZ_DIALECT - -include "mlir/IR/DialectBase.td" - -//===----------------------------------------------------------------------===// -// Quartz Dialect Definition -//===----------------------------------------------------------------------===// - -def Quartz_Dialect : Dialect { - let name = "quartz"; - - let summary = "The Quartz (reference semantics) dialect for quantum computing."; - - let description = [{ - The Quartz dialect uses **reference semantics** where quantum operations - modify qubits in place, similar to how hardware physically transforms - quantum states. This model provides: - - - Natural mapping to hardware execution models - - Intuitive representation for circuit descriptions - - Direct compatibility with imperative quantum programming languages - - Straightforward backend code generation - - The name "Quartz" reflects the crystalline, structured nature of - hardware-oriented representations—operations have fixed positions and - transform states in place, like atoms in a crystal lattice. - - Example: - ```mlir - quartz.h %q // Applies Hadamard to qubit %q in place - quartz.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets - ``` - }]; - - let cppNamespace = "::mlir::quartz"; - - let useDefaultTypePrinterParser = 1; - - let extraClassDeclaration = [{ - void registerTypes(); - }]; -} - -#endif // QUARTZ_DIALECT diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h deleted file mode 100644 index 119458f6c3..0000000000 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#ifndef MLIR_DIALECT_QUARTZ_IR_QUARTZOPS_H -#define MLIR_DIALECT_QUARTZ_IR_QUARTZOPS_H - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Quartz/IR/QuartzTypes.h" -#include "mlir/IR/OpDefinition.h" -#include "mlir/Interfaces/SideEffectInterfaces.h" - -#define GET_OP_CLASSES -#include "mlir/Dialect/Quartz/IR/QuartzOps.h.inc" - -#endif // MLIR_DIALECT_QUARTZ_IR_QUARTZOPS_H diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 44ddc68f79..cb81a47af9 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -9,26 +9,86 @@ #ifndef QUARTZ_OPS #define QUARTZ_OPS -include "mlir/Dialect/Quartz/IR/QuartzDialect.td" -include "mlir/Dialect/Quartz/IR/QuartzTypes.td" -include "mlir/Dialect/Quartz/IR/QuartzInterfaces.td" +include "mlir/IR/BuiltinTypeInterfaces.td" +include "mlir/IR/DialectBase.td" +include "mlir/IR/EnumAttr.td" include "mlir/IR/OpBase.td" -include "mlir/IR/BuiltinAttributes.td" -include "mlir/IR/BuiltinTypes.td" +include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" +//===----------------------------------------------------------------------===// +// Quartz Dialect Definition +//===----------------------------------------------------------------------===// + +def QuartzDialect : Dialect { + let name = "quartz"; + + let summary = "The Quartz (reference semantics) dialect for quantum computing."; + + let description = [{ + The Quartz dialect uses **reference semantics** where quantum operations + modify qubits in place, similar to how hardware physically transforms + quantum states. This model provides: + + - Natural mapping to hardware execution models + - Intuitive representation for circuit descriptions + - Direct compatibility with imperative quantum programming languages + - Straightforward backend code generation + + The name "Quartz" reflects the crystalline, structured nature of + hardware-oriented representations—operations have fixed positions and + transform states in place, like atoms in a crystal lattice. + + Example: + ```mlir + quartz.h %q // Applies Hadamard to qubit %q in place + quartz.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets + ``` + }]; + + let cppNamespace = "::mlir::quartz"; + + let useDefaultTypePrinterParser = 1; +} + +//===----------------------------------------------------------------------===// +// Quartz Type Definitions +//===----------------------------------------------------------------------===// + +class QuartzType traits = []> + : TypeDef { + let mnemonic = typeMnemonic; +} + +def QubitType : QuartzType<"Qubit", "qubit", [MemRefElementTypeInterface]> { + let summary = "Quartz qubit reference type"; + let description = [{ + The `!quartz.qubit` type represents a reference to a quantum bit in the + Quartz dialect. Operations using this type modify qubits in place using + reference semantics, similar to how classical imperative languages handle + mutable references. + + This type can be used as an element type in memref to represent quantum + registers: + ```mlir + %qreg = memref.alloc() : memref<2x!quartz.qubit> + %q0 = memref.load %qreg[%c0] : memref<2x!quartz.qubit> + ``` + }]; +} + //===----------------------------------------------------------------------===// // Base Operation Classes //===----------------------------------------------------------------------===// -class Quartz_Op traits = []> : - Op; +class QuartzOp traits = []> : + Op; //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// -def Quartz_AllocOp : Quartz_Op<"alloc", [Pure]> { +def AllocOp : QuartzOp<"alloc", [Pure]> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns a reference to it. @@ -40,11 +100,11 @@ def Quartz_AllocOp : Quartz_Op<"alloc", [Pure]> { ``` }]; - let results = (outs Quartz_QubitType:$result); + let results = (outs QubitType:$result); let assemblyFormat = "attr-dict `:` type($result)"; } -def Quartz_DeallocOp : Quartz_Op<"dealloc"> { +def DeallocOp : QuartzOp<"dealloc"> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. @@ -55,15 +115,33 @@ def Quartz_DeallocOp : Quartz_Op<"dealloc"> { ``` }]; - let arguments = (ins Quartz_QubitType:$qubit); + let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; } +def StaticOp : QuartzOp<"static", [Pure]> { + let summary = "Retrieve a static qubit by index"; + let description = [{ + The `quartz.static` operation produces an SSA value representing a qubit + identified by a static index. This is useful for referring to fixed + qubits in a quantum program or to hardware-mapped qubits. + + Example: + ```mlir + %q = quartz.static 0 : !quartz.qubit + ``` + }]; + + let arguments = (ins ConfinedAttr:$index); + let results = (outs QubitType:$qubit); + let assemblyFormat = "$index attr-dict `:` type($qubit)"; +} + //===----------------------------------------------------------------------===// // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def Quartz_MeasureOp : Quartz_Op<"measure"> { +def MeasureOp : QuartzOp<"measure"> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -75,12 +153,12 @@ def Quartz_MeasureOp : Quartz_Op<"measure"> { ``` }]; - let arguments = (ins Quartz_QubitType:$qubit); + let arguments = (ins QubitType:$qubit); let results = (outs I1:$result); let assemblyFormat = "$qubit attr-dict `:` type($qubit) `->` type($result)"; } -def Quartz_ResetOp : Quartz_Op<"reset"> { +def ResetOp : QuartzOp<"reset"> { let summary = "Reset a qubit to |0⟩ state"; let description = [{ Resets a qubit to the |0⟩ state, regardless of its current state. @@ -91,10 +169,8 @@ def Quartz_ResetOp : Quartz_Op<"reset"> { ``` }]; - let arguments = (ins Quartz_QubitType:$qubit); + let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; - - let hasCanonicalizer = 1; } #endif // QUARTZ_OPS diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h deleted file mode 100644 index 23a443b224..0000000000 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#ifndef MLIR_DIALECT_QUARTZ_IR_QUARTZTYPES_H -#define MLIR_DIALECT_QUARTZ_IR_QUARTZTYPES_H - -#include "mlir/IR/Types.h" - -#define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.h.inc" - -#endif // MLIR_DIALECT_QUARTZ_IR_QUARTZTYPES_H diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td deleted file mode 100644 index 26450c68e3..0000000000 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzTypes.td +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -// Copyright (c) 2025 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -#ifndef QUARTZ_TYPES -#define QUARTZ_TYPES - -include "mlir/Dialect/Quartz/IR/QuartzDialect.td" -include "mlir/IR/AttrTypeBase.td" -include "mlir/IR/BuiltinTypeInterfaces.td" - -//===----------------------------------------------------------------------===// -// Quartz Type Definitions -//===----------------------------------------------------------------------===// - -class Quartz_Type traits = []> - : TypeDef { - let mnemonic = typeMnemonic; -} - -def Quartz_QubitType : Quartz_Type<"Qubit", "qubit", [MemRefElementTypeInterface]> { - let summary = "Quartz qubit reference type"; - let description = [{ - The `!quartz.qubit` type represents a reference to a quantum bit in the - Quartz dialect. Operations using this type modify qubits in place using - reference semantics, similar to how classical imperative languages handle - mutable references. - - This type can be used as an element type in memref to represent quantum - registers: - ```mlir - %qreg = memref.alloc() : memref<2x!quartz.qubit> - %q0 = memref.load %qreg[%c0] : memref<2x!quartz.qubit> - ``` - }]; -} - -#endif // QUARTZ_TYPES diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt index e7d6cc2181..d22d95d4b2 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -7,8 +7,7 @@ # Licensed under the MIT License add_mlir_dialect_library( - MLIRQuartzDialect - QuartzDialect.cpp + MLIRQuartz QuartzOps.cpp ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz @@ -19,3 +18,23 @@ add_mlir_dialect_library( PUBLIC MLIRIR MLIRSideEffectInterfaces) + +# collect header files +file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/IR/*.h") +file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/Quartz/IR/*.inc") + +# add public headers using file sets +target_sources( + MLIRQuartz + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${IR_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${IR_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp deleted file mode 100644 index 68f1dc016e..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/QuartzDialect.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" - -#include "mlir/Dialect/Quartz/IR/QuartzOps.h" -#include "mlir/Dialect/Quartz/IR/QuartzTypes.h" -#include "mlir/IR/Builders.h" -#include "mlir/IR/DialectImplementation.h" - -using namespace mlir; -using namespace mlir::quartz; - -#include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.cpp.inc" - -void QuartzDialect::initialize() { - addOperations< -#define GET_OP_LIST -#include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" - >(); - registerTypes(); -} - -void QuartzDialect::registerTypes() { - addTypes< -#define GET_TYPEDEF_LIST -#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" - >(); -} diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index b44ac980c4..42eadb115a 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -8,23 +8,53 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzOps.h" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" // IWYU pragma: associated -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/IR/OpImplementation.h" -#include "mlir/IR/PatternMatch.h" +// The following headers are needed for some template instantiations. +// IWYU pragma: begin_keep +#include +#include +#include +// IWYU pragma: end_keep using namespace mlir; using namespace mlir::quartz; //===----------------------------------------------------------------------===// -// ResetOp Canonicalization +// Dialect //===----------------------------------------------------------------------===// -void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - // Patterns will be added in future phases +#include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.cpp.inc" + +void QuartzDialect::initialize() { + // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape) + addTypes< +#define GET_TYPEDEF_LIST +#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" + >(); + + addOperations< +#define GET_OP_LIST +#include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" + >(); } +//===----------------------------------------------------------------------===// +// Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" + +//===----------------------------------------------------------------------===// +// Interfaces +//===----------------------------------------------------------------------===// + +#include "mlir/Dialect/Quartz/IR/QuartzInterfaces.cpp.inc" + +//===----------------------------------------------------------------------===// +// Operations +//===----------------------------------------------------------------------===// + #define GET_OP_CLASSES #include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" From 0482840998d0611067a8f45d3b18cd1643f0e2ef Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 7 Oct 2025 17:59:16 +0200 Subject: [PATCH 023/419] =?UTF-8?q?=F0=9F=A9=B9=20being=20conservative=20w?= =?UTF-8?q?ith=20the=20`Pure`=20trait?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 10 +++++----- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index fb1327bfc5..1ce6c8ac50 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -89,7 +89,7 @@ class FluxOp traits = []> : // Resource Operations //===----------------------------------------------------------------------===// -def AllocOp : FluxOp<"alloc", [Pure]> { +def AllocOp : FluxOp<"alloc"> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns an SSA value representing it. @@ -105,7 +105,7 @@ def AllocOp : FluxOp<"alloc", [Pure]> { let assemblyFormat = "attr-dict `:` type($result)"; } -def DeallocOp : FluxOp<"dealloc", [Pure]> { +def DeallocOp : FluxOp<"dealloc"> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. @@ -120,7 +120,7 @@ def DeallocOp : FluxOp<"dealloc", [Pure]> { let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; } -def StaticOp : FluxOp<"static", [Pure]> { +def StaticOp : FluxOp<"static"> { let summary = "Retrieve a static qubit by index"; let description = [{ The `flux.static` operation produces an SSA value representing a qubit @@ -142,7 +142,7 @@ def StaticOp : FluxOp<"static", [Pure]> { // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp : FluxOp<"measure", [Pure]> { +def MeasureOp : FluxOp<"measure"> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -158,7 +158,7 @@ def MeasureOp : FluxOp<"measure", [Pure]> { let results = (outs QubitType:$qubit_out, I1:$result); } -def ResetOp : FluxOp<"reset", [Pure, Idempotent, SameOperandsAndResultType]> { +def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { let summary = "Reset a qubit to |0⟩ state"; let description = [{ Resets a qubit to the |0⟩ state, regardless of its current state, diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index cb81a47af9..409eadd46f 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -88,7 +88,7 @@ class QuartzOp traits = []> : // Resource Operations //===----------------------------------------------------------------------===// -def AllocOp : QuartzOp<"alloc", [Pure]> { +def AllocOp : QuartzOp<"alloc"> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns a reference to it. From fda806457ccb3b51dd6a54710825b512c4a3c181 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 7 Oct 2025 21:49:36 +0200 Subject: [PATCH 024/419] =?UTF-8?q?=F0=9F=9A=A7=20dialect=20adjustments=20?= =?UTF-8?q?to=20allow=20annotating=20qubit=20register=20information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 28 +++++++++++++--- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 33 +++++++++++++------ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 26 +++++++++++---- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 23 +++++++++++++ 4 files changed, 89 insertions(+), 21 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 1ce6c8ac50..628ac95ad1 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -61,7 +61,7 @@ class FluxType traits = []> let mnemonic = typeMnemonic; } -def QubitType : FluxType<"Qubit", "qubit", [MemRefElementTypeInterface]> { +def QubitType : FluxType<"Qubit", "qubit"> { let summary = "Flux qubit value type"; let description = [{ The `!flux.qubit` type represents an SSA value holding a quantum bit @@ -95,14 +95,34 @@ def AllocOp : FluxOp<"alloc"> { Allocates a new qubit dynamically and returns an SSA value representing it. The qubit is initialized to the |0⟩ state. - Example: + Optionally, the qubit can be part of a register by specifying: + - `register_name`: The name of the register this qubit belongs to + - `register_size`: The total size of the register + - `register_index`: The index of this qubit within the register + + Example (single qubit): ```mlir %q = flux.alloc : !flux.qubit ``` + + Example (qubits in a register): + ```mlir + %q0 = flux.alloc q[3, 0] : !flux.qubit + %q1 = flux.alloc q[3, 1] : !flux.qubit + %q2 = flux.alloc q[3, 2] : !flux.qubit + ``` }]; + let arguments = (ins OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); let results = (outs QubitType:$result); - let assemblyFormat = "attr-dict `:` type($result)"; + let assemblyFormat = [{ + ($register_name^ `[` $register_size `,` $register_index `]`)? + attr-dict `:` type($result) + }]; + + let hasVerifier = 1; } def DeallocOp : FluxOp<"dealloc"> { @@ -173,8 +193,6 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { let arguments = (ins QubitType:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; - - let hasCanonicalizer = 1; } #endif // FluxOPS diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 409eadd46f..a849ed05af 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -60,20 +60,13 @@ class QuartzType traits = []> let mnemonic = typeMnemonic; } -def QubitType : QuartzType<"Qubit", "qubit", [MemRefElementTypeInterface]> { +def QubitType : QuartzType<"Qubit", "qubit"> { let summary = "Quartz qubit reference type"; let description = [{ The `!quartz.qubit` type represents a reference to a quantum bit in the Quartz dialect. Operations using this type modify qubits in place using reference semantics, similar to how classical imperative languages handle mutable references. - - This type can be used as an element type in memref to represent quantum - registers: - ```mlir - %qreg = memref.alloc() : memref<2x!quartz.qubit> - %q0 = memref.load %qreg[%c0] : memref<2x!quartz.qubit> - ``` }]; } @@ -94,14 +87,34 @@ def AllocOp : QuartzOp<"alloc"> { Allocates a new qubit dynamically and returns a reference to it. The qubit is initialized to the |0⟩ state. - Example: + Optionally, the qubit can be part of a register by specifying: + - `register_name`: The name of the register this qubit belongs to + - `register_size`: The total size of the register + - `register_index`: The index of this qubit within the register + + Example (single qubit): ```mlir %q = quartz.alloc : !quartz.qubit ``` + + Example (qubits in a register): + ```mlir + %q0 = quartz.alloc q[3, 0] : !quartz.qubit + %q1 = quartz.alloc q[3, 1] : !quartz.qubit + %q2 = quartz.alloc q[3, 2] : !quartz.qubit + ``` }]; + let arguments = (ins OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); let results = (outs QubitType:$result); - let assemblyFormat = "attr-dict `:` type($result)"; + let assemblyFormat = [{ + ($register_name^ `[` $register_size `,` $register_index `]`)? + attr-dict `:` type($result) + }]; + + let hasVerifier = 1; } def DeallocOp : QuartzOp<"dealloc"> { diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index cde29cd3a5..137c799093 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -59,11 +59,25 @@ void FluxDialect::initialize() { #define GET_OP_CLASSES #include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" -//===----------------------------------------------------------------------===// -// ResetOp Canonicalization -//===----------------------------------------------------------------------===// +LogicalResult AllocOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } -void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - // Patterns will be added in future phases + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 42eadb115a..9828e00a53 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -58,3 +58,26 @@ void QuartzDialect::initialize() { #define GET_OP_CLASSES #include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" + +LogicalResult AllocOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} From 5d532a023b75f2934c2db1637bcc7804aee3faed Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 01:37:00 +0200 Subject: [PATCH 025/419] =?UTF-8?q?=F0=9F=9A=A7=20basic=20conversion=20inf?= =?UTF-8?q?rastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Conversion/CMakeLists.txt | 3 + .../Conversion/FluxToQuartz/CMakeLists.txt | 13 + .../Conversion/FluxToQuartz/FluxToQuartz.h | 19 ++ .../Conversion/FluxToQuartz/FluxToQuartz.td | 27 ++ .../Conversion/QuartzToFlux/CMakeLists.txt | 13 + .../Conversion/QuartzToFlux/QuartzToFlux.h | 19 ++ .../Conversion/QuartzToFlux/QuartzToFlux.td | 27 ++ .../Conversion/QuartzToQIR/CMakeLists.txt | 13 + .../mlir/Conversion/QuartzToQIR/QuartzToQIR.h | 19 ++ .../Conversion/QuartzToQIR/QuartzToQIR.td | 51 ++++ mlir/lib/Conversion/CMakeLists.txt | 3 + .../Conversion/FluxToQuartz/CMakeLists.txt | 21 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 92 ++++++ .../Conversion/QuartzToFlux/CMakeLists.txt | 21 ++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 95 +++++++ .../lib/Conversion/QuartzToQIR/CMakeLists.txt | 21 ++ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 268 ++++++++++++++++++ 17 files changed, 725 insertions(+) create mode 100644 mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt create mode 100644 mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h create mode 100644 mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td create mode 100644 mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt create mode 100644 mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h create mode 100644 mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td create mode 100644 mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt create mode 100644 mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h create mode 100644 mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td create mode 100644 mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt create mode 100644 mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp create mode 100644 mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt create mode 100644 mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp create mode 100644 mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt create mode 100644 mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp diff --git a/mlir/include/mlir/Conversion/CMakeLists.txt b/mlir/include/mlir/Conversion/CMakeLists.txt index ba233c542f..a4a8ae82ec 100644 --- a/mlir/include/mlir/Conversion/CMakeLists.txt +++ b/mlir/include/mlir/Conversion/CMakeLists.txt @@ -10,3 +10,6 @@ add_subdirectory(MQTRefToMQTOpt) add_subdirectory(MQTOptToMQTRef) add_subdirectory(MQTRefToQIR) add_subdirectory(QIRToMQTRef) +add_subdirectory(FluxToQuartz) +add_subdirectory(QuartzToFlux) +add_subdirectory(QuartzToQIR) diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt b/mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt new file mode 100644 index 0000000000..c6136ad39e --- /dev/null +++ b/mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS FluxToQuartz.td) +mlir_tablegen(FluxToQuartz.h.inc -gen-pass-decls -name FluxToQuartz) +add_public_tablegen_target(FluxToQuartzIncGen) + +add_mlir_doc(FluxToQuartz MLIRFluxToQuartz Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h new file mode 100644 index 0000000000..6dd18a21d2 --- /dev/null +++ b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include // from @llvm-project + +#define GEN_PASS_DECL +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td new file mode 100644 index 0000000000..ca438f8ec3 --- /dev/null +++ b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +include "mlir/Pass/PassBase.td" + +def FluxToQuartz : Pass<"flux-to-quartz"> { + let summary = "Convert Flux dialect to Quartz dialect."; + + let description = [{ + This pass converts all operations from the Flux dialect to their equivalent + operations in the Quartz dialect. It handles the transformation of qubit + values in Flux to qubit references in Quartz, ensuring that the semantics + of quantum operations are preserved during the conversion process. + }]; + + // Define dependent dialects + let dependentDialects = [ + "mlir::memref::MemRefDialect", + "mlir::flux::FluxDialect", + "mlir::quartz::QuartzDialect" + ]; +} diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt b/mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt new file mode 100644 index 0000000000..234b18fbdf --- /dev/null +++ b/mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QuartzToFlux.td) +mlir_tablegen(QuartzToFlux.h.inc -gen-pass-decls -name QuartzToFlux) +add_public_tablegen_target(QuartzToFluxIncGen) + +add_mlir_doc(QuartzToFlux MLIRQuartzToFlux Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h new file mode 100644 index 0000000000..7e99c0ae6e --- /dev/null +++ b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include // from @llvm-project + +#define GEN_PASS_DECL +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td new file mode 100644 index 0000000000..bf124704d7 --- /dev/null +++ b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +include "mlir/Pass/PassBase.td" + +def QuartzToFlux : Pass<"quartz-to-flux"> { + let summary = "Convert Quartz dialect to Flux dialect."; + + let description = [{ + This pass converts all operations from the Quartz dialect to their equivalent + operations in the Flux dialect. It handles the transformation of qubit + references in Quartz to qubit values in Flux, ensuring that the semantics + of quantum operations are preserved during the conversion process. + }]; + + // Define dependent dialects + let dependentDialects = [ + "mlir::memref::MemRefDialect", + "mlir::quartz::QuartzDialect", + "mlir::flux::FluxDialect" + ]; +} diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt b/mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt new file mode 100644 index 0000000000..94f91181e9 --- /dev/null +++ b/mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QuartzToQIR.td) +mlir_tablegen(QuartzToQIR.h.inc -gen-pass-decls -name QuartzToQIR) +add_public_tablegen_target(QuartzToQIRIncGen) + +add_mlir_doc(QuartzToQIR MLIRQuartzToQIR Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h new file mode 100644 index 0000000000..8868a5938c --- /dev/null +++ b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include // from @llvm-project + +#define GEN_PASS_DECL +#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td new file mode 100644 index 0000000000..506a79584c --- /dev/null +++ b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +include "mlir/Pass/PassBase.td" + +def QuartzToQIR : Pass<"quartz-to-qir"> { + let summary = "Lower the Quartz dialect to the LLVM dialect compliant with QIR 2.0"; + + let description = [{ + This pass lowers all operations from the Quartz dialect to their equivalent + operations in the LLVM dialect, ensuring compliance with the QIR 2.0 standard. + It translates quantum operations and types from Quartz to their corresponding + representations in QIR, facilitating interoperability with quantum computing + frameworks that utilize the QIR standard. + + Requirements: + - Input is a valid module in the Quartz dialect. + - The entry function must be marked with the `entry_point` attribute. + - The program must have straight-line control flow (i.e., Base Profile QIR). + + Behavior: + - Each Quartz quantum operation is replaced by a call to the corresponding QIR function in the LLVM dialect. + - Required QIR module flags are attached as attributes to the entry function. + - The entry function is split into four blocks to satisfy QIR Base Profile constraints: + 0. Initialization block: Sets up the execution environment and performs required runtime initialization. + 1. Reversible operations block: Contains only void-returning calls to reversible quantum operations. + 2. Irreversible operations block: Contains only void-returning calls to operations marked irreversible, e.g.: + `__quantum__qis__mz__body`, `__quantum__rt__qubit_release_array`, + `__quantum__rt__qubit_release`, `__quantum__qis__reset__body`. + 3. Epilogue block: Records measurement results and returns from the entry function. + - Blocks are connected via unconditional branches in the order listed above. + - Non-quantum dialects are lowered via MLIR's built-in conversions. + + Producing LLVM IR: + - After conversion to the LLVM dialect, produce LLVM IR with: + mlir-translate --mlir-to-llvmir input.mlir > output.ll + }]; + + + // Define dependent dialects + let dependentDialects = [ + "mlir::memref::MemRefDialect", + "mlir::LLVM::LLVMDialect", + "mlir::QuartzDialect", + ]; +} diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt index 98d9c3b8f0..87d8545c64 100644 --- a/mlir/lib/Conversion/CMakeLists.txt +++ b/mlir/lib/Conversion/CMakeLists.txt @@ -10,3 +10,6 @@ add_subdirectory(MQTOptToMQTRef) add_subdirectory(MQTRefToMQTOpt) add_subdirectory(MQTRefToQIR) add_subdirectory(QIRToMQTRef) +add_subdirectory(FluxToQuartz) +add_subdirectory(QuartzToFlux) +add_subdirectory(QuartzToQIR) diff --git a/mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt b/mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt new file mode 100644 index 0000000000..e2c17340c1 --- /dev/null +++ b/mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library( + FluxToQuartz + ${CONVERSION_SOURCES} + DEPENDS + FluxToQuartzIncGen + LINK_LIBS + PUBLIC + MLIRQuartzDialect + MLIRFluxDialect + MLIRArithDialect + MLIRFuncDialect) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp new file mode 100644 index 0000000000..f5bea923c4 --- /dev/null +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { +using namespace mlir::flux; +using namespace mlir::quartz; + +#define GEN_PASS_DEF_FLUXTOQUARTZ +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" + +class FluxToQuartzTypeConverter final : public TypeConverter { +public: + explicit FluxToQuartzTypeConverter(MLIRContext* ctx) { + // Identity conversion + addConversion([](Type type) { return type; }); + + // QubitType conversion + addConversion([ctx](flux::QubitType /*type*/) -> Type { + return quartz::QubitType::get(ctx); + }); + } +}; + +struct FluxToQuartz final : impl::FluxToQuartzBase { + using FluxToQuartzBase::FluxToQuartzBase; + void runOnOperation() override { + MLIRContext* context = &getContext(); + auto* module = getOperation(); + + ConversionTarget target(*context); + RewritePatternSet patterns(context); + FluxToQuartzTypeConverter typeConverter(context); + + target.addIllegalDialect(); + target.addLegalDialect(); + + // conversion of flux types in func.func signatures + populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); + target.addDynamicallyLegalOp([&](func::FuncOp op) { + return typeConverter.isSignatureLegal(op.getFunctionType()) && + typeConverter.isLegal(&op.getBody()); + }); + + // conversion of flux types in func.return + populateReturnOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); + + // conversion of flux types in func.call + populateCallOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::CallOp op) { return typeConverter.isLegal(op); }); + + // conversion of flux types in control-flow ops; e.g. cf.br + populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + + if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + signalPassFailure(); + } + }; +}; +} // namespace mlir diff --git a/mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt b/mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt new file mode 100644 index 0000000000..53fee2501a --- /dev/null +++ b/mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library( + QuartzToFlux + ${CONVERSION_SOURCES} + DEPENDS + QuartzToFluxIncGen + LINK_LIBS + PUBLIC + MLIRQuartzDialect + MLIRFluxDialect + MLIRArithDialect + MLIRFuncDialect) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp new file mode 100644 index 0000000000..1a5898c7aa --- /dev/null +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { +using namespace mlir::flux; +using namespace mlir::quartz; + +#define GEN_PASS_DEF_QUARTZTOFLUX +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" + +class QuartzToFluxTypeConverter final : public TypeConverter { +public: + explicit QuartzToFluxTypeConverter(MLIRContext* ctx) { + // Identity conversion + addConversion([](Type type) { return type; }); + + // QubitType conversion + addConversion([ctx](quartz::QubitType /*type*/) -> Type { + return flux::QubitType::get(ctx); + }); + } +}; + +struct QuartzToFlux final : impl::QuartzToFluxBase { + using QuartzToFluxBase::QuartzToFluxBase; + + void runOnOperation() override { + MLIRContext* context = &getContext(); + auto* module = getOperation(); + + ConversionTarget target(*context); + RewritePatternSet patterns(context); + QuartzToFluxTypeConverter typeConverter(context); + + target.addIllegalDialect(); + target.addLegalDialect(); + + // conversion of quartz types in func.func signatures + // does not work for now as signature needs to be changed + populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); + target.addDynamicallyLegalOp([&](func::FuncOp op) { + return typeConverter.isSignatureLegal(op.getFunctionType()) && + typeConverter.isLegal(&op.getBody()); + }); + // conversion of quartz types in func.return + populateReturnOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); + + // conversion of quartz types in func.call + populateCallOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](const func::CallOp op) { return typeConverter.isLegal(op); }); + + // conversion of quartz types in control-flow ops; e.g. cf.br + populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + signalPassFailure(); + } + }; +}; + +} // namespace mlir diff --git a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt b/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt new file mode 100644 index 0000000000..0369ff9965 --- /dev/null +++ b/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library( + QuartzToQIR + ${CONVERSION_SOURCES} + DEPENDS + QuartzToQIRIncGen + LINK_LIBS + PUBLIC + MLIRLLVMDialect + MLIRFluxDialect + MLIRArithDialect + MLIRFuncDialect) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp new file mode 100644 index 0000000000..5cd754d6a1 --- /dev/null +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { + +using namespace mlir::quartz; + +#define GEN_PASS_DEF_QUARTZTOQIR +#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" + +namespace { + +struct LoweringState { + // map a given index to a pointer value, to reuse the value instead of + // creating a new one every time + DenseMap ptrMap; + // map a given index to an address to record the classical output + DenseMap outputMap; + // Index for the next measure operation + size_t index{}; + // number of stored results in the module + size_t numResults{}; + // number of qubits in the module + size_t numQubits{}; + // boolean to check if the module uses dynamically addressed qubits + bool useDynamicQubit{}; + // boolean to check if the module uses dynamically addressed results + bool useDynamicResult{}; +}; + +template +class StatefulOpConversionPattern : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + +public: + StatefulOpConversionPattern(TypeConverter& typeConverter, MLIRContext* ctx, + LoweringState* state) + : OpConversionPattern(typeConverter, ctx), state_(state) {} + + /// @brief Return the state object as reference. + [[nodiscard]] LoweringState& getState() const { return *state_; } + +private: + LoweringState* state_; +}; + +} // namespace + +struct QuartzToQIRTypeConverter final : LLVMTypeConverter { + explicit QuartzToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { + // QubitType conversion + addConversion( + [ctx](QubitType /*type*/) { return LLVM::LLVMPointerType::get(ctx); }); + } +}; + +struct QuartzToQIR final : impl::QuartzToQIRBase { + using QuartzToQIRBase::QuartzToQIRBase; + + static constexpr StringLiteral FN_NAME_INITIALIZE = + "__quantum__rt__initialize"; + + /** + * @brief Finds the main function in the module + * + * @param op The module operation that holds all operations. + * @return The main function. + */ + static LLVM::LLVMFuncOp getMainFunction(Operation* op) { + auto module = dyn_cast(op); + // find the main function + for (auto funcOp : module.getOps()) { + auto passthrough = funcOp->getAttrOfType("passthrough"); + if (!passthrough) { + continue; + } + if (llvm::any_of(passthrough, [](Attribute attr) { + auto strAttr = dyn_cast(attr); + return strAttr && strAttr.getValue() == "entry_point"; + })) { + return funcOp; + } + } + return nullptr; + } + + /** + * @brief Adds the initialize operation to the first block of the main + * function. + * + * @param main The main function of the module. + * @param ctx The context of the module. + */ + static void addInitialize(LLVM::LLVMFuncOp& main, MLIRContext* ctx, + LoweringState* state) { + auto moduleOp = main->getParentOfType(); + + auto& firstBlock = *(main.getBlocks().begin()); + OpBuilder builder(main.getBody()); + + // create the zero op + builder.setInsertionPointToStart(&firstBlock); + auto zeroOperation = builder.create( + main->getLoc(), LLVM::LLVMPointerType::get(ctx)); + + // add the zero operation to the pointerMap + state->ptrMap.try_emplace(0, zeroOperation->getResult(0)); + + // create the initialize operation as the 2nd last operation in the first + // block after all constant operations and before the last jump operation + const auto insertPoint = std::prev(firstBlock.getOperations().end(), 1); + builder.setInsertionPoint(&*insertPoint); + + // get the function declaration of initialize otherwise create one + auto* fnDecl = SymbolTable::lookupNearestSymbolFrom( + main, builder.getStringAttr(FN_NAME_INITIALIZE)); + if (fnDecl == nullptr) { + const PatternRewriter::InsertionGuard insertGuard(builder); + builder.setInsertionPointToEnd(moduleOp.getBody()); + auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + fnDecl = builder.create( + main->getLoc(), FN_NAME_INITIALIZE, fnSignature); + } + // create and insert the initialize operation + builder.create(main->getLoc(), + static_cast(fnDecl), + ValueRange{zeroOperation->getResult(0)}); + } + /** + * @brief Sets the necessary attributes to the main function for the QIR base + * profile. The required module flags are also set as attributes. + * + * @param main The main function of the module. + * @param state The lowering state of the conversion pass. + */ + static void setAttributes(LLVM::LLVMFuncOp& main, LoweringState* state) { + OpBuilder builder(main.getBody()); + SmallVector attributes; + attributes.emplace_back(builder.getStringAttr("entry_point")); + attributes.emplace_back( + builder.getStrArrayAttr({"output_labeling_schema", "schema_id"})); + attributes.emplace_back( + builder.getStrArrayAttr({"qir_profiles", "base_profile"})); + attributes.emplace_back(builder.getStrArrayAttr( + {"required_num_qubits", std::to_string(state->numQubits)})); + attributes.emplace_back(builder.getStrArrayAttr( + {"required_num_results", std::to_string(state->numResults)})); + attributes.emplace_back( + builder.getStrArrayAttr({"qir_major_version", "1"})); + attributes.emplace_back( + builder.getStrArrayAttr({"qir_minor_version", "0"})); + attributes.emplace_back( + builder.getStrArrayAttr({"dynamic_qubit_management", + state->useDynamicQubit ? "true" : "false"})); + attributes.emplace_back( + builder.getStrArrayAttr({"dynamic_result_management", + state->useDynamicResult ? "true" : "false"})); + + main->setAttr("passthrough", builder.getArrayAttr(attributes)); + } + + void runOnOperation() override { + MLIRContext* ctx = &getContext(); + auto* moduleOp = getOperation(); + ConversionTarget target(*ctx); + RewritePatternSet funcPatterns(ctx); + RewritePatternSet mqtPatterns(ctx); + RewritePatternSet stdPatterns(ctx); + QuartzToQIRTypeConverter typeConverter(ctx); + + target.addLegalDialect(); + + // convert func to LLVM + target.addIllegalDialect(); + + populateFuncToLLVMConversionPatterns(typeConverter, funcPatterns); + + if (applyPartialConversion(moduleOp, target, std::move(funcPatterns)) + .failed()) { + signalPassFailure(); + } + + // convert Quartz to LLVM + auto main = getMainFunction(moduleOp); + // ensureBlocks(main); + LoweringState state; + addInitialize(main, ctx, &state); + + target.addIllegalDialect(); + + if (applyPartialConversion(moduleOp, target, std::move(mqtPatterns)) + .failed()) { + signalPassFailure(); + } + + setAttributes(main, &state); + + // convert arith and cf to LLVM + target.addIllegalDialect(); + target.addIllegalDialect(); + + cf::populateControlFlowToLLVMConversionPatterns(typeConverter, stdPatterns); + arith::populateArithToLLVMConversionPatterns(typeConverter, stdPatterns); + + if (applyPartialConversion(moduleOp, target, std::move(stdPatterns)) + .failed()) { + signalPassFailure(); + } + + PassManager passManager(ctx); + passManager.addPass(createReconcileUnrealizedCastsPass()); + if (passManager.run(moduleOp).failed()) { + signalPassFailure(); + } + }; +}; + +} // namespace mlir From d69ab5395e6b501962c3c6a6f7a0ebc01c6c055e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 01:37:42 +0200 Subject: [PATCH 026/419] =?UTF-8?q?=F0=9F=9A=A7=20basic=20translation=20in?= =?UTF-8?q?frastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Translation/ImportQuantumComputation.h | 22 ++ mlir/lib/Dialect/Quartz/CMakeLists.txt | 1 + .../Dialect/Quartz/Translation/CMakeLists.txt | 29 ++ .../Translation/ImportQuantumComputation.cpp | 289 ++++++++++++++ mlir/unittests/CMakeLists.txt | 1 + mlir/unittests/translation_new/CMakeLists.txt | 21 ++ .../translation_new/test_translation.cpp | 355 ++++++++++++++++++ 7 files changed, 718 insertions(+) create mode 100644 mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h create mode 100644 mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp create mode 100644 mlir/unittests/translation_new/CMakeLists.txt create mode 100644 mlir/unittests/translation_new/test_translation.cpp diff --git a/mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h b/mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h new file mode 100644 index 0000000000..ff7242dda1 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include + +namespace qc { +class QuantumComputation; +} + +mlir::OwningOpRef +translateQuantumComputationToMLIR(mlir::MLIRContext* context, + const qc::QuantumComputation& qc); diff --git a/mlir/lib/Dialect/Quartz/CMakeLists.txt b/mlir/lib/Dialect/Quartz/CMakeLists.txt index ace971aa36..301073f1ce 100644 --- a/mlir/lib/Dialect/Quartz/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(IR) +add_subdirectory(Translation) diff --git a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt b/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt new file mode 100644 index 0000000000..18fd204c73 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_compile_options("-fexceptions") + +add_mlir_library( + MLIRQuartzTranslation + ImportQuantumComputation.cpp + LINK_LIBS + MLIRArithDialect + MLIRFuncDialect + MLIRMemRefDialect + MLIRSCFDialect + MLIRQuartzDialect + MQT::CoreIR) + +# collect header files +file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/Translation/*.h) + +# add public headers using file sets +target_sources( + MLIRQuartzTranslation PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${TRANSLATION_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp new file mode 100644 index 0000000000..7606289d61 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h" + +#include "ir/QuantumComputation.hpp" +#include "ir/Register.hpp" +#include "ir/operations/Control.hpp" +#include "ir/operations/IfElseOperation.hpp" +#include "ir/operations/NonUnitaryOperation.hpp" +#include "ir/operations/OpType.hpp" +#include "ir/operations/Operation.hpp" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir { +using namespace mlir::quartz; + +namespace { + +struct QregInfo { + const qc::QuantumRegister* qregPtr; + mlir::Value qreg; + llvm::SmallVector qubits; +}; + +using BitMemInfo = std::pair; // (memref, localIdx) +using BitIndexVec = llvm::SmallVector; + +/** + * @brief Allocates a quantum register in the MLIR module. + * + * @param builder The MLIR OpBuilder used to create operations + * @param context The MLIR context in which types are created + * @param numQubits The number of qubits to allocate in the register + * @return mlir::Value The allocated quantum register value + */ +mlir::Value allocateQreg(mlir::OpBuilder& builder, mlir::MLIRContext* context, + const std::size_t numQubits) { + const auto& qubitType = QubitType::get(context); + auto memRefType = + mlir::MemRefType::get({static_cast(numQubits)}, qubitType); + auto memref = builder.create(builder.getUnknownLoc(), + memRefType); + return memref.getResult(); +} + +/** + * @brief Extracts all qubits from a quantum register. + * + * @param builder The MLIR OpBuilder used to create operations + * @param qreg The quantum register from which to extract qubits + * @param numQubits The number of qubits to extract + * @return llvm::SmallVector Vector of extracted qubit values + */ +llvm::SmallVector extractQubits(mlir::OpBuilder& builder, + mlir::Value qreg, + const std::size_t numQubits) { + llvm::SmallVector qubits; + qubits.reserve(numQubits); + + for (std::size_t qubit = 0; qubit < numQubits; ++qubit) { + auto index = builder.create( + builder.getUnknownLoc(), qubit); + qubits.emplace_back( + builder + .create(builder.getUnknownLoc(), qreg, + mlir::ValueRange{index}) + .getResult()); + } + + return qubits; +} + +/** + * @brief Allocates quantum registers and extracts qubits. + * + * @param builder The MLIR OpBuilder used to create operations + * @param context The MLIR context in which types are created + * @param quantumComputation The quantum computation to translate + * @return llvm::SmallVector Vector containing information about all + * quantum registers + */ +llvm::SmallVector +getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, + const qc::QuantumComputation& quantumComputation) { + // Build list of pointers for sorting + llvm::SmallVector qregPtrs; + qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + + quantumComputation.getAncillaRegisters().size()); + for (const auto& qreg : + quantumComputation.getQuantumRegisters() | std::views::values) { + qregPtrs.emplace_back(&qreg); + } + for (const auto& qreg : + quantumComputation.getAncillaRegisters() | std::views::values) { + qregPtrs.emplace_back(&qreg); + } + + // Sort by start index + std::ranges::sort( + qregPtrs, [](const qc::QuantumRegister* a, const qc::QuantumRegister* b) { + return a->getStartIndex() < b->getStartIndex(); + }); + + // Allocate quantum registers and extract qubits + llvm::SmallVector qregs; + for (const auto* qregPtr : qregPtrs) { + const auto qreg = allocateQreg(builder, context, qregPtr->getSize()); + auto qubits = extractQubits(builder, qreg, qregPtr->getSize()); + qregs.emplace_back(qregPtr, qreg, std::move(qubits)); + } + + return qregs; +} + +/** + * @brief Builds a mapping from global qubit index to extracted qubit value. + * + * @param builder The MLIR OpBuilder used to create operations + * @param context The MLIR context in which types are created + * @param qregs Vector containing information about all quantum registers + * @return llvm::SmallVector Sorted vector of qubit values + */ +llvm::SmallVector +getQubits(const qc::QuantumComputation& quantumComputation, + llvm::SmallVector& qregs) { + llvm::SmallVector flatQubits; + const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); + flatQubits.resize(static_cast(maxPhys) + 1); + for (const auto& qreg : qregs) { + for (std::size_t i = 0; i < qreg.qregPtr->getSize(); ++i) { + const auto globalIdx = + static_cast(qreg.qregPtr->getStartIndex() + i); + flatQubits[globalIdx] = qreg.qubits[i]; + } + } + + return flatQubits; +} + +/** + * @brief Deallocates the quantum register in the MLIR module. + * + * @param builder The MLIR OpBuilder used to create operations + * @param qreg The quantum register to deallocate + */ +void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { + builder.create(builder.getUnknownLoc(), qreg); +} + +/** + * @brief Allocates a classical register in the MLIR module. + * + * @param builder The MLIR OpBuilder used to create operations + * @param numBits The number of bits to allocate in the register + * @return mlir::Value The allocated classical register value + */ +mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { + auto memRefType = mlir::MemRefType::get({numBits}, builder.getI1Type()); + auto memref = builder.create(builder.getUnknownLoc(), + memRefType); + return memref.getResult(); +} + +/** + * @brief Builds a mapping from global bit index to (memref, localIdx). + * + * @param builder The MLIR OpBuilder used to create operations + * @param numBits The number of bits to allocate in the register + * @return mlir::Value The allocated classical register value + */ +BitIndexVec getBitMap(mlir::OpBuilder& builder, + const qc::QuantumComputation& quantumComputation) { + // Build list of pointers for sorting + llvm::SmallVector cregPtrs; + cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); + for (const auto& [_, reg] : quantumComputation.getClassicalRegisters()) { + cregPtrs.emplace_back(®); + } + + // Sort by start index + std::ranges::sort(cregPtrs, [](const qc::ClassicalRegister* a, + const qc::ClassicalRegister* b) { + return a->getStartIndex() < b->getStartIndex(); + }); + + // Build mapping + BitIndexVec bitMap; + bitMap.resize(quantumComputation.getNcbits()); + for (const auto* reg : cregPtrs) { + auto mem = allocateBits(builder, static_cast(reg->getSize())); + for (std::size_t i = 0; i < reg->getSize(); ++i) { + const auto globalIdx = static_cast(reg->getStartIndex() + i); + bitMap[globalIdx] = {mem, i}; + } + } + + return bitMap; +} +} // namespace + +/** + * @brief Translates a QuantumComputation to an MLIR module with Quartz + * operations. + * + * This function takes a quantum computation and translates it into an MLIR + * module containing Quartz dialect operations. It creates a main function that + * contains all quantum operations from the input computation. + * + * @param context The MLIR context in which the module will be created + * @param quantumComputation The quantum computation to translate + * @return mlir::OwningOpRef The translated MLIR module + */ +mlir::OwningOpRef translateQuantumComputationToMLIR( + mlir::MLIRContext* context, + const qc::QuantumComputation& quantumComputation) { + mlir::OpBuilder builder(context); + const auto loc = builder.getUnknownLoc(); + + // Create module + auto module = builder.create(loc); + builder.setInsertionPointToStart(module.getBody()); + + // Create main function as entry point + auto funcType = builder.getFunctionType({}, {}); + auto mainFunc = builder.create(loc, "main", funcType); + + // Add entry_point attribute to identify the main function + const auto entryPointAttr = mlir::StringAttr::get(context, "entry_point"); + mainFunc->setAttr("passthrough", + mlir::ArrayAttr::get(context, {entryPointAttr})); + + auto& entryBlock = mainFunc.getBody().emplaceBlock(); + builder.setInsertionPointToStart(&entryBlock); + + // Allocate quantum registers and extract qubits + auto qregs = getQregs(builder, context, quantumComputation); + auto qubits = getQubits(quantumComputation, qregs); + + // Allocate classical registers + auto bitMap = getBitMap(builder, quantumComputation); + + // Add operations and handle potential failures + // if (addOperations(builder, quantumComputation, qubits, bitMap).failed()) { + // // Even if operations fail, return the module with what we could + // translate emitError(loc) << "Failed to translate some quantum + // operations"; + // } + + // Deallocate quantum registers + for (const auto& qreg : qregs) { + deallocateQreg(builder, qreg.qreg); + } + + // Create terminator + builder.create(loc); + + return module; +} +} // namespace mlir diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt index fb4775eaa4..a362ea7062 100644 --- a/mlir/unittests/CMakeLists.txt +++ b/mlir/unittests/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(translation) +add_subdirectory(translation_new) diff --git a/mlir/unittests/translation_new/CMakeLists.txt b/mlir/unittests/translation_new/CMakeLists.txt new file mode 100644 index 0000000000..d23fd11d20 --- /dev/null +++ b/mlir/unittests/translation_new/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(testname "mqt-core-mlir-translation-test-new") +file(GLOB_RECURSE TRANSLATION_TEST_SOURCES *.cpp) + +if(NOT TARGET ${testname}) + # create an executable in which the tests will be stored + add_executable(${testname} ${TRANSLATION_TEST_SOURCES}) + # link the Google test infrastructure and a default main function to the test executable. + target_link_libraries(${testname} PRIVATE GTest::gmock GTest::gtest_main LLVMFileCheck MLIRPass + MLIRTransforms MLIRQuartzTranslation) + # discover tests + gtest_discover_tests(${testname} DISCOVERY_TIMEOUT 60) + set_target_properties(${testname} PROPERTIES FOLDER unittests) +endif() diff --git a/mlir/unittests/translation_new/test_translation.cpp b/mlir/unittests/translation_new/test_translation.cpp new file mode 100644 index 0000000000..564515142c --- /dev/null +++ b/mlir/unittests/translation_new/test_translation.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ir/QuantumComputation.hpp" +#include "ir/operations/Control.hpp" +#include "ir/operations/IfElseOperation.hpp" +#include "ir/operations/OpType.hpp" +#include "ir/operations/StandardOperation.hpp" +#include "mlir/Dialect/MQTRef/IR/MQTRefDialect.h" +#include "mlir/Dialect/MQTRef/Translation/ImportQuantumComputation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace qc; + +class ImportTest : public ::testing::Test { +protected: + std::unique_ptr context; + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + void runPasses(const mlir::ModuleOp module) const { + mlir::PassManager passManager(context.get()); + passManager.addPass(mlir::createCanonicalizerPass()); + passManager.addPass(mlir::createMem2Reg()); + passManager.addPass(mlir::createRemoveDeadValuesPass()); + if (passManager.run(module).failed()) { + FAIL() << "Failed to run passes"; + } + } + + void TearDown() override {} +}; + +// ################################################## +// # Helper functions +// ################################################## + +std::string getOutputString(mlir::OwningOpRef* module) { + std::string outputString; + llvm::raw_string_ostream os(outputString); + (*module)->print(os); + os.flush(); + return outputString; +} + +std::string formatTargets(std::initializer_list targets) { + std::string s; + bool first = true; + for (auto t : targets) { + if (!first) { + s += ", "; + } + first = false; + s += "%[[Q" + std::to_string(t) + "]]"; + } + return s; +} + +std::string formatParams(std::initializer_list params) { + if (params.size() == 0) { + return ""; + } + std::ostringstream os; + os.setf(std::ios::scientific); + os << std::setprecision(6); + bool first = true; + os << "static ["; + for (const double p : params) { + if (!first) { + os << ", "; + } + first = false; + os << p; + } + os << "]"; + return os.str(); +} + +std::string getCheckStringOperation(const char* op, + std::initializer_list targets) { + return std::string("CHECK: mqtref.") + op + "() " + formatTargets(targets); +} + +std::string +getCheckStringOperationParams(const char* op, + std::initializer_list params, + std::initializer_list targets) { + return std::string("CHECK: mqtref.") + op + "(" + formatParams(params) + + ") " + formatTargets(targets); +} + +// Adapted from +// https://github.com/llvm/llvm-project/blob/d2b3e86321eaf954451e0a49534fa654dd67421e/llvm/unittests/MIR/MachineMetadata.cpp#L181 +bool checkOutput(const std::string& checkString, + const std::string& outputString) { + auto checkBuffer = llvm::MemoryBuffer::getMemBuffer(checkString, ""); + auto outputBuffer = + llvm::MemoryBuffer::getMemBuffer(outputString, "Output", false); + + llvm::SmallString<4096> checkFileBuffer; + const llvm::FileCheckRequest request; + llvm::FileCheck fc(request); + const llvm::StringRef checkFileText = + fc.CanonicalizeFile(*checkBuffer, checkFileBuffer); + + llvm::SourceMgr sm; + sm.AddNewSourceBuffer( + llvm::MemoryBuffer::getMemBuffer(checkFileText, "CheckFile"), + llvm::SMLoc()); + if (fc.readCheckFile(sm, checkFileText)) { + return false; + } + + auto outputBufferBuffer = outputBuffer->getBuffer(); + sm.AddNewSourceBuffer(std::move(outputBuffer), llvm::SMLoc()); + return fc.checkInput(sm, outputBufferBuffer); +} + +// ################################################## +// # Basic tests +// ################################################## + +TEST_F(ImportTest, EntryPoint) { + const QuantumComputation qc{}; + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto outputString = getOutputString(&module); + const auto* checkString = R"( + CHECK: func.func @main() attributes {passthrough = ["entry_point"]} + CHECK: return + )"; + + ASSERT_TRUE(checkOutput(checkString, outputString)); +} + +TEST_F(ImportTest, AllocationAndDeallocation) { + const QuantumComputation qc(3, 2); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto outputString = getOutputString(&module); + const auto* checkString = R"( + CHECK: %[[Qreg:.*]] = memref.alloc() : memref<3x!mqtref.Qubit> + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<3x!mqtref.Qubit> + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<3x!mqtref.Qubit> + CHECK: %[[I2:.*]] = arith.constant 2 : index + CHECK: %[[Q2:.*]] = memref.load %[[Qreg]][%[[I2]]] : memref<3x!mqtref.Qubit> + CHECK: %[[Creg:.*]] = memref.alloca() : memref<2xi1> + CHECK: memref.dealloc %[[Qreg]] : memref<3x!mqtref.Qubit> + )"; + + ASSERT_TRUE(checkOutput(checkString, outputString)); +} + +TEST_F(ImportTest, Measure01) { + QuantumComputation qc(2, 2); + qc.measure({0, 1}, {0, 1}); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto outputString = getOutputString(&module); + const auto* checkString = R"( + CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> + CHECK: %[[Creg:.*]] = memref.alloca() : memref<2xi1> + CHECK: %[[M0:.*]] = mqtref.measure %[[Q0]] + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: memref.store %[[M0]], %[[Creg]][%[[I0]]] : memref<2xi1> + CHECK: %[[M1:.*]] = mqtref.measure %[[Q1]] + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: memref.store %[[M1]], %[[Creg]][%[[I1]]] : memref<2xi1> + )"; + + ASSERT_TRUE(checkOutput(checkString, outputString)); +} + +TEST_F(ImportTest, Measure0) { + QuantumComputation qc(2, 2); + qc.measure(0, 0); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto outputString = getOutputString(&module); + const auto* checkString = R"( + CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> + CHECK: %[[Creg:.*]] = memref.alloca() : memref<2xi1> + CHECK: %[[M0:.*]] = mqtref.measure %[[Q0]] + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: memref.store %[[M0]], %[[Creg]][%[[I0]]] : memref<2xi1> + CHECK-NOT: mqtref.measure %[[Q1]] + CHECK-NOT: arith.constant 1 : index + CHECK-NOT: memref.store %[[ANY:.*]], %[[Creg]][%[[ANY:.*]]] : memref<2xi1> + )"; + + ASSERT_TRUE(checkOutput(checkString, outputString)); +} + +TEST_F(ImportTest, Reset01) { + QuantumComputation qc(2); + qc.reset({0, 1}); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto outputString = getOutputString(&module); + const auto* checkString = R"( + CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> + CHECK: mqtref.reset %[[Q0]] + CHECK: mqtref.reset %[[Q1]] + )"; + + ASSERT_TRUE(checkOutput(checkString, outputString)); +} + +TEST_F(ImportTest, Reset0) { + QuantumComputation qc(2); + qc.reset(0); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto outputString = getOutputString(&module); + const auto* checkString = R"( + CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> + CHECK: mqtref.reset %[[Q0]] + CHECK-NOT: mqtref.reset %[[Q1]] + )"; + + ASSERT_TRUE(checkOutput(checkString, outputString)); +} + +// ################################################## +// # Test full programs +// ################################################## + +TEST_F(ImportTest, MultipleClassicalRegistersMeasureStores) { + QuantumComputation qc(2, 0); + qc.addClassicalRegister(1, "c0"); + qc.addClassicalRegister(1, "c1"); + qc.measure({0, 1}, {0, 1}); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + // We do not run passes here; pattern should match raw allocation and stores + + const auto output = getOutputString(&module); + const std::string check = R"( + CHECK: func.func @main() attributes {passthrough = ["entry_point"]} + CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> + CHECK: %[[I0:.*]] = arith.constant 0 : index + CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> + CHECK: %[[I1:.*]] = arith.constant 1 : index + CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> + CHECK: %[[CregA:.*]] = memref.alloca() : memref<1xi1> + CHECK: %[[CregB:.*]] = memref.alloca() : memref<1xi1> + CHECK: %[[M0:.*]] = mqtref.measure %[[Q0]] + CHECK: %[[I0A:.*]] = arith.constant 0 : index + CHECK: memref.store %[[M0]], %[[CregA]][%[[I0A]]] : memref<1xi1> + CHECK: %[[M1:.*]] = mqtref.measure %[[Q1]] + CHECK: %[[I0B:.*]] = arith.constant 0 : index + CHECK: memref.store %[[M1]], %[[CregB]][%[[I0B]]] : memref<1xi1> + CHECK: memref.dealloc %[[Qreg]] : memref<2x!mqtref.Qubit> + CHECK: return + )"; + + ASSERT_TRUE(checkOutput(check, output)); +} + +TEST_F(ImportTest, MultipleQuantumRegistersCX) { + QuantumComputation qc(0, 0); + qc.addQubitRegister(1, "q0"); + qc.addQubitRegister(1, "q1"); + qc.cx(0, 1); + + auto module = translateQuantumComputationToMLIR(context.get(), qc); + + const auto output = getOutputString(&module); + const std::string check = R"( + CHECK: func.func @main() attributes {passthrough = ["entry_point"]} + CHECK: %[[QregA:.*]] = memref.alloc() : memref<1x!mqtref.Qubit> + CHECK: %[[I0A:.*]] = arith.constant 0 : index + CHECK: %[[Q0A:.*]] = memref.load %[[QregA]][%[[I0A]]] : memref<1x!mqtref.Qubit> + CHECK: %[[QregB:.*]] = memref.alloc() : memref<1x!mqtref.Qubit> + CHECK: %[[I0B:.*]] = arith.constant 0 : index + CHECK: %[[Q0B:.*]] = memref.load %[[QregB]][%[[I0B]]] : memref<1x!mqtref.Qubit> + CHECK: mqtref.x() %[[Q0B]] ctrl %[[Q0A]] + CHECK: memref.dealloc %[[QregA]] : memref<1x!mqtref.Qubit> + CHECK: memref.dealloc %[[QregB]] : memref<1x!mqtref.Qubit> + CHECK: return + )"; + + ASSERT_TRUE(checkOutput(check, output)); +} + +} // namespace From 69dc81a0e9adfdf9571b5c3a40a16982f7deab0f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 01:38:02 +0200 Subject: [PATCH 027/419] =?UTF-8?q?=F0=9F=9A=A7=20basic=20wip=20builder=20?= =?UTF-8?q?infrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Quartz/Builder/QuartzProgramBuilder.h | 56 +++++++++++++++++++ .../lib/Dialect/Quartz/Builder/CMakeLists.txt | 27 +++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 16 ++++++ mlir/lib/Dialect/Quartz/CMakeLists.txt | 1 + 4 files changed, 100 insertions(+) create mode 100644 mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h create mode 100644 mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h new file mode 100644 index 0000000000..3e0f24fab2 --- /dev/null +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include +#include +#include + +namespace mlir { +using namespace mlir::quartz; + +class QuartzProgramBuilder { +public: + explicit QuartzProgramBuilder(MLIRContext* context); + + //===--------------------------------------------------------------------===// + // Initialization + //===--------------------------------------------------------------------===// + + void initialize(); + + //===--------------------------------------------------------------------===// + // Memory Management + //===--------------------------------------------------------------------===// + + // Dynamic allocation + Value allocQubit(); + + // Static qubit reference + Value qubit(size_t index); + + /// TODO + + //===--------------------------------------------------------------------===// + // Finalization + //===--------------------------------------------------------------------===// + + ModuleOp finalize(); + ModuleOp getModule() const; + +private: + OpBuilder builder; + ModuleOp module; + Location loc; +}; +} // namespace mlir diff --git a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt b/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt new file mode 100644 index 0000000000..619920a027 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_library( + MLIRQuartzProgramBuilder + QuartzProgramBuilder.cpp + LINK_LIBS + PUBLIC + MLIRArithDialect + MLIRFuncDialect + MLIRMemRefDialect + MLIRSCFDialect + MLIRQuartzDialect) + +# collect header files +file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/Builder/*.h) + +# add public headers using file sets +target_sources( + MLIRQuartzProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${TRANSLATION_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp new file mode 100644 index 0000000000..abfbe9e118 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +using namespace mlir; +using namespace mlir::quartz; diff --git a/mlir/lib/Dialect/Quartz/CMakeLists.txt b/mlir/lib/Dialect/Quartz/CMakeLists.txt index 301073f1ce..2af82c84a4 100644 --- a/mlir/lib/Dialect/Quartz/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/CMakeLists.txt @@ -7,4 +7,5 @@ # Licensed under the MIT License add_subdirectory(IR) +add_subdirectory(Builder) add_subdirectory(Translation) From 338f48e90bb770caba94945e2d761a6a330b4436 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 01:38:17 +0200 Subject: [PATCH 028/419] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20fix=20c&p=20mistak?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index c3ecc9e972..0b712e018a 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -24,7 +24,7 @@ #pragma clang diagnostic pop #endif -#define DIALECT_NAME_FLUX "quartz" +#define DIALECT_NAME_QUARTZ "quartz" //===----------------------------------------------------------------------===// // Dialect From 3d6134fd7fe901ca452e93ac1b9c5815a1918d4f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 01:38:44 +0200 Subject: [PATCH 029/419] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20idiomatic=20naming?= =?UTF-8?q?=20for=20dialect=20targets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 4 ++-- mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt index 338c7679f7..7babc99a63 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License add_mlir_dialect_library( - MLIRFlux + MLIRFluxDialect FluxOps.cpp ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux @@ -25,7 +25,7 @@ file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/F # add public headers using file sets target_sources( - MLIRFlux + MLIRFluxDialect PUBLIC FILE_SET HEADERS BASE_DIRS diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt index d22d95d4b2..0c0e3aa280 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License add_mlir_dialect_library( - MLIRQuartz + MLIRQuartzDialect QuartzOps.cpp ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz @@ -25,7 +25,7 @@ file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/Q # add public headers using file sets target_sources( - MLIRQuartz + MLIRQuartzDialect PUBLIC FILE_SET HEADERS BASE_DIRS From 6a05a8b2c22e78dbdace3ac86c32596675a98ae6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 10:39:54 +0200 Subject: [PATCH 030/419] =?UTF-8?q?=F0=9F=9A=B8=20add=20dedicated=20builde?= =?UTF-8?q?rs=20to=20AllocOp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows calls like `builder.create(loc)` and produces less code than the inferred type variant. Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index a849ed05af..36f9514b51 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -114,6 +114,18 @@ def AllocOp : QuartzOp<"alloc"> { attr-dict `:` type($result) }]; + let builders = [ + OpBuilder<(ins), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); + }]>, + OpBuilder<(ins "::mlir::StringAttr":$register_name, + "::mlir::IntegerAttr":$register_size, + "::mlir::IntegerAttr":$register_index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), + register_name, register_size, register_index); + }]> + ]; + let hasVerifier = 1; } From 3ce27c466b3e34efaf05701eceab35af7f5976c8 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 10:40:17 +0200 Subject: [PATCH 031/419] =?UTF-8?q?=F0=9F=9A=A7=20first=20version=20of=20Q?= =?UTF-8?q?uartzProgramBuilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Quartz/Builder/QuartzProgramBuilder.h | 289 +++++++++++++++++- .../Quartz/Builder/QuartzProgramBuilder.cpp | 118 ++++++- 2 files changed, 396 insertions(+), 11 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 3e0f24fab2..dfdb0e4ab9 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -12,45 +12,316 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include +#include #include #include #include +#include -namespace mlir { -using namespace mlir::quartz; +namespace mlir::quartz { +/** + * @brief Builder API for constructing quantum programs in the Quartz dialect + * + * @details + * The QuartzProgramBuilder provides a type-safe, ergonomic interface for + * programmatically constructing quantum circuits using reference semantics. + * Gates operate on qubit references without explicit SSA value threading, + * making it natural to express imperative quantum programs similar to how + * hardware physically transforms quantum states. + * + * The builder follows the Quartz dialect's philosophy of reference semantics, + * where operations modify qubits in place. This provides: + * - Natural mapping to hardware execution models + * - Intuitive representation for circuit descriptions + * - Direct compatibility with imperative quantum programming languages + * - Support for method chaining through fluent interface design + * + * @par Example Usage: + * ```c++ + * QuartzProgramBuilder builder(context); + * builder.initialize(); + * + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * + * // Create Bell state using method chaining + * builder.h(q0).cx(q0, q1); + * + * auto module = builder.finalize(); + * ``` + * + * This produces the following MLIR module with a main function: + * ```mlir + * module { + * func.func @main() attributes {passthrough = ["entry_point"]} { + * %q0 = quartz.static 0 : !quartz.qubit + * %q1 = quartz.static 1 : !quartz.qubit + * quartz.h %q0 : !quartz.qubit + * quartz.cx %q0, %q1 : !quartz.qubit, !quartz.qubit + * func.return + * } + * } + * ``` + */ class QuartzProgramBuilder { public: + /** + * @brief Construct a new QuartzProgramBuilder + * @param context The MLIR context to use for building operations + */ explicit QuartzProgramBuilder(MLIRContext* context); //===--------------------------------------------------------------------===// // Initialization //===--------------------------------------------------------------------===// + /** + * @brief Initialize the builder and prepare for program construction + * + * @details + * This method must be called before any operations are added to the program. + * It creates a main function with an entry_point attribute and sets up the + * builder's insertion point. All subsequent operations will be added to the + * body of this main function. + * + * The generated function structure: + * ```mlir + * func.func @main() attributes {passthrough = ["entry_point"]} { + * // Operations added here + * } + * ``` + */ void initialize(); //===--------------------------------------------------------------------===// // Memory Management //===--------------------------------------------------------------------===// - // Dynamic allocation + /** + * @brief Dynamically allocate a single qubit + * + * @details + * Allocates a new qubit dynamically and returns a reference to it. + * The qubit is initialized to the |0⟩ state. + * + * @return A Value representing the allocated qubit + * + * @par Example: + * ```c++ + * auto q = builder.allocQubit(); + * ``` + * + * This generates: + * ```mlir + * %q = quartz.alloc : !quartz.qubit + * ``` + */ Value allocQubit(); - // Static qubit reference - Value qubit(size_t index); + /** + * @brief Get a static reference to a qubit by index + * + * @details + * Creates a reference to a qubit identified by a static index. This is + * useful for referring to fixed qubits in a quantum program or to + * hardware-mapped qubits. + * + * @param index The index of the qubit to reference + * @return A Value representing the qubit at the given index + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * ``` + * + * This generates: + * ```mlir + * %q0 = quartz.static 0 : !quartz.qubit + * %q1 = quartz.static 1 : !quartz.qubit + * ``` + */ + Value staticQubit(size_t index); + + /** + * @brief Allocate a qubit register as a sequence of individual qubits + * + * @details + * Allocates multiple qubits that form a logical register. Each qubit is + * allocated individually with metadata indicating its membership in the + * register, including the register name, size, and index within the register. + * + * The returned SmallVector can be used directly or converted to ValueRange + * as needed. + * + * @param size The number of qubits in the register + * @param name The symbolic name for the register (default: "q") + * @return A SmallVector containing the allocated qubits + * + * @par Example: + * ```c++ + * auto q = builder.allocQubitRegister(3, "q"); + * // Use q[0], q[1], q[2] directly + * ``` + * + * This generates: + * ```mlir + * %q0 = quartz.alloc q[3, 0] : !quartz.qubit + * %q1 = quartz.alloc q[3, 1] : !quartz.qubit + * %q2 = quartz.alloc q[3, 2] : !quartz.qubit + * ``` + */ + SmallVector allocQubitRegister(size_t size, StringRef name = "q"); + + /** + * @brief Allocate a classical bit register + * + * @details + * Allocates a classical register for storing measurement results or other + * classical data. The register is represented as a memref of i1 elements. + * + * @param size The number of bits in the register + * @param name The symbolic name for the register (default: "c") + * @return A Value representing the allocated memref of i1 elements + * + * @par Example: + * ```c++ + * auto c = builder.allocClassicalBitRegister(3, "c"); + * ``` + * + * This generates: + * ```mlir + * %c = memref.alloc() {sym_name = "c"} : memref<3xi1> + * ``` + */ + Value allocClassicalBitRegister(size_t size, StringRef name = "c"); + + //===--------------------------------------------------------------------===// + // Measurement and Reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measure a qubit and return the measurement result + * + * @details + * Measures a qubit in the computational (Z) basis, collapsing the state + * and returning a classical bit result (i1). + * + * @param qubit The qubit to measure + * @return A Value representing the classical measurement result (i1) + * + * @par Example: + * ```c++ + * auto q = builder.staticQubit(0); + * auto result = builder.measure(q); + * ``` + * + * This generates: + * ```mlir + * %q = quartz.static 0 : !quartz.qubit + * %result = quartz.measure %q : !quartz.qubit -> i1 + * ``` + */ + Value measure(Value qubit); + + /** + * @brief Measure a qubit and store the result in a classical register + * + * @details + * Measures a qubit in the computational basis and stores the result at + * the specified index in a classical register (memref). This is useful + * for accumulating multiple measurement results in a register. + * + * @param qubit The qubit to measure + * @param memref The classical register to store the result in + * @param index The index in the register where the result should be stored + * @return A reference to this builder for method chaining + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * auto c = builder.allocClassicalBitRegister(2); + * + * builder.measure(q0, c, 0) + * .measure(q1, c, 1); + * ``` + * + * This generates (within the main function body): + * ```mlir + * %q0 = quartz.static 0 : !quartz.qubit + * %q1 = quartz.static 1 : !quartz.qubit + * %c = memref.alloc() {sym_name = "c"} : memref<2xi1> + * %r0 = quartz.measure %q0 : !quartz.qubit -> i1 + * %c0 = arith.constant 0 : index + * memref.store %r0, %c[%c0] : memref<2xi1> + * %r1 = quartz.measure %q1 : !quartz.qubit -> i1 + * %c1 = arith.constant 1 : index + * memref.store %r1, %c[%c1] : memref<2xi1> + * ``` + */ + QuartzProgramBuilder& measure(Value qubit, Value memref, size_t index); - /// TODO + /** + * @brief Reset a qubit to the |0⟩ state + * + * @details + * Resets a qubit to the |0⟩ state, regardless of its current state. + * This operation is often used to recycle qubits in quantum algorithms + * or to initialize qubits to a known state. + * + * @param qubit The qubit to reset + * @return A reference to this builder for method chaining + * + * @par Example: + * ```c++ + * auto q = builder.staticQubit(0); + * builder.reset(q); + * ``` + * + * This generates: + * ```mlir + * %q = quartz.static 0 : !quartz.qubit + * quartz.reset %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& reset(Value qubit); //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// - ModuleOp finalize(); - ModuleOp getModule() const; + /** + * @brief Finalize the program and return the constructed module + * + * @details + * Completes the construction of the quantum program by: + * 1. Adding a return statement to the main function + * 2. Transferring ownership of the module to the caller + * + * After calling this method, the builder is invalidated and should not be + * used to add more operations. The returned OwningOpRef takes ownership of + * the module. + * + * @return OwningOpRef containing the constructed quantum program module + * + * @par Example: + * ```c++ + * QuartzProgramBuilder builder(context); + * builder.initialize(); + * auto q = builder.staticQubit(0); + * builder.h(q); + * auto module = builder.finalize(); + * // module now owns the MLIR module, builder is invalidated + * ``` + */ + OwningOpRef finalize(); private: OpBuilder builder; ModuleOp module; Location loc; }; -} // namespace mlir +} // namespace mlir::quartz diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index abfbe9e118..23c6e6a5e0 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -12,5 +12,119 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -using namespace mlir; -using namespace mlir::quartz; +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::quartz { + +QuartzProgramBuilder::QuartzProgramBuilder(MLIRContext* context) + : builder(context), + module(builder.create(UnknownLoc::get(context))), + loc(UnknownLoc::get(context)) {} + +void QuartzProgramBuilder::initialize() { + // Ensure the Quartz dialect is loaded + builder.getContext()->loadDialect(); + + // Set insertion point to the module body + builder.setInsertionPointToStart(module.getBody()); + + // Create main function as entry point + auto funcType = builder.getFunctionType({}, {}); + auto mainFunc = builder.create(loc, "main", funcType); + + // Add entry_point attribute to identify the main function + auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); + mainFunc->setAttr("passthrough", + ArrayAttr::get(builder.getContext(), {entryPointAttr})); + + // Create entry block and set insertion point + auto& entryBlock = mainFunc.getBody().emplaceBlock(); + builder.setInsertionPointToStart(&entryBlock); +} + +Value QuartzProgramBuilder::allocQubit() { + // Create the AllocOp without register metadata + auto allocOp = builder.create(loc); + return allocOp.getResult(); +} + +Value QuartzProgramBuilder::staticQubit(size_t index) { + // Create the StaticOp with the given index + auto indexAttr = builder.getI64IntegerAttr(static_cast(index)); + auto staticOp = builder.create(loc, indexAttr); + return staticOp.getQubit(); +} + +SmallVector QuartzProgramBuilder::allocQubitRegister(size_t size, + StringRef name) { + // Allocate a sequence of qubits with register metadata + SmallVector qubits; + qubits.reserve(size); + + auto nameAttr = builder.getStringAttr(name); + auto sizeAttr = builder.getI64IntegerAttr(static_cast(size)); + + for (size_t i = 0; i < size; ++i) { + auto indexAttr = builder.getI64IntegerAttr(static_cast(i)); + auto allocOp = builder.create(loc, nameAttr, sizeAttr, indexAttr); + qubits.push_back(allocOp.getResult()); + } + + return qubits; +} + +Value QuartzProgramBuilder::allocClassicalBitRegister(size_t size, + StringRef name) { + // Create memref type + auto memrefType = + MemRefType::get({static_cast(size)}, builder.getI1Type()); + + // Allocate the memref + auto allocOp = builder.create(loc, memrefType); + + allocOp->setAttr("sym_name", builder.getStringAttr(name)); + + return allocOp.getResult(); +} + +Value QuartzProgramBuilder::measure(Value qubit) { + auto measureOp = builder.create(loc, qubit); + return measureOp.getResult(); +} + +QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, Value memref, + size_t index) { + // Measure the qubit + auto result = measure(qubit); + + // Create constant index for the store operation + auto indexValue = builder.create( + loc, static_cast(index)); + + // Store the result in the memref at the given index + builder.create(loc, result, memref, + ValueRange{indexValue.getResult()}); + + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { + builder.create(loc, qubit); + return *this; +} + +OwningOpRef QuartzProgramBuilder::finalize() { + // Add return statement to the main function + builder.create(loc); + + // Transfer ownership to the caller + return module; +} + +} // namespace mlir::quartz From 4acd5db03c06c88cbaf2f92858969443c5392779 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 11:01:07 +0200 Subject: [PATCH 032/419] =?UTF-8?q?=F0=9F=8E=A8=20improve=20builder=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Quartz/Builder/QuartzProgramBuilder.h | 13 ++++---- .../Quartz/Builder/QuartzProgramBuilder.cpp | 30 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index dfdb0e4ab9..5bc9e02564 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -128,7 +128,7 @@ class QuartzProgramBuilder { * useful for referring to fixed qubits in a quantum program or to * hardware-mapped qubits. * - * @param index The index of the qubit to reference + * @param index The index of the qubit to reference (must be non-negative) * @return A Value representing the qubit at the given index * * @par Example: @@ -143,7 +143,7 @@ class QuartzProgramBuilder { * %q1 = quartz.static 1 : !quartz.qubit * ``` */ - Value staticQubit(size_t index); + Value staticQubit(int64_t index); /** * @brief Allocate a qubit register as a sequence of individual qubits @@ -156,7 +156,7 @@ class QuartzProgramBuilder { * The returned SmallVector can be used directly or converted to ValueRange * as needed. * - * @param size The number of qubits in the register + * @param size The number of qubits in the register (must be positive) * @param name The symbolic name for the register (default: "q") * @return A SmallVector containing the allocated qubits * @@ -173,7 +173,7 @@ class QuartzProgramBuilder { * %q2 = quartz.alloc q[3, 2] : !quartz.qubit * ``` */ - SmallVector allocQubitRegister(size_t size, StringRef name = "q"); + SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); /** * @brief Allocate a classical bit register @@ -196,7 +196,7 @@ class QuartzProgramBuilder { * %c = memref.alloc() {sym_name = "c"} : memref<3xi1> * ``` */ - Value allocClassicalBitRegister(size_t size, StringRef name = "c"); + Value allocClassicalBitRegister(int64_t size, StringRef name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset @@ -237,6 +237,7 @@ class QuartzProgramBuilder { * @param qubit The qubit to measure * @param memref The classical register to store the result in * @param index The index in the register where the result should be stored + * (must be non-negative) * @return A reference to this builder for method chaining * * @par Example: @@ -262,7 +263,7 @@ class QuartzProgramBuilder { * memref.store %r1, %c[%c1] : memref<2xi1> * ``` */ - QuartzProgramBuilder& measure(Value qubit, Value memref, size_t index); + QuartzProgramBuilder& measure(Value qubit, Value memref, int64_t index); /** * @brief Reset a qubit to the |0⟩ state diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 23c6e6a5e0..c2ce36bf6a 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -36,7 +36,7 @@ void QuartzProgramBuilder::initialize() { // Create main function as entry point auto funcType = builder.getFunctionType({}, {}); - auto mainFunc = builder.create(loc, "main", funcType); + auto mainFunc = builder.create(loc, "main", funcType); // Add entry_point attribute to identify the main function auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); @@ -54,24 +54,24 @@ Value QuartzProgramBuilder::allocQubit() { return allocOp.getResult(); } -Value QuartzProgramBuilder::staticQubit(size_t index) { +Value QuartzProgramBuilder::staticQubit(int64_t index) { // Create the StaticOp with the given index - auto indexAttr = builder.getI64IntegerAttr(static_cast(index)); + auto indexAttr = builder.getI64IntegerAttr(index); auto staticOp = builder.create(loc, indexAttr); return staticOp.getQubit(); } -SmallVector QuartzProgramBuilder::allocQubitRegister(size_t size, +SmallVector QuartzProgramBuilder::allocQubitRegister(int64_t size, StringRef name) { // Allocate a sequence of qubits with register metadata SmallVector qubits; - qubits.reserve(size); + qubits.reserve(static_cast(size)); auto nameAttr = builder.getStringAttr(name); - auto sizeAttr = builder.getI64IntegerAttr(static_cast(size)); + auto sizeAttr = builder.getI64IntegerAttr(size); - for (size_t i = 0; i < size; ++i) { - auto indexAttr = builder.getI64IntegerAttr(static_cast(i)); + for (int64_t i = 0; i < size; ++i) { + auto indexAttr = builder.getI64IntegerAttr(i); auto allocOp = builder.create(loc, nameAttr, sizeAttr, indexAttr); qubits.push_back(allocOp.getResult()); } @@ -79,14 +79,13 @@ SmallVector QuartzProgramBuilder::allocQubitRegister(size_t size, return qubits; } -Value QuartzProgramBuilder::allocClassicalBitRegister(size_t size, +Value QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { // Create memref type - auto memrefType = - MemRefType::get({static_cast(size)}, builder.getI1Type()); + auto memrefType = MemRefType::get({size}, builder.getI1Type()); // Allocate the memref - auto allocOp = builder.create(loc, memrefType); + auto allocOp = builder.create(loc, memrefType); allocOp->setAttr("sym_name", builder.getStringAttr(name)); @@ -99,13 +98,12 @@ Value QuartzProgramBuilder::measure(Value qubit) { } QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, Value memref, - size_t index) { + int64_t index) { // Measure the qubit auto result = measure(qubit); // Create constant index for the store operation - auto indexValue = builder.create( - loc, static_cast(index)); + auto indexValue = builder.create(loc, index); // Store the result in the memref at the given index builder.create(loc, result, memref, @@ -121,7 +119,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { OwningOpRef QuartzProgramBuilder::finalize() { // Add return statement to the main function - builder.create(loc); + builder.create(loc); // Transfer ownership to the caller return module; From 05a42890c66ef8430ba1ac35bc4d7bc487cb6a61 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 11:07:16 +0200 Subject: [PATCH 033/419] =?UTF-8?q?=F0=9F=9A=A7=20improved=20version=20of?= =?UTF-8?q?=20translation=20to=20quartz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Translation/ImportQuantumComputation.cpp | 360 ++++++++++-------- 1 file changed, 194 insertions(+), 166 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp index 7606289d61..50320179cb 100644 --- a/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp @@ -12,108 +12,59 @@ #include "ir/QuantumComputation.hpp" #include "ir/Register.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" #include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" +#include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include -#include #include -#include #include #include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include -#include -#include #include -#include -namespace mlir { -using namespace mlir::quartz; +namespace mlir::quartz { namespace { +/** + * @brief Structure to store information about quantum registers + * + * @details + * Maps quantum registers from the input QuantumComputation to their + * corresponding MLIR qubit values allocated via the QuartzProgramBuilder. + */ struct QregInfo { const qc::QuantumRegister* qregPtr; - mlir::Value qreg; - llvm::SmallVector qubits; + SmallVector qubits; }; -using BitMemInfo = std::pair; // (memref, localIdx) -using BitIndexVec = llvm::SmallVector; +using BitMemInfo = std::pair; // (memref, localIdx) +using BitIndexVec = SmallVector; /** - * @brief Allocates a quantum register in the MLIR module. + * @brief Allocates quantum registers using the QuartzProgramBuilder * - * @param builder The MLIR OpBuilder used to create operations - * @param context The MLIR context in which types are created - * @param numQubits The number of qubits to allocate in the register - * @return mlir::Value The allocated quantum register value - */ -mlir::Value allocateQreg(mlir::OpBuilder& builder, mlir::MLIRContext* context, - const std::size_t numQubits) { - const auto& qubitType = QubitType::get(context); - auto memRefType = - mlir::MemRefType::get({static_cast(numQubits)}, qubitType); - auto memref = builder.create(builder.getUnknownLoc(), - memRefType); - return memref.getResult(); -} - -/** - * @brief Extracts all qubits from a quantum register. + * @details + * Processes all quantum and ancilla registers from the QuantumComputation, + * sorting them by start index, and allocates them using the builder's + * allocQubitRegister method which generates quartz.alloc operations with + * proper register metadata. * - * @param builder The MLIR OpBuilder used to create operations - * @param qreg The quantum register from which to extract qubits - * @param numQubits The number of qubits to extract - * @return llvm::SmallVector Vector of extracted qubit values - */ -llvm::SmallVector extractQubits(mlir::OpBuilder& builder, - mlir::Value qreg, - const std::size_t numQubits) { - llvm::SmallVector qubits; - qubits.reserve(numQubits); - - for (std::size_t qubit = 0; qubit < numQubits; ++qubit) { - auto index = builder.create( - builder.getUnknownLoc(), qubit); - qubits.emplace_back( - builder - .create(builder.getUnknownLoc(), qreg, - mlir::ValueRange{index}) - .getResult()); - } - - return qubits; -} - -/** - * @brief Allocates quantum registers and extracts qubits. - * - * @param builder The MLIR OpBuilder used to create operations - * @param context The MLIR context in which types are created + * @param builder The QuartzProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate - * @return llvm::SmallVector Vector containing information about all - * quantum registers + * @return Vector containing information about all quantum registers */ -llvm::SmallVector -getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, - const qc::QuantumComputation& quantumComputation) { +SmallVector +allocateQregs(quartz::QuartzProgramBuilder& builder, + const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - llvm::SmallVector qregPtrs; + SmallVector qregPtrs; qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + quantumComputation.getAncillaRegisters().size()); for (const auto& qreg : @@ -131,31 +82,36 @@ getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, return a->getStartIndex() < b->getStartIndex(); }); - // Allocate quantum registers and extract qubits - llvm::SmallVector qregs; + // Allocate quantum registers using the builder + SmallVector qregs; for (const auto* qregPtr : qregPtrs) { - const auto qreg = allocateQreg(builder, context, qregPtr->getSize()); - auto qubits = extractQubits(builder, qreg, qregPtr->getSize()); - qregs.emplace_back(qregPtr, qreg, std::move(qubits)); + auto qubits = + builder.allocQubitRegister(qregPtr->getSize(), qregPtr->getName()); + qregs.emplace_back(qregPtr, std::move(qubits)); } return qregs; } /** - * @brief Builds a mapping from global qubit index to extracted qubit value. + * @brief Builds a flat mapping from global qubit index to qubit value + * + * @details + * Creates a flat array where each index corresponds to a physical qubit + * index, and the value is the MLIR Value representing that qubit. This + * simplifies operation translation by providing direct qubit lookup. * - * @param builder The MLIR OpBuilder used to create operations - * @param context The MLIR context in which types are created + * @param quantumComputation The quantum computation being translated * @param qregs Vector containing information about all quantum registers - * @return llvm::SmallVector Sorted vector of qubit values + * @return Flat vector of qubit values indexed by physical qubit index */ -llvm::SmallVector -getQubits(const qc::QuantumComputation& quantumComputation, - llvm::SmallVector& qregs) { - llvm::SmallVector flatQubits; +SmallVector +buildQubitMap(const qc::QuantumComputation& quantumComputation, + const SmallVector& qregs) { + SmallVector flatQubits; const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); flatQubits.resize(static_cast(maxPhys) + 1); + for (const auto& qreg : qregs) { for (std::size_t i = 0; i < qreg.qregPtr->getSize(); ++i) { const auto globalIdx = @@ -168,40 +124,22 @@ getQubits(const qc::QuantumComputation& quantumComputation, } /** - * @brief Deallocates the quantum register in the MLIR module. + * @brief Allocates classical registers using the QuartzProgramBuilder * - * @param builder The MLIR OpBuilder used to create operations - * @param qreg The quantum register to deallocate - */ -void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { - builder.create(builder.getUnknownLoc(), qreg); -} - -/** - * @brief Allocates a classical register in the MLIR module. - * - * @param builder The MLIR OpBuilder used to create operations - * @param numBits The number of bits to allocate in the register - * @return mlir::Value The allocated classical register value - */ -mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { - auto memRefType = mlir::MemRefType::get({numBits}, builder.getI1Type()); - auto memref = builder.create(builder.getUnknownLoc(), - memRefType); - return memref.getResult(); -} - -/** - * @brief Builds a mapping from global bit index to (memref, localIdx). + * @details + * Creates classical bit registers (memrefs) and builds a mapping from + * global classical bit indices to (memref, local_index) pairs. This is + * used for measurement result storage. * - * @param builder The MLIR OpBuilder used to create operations - * @param numBits The number of bits to allocate in the register - * @return mlir::Value The allocated classical register value + * @param builder The QuartzProgramBuilder used to create operations + * @param quantumComputation The quantum computation to translate + * @return Vector mapping global bit indices to memref and local indices */ -BitIndexVec getBitMap(mlir::OpBuilder& builder, - const qc::QuantumComputation& quantumComputation) { +BitIndexVec +allocateClassicalRegisters(quartz::QuartzProgramBuilder& builder, + const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - llvm::SmallVector cregPtrs; + SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); for (const auto& [_, reg] : quantumComputation.getClassicalRegisters()) { cregPtrs.emplace_back(®); @@ -213,11 +151,12 @@ BitIndexVec getBitMap(mlir::OpBuilder& builder, return a->getStartIndex() < b->getStartIndex(); }); - // Build mapping + // Build mapping using the builder BitIndexVec bitMap; bitMap.resize(quantumComputation.getNcbits()); for (const auto* reg : cregPtrs) { - auto mem = allocateBits(builder, static_cast(reg->getSize())); + auto mem = + builder.allocClassicalBitRegister(reg->getSize(), reg->getName()); for (std::size_t i = 0; i < reg->getSize(); ++i) { const auto globalIdx = static_cast(reg->getStartIndex() + i); bitMap[globalIdx] = {mem, i}; @@ -226,64 +165,153 @@ BitIndexVec getBitMap(mlir::OpBuilder& builder, return bitMap; } + +/** + * @brief Adds measurement operations for a single operation + * + * @details + * Translates measurement operations from the QuantumComputation to + * quartz.measure operations, storing results in classical registers. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The measurement operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + * @param bitMap Mapping from global bit index to (memref, local_index) + */ +void addMeasureOp(quartz::QuartzProgramBuilder& builder, + const qc::Operation& operation, + const SmallVector& qubits, const BitIndexVec& bitMap) { + const auto& measureOp = + dynamic_cast(operation); + const auto& targets = measureOp.getTargets(); + const auto& classics = measureOp.getClassics(); + + for (std::size_t i = 0; i < targets.size(); ++i) { + const auto& qubit = qubits[targets[i]]; + const auto bitIdx = static_cast(classics[i]); + const auto& [mem, localIdx] = bitMap[bitIdx]; + + // Use builder's measure method which stores to memref + builder.measure(qubit, mem, localIdx); + } +} + +/** + * @brief Adds reset operations for a single operation + * + * @details + * Translates reset operations from the QuantumComputation to + * quartz.reset operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The reset operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addResetOp(quartz::QuartzProgramBuilder& builder, + const qc::Operation& operation, + const SmallVector& qubits) { + for (const auto& target : operation.getTargets()) { + const Value qubit = qubits[target]; + builder.reset(qubit); + } +} + +/** + * @brief Translates operations from QuantumComputation to Quartz dialect + * + * @details + * Iterates through all operations in the QuantumComputation and translates + * them to Quartz dialect operations. Currently supports: + * - Measurement operations + * - Reset operations + * + * Unary gates and other operations will be added as the Quartz dialect + * is expanded. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param quantumComputation The quantum computation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + * @param bitMap Mapping from global bit index to (memref, local_index) + * @return Success if all supported operations were translated + */ +LogicalResult +translateOperations(quartz::QuartzProgramBuilder& builder, + const qc::QuantumComputation& quantumComputation, + const SmallVector& qubits, + const BitIndexVec& bitMap) { + for (const auto& operation : quantumComputation) { + switch (operation->getType()) { + case qc::OpType::Measure: + addMeasureOp(builder, *operation, qubits, bitMap); + break; + case qc::OpType::Reset: + addResetOp(builder, *operation, qubits); + break; + default: + // Unsupported operation - skip for now + // As the Quartz dialect is expanded, more operations will be supported + continue; + } + } + + return success(); +} + } // namespace /** * @brief Translates a QuantumComputation to an MLIR module with Quartz - * operations. + * operations * + * @details * This function takes a quantum computation and translates it into an MLIR - * module containing Quartz dialect operations. It creates a main function that - * contains all quantum operations from the input computation. + * module containing Quartz dialect operations. It uses the QuartzProgramBuilder + * to handle module and function creation, resource allocation, and operation + * translation. + * + * The translation process: + * 1. Creates a QuartzProgramBuilder and initializes it (creates main function) + * 2. Allocates quantum registers using quartz.alloc with register metadata + * 3. Allocates classical registers using memref.alloc + * 4. Translates operations (currently: measure, reset) + * 5. Finalizes the module (adds return statement) + * + * Currently supported operations: + * - Measurement (quartz.measure) + * - Reset (quartz.reset) + * + * Operations not yet supported are silently skipped. As the Quartz dialect + * is expanded with gate operations, this translation will be enhanced. * * @param context The MLIR context in which the module will be created * @param quantumComputation The quantum computation to translate - * @return mlir::OwningOpRef The translated MLIR module + * @return OwningOpRef containing the translated MLIR module */ -mlir::OwningOpRef translateQuantumComputationToMLIR( - mlir::MLIRContext* context, - const qc::QuantumComputation& quantumComputation) { - mlir::OpBuilder builder(context); - const auto loc = builder.getUnknownLoc(); - - // Create module - auto module = builder.create(loc); - builder.setInsertionPointToStart(module.getBody()); - - // Create main function as entry point - auto funcType = builder.getFunctionType({}, {}); - auto mainFunc = builder.create(loc, "main", funcType); - - // Add entry_point attribute to identify the main function - const auto entryPointAttr = mlir::StringAttr::get(context, "entry_point"); - mainFunc->setAttr("passthrough", - mlir::ArrayAttr::get(context, {entryPointAttr})); - - auto& entryBlock = mainFunc.getBody().emplaceBlock(); - builder.setInsertionPointToStart(&entryBlock); - - // Allocate quantum registers and extract qubits - auto qregs = getQregs(builder, context, quantumComputation); - auto qubits = getQubits(quantumComputation, qregs); - - // Allocate classical registers - auto bitMap = getBitMap(builder, quantumComputation); - - // Add operations and handle potential failures - // if (addOperations(builder, quantumComputation, qubits, bitMap).failed()) { - // // Even if operations fail, return the module with what we could - // translate emitError(loc) << "Failed to translate some quantum - // operations"; - // } - - // Deallocate quantum registers - for (const auto& qreg : qregs) { - deallocateQreg(builder, qreg.qreg); +OwningOpRef translateQuantumComputationToMLIR( + MLIRContext* context, const qc::QuantumComputation& quantumComputation) { + // Create and initialize the builder (creates module and main function) + quartz::QuartzProgramBuilder builder(context); + builder.initialize(); + + // Allocate quantum registers using the builder + auto qregs = allocateQregs(builder, quantumComputation); + + // Build flat qubit mapping for easy lookup + auto qubits = buildQubitMap(quantumComputation, qregs); + + // Allocate classical registers using the builder + auto bitMap = allocateClassicalRegisters(builder, quantumComputation); + + // Translate operations + if (translateOperations(builder, quantumComputation, qubits, bitMap) + .failed()) { + // Note: Currently all operations succeed or are skipped + // This check is here for future error handling } - // Create terminator - builder.create(loc); - - return module; + // Finalize and return the module (adds return statement and transfers + // ownership) + return builder.finalize(); } -} // namespace mlir + +} // namespace mlir::quartz From a04a16f1f452761e1f9e068e43f59cbe18e42c1a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 8 Oct 2025 21:45:03 +0200 Subject: [PATCH 034/419] =?UTF-8?q?=F0=9F=9A=A7=20Quartz=20to=20QIR=20conv?= =?UTF-8?q?ersion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 780 ++++++++++++++++-- 1 file changed, 710 insertions(+), 70 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 5cd754d6a1..30e453a53d 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -17,38 +17,30 @@ #include #include #include -#include -#include #include #include #include -#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include -#include #include #include #include #include -#include #include #include #include #include #include -#include namespace mlir { @@ -59,24 +51,89 @@ using namespace mlir::quartz; namespace { +/** + * @brief Looks up or creates a QIR function declaration + * + * @details + * Searches for an existing function declaration in the symbol table. If not + * found, creates a new function declaration at the end of the module. + * + * For QIR functions that are irreversible (measurement, reset, deallocation), + * the "irreversible" attribute is added automatically. + * + * @param rewriter The pattern rewriter to use + * @param op The operation requesting the function + * @param fnName The name of the QIR function + * @param fnType The LLVM function type signature + * @return The LLVM function declaration + */ +LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, + Operation* op, StringRef fnName, + Type fnType) { + // Check if the function already exists + auto* fnDecl = + SymbolTable::lookupNearestSymbolFrom(op, rewriter.getStringAttr(fnName)); + + if (fnDecl == nullptr) { + // Create the declaration at the end of the module + const PatternRewriter::InsertionGuard insertGuard(rewriter); + auto module = op->getParentOfType(); + rewriter.setInsertionPointToEnd(module.getBody()); + + fnDecl = rewriter.create(op->getLoc(), fnName, fnType); + + // Add irreversible attribute to irreversible quantum operations + if (fnName == "__quantum__qis__mz__body" || + fnName == "__quantum__rt__qubit_release" || + fnName == "__quantum__qis__reset__body") { + fnDecl->setAttr("passthrough", + rewriter.getStrArrayAttr({"irreversible"})); + } + } + + return cast(fnDecl); +} + +/** + * @brief State object for tracking lowering information during QIR conversion + * + * @details + * This struct maintains state during the conversion of Quartz dialect + * operations to QIR (Quantum Intermediate Representation). It tracks: + * - Qubit and result counts for QIR metadata + * - Pointer value caching for reuse + * - Result output mapping + * - Whether dynamic memory management is needed + */ struct LoweringState { - // map a given index to a pointer value, to reuse the value instead of - // creating a new one every time + /// Map from qubit index to pointer value for reuse DenseMap ptrMap; - // map a given index to an address to record the classical output + /// Map from result index to addressOf operation for output recording DenseMap outputMap; - // Index for the next measure operation + /// Index for the next measure operation label size_t index{}; - // number of stored results in the module - size_t numResults{}; - // number of qubits in the module + /// Number of qubits used in the module size_t numQubits{}; - // boolean to check if the module uses dynamically addressed qubits + /// Number of measurement results stored in the module + size_t numResults{}; + /// Whether the module uses dynamic qubit management (true when `quartz.alloc` + /// is used, false when only `quartz.static` is used) bool useDynamicQubit{}; - // boolean to check if the module uses dynamically addressed results + /// Whether the module uses dynamic result management (expected: false for + /// Quartz at the moment) bool useDynamicResult{}; }; +/** + * @brief Base class for conversion patterns that need access to lowering state + * + * @details + * Extends OpConversionPattern to provide access to a shared LoweringState + * object, which tracks qubit/result counts and caches values across multiple + * pattern applications. + * + * @tparam OpType The operation type to convert + */ template class StatefulOpConversionPattern : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -86,7 +143,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state) : OpConversionPattern(typeConverter, ctx), state_(state) {} - /// @brief Return the state object as reference. + /// Returns the shared lowering state object [[nodiscard]] LoweringState& getState() const { return *state_; } private: @@ -95,29 +152,457 @@ class StatefulOpConversionPattern : public OpConversionPattern { } // namespace +/** + * @brief Type converter for lowering Quartz dialect types to LLVM types + * + * @details + * Converts Quartz dialect types to their LLVM equivalents for QIR emission. + * + * Type conversions: + * - `!quartz.qubit` -> `!llvm.ptr` (opaque pointer to qubit in QIR) + */ struct QuartzToQIRTypeConverter final : LLVMTypeConverter { explicit QuartzToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { - // QubitType conversion + // Convert QubitType to LLVM pointer (QIR uses opaque pointers for qubits) addConversion( [ctx](QubitType /*type*/) { return LLVM::LLVMPointerType::get(ctx); }); } }; +namespace { + +/** + * @brief Converts quartz.static operation to QIR inttoptr + * + * @details + * Converts a static qubit reference to an LLVM pointer by creating a constant + * with the qubit index and converting it to a pointer. The pointer is cached + * in the lowering state for reuse. + * + * @par Example: + * ```mlir + * %q0 = quartz.static 0 : !quartz.qubit + * ``` + * becomes: + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * ``` + */ +struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + const auto index = static_cast(op.getIndex()); + + // Get or create a pointer to the qubit + if (getState().ptrMap.contains(index)) { + // Reuse existing pointer + rewriter.replaceOp(op, getState().ptrMap.at(index)); + } else { + // Create constant and inttoptr operations + const auto constantOp = rewriter.create( + op.getLoc(), rewriter.getI64IntegerAttr(static_cast(index))); + const auto intToPtrOp = rewriter.replaceOpWithNewOp( + op, LLVM::LLVMPointerType::get(ctx), constantOp->getResult(0)); + + // Cache for reuse + getState().ptrMap.try_emplace(index, intToPtrOp->getResult(0)); + } + + // Track maximum qubit index + if (index >= getState().numQubits) { + getState().numQubits = index + 1; + } + + return success(); + } +}; + +/** + * @brief Converts quartz.alloc operation to QIR qubit_allocate + * + * @details + * Converts dynamic qubit allocation to a call to the QIR + * __quantum__rt__qubit_allocate function. + * + * Register metadata (register_name, register_size, register_index) is ignored + * during QIR conversion as it is used for analysis and readability only. + * + * @par Example: + * ```mlir + * %q = quartz.alloc : !quartz.qubit + * ``` + * becomes: + * ```mlir + * %q = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * ``` + */ +struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + static constexpr StringLiteral FN_NAME_ALLOCATE = + "__quantum__rt__qubit_allocate"; + + LogicalResult + matchAndRewrite(AllocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: () -> ptr + const auto qirSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMPointerType::get(ctx), {}); + + // Get or create function declaration + const auto fnDecl = + getFunctionDeclaration(rewriter, op, FN_NAME_ALLOCATE, qirSignature); + + // Replace with call to qubit_allocate + rewriter.replaceOpWithNewOp(op, fnDecl, ValueRange{}); + + // Track qubit count and mark as using dynamic allocation + getState().numQubits++; + getState().useDynamicQubit = true; + + return success(); + } +}; + +/** + * @brief Converts quartz.dealloc operation to QIR qubit_release + * + * @details + * Converts dynamic qubit deallocation to a call to the QIR + * __quantum__rt__qubit_release function, which releases a dynamically + * allocated qubit. + * + * @par Example: + * ```mlir + * quartz.dealloc %q : !quartz.qubit + * ``` + * becomes: + * ```mlir + * llvm.call @__quantum__rt__qubit_release(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzDeallocQIR final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + static constexpr StringLiteral FN_NAME_QUBIT_RELEASE = + "__quantum__rt__qubit_release"; + + LogicalResult + matchAndRewrite(DeallocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (ptr) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + + // Get or create function declaration + const auto fnDecl = getFunctionDeclaration( + rewriter, op, FN_NAME_QUBIT_RELEASE, qirSignature); + + // Replace with call to qubit_release + rewriter.replaceOpWithNewOp(op, fnDecl, + adaptor.getOperands()); + return success(); + } +}; + +/** + * @brief Converts quartz.reset operation to QIR reset + * + * @details + * Converts qubit reset to a call to the QIR __quantum__qis__reset__body + * function, which resets a qubit to the |0⟩ state. + * + * @par Example: + * ```mlir + * quartz.reset %q : !quartz.qubit + * ``` + * becomes: + * ```mlir + * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzResetQIR final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + static constexpr StringLiteral FN_NAME_RESET = "__quantum__qis__reset__body"; + + LogicalResult + matchAndRewrite(ResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (ptr) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + + // Get or create function declaration + const auto fnDecl = + getFunctionDeclaration(rewriter, op, FN_NAME_RESET, qirSignature); + + // Replace with call to reset + rewriter.replaceOpWithNewOp(op, fnDecl, + adaptor.getOperands()); + return success(); + } +}; + +/** + * @brief Converts quartz.measure operation to QIR measurement and output + * recording + * + * @details + * Converts qubit measurement to two QIR calls: + * 1. `__quantum__qis__mz__body`: Performs the measurement + * 2. `__quantum__rt__result_record_output`: Records the result for output + * + * The converter examines the users of the measurement result to find + * memref.store operations, extracting the classical register index from them. + * This ensures faithful translation of which classical bit receives the + * measurement result, including cases where multiple measurements target the + * same bit. + * + * The result pointer is created using inttoptr and cached for reuse. A global + * constant is created for the result label (r0, r1, etc.) based on the + * classical register index. + * + * @par Example: + * ```mlir + * %result = quartz.measure %q : !quartz.qubit -> i1 + * %c0 = arith.constant 0 : index + * memref.store %result, %creg[%c0] : memref<2xi1> + * ``` + * becomes: + * ```mlir + * %c0_i64 = llvm.mlir.constant(0 : i64) : i64 + * %result_ptr = llvm.inttoptr %c0_i64 : i64 to !llvm.ptr + * llvm.call @__quantum__qis__mz__body(%q, %result_ptr) : (!llvm.ptr, !llvm.ptr) + * -> () llvm.call @__quantum__rt__result_record_output(%result_ptr, %label0) : + * (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + static constexpr StringLiteral FN_NAME_MEASURE = "__quantum__qis__mz__body"; + static constexpr StringLiteral FN_NAME_RECORD_OUTPUT = + "__quantum__rt__result_record_output"; + + /** + * @brief Creates a global constant and addressOf for result labeling + * + * @details + * Creates a global string constant at module level with the label "rN\0" + * where N is the index, and returns an addressOf operation pointing to it. + * The addressOf is placed at the start of the main function. + * + * @param op The measure operation + * @param rewriter The rewriter + * @param state The lowering state + * @return The addressOf operation for the global constant + */ + static Operation* getAddressOfOp(Operation* op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + // Create global at module level + auto module = op->getParentOfType(); + rewriter.setInsertionPointToStart(module.getBody()); + + // Calculate string length for array size + auto num = state.index; + auto digits = 1; + while (num >= 10) { + num /= 10; + ++digits; + } + + // Create global with label "r0\0", "r1\0", etc. + const auto symbolName = rewriter.getStringAttr( + "mlir.llvm.nameless_global_" + std::to_string(state.index)); + const auto llvmArrayType = + LLVM::LLVMArrayType::get(rewriter.getIntegerType(8), digits + 2); + const auto stringInitializer = + rewriter.getStringAttr("r" + std::to_string(state.index) + '\0'); + + auto globalOp = rewriter.create( + op->getLoc(), llvmArrayType, /*isConstant=*/true, + LLVM::Linkage::Internal, symbolName, stringInitializer); + globalOp->setAttr("addr_space", rewriter.getI32IntegerAttr(0)); + globalOp->setAttr("dso_local", rewriter.getUnitAttr()); + + // Create addressOf at start of main function + auto main = op->getParentOfType(); + auto& firstBlock = *(main.getBlocks().begin()); + rewriter.setInsertionPointToStart(&firstBlock); + const auto addressOfOp = rewriter.create( + op->getLoc(), LLVM::LLVMPointerType::get(rewriter.getContext()), + symbolName); + + // Restore insertion point + rewriter.setInsertionPoint(op); + state.index++; + + return addressOfOp; + } + + LogicalResult + matchAndRewrite(MeasureOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + auto& ptrMap = getState().ptrMap; + auto& outputMap = getState().outputMap; + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + + // Get or create result pointer + Value resultValue = nullptr; + if (ptrMap.contains(getState().numResults)) { + resultValue = ptrMap.at(getState().numResults); + } else { + auto constantOp = rewriter.create( + op.getLoc(), rewriter.getI64IntegerAttr( + static_cast(getState().numResults))); + resultValue = rewriter + .create(op.getLoc(), ptrType, + constantOp->getResult(0)) + .getResult(); + ptrMap.try_emplace(getState().numResults, resultValue); + } + + // Create mz (measure) call: mz(qubit, result) + const auto mzSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); + const auto mzDecl = + getFunctionDeclaration(rewriter, op, FN_NAME_MEASURE, mzSignature); + rewriter.create(op.getLoc(), mzDecl, + ValueRange{adaptor.getQubit(), resultValue}); + + // Find the classical register index by examining store operations + size_t index = 0; + for (const auto* user : op->getResult(0).getUsers()) { + if (auto storeOp = dyn_cast(user)) { + // Extract the index from the store operation + auto constantOp = + storeOp.getIndices()[0].getDefiningOp(); + if (!constantOp) { + op.emitError("Measurement index could not be resolved. Index is not " + "a ConstantOp."); + return failure(); + } + + auto integerAttr = dyn_cast(constantOp.getValue()); + if (!integerAttr) { + op.emitError("Measurement index could not be resolved. Index cannot " + "be cast to IntegerAttr."); + return failure(); + } + index = integerAttr.getInt(); + + const auto allocaOp = storeOp.getMemref(); + + // Clean up the store operation and related ops + storeOp->dropAllReferences(); + rewriter.eraseOp(storeOp); + + // Erase the alloca if all stores are removed + if (allocaOp.use_empty()) { + rewriter.eraseOp(allocaOp.getDefiningOp()); + } + + // Delete the constant if there are no users left + if (constantOp->use_empty()) { + rewriter.eraseOp(constantOp); + } + break; + } + } + + // Create result_record_output call + const auto recordSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); + const auto recordDecl = getFunctionDeclaration( + rewriter, op, FN_NAME_RECORD_OUTPUT, recordSignature); + + // Get or create output label addressOf for this index + Operation* labelOp = nullptr; + if (outputMap.contains(index)) { + // Reuse existing label for this index (handles multiple measurements to + // same bit) + labelOp = outputMap.at(index); + } else { + // Create new label + labelOp = getAddressOfOp(op, rewriter, getState()); + outputMap.try_emplace(index, labelOp); + // Only increment result count for new labels + getState().numResults++; + } + + rewriter.create( + op.getLoc(), recordDecl, + ValueRange{resultValue, labelOp->getResult(0)}); + + // Remove the original operation + rewriter.eraseOp(op); + + return success(); + } +}; + +} // namespace + +/** + * @brief Pass for converting Quartz dialect operations to QIR + * + * @details + * This pass converts Quartz dialect quantum operations to QIR (Quantum + * Intermediate Representation) by lowering them to LLVM dialect operations + * that call QIR runtime functions. + * + * Conversion stages: + * 1. Convert func dialect to LLVM + * 2. Ensure proper block structure for QIR base profile and add initialization + * 3. Convert Quartz operations to QIR calls + * 4. Set QIR metadata attributes + * 5. Convert arith and cf dialects to LLVM + * 6. Reconcile unrealized casts + * + * Current scope: + * - Quartz operations: static, alloc, dealloc, measure, reset + * - Gate operations will be added as the dialect expands + * - Supports both static and dynamic qubit management + */ struct QuartzToQIR final : impl::QuartzToQIRBase { using QuartzToQIRBase::QuartzToQIRBase; + /// QIR runtime initialization function name static constexpr StringLiteral FN_NAME_INITIALIZE = "__quantum__rt__initialize"; /** - * @brief Finds the main function in the module + * @brief Finds the main entry point function in the module + * + * @details + * Searches for the LLVM function marked with the "entry_point" attribute in + * the passthrough attributes. This is the main entry point created by the + * QuartzProgramBuilder. * - * @param op The module operation that holds all operations. - * @return The main function. + * @param op The module operation to search in + * @return The main LLVM function, or nullptr if not found */ static LLVM::LLVMFuncOp getMainFunction(Operation* op) { auto module = dyn_cast(op); - // find the main function + if (!module) { + return nullptr; + } + + // Search for function with entry_point attribute for (auto funcOp : module.getOps()) { auto passthrough = funcOp->getAttrOfType("passthrough"); if (!passthrough) { @@ -134,33 +619,108 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { } /** - * @brief Adds the initialize operation to the first block of the main - * function. + * @brief Ensures proper block structure for QIR base profile + * + * @details + * The QIR base profile requires a specific 4-block structure: + * 1. **Entry block**: Contains constant operations and initialization + * 2. **Main block**: Contains reversible quantum operations (gates) + * 3. **Irreversible block**: Contains irreversible operations (measure, + * reset, dealloc) + * 4. **End block**: Contains return operation + * + * Blocks are connected with unconditional jumps (entry → main → + * irreversible → end). This structure ensures proper QIR semantics and + * enables optimizations. + * + * If the function already has multiple blocks, this function does nothing. * - * @param main The main function of the module. - * @param ctx The context of the module. + * @param main The main LLVM function to restructure + */ + static void ensureBlocks(LLVM::LLVMFuncOp& main) { + // Return if there are already multiple blocks + if (main.getBlocks().size() > 1) { + return; + } + + // Get the existing block + auto* mainBlock = &main.front(); + OpBuilder builder(main.getBody()); + + // Create the required blocks + auto* entryBlock = builder.createBlock(&main.getBody()); + // Move the entry block before the main block + main.getBlocks().splice(Region::iterator(mainBlock), main.getBlocks(), + entryBlock); + Block* irreversibleBlock = builder.createBlock(&main.getBody()); + Block* endBlock = builder.createBlock(&main.getBody()); + + auto& mainBlockOps = mainBlock->getOperations(); + auto& endBlockOps = endBlock->getOperations(); + auto& irreversibleBlockOps = irreversibleBlock->getOperations(); + + // Move operations to appropriate blocks + for (auto it = mainBlock->begin(); it != mainBlock->end();) { + // Ensure iterator remains valid after potential move + auto& op = *it++; + + // Check for irreversible operations + if (op.getDialect()->getNamespace() == "memref") { + // Keep memref operations for classical bits in place (they're not + // quantum operations) + continue; + } + if (isa(op) || isa(op) || isa(op)) { + // Move irreversible quantum operations to irreversible block + irreversibleBlockOps.splice(irreversibleBlock->end(), mainBlockOps, + Block::iterator(op)); + } else if (isa(op)) { + // Move return to end block + endBlockOps.splice(endBlock->end(), mainBlockOps, Block::iterator(op)); + } + // All other operations (gates, etc.) stay in main block + } + + // Add unconditional jumps between blocks + builder.setInsertionPointToEnd(entryBlock); + builder.create(main->getLoc(), mainBlock); + + builder.setInsertionPointToEnd(mainBlock); + builder.create(main->getLoc(), irreversibleBlock); + + builder.setInsertionPointToEnd(irreversibleBlock); + builder.create(main->getLoc(), endBlock); + } + + /** + * @brief Adds QIR initialization call to the entry block + * + * @details + * Inserts a call to `__quantum__rt__initialize` at the end of the entry + * block (before the jump to main block). This QIR runtime function + * initializes the quantum execution environment and takes a null pointer as + * argument. + * + * @param main The main LLVM function + * @param ctx The MLIR context + * @param state The lowering state (unused but kept for consistency) */ static void addInitialize(LLVM::LLVMFuncOp& main, MLIRContext* ctx, - LoweringState* state) { + LoweringState* /*state*/) { auto moduleOp = main->getParentOfType(); - auto& firstBlock = *(main.getBlocks().begin()); OpBuilder builder(main.getBody()); - // create the zero op + // Create a zero (null) pointer for the initialize call builder.setInsertionPointToStart(&firstBlock); - auto zeroOperation = builder.create( - main->getLoc(), LLVM::LLVMPointerType::get(ctx)); - - // add the zero operation to the pointerMap - state->ptrMap.try_emplace(0, zeroOperation->getResult(0)); + auto zeroOp = builder.create(main->getLoc(), + LLVM::LLVMPointerType::get(ctx)); - // create the initialize operation as the 2nd last operation in the first - // block after all constant operations and before the last jump operation + // Insert the initialize call before the jump to main block const auto insertPoint = std::prev(firstBlock.getOperations().end(), 1); builder.setInsertionPoint(&*insertPoint); - // get the function declaration of initialize otherwise create one + // Get or create the initialize function declaration auto* fnDecl = SymbolTable::lookupNearestSymbolFrom( main, builder.getStringAttr(FN_NAME_INITIALIZE)); if (fnDecl == nullptr) { @@ -171,34 +731,57 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { fnDecl = builder.create( main->getLoc(), FN_NAME_INITIALIZE, fnSignature); } - // create and insert the initialize operation - builder.create(main->getLoc(), - static_cast(fnDecl), - ValueRange{zeroOperation->getResult(0)}); + + // Create the initialization call + builder.create(main->getLoc(), cast(fnDecl), + ValueRange{zeroOp->getResult(0)}); } + /** - * @brief Sets the necessary attributes to the main function for the QIR base - * profile. The required module flags are also set as attributes. + * @brief Sets QIR base profile metadata attributes on the main function + * + * @details + * Adds the required metadata attributes for QIR base profile compliance: + * - `entry_point`: Marks the main entry point function + * - `output_labeling_schema`: schema_id + * - `qir_profiles`: base_profile + * - `required_num_qubits`: Number of qubits used + * - `required_num_results`: Number of measurement results + * - `qir_major_version`: 1 + * - `qir_minor_version`: 0 + * - `dynamic_qubit_management`: true/false + * - `dynamic_result_management`: true/false * - * @param main The main function of the module. - * @param state The lowering state of the conversion pass. + * These attributes are required by the QIR specification and inform QIR + * consumers about the module's resource requirements and capabilities. + * + * @param main The main LLVM function to annotate + * @param state The lowering state containing qubit/result counts */ static void setAttributes(LLVM::LLVMFuncOp& main, LoweringState* state) { OpBuilder builder(main.getBody()); SmallVector attributes; + + // Core QIR attributes attributes.emplace_back(builder.getStringAttr("entry_point")); attributes.emplace_back( builder.getStrArrayAttr({"output_labeling_schema", "schema_id"})); attributes.emplace_back( builder.getStrArrayAttr({"qir_profiles", "base_profile"})); + + // Resource requirements attributes.emplace_back(builder.getStrArrayAttr( {"required_num_qubits", std::to_string(state->numQubits)})); attributes.emplace_back(builder.getStrArrayAttr( {"required_num_results", std::to_string(state->numResults)})); + + // QIR version attributes.emplace_back( builder.getStrArrayAttr({"qir_major_version", "1"})); attributes.emplace_back( builder.getStrArrayAttr({"qir_minor_version", "0"})); + + // Management model attributes.emplace_back( builder.getStrArrayAttr({"dynamic_qubit_management", state->useDynamicQubit ? "true" : "false"})); @@ -209,60 +792,117 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { main->setAttr("passthrough", builder.getArrayAttr(attributes)); } + /** + * @brief Executes the Quartz to QIR conversion pass + * + * @details + * Performs the conversion in six stages: + * + * **Stage 1: Func to LLVM** + * Convert func dialect operations (main function) to LLVM dialect + * equivalents. + * + * **Stage 2: Block structure and initialization** + * Create proper 4-block structure for QIR base profile (entry, main, + * irreversible, end blocks) and insert the `__quantum__rt__initialize` call + * in the entry block. + * + * **Stage 3: Quartz to LLVM** + * Convert Quartz dialect operations to QIR calls (static, alloc, dealloc, + * measure, reset). + * + * **Stage 4: QIR attributes** + * Add QIR base profile metadata to the main function, including qubit/result + * counts and version information. + * + * **Stage 5: Standard dialects to LLVM** + * Convert arith and control flow dialects to LLVM (for index arithmetic and + * function control flow). + * + * **Stage 6: Reconcile casts** + * Clean up any unrealized cast operations introduced during type conversion. + */ void runOnOperation() override { MLIRContext* ctx = &getContext(); auto* moduleOp = getOperation(); ConversionTarget target(*ctx); - RewritePatternSet funcPatterns(ctx); - RewritePatternSet mqtPatterns(ctx); - RewritePatternSet stdPatterns(ctx); QuartzToQIRTypeConverter typeConverter(ctx); target.addLegalDialect(); - // convert func to LLVM - target.addIllegalDialect(); + // Stage 1: Convert func dialect to LLVM + { + RewritePatternSet funcPatterns(ctx); + target.addIllegalDialect(); + populateFuncToLLVMConversionPatterns(typeConverter, funcPatterns); - populateFuncToLLVMConversionPatterns(typeConverter, funcPatterns); + if (applyPartialConversion(moduleOp, target, std::move(funcPatterns)) + .failed()) { + signalPassFailure(); + return; + } + } - if (applyPartialConversion(moduleOp, target, std::move(funcPatterns)) - .failed()) { + // Stage 2: Ensure proper block structure and add initialization + auto main = getMainFunction(moduleOp); + if (!main) { + moduleOp->emitError("No main function with entry_point attribute found"); signalPassFailure(); + return; } - // convert Quartz to LLVM - auto main = getMainFunction(moduleOp); - // ensureBlocks(main); + ensureBlocks(main); LoweringState state; addInitialize(main, ctx, &state); - target.addIllegalDialect(); + // Stage 3: Convert Quartz dialect to LLVM (QIR calls) + { + RewritePatternSet quartzPatterns(ctx); + target.addIllegalDialect(); - if (applyPartialConversion(moduleOp, target, std::move(mqtPatterns)) - .failed()) { - signalPassFailure(); + // Add conversion patterns for Quartz operations + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx); + quartzPatterns.add(typeConverter, ctx); + quartzPatterns.add(typeConverter, ctx, &state); + + // Gate operations will be added here as the dialect expands + + if (applyPartialConversion(moduleOp, target, std::move(quartzPatterns)) + .failed()) { + signalPassFailure(); + return; + } } + // Stage 4: Set QIR metadata attributes setAttributes(main, &state); - // convert arith and cf to LLVM - target.addIllegalDialect(); - target.addIllegalDialect(); + // Stage 5: Convert standard dialects to LLVM + { + RewritePatternSet stdPatterns(ctx); + target.addIllegalDialect(); + target.addIllegalDialect(); - cf::populateControlFlowToLLVMConversionPatterns(typeConverter, stdPatterns); - arith::populateArithToLLVMConversionPatterns(typeConverter, stdPatterns); + cf::populateControlFlowToLLVMConversionPatterns(typeConverter, + stdPatterns); + arith::populateArithToLLVMConversionPatterns(typeConverter, stdPatterns); - if (applyPartialConversion(moduleOp, target, std::move(stdPatterns)) - .failed()) { - signalPassFailure(); + if (applyPartialConversion(moduleOp, target, std::move(stdPatterns)) + .failed()) { + signalPassFailure(); + return; + } } + // Stage 6: Reconcile unrealized casts PassManager passManager(ctx); passManager.addPass(createReconcileUnrealizedCastsPass()); if (passManager.run(moduleOp).failed()) { signalPassFailure(); } - }; + } }; } // namespace mlir From e18e45da677175aa4c0b53be6b593e82a1c75111 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 10:21:12 +0200 Subject: [PATCH 035/419] =?UTF-8?q?=F0=9F=9A=A7=20initial=20quartz=20to=20?= =?UTF-8?q?flux=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 357 +++++++++++++++++- 1 file changed, 348 insertions(+), 9 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 1a5898c7aa..7777806163 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -33,25 +33,350 @@ #include namespace mlir { -using namespace mlir::flux; -using namespace mlir::quartz; +using namespace flux; +using namespace quartz; #define GEN_PASS_DEF_QUARTZTOFLUX #include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" +namespace { + +/** + * @brief State object for tracking qubit value flow during conversion + * + * @details + * This struct maintains the mapping between Quartz dialect qubits (which use + * reference semantics) and their corresponding Flux dialect qubit values + * (which use value semantics). As the conversion progresses, each Quartz + * qubit reference is mapped to its latest Flux SSA value. + * + * The key insight is that Quartz operations modify qubits in-place: + * ```mlir + * %q = quartz.alloc : !quartz.qubit + * quartz.h %q : !quartz.qubit // modifies %q in-place + * quartz.x %q : !quartz.qubit // modifies %q in-place + * ``` + * + * While Flux operations consume inputs and produce new outputs: + * ```mlir + * %q0 = flux.alloc : !flux.qubit + * %q1 = flux.h %q0 : !flux.qubit -> !flux.qubit // %q0 consumed, %q1 produced + * %q2 = flux.x %q1 : !flux.qubit -> !flux.qubit // %q1 consumed, %q2 produced + * ``` + * + * The qubitMap tracks that the Quartz qubit %q corresponds to: + * - %q0 after allocation + * - %q1 after the H gate + * - %q2 after the X gate + */ +struct LoweringState { + /// Map from original Quartz qubit references to their latest Flux SSA values + llvm::DenseMap qubitMap; +}; + +/** + * @brief Base class for conversion patterns that need access to lowering state + * + * @details + * Extends OpConversionPattern to provide access to a shared LoweringState + * object, which tracks the mapping from reference-semantics Quartz qubits + * to value-semantics Flux qubits across multiple pattern applications. + * + * This stateful approach is necessary because the conversion needs to: + * 1. Track which Flux value corresponds to each Quartz qubit reference + * 2. Update these mappings as operations transform qubits + * 3. Share this information across different conversion patterns + * + * @tparam OpType The Quartz operation type to convert + */ +template +class StatefulOpConversionPattern : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + +public: + StatefulOpConversionPattern(TypeConverter& typeConverter, + MLIRContext* context, LoweringState* state) + : OpConversionPattern(typeConverter, context), state_(state) {} + + /// Returns the shared lowering state object + [[nodiscard]] LoweringState& getState() const { return *state_; } + +private: + LoweringState* state_; +}; + +} // namespace + +/** + * @brief Type converter for Quartz-to-Flux conversion + * + * @details + * Handles type conversion between the Quartz and Flux dialects. + * The primary conversion is from !quartz.qubit to !flux.qubit, which + * represents the semantic shift from reference types to value types. + * + * Other types (integers, booleans, etc.) pass through unchanged via + * the identity conversion. + */ class QuartzToFluxTypeConverter final : public TypeConverter { public: explicit QuartzToFluxTypeConverter(MLIRContext* ctx) { - // Identity conversion + // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // QubitType conversion + // Convert Quartz qubit references to Flux qubit values addConversion([ctx](quartz::QubitType /*type*/) -> Type { return flux::QubitType::get(ctx); }); } }; +/** + * @brief Converts quartz.alloc to flux.alloc + * + * @details + * Allocates a new qubit and establishes the initial mapping in the state. + * Both dialects initialize qubits to the |0⟩ state. + * + * Register metadata (name, size, index) is preserved during conversion, + * allowing the Flux representation to maintain register information for + * debugging and visualization. + * + * Example transformation: + * ```mlir + * %q = quartz.alloc q[3, 0] : !quartz.qubit + * // becomes: + * %q0 = flux.alloc q[3, 0] : !flux.qubit + * ``` + */ +struct ConvertQuartzAllocOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::AllocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto& quartzQubit = op.getResult(); + + // Create the flux.alloc operation with preserved register metadata + auto fluxOp = rewriter.replaceOpWithNewOp( + op, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), + op.getRegisterIndexAttr()); + + const auto& fluxQubit = fluxOp.getResult(); + + // Establish initial mapping: this Quartz qubit reference now corresponds + // to this Flux SSA value + getState().qubitMap.try_emplace(quartzQubit, fluxQubit); + + return success(); + } +}; + +/** + * @brief Converts quartz.dealloc to flux.dealloc + * + * @details + * Deallocates a qubit by looking up its latest Flux value and creating + * a corresponding flux.dealloc operation. The mapping is removed from + * the state as the qubit is no longer in use. + * + * Example transformation: + * ```mlir + * quartz.dealloc %q : !quartz.qubit + * // becomes (where %q maps to %q_final): + * flux.dealloc %q_final : !flux.qubit + * ``` + */ +struct ConvertQuartzDeallocOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::DeallocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto& quartzQubit = op.getQubit(); + + // Look up the latest Flux value for this Quartz qubit + const auto& fluxQubit = getState().qubitMap[quartzQubit]; + + // Create the dealloc operation + rewriter.replaceOpWithNewOp(op, fluxQubit); + + // Remove from state as qubit is no longer in use + getState().qubitMap.erase(quartzQubit); + + return success(); + } +}; + +/** + * @brief Converts quartz.static to flux.static + * + * @details + * Static qubits represent references to hardware-mapped or fixed-position + * qubits identified by an index. This conversion creates the corresponding + * flux.static operation and establishes the mapping. + * + * Example transformation: + * ```mlir + * %q = quartz.static 0 : !quartz.qubit + * // becomes: + * %q0 = flux.static 0 : !flux.qubit + * ``` + */ +struct ConvertQuartzStaticOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::StaticOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + // Prepare result type + const auto& qubitType = flux::QubitType::get(rewriter.getContext()); + + // Create new flux.static operation with the same index + auto fluxOp = + rewriter.create(op.getLoc(), qubitType, op.getIndex()); + + // Collect Quartz and Flux SSA values + const auto& quartzQubit = op.getQubit(); + const auto& fluxQubit = fluxOp.getQubit(); + + // Establish mapping from Quartz reference to Flux value + getState().qubitMap[quartzQubit] = fluxQubit; + + // Replace the old operation result with the new result + rewriter.replaceOp(op, fluxQubit); + + return success(); + } +}; + +/** + * @brief Converts quartz.measure to flux.measure + * + * @details + * Measurement is a key operation where the semantic difference is visible: + * - Quartz: Measures in-place, returning only the classical bit + * - Flux: Consumes input qubit, returns both output qubit and classical bit + * + * The conversion looks up the latest Flux value for the Quartz qubit, + * performs the measurement, updates the mapping with the output qubit, + * and returns the classical bit result. + * + * Example transformation: + * ```mlir + * %c = quartz.measure %q : !quartz.qubit -> i1 + * // becomes (where %q maps to %q_in): + * %q_out, %c = flux.measure %q_in : !flux.qubit -> (!flux.qubit, i1) + * // state updated: %q now maps to %q_out + * ``` + */ +struct ConvertQuartzMeasureOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::MeasureOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + + // Prepare result type + const auto& qubitType = flux::QubitType::get(rewriter.getContext()); + + const auto& quartzQubit = op.getQubit(); + + // Get the latest Flux qubit value from the state map + const Value fluxQubit = getState().qubitMap[quartzQubit]; + + // Create flux.measure operation (returns both output qubit and bit result) + auto fluxOp = rewriter.create( + op.getLoc(), qubitType, op.getResult().getType(), fluxQubit); + + auto outFluxQubit = fluxOp.getQubitOut(); + auto newBit = fluxOp.getResult(); + + // Update mapping: the Quartz qubit now corresponds to the output qubit + getState().qubitMap[quartzQubit] = outFluxQubit; + + // Replace the Quartz operation's bit result with the Flux bit result + rewriter.replaceOp(op, newBit); + + return success(); + } +}; + +/** + * @brief Converts quartz.reset to flux.reset + * + * @details + * Reset operations force a qubit to the |0⟩ state. The semantic difference: + * - Quartz: Resets in-place (no result value) + * - Flux: Consumes input qubit, returns reset output qubit + * + * The conversion looks up the latest Flux value, performs the reset, + * and updates the mapping with the output qubit. The Quartz operation + * is erased as it has no results to replace. + * + * Example transformation: + * ```mlir + * quartz.reset %q : !quartz.qubit + * // becomes (where %q maps to %q_in): + * %q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit + * // state updated: %q now maps to %q_out + * ``` + */ +struct ConvertQuartzResetOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::ResetOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + + // Prepare result type + const auto& qubitType = flux::QubitType::get(rewriter.getContext()); + + const auto& quartzQubit = op.getQubit(); + + // Get the latest Flux qubit value from the state map + const Value fluxQubit = getState().qubitMap[quartzQubit]; + + // Create flux.reset operation (consumes input, produces output) + auto fluxOp = + rewriter.create(op.getLoc(), qubitType, fluxQubit); + + auto outFluxQubit = fluxOp.getQubitOut(); + + // Update mapping: the Quartz qubit now corresponds to the reset output + getState().qubitMap[quartzQubit] = outFluxQubit; + + // Erase the old operation (it has no results to replace) + rewriter.eraseOp(op); + + return success(); + } +}; + +/** + * @brief Pass implementation for Quartz-to-Flux conversion + * + * @details + * This pass converts Quartz dialect operations (reference semantics) to + * Flux dialect operations (value semantics). The conversion is essential + * for enabling optimization passes that rely on SSA form and explicit + * dataflow analysis. + * + * The pass operates in several phases: + * 1. Type conversion: !quartz.qubit -> !flux.qubit + * 2. Operation conversion: Each Quartz op is converted to its Flux equivalent + * 3. State tracking: A LoweringState maintains qubit value mappings + * 4. Function/control-flow adaptation: Function signatures and control flow + * are updated to use Flux types + * + * The conversion maintains semantic equivalence while transforming the + * representation from imperative (mutation-based) to functional (SSA-based). + */ struct QuartzToFlux final : impl::QuartzToFluxBase { using QuartzToFluxBase::QuartzToFluxBase; @@ -59,33 +384,47 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { MLIRContext* context = &getContext(); auto* module = getOperation(); + // Create state object to track qubit value flow + LoweringState state; + ConversionTarget target(*context); RewritePatternSet patterns(context); QuartzToFluxTypeConverter typeConverter(context); + // Configure conversion target: Quartz illegal, Flux legal target.addIllegalDialect(); target.addLegalDialect(); - // conversion of quartz types in func.func signatures - // does not work for now as signature needs to be changed + // Register operation conversion patterns with state tracking + patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); + + // Conversion of quartz types in func.func signatures + // Note: This currently has limitations with signature changes populateFunctionOpInterfaceTypeConversionPattern( patterns, typeConverter); target.addDynamicallyLegalOp([&](func::FuncOp op) { return typeConverter.isSignatureLegal(op.getFunctionType()) && typeConverter.isLegal(&op.getBody()); }); - // conversion of quartz types in func.return + + // Conversion of quartz types in func.return populateReturnOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); - // conversion of quartz types in func.call + // Conversion of quartz types in func.call populateCallOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::CallOp op) { return typeConverter.isLegal(op); }); - // conversion of quartz types in control-flow ops; e.g. cf.br + // Conversion of quartz types in control-flow ops (e.g., cf.br, cf.cond_br) populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + + // Apply the conversion if (failed(applyPartialConversion(module, target, std::move(patterns)))) { signalPassFailure(); } From 2c199046d01b59b63a46c6f54c57555007b3e394 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 11:13:31 +0200 Subject: [PATCH 036/419] =?UTF-8?q?=F0=9F=9A=A7=20initial=20flux=20to=20qu?= =?UTF-8?q?artz=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 239 +++++++++++++++++- 1 file changed, 231 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index f5bea923c4..bd78620f83 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -31,27 +31,238 @@ #include namespace mlir { -using namespace mlir::flux; -using namespace mlir::quartz; +using namespace flux; +using namespace quartz; #define GEN_PASS_DEF_FLUXTOQUARTZ #include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" +/** + * @brief Type converter for Flux-to-Quartz conversion + * + * @details + * Handles type conversion between the Flux and Quartz dialects. + * The primary conversion is from !flux.qubit to !quartz.qubit, which + * represents the semantic shift from value types to reference types. + * + * Other types (integers, booleans, etc.) pass through unchanged via + * the identity conversion. + */ class FluxToQuartzTypeConverter final : public TypeConverter { public: explicit FluxToQuartzTypeConverter(MLIRContext* ctx) { - // Identity conversion + // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // QubitType conversion + // Convert Flux qubit values to Quartz qubit references addConversion([ctx](flux::QubitType /*type*/) -> Type { return quartz::QubitType::get(ctx); }); } }; +/** + * @brief Converts flux.alloc to quartz.alloc + * + * @details + * Allocates a new qubit initialized to the |0⟩ state. Register metadata + * (name, size, index) is preserved during conversion. + * + * The conversion is straightforward: the Flux allocation produces an SSA + * value, while the Quartz allocation produces a reference. MLIR's type + * conversion system automatically handles the semantic shift. + * + * Example transformation: + * ```mlir + * %q0 = flux.alloc q[3, 0] : !flux.qubit + * // becomes: + * %q = quartz.alloc q[3, 0] : !quartz.qubit + * ``` + */ +struct ConvertFluxAllocOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::AllocOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + // Create quartz.alloc with preserved register metadata + rewriter.replaceOpWithNewOp(op, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), + op.getRegisterIndexAttr()); + + return success(); + } +}; + +/** + * @brief Converts flux.dealloc to quartz.dealloc + * + * @details + * Deallocates a qubit, releasing its resources. The OpAdaptor automatically + * provides the type-converted qubit operand (!quartz.qubit instead of + * !flux.qubit), so we simply pass it through to the new operation. + * + * Example transformation: + * ```mlir + * flux.dealloc %q_flux : !flux.qubit + * // becomes: + * quartz.dealloc %q_quartz : !quartz.qubit + * ``` + */ +struct ConvertFluxDeallocOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::DeallocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted qubit + rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); + return success(); + } +}; + +/** + * @brief Converts flux.static to quartz.static + * + * @details + * Static qubits represent references to hardware-mapped or fixed-position + * qubits identified by an index. The conversion preserves the index attribute + * and creates the corresponding quartz.static operation. + * + * Example transformation: + * ```mlir + * %q0 = flux.static 0 : !flux.qubit + * // becomes: + * %q = quartz.static 0 : !quartz.qubit + * ``` + */ +struct ConvertFluxStaticOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::StaticOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + // Create quartz.static with the same index + rewriter.replaceOpWithNewOp(op, op.getIndex()); + return success(); + } +}; + +/** + * @brief Converts flux.measure to quartz.measure + * + * @details + * Measurement demonstrates the key semantic difference between the dialects: + * - Flux (value semantics): Consumes input qubit, returns both output qubit + * and classical bit result + * - Quartz (reference semantics): Measures qubit in-place, returns only the + * classical bit result + * + * The OpAdaptor provides the input qubit already converted to !quartz.qubit. + * Since Quartz operations are in-place, we return the same qubit reference + * alongside the measurement bit. MLIR's conversion infrastructure automatically + * routes subsequent uses of the Flux output qubit to this Quartz reference. + * + * Example transformation: + * ```mlir + * %q_out, %c = flux.measure %q_in : !flux.qubit -> (!flux.qubit, i1) + * // becomes: + * %c = quartz.measure %q : !quartz.qubit -> i1 + * // %q_out uses are replaced with %q (the adaptor-converted input) + * ``` + */ +struct ConvertFluxMeasureOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::MeasureOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create quartz.measure (in-place operation, returns only bit) + auto quartzOp = rewriter.create( + op.getLoc(), op.getResult().getType(), quartzQubit); + + auto measureBit = quartzOp.getResult(); + + // Replace both results: qubit output → same quartz reference, bit → new bit + rewriter.replaceOp(op, {quartzQubit, measureBit}); + + return success(); + } +}; + +/** + * @brief Converts flux.reset to quartz.reset + * + * @details + * Reset operations force a qubit to the |0⟩ state: + * - Flux (value semantics): Consumes input qubit, returns reset output qubit + * - Quartz (reference semantics): Resets qubit in-place, no result value + * + * The OpAdaptor provides the input qubit already converted to !quartz.qubit. + * Since Quartz's reset is in-place, we return the same qubit reference. + * MLIR's conversion infrastructure automatically routes subsequent uses of + * the Flux output qubit to this Quartz reference. + * + * Example transformation: + * ```mlir + * %q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit + * // becomes: + * quartz.reset %q : !quartz.qubit + * // %q_out uses are replaced with %q (the adaptor-converted input) + * ``` + */ +struct ConvertFluxResetOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::ResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create quartz.reset (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit); + + // Replace the output qubit with the same quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); + } +}; + +/** + * @brief Pass implementation for Flux-to-Quartz conversion + * + * @details + * This pass converts Flux dialect operations (value semantics) to + * Quartz dialect operations (reference semantics). The conversion is useful + * for lowering optimized SSA-form code back to a hardware-oriented + * representation suitable for backend code generation. + * + * The conversion leverages MLIR's built-in type conversion infrastructure: + * The TypeConverter handles !flux.qubit → !quartz.qubit transformations, + * and the OpAdaptor automatically provides type-converted operands to each + * conversion pattern. This eliminates the need for manual state tracking. + * + * Key semantic transformation: + * - Flux operations form explicit SSA chains where each operation consumes + * inputs and produces new outputs + * - Quartz operations modify qubits in-place using references + * - The conversion maps each Flux SSA chain to a single Quartz reference, + * with MLIR's conversion framework automatically handling the plumbing + * + * The pass operates through: + * 1. Type conversion: !flux.qubit → !quartz.qubit + * 2. Operation conversion: Each Flux op converted to its Quartz equivalent + * 3. Automatic operand mapping: OpAdaptors provide converted operands + * 4. Function/control-flow adaptation: Signatures updated to use Quartz types + */ struct FluxToQuartz final : impl::FluxToQuartzBase { using FluxToQuartzBase::FluxToQuartzBase; + void runOnOperation() override { MLIRContext* context = &getContext(); auto* module = getOperation(); @@ -60,10 +271,20 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { RewritePatternSet patterns(context); FluxToQuartzTypeConverter typeConverter(context); + // Configure conversion target: Flux illegal, Quartz legal target.addIllegalDialect(); target.addLegalDialect(); - // conversion of flux types in func.func signatures + // Register operation conversion patterns + // Note: No state tracking needed - OpAdaptors handle type conversion + patterns.add(typeConverter, context); + patterns.add(typeConverter, context); + patterns.add(typeConverter, context); + patterns.add(typeConverter, context); + patterns.add(typeConverter, context); + + // Conversion of flux types in func.func signatures + // Note: This currently has limitations with signature changes populateFunctionOpInterfaceTypeConversionPattern( patterns, typeConverter); target.addDynamicallyLegalOp([&](func::FuncOp op) { @@ -71,22 +292,24 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { typeConverter.isLegal(&op.getBody()); }); - // conversion of flux types in func.return + // Conversion of flux types in func.return populateReturnOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); - // conversion of flux types in func.call + // Conversion of flux types in func.call populateCallOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::CallOp op) { return typeConverter.isLegal(op); }); - // conversion of flux types in control-flow ops; e.g. cf.br + // Conversion of flux types in control-flow ops (e.g., cf.br, cf.cond_br) populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + // Apply the conversion if (failed(applyPartialConversion(module, target, std::move(patterns)))) { signalPassFailure(); } }; }; + } // namespace mlir From d6b5f0a161fe8895edb702b4ab7a04602abb7778 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 16:05:34 +0200 Subject: [PATCH 037/419] =?UTF-8?q?=F0=9F=A9=B9=20fix=20passes=20namespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h | 4 +++- mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h | 4 +++- mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h | 4 +++- mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt | 4 +++- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h index 6dd18a21d2..98a205924d 100644 --- a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h +++ b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h @@ -12,8 +12,10 @@ #include // from @llvm-project -#define GEN_PASS_DECL +namespace mlir { +#define GEN_PASS_DECL_FLUXTOQUARTZ #include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" #define GEN_PASS_REGISTRATION #include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" +} // namespace mlir diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h index 7e99c0ae6e..4602f5c801 100644 --- a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h +++ b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h @@ -12,8 +12,10 @@ #include // from @llvm-project -#define GEN_PASS_DECL +namespace mlir { +#define GEN_PASS_DECL_QUARTZTOFLUX #include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" #define GEN_PASS_REGISTRATION #include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" +} // namespace mlir diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h index 8868a5938c..06ed278374 100644 --- a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h +++ b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h @@ -12,8 +12,10 @@ #include // from @llvm-project -#define GEN_PASS_DECL +namespace mlir { +#define GEN_PASS_DECL_QUARTZTOQIR #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" #define GEN_PASS_REGISTRATION #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" +} // namespace mlir diff --git a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt b/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt index 0369ff9965..3442693da8 100644 --- a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt +++ b/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt @@ -18,4 +18,6 @@ add_mlir_library( MLIRLLVMDialect MLIRFluxDialect MLIRArithDialect - MLIRFuncDialect) + MLIRFuncDialect + MLIRFuncToLLVM + MLIRReconcileUnrealizedCasts) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 30e453a53d..0bafde365f 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -44,7 +44,7 @@ namespace mlir { -using namespace mlir::quartz; +using namespace quartz; #define GEN_PASS_DEF_QUARTZTOQIR #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" From 7979b981f3902d8a75f5e17d8c874fc80c5b9e8f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 16:12:52 +0200 Subject: [PATCH 038/419] =?UTF-8?q?=F0=9F=9A=A7=20add=20CompilerPipeline?= =?UTF-8?q?=20and=20`mqt-cc`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Compiler/CompilerPipeline.h | 151 +++++++++++++++ mlir/lib/CMakeLists.txt | 1 + mlir/lib/Compiler/CMakeLists.txt | 29 +++ mlir/lib/Compiler/CompilerPipeline.cpp | 179 ++++++++++++++++++ mlir/tools/CMakeLists.txt | 1 + mlir/tools/mqt-cc/CMakeLists.txt | 21 ++ mlir/tools/mqt-cc/mqt-cc.cpp | 155 +++++++++++++++ 7 files changed, 537 insertions(+) create mode 100644 mlir/include/mlir/Compiler/CompilerPipeline.h create mode 100644 mlir/lib/Compiler/CMakeLists.txt create mode 100644 mlir/lib/Compiler/CompilerPipeline.cpp create mode 100644 mlir/tools/mqt-cc/CMakeLists.txt create mode 100644 mlir/tools/mqt-cc/mqt-cc.cpp diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h new file mode 100644 index 0000000000..0cf5de859d --- /dev/null +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +namespace mlir { +class ModuleOp; + +/** + * @brief Configuration for the quantum compiler pipeline + * + * @details + * Controls which stages of the compilation pipeline are executed and + * diagnostic options for profiling and debugging. + */ +struct QuantumCompilerConfig { + /// Run quantum optimization passes (placeholder for future passes) + bool runOptimization = false; + + /// Convert to QIR at the end of the pipeline + bool convertToQIR = false; + + /// Record intermediate IR at each stage for debugging/testing + bool recordIntermediates = false; + + /// Enable pass timing statistics (MLIR builtin) + bool enableTiming = false; + + /// Enable pass statistics (MLIR builtin) + bool enableStatistics = false; + + /// Print IR after each pass (MLIR builtin, for debugging) + bool printIRAfterAll = false; + + /// Print IR after failures only (MLIR builtin) + bool printIRAfterFailure = false; +}; + +/** + * @brief Records the state of IR at various compilation stages + * + * @details + * Stores string representations of the MLIR module at different + * points in the compilation pipeline. Useful for testing and debugging. + * All stages are recorded when recordIntermediates is enabled. + */ +struct CompilationRecord { + std::string afterQuartzImport; + std::string afterInitialCanon; + std::string afterFluxConversion; + std::string afterFluxCanon; + std::string afterOptimization; + std::string afterOptimizationCanon; + std::string afterQuartzConversion; + std::string afterQuartzCanon; + std::string afterQIRConversion; + std::string afterQIRCanon; +}; + +/** + * @brief Main quantum compiler pipeline + * + * @details + * Provides a high-level interface for compiling quantum programs through + * the MQT compiler infrastructure. The pipeline stages are: + * + * 1. Quartz dialect (reference semantics) - imported from + * qc::QuantumComputation + * 2. Canonicalization + cleanup + * 3. Flux dialect (value semantics) - enables SSA-based optimizations + * 4. Canonicalization + cleanup + * 5. Quantum optimization passes (optional, TODO: to be implemented) + * 6. Canonicalization + cleanup + * 7. Quartz dialect - converted back for backend lowering + * 8. Canonicalization + cleanup + * 9. QIR (Quantum Intermediate Representation) - optional final lowering + * 10. Canonicalization + cleanup + * + * Following MLIR best practices, canonicalization and dead value removal + * are always run after each major transformation stage. + */ +class QuantumCompilerPipeline { +public: + explicit QuantumCompilerPipeline(const QuantumCompilerConfig& config = {}) + : config_(config) {} + + /** + * @brief Run the complete compilation pipeline on a module + * + * @details + * Executes all enabled compilation stages on the provided MLIR module. + * If recordIntermediates is enabled in the config, captures IR snapshots + * at every stage (10 snapshots total for full pipeline). + * + * Automatically configures the PassManager with: + * - Timing statistics if enableTiming is true + * - Pass statistics if enableStatistics is true + * - IR printing options if printIRAfterAll or printIRAfterFailure is true + * + * @param module The MLIR module to compile + * @param record Optional pointer to record intermediate states + * @return success() if compilation succeeded, failure() otherwise + */ + LogicalResult runPipeline(ModuleOp module, + CompilationRecord* record = nullptr) const; + +private: + /** + * @brief Add canonicalization and cleanup passes + * + * @details + * Always adds the standard MLIR canonicalization pass followed by dead + * value removal. + */ + static void addCleanupPasses(PassManager& pm); + + /** + * @brief Configure PassManager with diagnostic options + * + * @details + * Enables timing, statistics, and IR printing based on config flags. + * Uses MLIR's builtin PassManager configuration methods. + */ + void configurePassManager(PassManager& pm) const; + + QuantumCompilerConfig config_; +}; + +/** + * @brief Utility to capture IR as string + * + * @details + * Prints the MLIR module to a string for recording or comparison. + * + * @param module The module to convert to string + * @return String representation of the IR + */ +std::string captureIR(ModuleOp module); + +} // namespace mlir diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index 9add328fef..7a88757150 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(Dialect) add_subdirectory(Conversion) +add_subdirectory(Compiler) diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt new file mode 100644 index 0000000000..6fb8e033f5 --- /dev/null +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Build the compiler pipeline library +add_mlir_library( + MQTCompilerPipeline + CompilerPipeline.cpp + ADDITIONAL_HEADER_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Compiler + LINK_LIBS + PUBLIC + MLIRPass + MLIRTransforms + MLIRTransformUtils + QuartzToFlux + FluxToQuartz + QuartzToQIR + MQT::ProjectOptions) + +# collect header files +file(GLOB_RECURSE COMPILER_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Compiler/*.h") + +target_sources(MQTCompilerPipeline PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES ${COMPILER_HEADERS_SOURCE}) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp new file mode 100644 index 0000000000..b65e8b24bf --- /dev/null +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Compiler/CompilerPipeline.h" + +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" +#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" + +#include +#include +#include +#include +#include + +namespace mlir { + +void QuantumCompilerPipeline::addCleanupPasses(PassManager& pm) { + // Always run canonicalization and dead value removal + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); +} + +void QuantumCompilerPipeline::configurePassManager(PassManager& pm) const { + // Enable timing statistics if requested + if (config_.enableTiming) { + pm.enableTiming(); + } + + // Enable pass statistics if requested + if (config_.enableStatistics) { + pm.enableStatistics(); + } + + // Enable IR printing options if requested + if (config_.printIRAfterAll) { + pm.enableIRPrinting( + /*shouldPrintBeforePass=*/[](Pass*, Operation*) { return false; }, + /*shouldPrintAfterPass=*/[](Pass*, Operation*) { return true; }, + /*printModuleScope=*/true, + /*printAfterOnlyOnChange=*/true, + /*printAfterOnlyOnFailure=*/false); + } else if (config_.printIRAfterFailure) { + pm.enableIRPrinting( + /*shouldPrintBeforePass=*/[](Pass*, Operation*) { return false; }, + /*shouldPrintAfterPass=*/[](Pass*, Operation*) { return false; }, + /*printModuleScope=*/true, + /*printAfterOnlyOnChange=*/false, + /*printAfterOnlyOnFailure=*/true); + } +} + +LogicalResult +QuantumCompilerPipeline::runPipeline(ModuleOp module, + CompilationRecord* record) const { + PassManager pm(module.getContext()); + + // Configure PassManager with diagnostic options + configurePassManager(pm); + + // Record initial state if requested + if (record != nullptr && config_.recordIntermediates) { + record->afterQuartzImport = captureIR(module); + } + + // Stage 1: Initial canonicalization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterInitialCanon = captureIR(module); + } + pm.clear(); + + // Stage 2: Convert to Flux + pm.addPass(createQuartzToFlux()); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterFluxConversion = captureIR(module); + } + pm.clear(); + + // Stage 3: Canonicalize Flux + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterFluxCanon = captureIR(module); + } + pm.clear(); + + // Stage 4: Optimization passes (if enabled) + if (config_.runOptimization) { + // TODO: Add optimization passes + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimization = captureIR(module); + } + pm.clear(); + + // Canonicalize after optimization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimizationCanon = captureIR(module); + } + pm.clear(); + } + + // Stage 5: Convert back to Quartz + pm.addPass(createFluxToQuartz()); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQuartzConversion = captureIR(module); + } + pm.clear(); + + // Stage 6: Canonicalize Quartz + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQuartzCanon = captureIR(module); + } + pm.clear(); + + // Stage 7: Optional QIR conversion + if (config_.convertToQIR) { + pm.addPass(createQuartzToQIR()); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQIRConversion = captureIR(module); + } + pm.clear(); + + // Final canonicalization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterQIRCanon = captureIR(module); + } + } + + return success(); +} + +std::string captureIR(ModuleOp module) { + module.dump(); + std::string result; + llvm::raw_string_ostream os(result); + module.print(os); + os.flush(); + return result; +} + +} // namespace mlir diff --git a/mlir/tools/CMakeLists.txt b/mlir/tools/CMakeLists.txt index e845e91ec8..fc2782a9df 100644 --- a/mlir/tools/CMakeLists.txt +++ b/mlir/tools/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(quantum-opt) +add_subdirectory(mqt-cc) diff --git a/mlir/tools/mqt-cc/CMakeLists.txt b/mlir/tools/mqt-cc/CMakeLists.txt new file mode 100644 index 0000000000..bae241ddd0 --- /dev/null +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +# Build the compiler driver executable +add_mlir_tool(mqt-cc mqt-cc.cpp DEPENDS MQTCompilerPipeline SUPPORT_PLUGINS) +target_compile_options(mqt-cc PRIVATE -fexceptions) +target_link_libraries( + mqt-cc + PRIVATE MQTCompilerPipeline + # Required for parsing .mlir files + MLIRParser + # Required for file I/O + MLIRSupport) +llvm_update_compile_flags(mqt-cc) +mlir_check_all_link_libraries(mqt-cc) +export_executable_symbols_for_plugins(mqt-cc) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp new file mode 100644 index 0000000000..aaffd71e6e --- /dev/null +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace mlir; + +// Command-line options +static cl::opt inputFilename(cl::Positional, + cl::desc(""), + cl::init("-")); + +static cl::opt outputFilename("o", cl::desc("Output filename"), + cl::value_desc("filename"), + cl::init("-")); + +static cl::opt + runOptimization("optimize", cl::desc("Run quantum optimization passes"), + cl::init(false)); + +static cl::opt convertToQIR("emit-qir", + cl::desc("Convert to QIR at the end"), + cl::init(false)); + +static cl::opt enableTiming("mlir-timing", + cl::desc("Enable pass timing statistics"), + cl::init(false)); + +static cl::opt enableStatistics("mlir-statistics", + cl::desc("Enable pass statistics"), + cl::init(false)); + +static cl::opt printIRAfterAll("mlir-print-ir-after-all", + cl::desc("Print IR after each pass"), + cl::init(false)); + +static cl::opt printIRAfterFailure("mlir-print-ir-after-failure", + cl::desc("Print IR after failures"), + cl::init(false)); + +static cl::opt verifyDiagnostics("verify-diagnostics", + cl::desc("Verify expected diagnostics"), + cl::init(false)); + +/** + * @brief Load and parse a .mlir file + */ +static OwningOpRef loadMLIRFile(StringRef filename, + MLIRContext* context) { + // Set up the input file + std::string errorMessage; + auto file = openInputFile(filename, &errorMessage); + if (!file) { + errs() << errorMessage << "\n"; + return nullptr; + } + + // Parse the input MLIR + SourceMgr sourceMgr; + sourceMgr.AddNewSourceBuffer(std::move(file), SMLoc()); + return parseSourceFile(sourceMgr, context); +} + +/** + * @brief Write the module to the output file + */ +static LogicalResult writeOutput(ModuleOp module, const StringRef filename) { + std::string errorMessage; + const auto output = openOutputFile(filename, &errorMessage); + if (!output) { + errs() << errorMessage << "\n"; + return failure(); + } + + module.print(output->os()); + output->keep(); + return success(); +} + +int main(int argc, char** argv) { + InitLLVM y(argc, argv); + + // Parse command-line options + cl::ParseCommandLineOptions(argc, argv, "MQT Core Compiler Driver\n"); + + // Set up MLIR context with all required dialects + DialectRegistry registry; + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + + MLIRContext context(registry); + context.loadAllAvailableDialects(); + + // Load the input .mlir file + const auto module = loadMLIRFile(inputFilename, &context); + if (!module) { + errs() << "Failed to load input file: " << inputFilename << "\n"; + return 1; + } + + // Configure the compiler pipeline + QuantumCompilerConfig config; + config.runOptimization = runOptimization; + config.convertToQIR = convertToQIR; + config.enableTiming = enableTiming; + config.enableStatistics = enableStatistics; + config.printIRAfterAll = printIRAfterAll; + config.printIRAfterFailure = printIRAfterFailure; + + // Run the compilation pipeline + if (const QuantumCompilerPipeline pipeline(config); + failed(pipeline.runPipeline(module.get()))) { + errs() << "Compilation pipeline failed\n"; + return 1; + } + + // Write the output + if (failed(writeOutput(module.get(), outputFilename))) { + errs() << "Failed to write output file: " << outputFilename << "\n"; + return 1; + } + + return 0; +} From 491f1b79b7c94cb105dab98467a6c0998101e864 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 16:53:37 +0200 Subject: [PATCH 039/419] =?UTF-8?q?=F0=9F=9A=9A=20rename=20translator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- ... => TranslateQuantumComputationToQuartz.h} | 9 +- .../Dialect/Quartz/Translation/CMakeLists.txt | 2 +- ...> TranslateQuantumComputationToQuartz.cpp} | 30 +- mlir/unittests/CMakeLists.txt | 2 +- mlir/unittests/translation_new/CMakeLists.txt | 21 -- .../translation_new/test_translation.cpp | 355 ------------------ 6 files changed, 23 insertions(+), 396 deletions(-) rename mlir/include/mlir/Dialect/Quartz/Translation/{ImportQuantumComputation.h => TranslateQuantumComputationToQuartz.h} (67%) rename mlir/lib/Dialect/Quartz/Translation/{ImportQuantumComputation.cpp => TranslateQuantumComputationToQuartz.cpp} (92%) delete mode 100644 mlir/unittests/translation_new/CMakeLists.txt delete mode 100644 mlir/unittests/translation_new/test_translation.cpp diff --git a/mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h b/mlir/include/mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h similarity index 67% rename from mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h rename to mlir/include/mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h index ff7242dda1..3cabf71ff9 100644 --- a/mlir/include/mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h +++ b/mlir/include/mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h @@ -17,6 +17,9 @@ namespace qc { class QuantumComputation; } -mlir::OwningOpRef -translateQuantumComputationToMLIR(mlir::MLIRContext* context, - const qc::QuantumComputation& qc); +namespace mlir { + +OwningOpRef +translateQuantumComputationToQuartz(MLIRContext* context, + const qc::QuantumComputation& qc); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt b/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt index 18fd204c73..d1718b8885 100644 --- a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt @@ -10,7 +10,7 @@ add_compile_options("-fexceptions") add_mlir_library( MLIRQuartzTranslation - ImportQuantumComputation.cpp + TranslateQuantumComputationToQuartz.cpp LINK_LIBS MLIRArithDialect MLIRFuncDialect diff --git a/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp similarity index 92% rename from mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp rename to mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 50320179cb..66b521f399 100644 --- a/mlir/lib/Dialect/Quartz/Translation/ImportQuantumComputation.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/Translation/ImportQuantumComputation.h" +#include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" #include "ir/QuantumComputation.hpp" #include "ir/Register.hpp" @@ -28,7 +28,9 @@ #include #include -namespace mlir::quartz { +namespace mlir { + +using namespace quartz; namespace { @@ -61,7 +63,7 @@ using BitIndexVec = SmallVector; * @return Vector containing information about all quantum registers */ SmallVector -allocateQregs(quartz::QuartzProgramBuilder& builder, +allocateQregs(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting SmallVector qregPtrs; @@ -114,8 +116,7 @@ buildQubitMap(const qc::QuantumComputation& quantumComputation, for (const auto& qreg : qregs) { for (std::size_t i = 0; i < qreg.qregPtr->getSize(); ++i) { - const auto globalIdx = - static_cast(qreg.qregPtr->getStartIndex() + i); + const auto globalIdx = qreg.qregPtr->getStartIndex() + i; flatQubits[globalIdx] = qreg.qubits[i]; } } @@ -136,12 +137,13 @@ buildQubitMap(const qc::QuantumComputation& quantumComputation, * @return Vector mapping global bit indices to memref and local indices */ BitIndexVec -allocateClassicalRegisters(quartz::QuartzProgramBuilder& builder, +allocateClassicalRegisters(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); - for (const auto& [_, reg] : quantumComputation.getClassicalRegisters()) { + for (const auto& reg : + quantumComputation.getClassicalRegisters() | std::views::values) { cregPtrs.emplace_back(®); } @@ -178,8 +180,7 @@ allocateClassicalRegisters(quartz::QuartzProgramBuilder& builder, * @param qubits Flat vector of qubit values indexed by physical qubit index * @param bitMap Mapping from global bit index to (memref, local_index) */ -void addMeasureOp(quartz::QuartzProgramBuilder& builder, - const qc::Operation& operation, +void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const SmallVector& qubits, const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); @@ -207,8 +208,7 @@ void addMeasureOp(quartz::QuartzProgramBuilder& builder, * @param operation The reset operation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index */ -void addResetOp(quartz::QuartzProgramBuilder& builder, - const qc::Operation& operation, +void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const SmallVector& qubits) { for (const auto& target : operation.getTargets()) { const Value qubit = qubits[target]; @@ -235,7 +235,7 @@ void addResetOp(quartz::QuartzProgramBuilder& builder, * @return Success if all supported operations were translated */ LogicalResult -translateOperations(quartz::QuartzProgramBuilder& builder, +translateOperations(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation, const SmallVector& qubits, const BitIndexVec& bitMap) { @@ -287,10 +287,10 @@ translateOperations(quartz::QuartzProgramBuilder& builder, * @param quantumComputation The quantum computation to translate * @return OwningOpRef containing the translated MLIR module */ -OwningOpRef translateQuantumComputationToMLIR( +OwningOpRef translateQuantumComputationToQuartz( MLIRContext* context, const qc::QuantumComputation& quantumComputation) { // Create and initialize the builder (creates module and main function) - quartz::QuartzProgramBuilder builder(context); + QuartzProgramBuilder builder(context); builder.initialize(); // Allocate quantum registers using the builder @@ -314,4 +314,4 @@ OwningOpRef translateQuantumComputationToMLIR( return builder.finalize(); } -} // namespace mlir::quartz +} // namespace mlir diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt index a362ea7062..7f7e458e15 100644 --- a/mlir/unittests/CMakeLists.txt +++ b/mlir/unittests/CMakeLists.txt @@ -7,4 +7,4 @@ # Licensed under the MIT License add_subdirectory(translation) -add_subdirectory(translation_new) +add_subdirectory(pipeline) diff --git a/mlir/unittests/translation_new/CMakeLists.txt b/mlir/unittests/translation_new/CMakeLists.txt deleted file mode 100644 index d23fd11d20..0000000000 --- a/mlir/unittests/translation_new/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -set(testname "mqt-core-mlir-translation-test-new") -file(GLOB_RECURSE TRANSLATION_TEST_SOURCES *.cpp) - -if(NOT TARGET ${testname}) - # create an executable in which the tests will be stored - add_executable(${testname} ${TRANSLATION_TEST_SOURCES}) - # link the Google test infrastructure and a default main function to the test executable. - target_link_libraries(${testname} PRIVATE GTest::gmock GTest::gtest_main LLVMFileCheck MLIRPass - MLIRTransforms MLIRQuartzTranslation) - # discover tests - gtest_discover_tests(${testname} DISCOVERY_TIMEOUT 60) - set_target_properties(${testname} PROPERTIES FOLDER unittests) -endif() diff --git a/mlir/unittests/translation_new/test_translation.cpp b/mlir/unittests/translation_new/test_translation.cpp deleted file mode 100644 index 564515142c..0000000000 --- a/mlir/unittests/translation_new/test_translation.cpp +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/IfElseOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "mlir/Dialect/MQTRef/IR/MQTRefDialect.h" -#include "mlir/Dialect/MQTRef/Translation/ImportQuantumComputation.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -using namespace qc; - -class ImportTest : public ::testing::Test { -protected: - std::unique_ptr context; - - void SetUp() override { - mlir::DialectRegistry registry; - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); - - context = std::make_unique(); - context->appendDialectRegistry(registry); - context->loadAllAvailableDialects(); - } - - void runPasses(const mlir::ModuleOp module) const { - mlir::PassManager passManager(context.get()); - passManager.addPass(mlir::createCanonicalizerPass()); - passManager.addPass(mlir::createMem2Reg()); - passManager.addPass(mlir::createRemoveDeadValuesPass()); - if (passManager.run(module).failed()) { - FAIL() << "Failed to run passes"; - } - } - - void TearDown() override {} -}; - -// ################################################## -// # Helper functions -// ################################################## - -std::string getOutputString(mlir::OwningOpRef* module) { - std::string outputString; - llvm::raw_string_ostream os(outputString); - (*module)->print(os); - os.flush(); - return outputString; -} - -std::string formatTargets(std::initializer_list targets) { - std::string s; - bool first = true; - for (auto t : targets) { - if (!first) { - s += ", "; - } - first = false; - s += "%[[Q" + std::to_string(t) + "]]"; - } - return s; -} - -std::string formatParams(std::initializer_list params) { - if (params.size() == 0) { - return ""; - } - std::ostringstream os; - os.setf(std::ios::scientific); - os << std::setprecision(6); - bool first = true; - os << "static ["; - for (const double p : params) { - if (!first) { - os << ", "; - } - first = false; - os << p; - } - os << "]"; - return os.str(); -} - -std::string getCheckStringOperation(const char* op, - std::initializer_list targets) { - return std::string("CHECK: mqtref.") + op + "() " + formatTargets(targets); -} - -std::string -getCheckStringOperationParams(const char* op, - std::initializer_list params, - std::initializer_list targets) { - return std::string("CHECK: mqtref.") + op + "(" + formatParams(params) + - ") " + formatTargets(targets); -} - -// Adapted from -// https://github.com/llvm/llvm-project/blob/d2b3e86321eaf954451e0a49534fa654dd67421e/llvm/unittests/MIR/MachineMetadata.cpp#L181 -bool checkOutput(const std::string& checkString, - const std::string& outputString) { - auto checkBuffer = llvm::MemoryBuffer::getMemBuffer(checkString, ""); - auto outputBuffer = - llvm::MemoryBuffer::getMemBuffer(outputString, "Output", false); - - llvm::SmallString<4096> checkFileBuffer; - const llvm::FileCheckRequest request; - llvm::FileCheck fc(request); - const llvm::StringRef checkFileText = - fc.CanonicalizeFile(*checkBuffer, checkFileBuffer); - - llvm::SourceMgr sm; - sm.AddNewSourceBuffer( - llvm::MemoryBuffer::getMemBuffer(checkFileText, "CheckFile"), - llvm::SMLoc()); - if (fc.readCheckFile(sm, checkFileText)) { - return false; - } - - auto outputBufferBuffer = outputBuffer->getBuffer(); - sm.AddNewSourceBuffer(std::move(outputBuffer), llvm::SMLoc()); - return fc.checkInput(sm, outputBufferBuffer); -} - -// ################################################## -// # Basic tests -// ################################################## - -TEST_F(ImportTest, EntryPoint) { - const QuantumComputation qc{}; - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto outputString = getOutputString(&module); - const auto* checkString = R"( - CHECK: func.func @main() attributes {passthrough = ["entry_point"]} - CHECK: return - )"; - - ASSERT_TRUE(checkOutput(checkString, outputString)); -} - -TEST_F(ImportTest, AllocationAndDeallocation) { - const QuantumComputation qc(3, 2); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto outputString = getOutputString(&module); - const auto* checkString = R"( - CHECK: %[[Qreg:.*]] = memref.alloc() : memref<3x!mqtref.Qubit> - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<3x!mqtref.Qubit> - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<3x!mqtref.Qubit> - CHECK: %[[I2:.*]] = arith.constant 2 : index - CHECK: %[[Q2:.*]] = memref.load %[[Qreg]][%[[I2]]] : memref<3x!mqtref.Qubit> - CHECK: %[[Creg:.*]] = memref.alloca() : memref<2xi1> - CHECK: memref.dealloc %[[Qreg]] : memref<3x!mqtref.Qubit> - )"; - - ASSERT_TRUE(checkOutput(checkString, outputString)); -} - -TEST_F(ImportTest, Measure01) { - QuantumComputation qc(2, 2); - qc.measure({0, 1}, {0, 1}); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto outputString = getOutputString(&module); - const auto* checkString = R"( - CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> - CHECK: %[[Creg:.*]] = memref.alloca() : memref<2xi1> - CHECK: %[[M0:.*]] = mqtref.measure %[[Q0]] - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: memref.store %[[M0]], %[[Creg]][%[[I0]]] : memref<2xi1> - CHECK: %[[M1:.*]] = mqtref.measure %[[Q1]] - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: memref.store %[[M1]], %[[Creg]][%[[I1]]] : memref<2xi1> - )"; - - ASSERT_TRUE(checkOutput(checkString, outputString)); -} - -TEST_F(ImportTest, Measure0) { - QuantumComputation qc(2, 2); - qc.measure(0, 0); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto outputString = getOutputString(&module); - const auto* checkString = R"( - CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> - CHECK: %[[Creg:.*]] = memref.alloca() : memref<2xi1> - CHECK: %[[M0:.*]] = mqtref.measure %[[Q0]] - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: memref.store %[[M0]], %[[Creg]][%[[I0]]] : memref<2xi1> - CHECK-NOT: mqtref.measure %[[Q1]] - CHECK-NOT: arith.constant 1 : index - CHECK-NOT: memref.store %[[ANY:.*]], %[[Creg]][%[[ANY:.*]]] : memref<2xi1> - )"; - - ASSERT_TRUE(checkOutput(checkString, outputString)); -} - -TEST_F(ImportTest, Reset01) { - QuantumComputation qc(2); - qc.reset({0, 1}); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto outputString = getOutputString(&module); - const auto* checkString = R"( - CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> - CHECK: mqtref.reset %[[Q0]] - CHECK: mqtref.reset %[[Q1]] - )"; - - ASSERT_TRUE(checkOutput(checkString, outputString)); -} - -TEST_F(ImportTest, Reset0) { - QuantumComputation qc(2); - qc.reset(0); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto outputString = getOutputString(&module); - const auto* checkString = R"( - CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> - CHECK: mqtref.reset %[[Q0]] - CHECK-NOT: mqtref.reset %[[Q1]] - )"; - - ASSERT_TRUE(checkOutput(checkString, outputString)); -} - -// ################################################## -// # Test full programs -// ################################################## - -TEST_F(ImportTest, MultipleClassicalRegistersMeasureStores) { - QuantumComputation qc(2, 0); - qc.addClassicalRegister(1, "c0"); - qc.addClassicalRegister(1, "c1"); - qc.measure({0, 1}, {0, 1}); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - // We do not run passes here; pattern should match raw allocation and stores - - const auto output = getOutputString(&module); - const std::string check = R"( - CHECK: func.func @main() attributes {passthrough = ["entry_point"]} - CHECK: %[[Qreg:.*]] = memref.alloc() : memref<2x!mqtref.Qubit> - CHECK: %[[I0:.*]] = arith.constant 0 : index - CHECK: %[[Q0:.*]] = memref.load %[[Qreg]][%[[I0]]] : memref<2x!mqtref.Qubit> - CHECK: %[[I1:.*]] = arith.constant 1 : index - CHECK: %[[Q1:.*]] = memref.load %[[Qreg]][%[[I1]]] : memref<2x!mqtref.Qubit> - CHECK: %[[CregA:.*]] = memref.alloca() : memref<1xi1> - CHECK: %[[CregB:.*]] = memref.alloca() : memref<1xi1> - CHECK: %[[M0:.*]] = mqtref.measure %[[Q0]] - CHECK: %[[I0A:.*]] = arith.constant 0 : index - CHECK: memref.store %[[M0]], %[[CregA]][%[[I0A]]] : memref<1xi1> - CHECK: %[[M1:.*]] = mqtref.measure %[[Q1]] - CHECK: %[[I0B:.*]] = arith.constant 0 : index - CHECK: memref.store %[[M1]], %[[CregB]][%[[I0B]]] : memref<1xi1> - CHECK: memref.dealloc %[[Qreg]] : memref<2x!mqtref.Qubit> - CHECK: return - )"; - - ASSERT_TRUE(checkOutput(check, output)); -} - -TEST_F(ImportTest, MultipleQuantumRegistersCX) { - QuantumComputation qc(0, 0); - qc.addQubitRegister(1, "q0"); - qc.addQubitRegister(1, "q1"); - qc.cx(0, 1); - - auto module = translateQuantumComputationToMLIR(context.get(), qc); - - const auto output = getOutputString(&module); - const std::string check = R"( - CHECK: func.func @main() attributes {passthrough = ["entry_point"]} - CHECK: %[[QregA:.*]] = memref.alloc() : memref<1x!mqtref.Qubit> - CHECK: %[[I0A:.*]] = arith.constant 0 : index - CHECK: %[[Q0A:.*]] = memref.load %[[QregA]][%[[I0A]]] : memref<1x!mqtref.Qubit> - CHECK: %[[QregB:.*]] = memref.alloc() : memref<1x!mqtref.Qubit> - CHECK: %[[I0B:.*]] = arith.constant 0 : index - CHECK: %[[Q0B:.*]] = memref.load %[[QregB]][%[[I0B]]] : memref<1x!mqtref.Qubit> - CHECK: mqtref.x() %[[Q0B]] ctrl %[[Q0A]] - CHECK: memref.dealloc %[[QregA]] : memref<1x!mqtref.Qubit> - CHECK: memref.dealloc %[[QregB]] : memref<1x!mqtref.Qubit> - CHECK: return - )"; - - ASSERT_TRUE(checkOutput(check, output)); -} - -} // namespace From da23d3cc012f1730b224d4e6c253cce473bdd5ab Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 16:58:02 +0200 Subject: [PATCH 040/419] =?UTF-8?q?=F0=9F=9A=A7=20initial=20end-to-end=20t?= =?UTF-8?q?ests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/unittests/pipeline/CMakeLists.txt | 16 + .../pipeline/test_compiler_pipeline.cpp | 364 ++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 mlir/unittests/pipeline/CMakeLists.txt create mode 100644 mlir/unittests/pipeline/test_compiler_pipeline.cpp diff --git a/mlir/unittests/pipeline/CMakeLists.txt b/mlir/unittests/pipeline/CMakeLists.txt new file mode 100644 index 0000000000..8d8586f985 --- /dev/null +++ b/mlir/unittests/pipeline/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_executable(mqt-core-mlir-compiler-pipeline-test test_compiler_pipeline.cpp) + +target_link_libraries( + mqt-core-mlir-compiler-pipeline-test + PRIVATE GTest::gtest_main MQTCompilerPipeline MLIRQuartzTranslation MLIRQuartzProgramBuilder + MQT::CoreIR MQT::ProjectOptions) + +gtest_discover_tests(mqt-core-mlir-compiler-pipeline-test) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp new file mode 100644 index 0000000000..0c358dea69 --- /dev/null +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "ir/QuantumComputation.hpp" +#include "ir/operations/OpType.hpp" +#include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace qc; +using namespace mlir; + +/** + * @brief Base test fixture for end-to-end compiler pipeline tests + * + * @details + * Provides a configured MLIR context with all necessary dialects loaded + * and utility methods for creating quantum circuits and running the + * compilation pipeline. + */ +class CompilerPipelineTest : public testing::Test { +protected: + std::unique_ptr context; + + void SetUp() override { + // Register all dialects needed for the full compilation pipeline + DialectRegistry registry; + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + /** + * @brief Import a QuantumComputation into Quartz dialect + */ + OwningOpRef + importQuantumCircuit(const QuantumComputation& qc) const { + return translateQuantumComputationToQuartz(context.get(), qc); + } + + /** + * @brief Run the compiler pipeline with specified configuration + */ + static LogicalResult runCompiler(ModuleOp module, + const QuantumCompilerConfig& config, + CompilationRecord* record = nullptr) { + const QuantumCompilerPipeline pipeline(config); + return pipeline.runPipeline(module, record); + } + + /** + * @brief Check if IR contains a specific pattern + */ + static bool irContains(const std::string& ir, const std::string& pattern) { + return ir.find(pattern) != std::string::npos; + } + + void TearDown() override {} +}; + +// ################################################## +// # Basic Circuit Tests +// ################################################## + +/** + * @brief Test: Single qubit circuit compilation + * + * @details + * Creates a simple circuit with H and X gates, runs through the full + * pipeline, and verifies the output. + */ +TEST_F(CompilerPipelineTest, SingleQubitCircuit) { + // Create a simple quantum circuit + QuantumComputation qc(1); + qc.h(0); + qc.x(0); + + // Import to Quartz dialect + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Configure compiler to run full pipeline + QuantumCompilerConfig config; + config.recordIntermediates = true; + + // Run compilation + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify initial Quartz IR contains the gates + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.alloc")); + + // Verify final IR is valid + EXPECT_FALSE(record.afterQuartzCanon.empty()); +} + +/** + * @brief Test: Two qubit entangling circuit + * + * @details + * Creates a Bell state circuit (H followed by CNOT), runs through + * the pipeline, and verifies transformations. + */ +TEST_F(CompilerPipelineTest, BellStateCircuit) { + // Create Bell state circuit + QuantumComputation qc(2); + qc.h(0); + qc.cx(0, 1); // CNOT with control on qubit 0 + + // Import to Quartz dialect + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Configure and run compiler + QuantumCompilerConfig config; + config.recordIntermediates = true; + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify circuit structure + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.alloc")); + + // After canonicalization, IR should be simplified + EXPECT_FALSE(record.afterQuartzCanon.empty()); +} + +/** + * @brief Test: Circuit with measurement + * + * @details + * Creates a circuit that includes measurement operations, which + * have different semantics in Quartz (in-place) vs Flux (SSA). + */ +TEST_F(CompilerPipelineTest, CircuitWithMeasurement) { + // Create circuit with measurement + QuantumComputation qc(2); + qc.h(0); + qc.cx(0, 1); // CNOT + qc.measure(0, 0); + qc.measure(1, 1); + + // Import and compile + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + QuantumCompilerConfig config; + config.recordIntermediates = true; + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify measurement operations are present + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.measure")); +} + +/** + * @brief Test: Circuit with reset operation + * + * @details + * Tests reset operations which also have different semantics between + * reference-based (Quartz) and value-based (Flux) representations. + */ +TEST_F(CompilerPipelineTest, CircuitWithReset) { + // Create circuit with reset + QuantumComputation qc(2); + qc.h(0); + qc.reset(0); + qc.x(0); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + QuantumCompilerConfig config; + config.recordIntermediates = true; + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.reset")); +} + +// ################################################## +// # Pipeline Stage Tests +// ################################################## + +/** + * @brief Test: Quartz to Flux conversion + * + * @details + * Verifies that the conversion from reference semantics (Quartz) + * to value semantics (Flux) preserves circuit semantics. + */ +TEST_F(CompilerPipelineTest, QuartzToFluxConversion) { + QuantumComputation qc(1); + qc.h(0); + qc.x(0); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Run only up to Flux conversion + PassManager pm(context.get()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createQuartzToFlux()); + pm.addPass(createCanonicalizerPass()); + + ASSERT_TRUE(succeeded(pm.run(module.get()))); + + // Verify Flux dialect is present + std::string ir = captureIR(module.get()); + EXPECT_TRUE(irContains(ir, "flux.alloc")); +} + +/** + * @brief Test: Flux to Quartz round-trip + * + * @details + * Verifies that converting Quartz -> Flux -> Quartz produces + * semantically equivalent IR. + */ +TEST_F(CompilerPipelineTest, FluxToQuartzRoundTrip) { + QuantumComputation qc(1); + qc.h(0); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Capture initial state + std::string initialIR = captureIR(module.get()); + + // Run round-trip conversion + PassManager pm(context.get()); + pm.addPass(createQuartzToFlux()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createFluxToQuartz()); + pm.addPass(createCanonicalizerPass()); + + ASSERT_TRUE(succeeded(pm.run(module.get()))); + + // Verify we're back in Quartz dialect + std::string finalIR = captureIR(module.get()); + EXPECT_TRUE(irContains(finalIR, "quartz.alloc")); + EXPECT_FALSE(irContains(finalIR, "flux.alloc")); +} + +/** + * @brief Test: QIR conversion + * + * @details + * Tests the final lowering from Quartz to QIR (LLVM-based + * quantum intermediate representation). + */ +TEST_F(CompilerPipelineTest, QIRConversion) { + QuantumComputation qc(1); + qc.h(0); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + QuantumCompilerConfig config; + config.convertToQIR = true; + config.recordIntermediates = true; + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify QIR (LLVM dialect) is present in final output + EXPECT_TRUE(irContains(record.afterQIRCanon, "llvm.func")); +} + +// ################################################## +// # Canonicalization Tests +// ################################################## + +/** + * @brief Test: Dead code elimination + * + * @details + * Verifies that dead value removal correctly eliminates unused + * operations and values from the IR. Dead value removal now always + * runs as part of the cleanup passes after each stage. + */ +TEST_F(CompilerPipelineTest, DeadCodeElimination) { + QuantumComputation qc(3); + qc.h(0); + qc.h(1); + qc.h(2); + // Only use qubit 0 in actual computation + qc.x(0); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + QuantumCompilerConfig config; + // Dead value removal always runs automatically + + ASSERT_TRUE(succeeded(runCompiler(module.get(), config))); + + // After dead code elimination, unused allocations may be removed + std::string ir = captureIR(module.get()); + EXPECT_FALSE(ir.empty()); +} + +// ################################################## +// # Error Handling Tests +// ################################################## + +/** + * @brief Test: Empty circuit handling + * + * @details + * Verifies that the compiler correctly handles edge cases like + * empty quantum circuits. + */ +TEST_F(CompilerPipelineTest, EmptyCircuit) { + QuantumComputation qc(1); + // No operations added + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + QuantumCompilerConfig config; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config))); +} + +} // namespace From b93bb994b1c973d4a0182faa03017bbac530b24b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 18:29:34 +0200 Subject: [PATCH 041/419] =?UTF-8?q?=F0=9F=9A=B8=20nice=20pretty=20printing?= =?UTF-8?q?=20for=20debugging=20in=20compiler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Compiler/CompilerPipeline.h | 12 +- mlir/include/mlir/Support/PrettyPrinting.h | 96 +++++++ mlir/include/mlir/Support/TestUtils.h | 64 +++++ mlir/lib/CMakeLists.txt | 1 + mlir/lib/Compiler/CMakeLists.txt | 1 + mlir/lib/Compiler/CompilerPipeline.cpp | 155 +++++++--- mlir/lib/Support/CMakeLists.txt | 24 ++ mlir/lib/Support/PrettyPrinting.cpp | 267 ++++++++++++++++++ mlir/lib/Support/TestUtils.cpp | 135 +++++++++ mlir/tools/mqt-cc/mqt-cc.cpp | 23 +- 10 files changed, 713 insertions(+), 65 deletions(-) create mode 100644 mlir/include/mlir/Support/PrettyPrinting.h create mode 100644 mlir/include/mlir/Support/TestUtils.h create mode 100644 mlir/lib/Support/CMakeLists.txt create mode 100644 mlir/lib/Support/PrettyPrinting.cpp create mode 100644 mlir/lib/Support/TestUtils.cpp diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index 0cf5de859d..d86bd1ea46 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -25,9 +25,6 @@ class ModuleOp; * diagnostic options for profiling and debugging. */ struct QuantumCompilerConfig { - /// Run quantum optimization passes (placeholder for future passes) - bool runOptimization = false; - /// Convert to QIR at the end of the pipeline bool convertToQIR = false; @@ -40,11 +37,8 @@ struct QuantumCompilerConfig { /// Enable pass statistics (MLIR builtin) bool enableStatistics = false; - /// Print IR after each pass (MLIR builtin, for debugging) - bool printIRAfterAll = false; - - /// Print IR after failures only (MLIR builtin) - bool printIRAfterFailure = false; + /// Print IR after each stage + bool printIRAfterAllStages = false; }; /** @@ -80,7 +74,7 @@ struct CompilationRecord { * 2. Canonicalization + cleanup * 3. Flux dialect (value semantics) - enables SSA-based optimizations * 4. Canonicalization + cleanup - * 5. Quantum optimization passes (optional, TODO: to be implemented) + * 5. Quantum optimization passes * 6. Canonicalization + cleanup * 7. Quartz dialect - converted back for backend lowering * 8. Canonicalization + cleanup diff --git a/mlir/include/mlir/Support/PrettyPrinting.h b/mlir/include/mlir/Support/PrettyPrinting.h new file mode 100644 index 0000000000..f1c8b2934c --- /dev/null +++ b/mlir/include/mlir/Support/PrettyPrinting.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +namespace mlir { + +/** + * @brief Calculate UTF-8 display width of a string + * + * @details + * Counts the visual display width, not byte count. UTF-8 multi-byte + * characters like → and ✓ are counted as 1 display column. + * + * @param str The string to measure + * @return The display width in columns + */ +int calculateDisplayWidth(const std::string& str); + +/** + * @brief Wrap a long line into multiple lines that fit within the box + * + * @details + * Splits a line that's too long into multiple lines, preferring to break + * at whitespace when possible. Each wrapped line will fit within the + * available width inside the box. + * + * @param line The line to wrap + * @param maxWidth Maximum width for each line (excluding box borders and + * indent) + * @param indent Number of spaces to indent wrapped lines + * @return Vector of wrapped lines + */ +std::vector wrapLine(const std::string& line, int maxWidth, + int indent = 0); + +/** + * @brief Print top border of a box + * + * @param os Output stream to write to + */ +void printBoxTop(llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print middle separator of a box + * + * @param os Output stream to write to + */ +void printBoxMiddle(llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print bottom border of a box + * + * @param os Output stream to write to + */ +void printBoxBottom(llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print a box line with text and proper padding + * + * @details + * If the text is too long, it will be wrapped across multiple lines. + * + * @param text The text to display in the box + * @param indent Number of spaces to indent the text (0 for left-aligned) + * @param os Output stream to write to + */ +void printBoxLine(const std::string& text, int indent = 0, + llvm::raw_ostream& os = llvm::errs()); + +/** + * @brief Print multiple lines of text within the box, with line wrapping + * + * @details + * Takes a multi-line string and prints each line within the box borders, + * wrapping long lines as needed. + * + * @param text The text to display (may contain newlines) + * @param indent Number of spaces to indent the text + * @param os Output stream to write to + */ +void printBoxText(const std::string& text, int indent = 0, + llvm::raw_ostream& os = llvm::errs()); + +} // namespace mlir diff --git a/mlir/include/mlir/Support/TestUtils.h b/mlir/include/mlir/Support/TestUtils.h new file mode 100644 index 0000000000..034e56be78 --- /dev/null +++ b/mlir/include/mlir/Support/TestUtils.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +namespace mlir { +class Block; +class ModuleOp; +class Operation; +class Region; + +/** + * @brief Compare two modules for structural equivalence using MLIR's APIs + * + * @details + * Uses MLIR's IR walking and comparison mechanisms to verify that two + * modules are structurally equivalent. This is more robust than string + * comparison as it is insensitive to formatting differences. + * + * @param lhs First module to compare + * @param rhs Second module to compare + * @return true if modules are structurally equivalent, false otherwise + */ +bool modulesAreEquivalent(ModuleOp lhs, ModuleOp rhs); + +/** + * @brief Compare two operations for structural equivalence + * + * @details + * Recursively compares operations, their attributes, operands, results, + * and nested regions. This implements MLIR's structural equivalence check. + * + * @param lhs First operation to compare + * @param rhs Second operation to compare + * @return true if operations are structurally equivalent, false otherwise + */ +bool operationsAreEquivalent(Operation* lhs, Operation* rhs); + +/** + * @brief Compare two regions for structural equivalence + * + * @param lhs First region to compare + * @param rhs Second region to compare + * @return true if regions are structurally equivalent, false otherwise + */ +bool regionsAreEquivalent(Region* lhs, Region* rhs); + +/** + * @brief Compare two blocks for structural equivalence + * + * @param lhs First block to compare + * @param rhs Second block to compare + * @return true if blocks are structurally equivalent, false otherwise + */ +bool blocksAreEquivalent(Block* lhs, Block* rhs); + +} // namespace mlir diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index 7a88757150..8b2ef74e7c 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -9,3 +9,4 @@ add_subdirectory(Dialect) add_subdirectory(Conversion) add_subdirectory(Compiler) +add_subdirectory(Support) diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index 6fb8e033f5..cefa0e7d43 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -20,6 +20,7 @@ add_mlir_library( QuartzToFlux FluxToQuartz QuartzToQIR + MQT::MLIRSupport MQT::ProjectOptions) # collect header files diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index b65e8b24bf..150097b5b5 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -13,6 +13,7 @@ #include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" #include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" +#include "mlir/Support/PrettyPrinting.h" #include #include @@ -38,23 +39,40 @@ void QuantumCompilerPipeline::configurePassManager(PassManager& pm) const { if (config_.enableStatistics) { pm.enableStatistics(); } +} - // Enable IR printing options if requested - if (config_.printIRAfterAll) { - pm.enableIRPrinting( - /*shouldPrintBeforePass=*/[](Pass*, Operation*) { return false; }, - /*shouldPrintAfterPass=*/[](Pass*, Operation*) { return true; }, - /*printModuleScope=*/true, - /*printAfterOnlyOnChange=*/true, - /*printAfterOnlyOnFailure=*/false); - } else if (config_.printIRAfterFailure) { - pm.enableIRPrinting( - /*shouldPrintBeforePass=*/[](Pass*, Operation*) { return false; }, - /*shouldPrintAfterPass=*/[](Pass*, Operation*) { return false; }, - /*printModuleScope=*/true, - /*printAfterOnlyOnChange=*/false, - /*printAfterOnlyOnFailure=*/true); - } +/** + * @brief Pretty print IR with ASCII art borders and stage identifier + * + * @param module The module to print + * @param stageName Name of the compilation stage + * @param stageNumber Current stage number + * @param totalStages Total number of stages (for progress indication) + */ +static void prettyPrintStage(ModuleOp module, const StringRef stageName, + const int stageNumber, const int totalStages) { + llvm::errs() << "\n"; + printBoxTop(); + + // Build the stage header + const std::string stageHeader = "Stage " + std::to_string(stageNumber) + "/" + + std::to_string(totalStages) + ": " + + stageName.str(); + printBoxLine(stageHeader); + + printBoxMiddle(); + + // Capture the IR to a string so we can wrap it in box lines + std::string irString; + llvm::raw_string_ostream irStream(irString); + module.print(irStream); + irStream.flush(); + + // Print the IR with box lines and wrapping + printBoxText(irString); + + printBoxBottom(); + llvm::errs().flush(); } LogicalResult @@ -65,9 +83,22 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, // Configure PassManager with diagnostic options configurePassManager(pm); + // Determine total number of stages for progress indication + auto totalStages = + 8; // Base stages: Initial + Flux + FluxCanon + Optimization + + // OptimizationCanon + QuartzBack + QuartzCanon + if (config_.convertToQIR) { + totalStages += 2; // QIR + QIRCanon + } + auto currentStage = 0; + // Record initial state if requested if (record != nullptr && config_.recordIntermediates) { record->afterQuartzImport = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Quartz Import (Initial)", ++currentStage, + totalStages); + } } // Stage 1: Initial canonicalization @@ -77,6 +108,10 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } if (record != nullptr && config_.recordIntermediates) { record->afterInitialCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Initial Canonicalization", ++currentStage, + totalStages); + } } pm.clear(); @@ -87,6 +122,10 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } if (record != nullptr && config_.recordIntermediates) { record->afterFluxConversion = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Quartz → Flux Conversion", ++currentStage, + totalStages); + } } pm.clear(); @@ -97,53 +136,71 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } if (record != nullptr && config_.recordIntermediates) { record->afterFluxCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Flux Canonicalization", ++currentStage, + totalStages); + } } pm.clear(); - // Stage 4: Optimization passes (if enabled) - if (config_.runOptimization) { - // TODO: Add optimization passes - addCleanupPasses(pm); - if (failed(pm.run(module))) { - return failure(); - } - if (record != nullptr && config_.recordIntermediates) { - record->afterOptimization = captureIR(module); + // Stage 4: Optimization passes + // TODO: Add optimization passes + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimization = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Optimization Passes", ++currentStage, + totalStages); } - pm.clear(); + } + pm.clear(); - // Canonicalize after optimization - addCleanupPasses(pm); - if (failed(pm.run(module))) { - return failure(); - } - if (record != nullptr && config_.recordIntermediates) { - record->afterOptimizationCanon = captureIR(module); + // Stage 5: Canonicalize after optimization + addCleanupPasses(pm); + if (failed(pm.run(module))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimizationCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Post-Optimization Canonicalization", + ++currentStage, totalStages); } - pm.clear(); } + pm.clear(); - // Stage 5: Convert back to Quartz + // Stage 6: Convert back to Quartz pm.addPass(createFluxToQuartz()); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { record->afterQuartzConversion = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Flux → Quartz Conversion", ++currentStage, + totalStages); + } } pm.clear(); - // Stage 6: Canonicalize Quartz + // Stage 7: Canonicalize Quartz addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { record->afterQuartzCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Final Quartz Canonicalization", ++currentStage, + totalStages); + } } pm.clear(); - // Stage 7: Optional QIR conversion + // Stage 8: Optional QIR conversion if (config_.convertToQIR) { pm.addPass(createQuartzToQIR()); if (failed(pm.run(module))) { @@ -151,6 +208,10 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } if (record != nullptr && config_.recordIntermediates) { record->afterQIRConversion = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Quartz → QIR Conversion", ++currentStage, + totalStages); + } } pm.clear(); @@ -161,14 +222,32 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } if (record != nullptr && config_.recordIntermediates) { record->afterQIRCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Final QIR Canonicalization", ++currentStage, + totalStages); + } } } + // Print compilation summary + if (config_.printIRAfterAllStages) { + llvm::errs() << "\n"; + printBoxTop(); + + printBoxLine("✓ Compilation Complete"); + + std::string summaryLine = + "Successfully processed " + std::to_string(currentStage) + " stages"; + printBoxLine(summaryLine, 1); // Indent by 1 space + + printBoxBottom(); + llvm::errs() << "\n"; + llvm::errs().flush(); + } return success(); } std::string captureIR(ModuleOp module) { - module.dump(); std::string result; llvm::raw_string_ostream os(result); module.print(os); diff --git a/mlir/lib/Support/CMakeLists.txt b/mlir/lib/Support/CMakeLists.txt new file mode 100644 index 0000000000..5e7c709984 --- /dev/null +++ b/mlir/lib/Support/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT + +add_mlir_library( + MLIRSupportMQT + PrettyPrinting.cpp + TestUtils.cpp + ADDITIONAL_HEADER_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Support + LINK_LIBS + PUBLIC + MLIRSupport + MLIRIR) + +# add library alias +add_library(MQT::MLIRSupport ALIAS MLIRSupportMQT) + +# collect header files +file(GLOB_RECURSE SUPPORT_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Support/*.h") +target_sources(MLIRSupportMQT PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES ${SUPPORT_HEADERS_SOURCE}) diff --git a/mlir/lib/Support/PrettyPrinting.cpp b/mlir/lib/Support/PrettyPrinting.cpp new file mode 100644 index 0000000000..ce6c0c5c9a --- /dev/null +++ b/mlir/lib/Support/PrettyPrinting.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Support/PrettyPrinting.h" + +#include +#include +#include +#include + +namespace mlir { + +constexpr auto TOTAL_WIDTH = 120; +constexpr auto BORDER_WIDTH = 2; // "║ " on each side + +/** + * @brief Trim trailing whitespace from a string + */ +static std::string trimTrailingWhitespace(const std::string& str) { + const size_t end = str.find_last_not_of(" \t\r\n"); + return (end == std::string::npos) ? "" : str.substr(0, end + 1); +} + +int calculateDisplayWidth(const std::string& str) { + auto displayWidth = 0; + for (size_t i = 0; i < str.size();) { + if (const unsigned char c = str[i]; (c & 0x80) == 0) { + // ASCII character (1 byte) + displayWidth++; + i++; + } else if ((c & 0xE0) == 0xC0) { + // 2-byte UTF-8 character + displayWidth++; + i += 2; + } else if ((c & 0xF0) == 0xE0) { + // 3-byte UTF-8 character (like → and ✓) + displayWidth++; + i += 3; + } else if ((c & 0xF8) == 0xF0) { + // 4-byte UTF-8 character + displayWidth += 2; // Most emojis take 2 display columns + i += 4; + } else { + // Invalid UTF-8, skip + i++; + } + } + return displayWidth; +} + +std::vector wrapLine(const std::string& line, const int maxWidth, + const int indent) { + std::vector wrapped; + + if (line.empty()) { + wrapped.emplace_back(""); + return wrapped; + } + + // Detect leading whitespace (indentation) in the original line + size_t leadingSpaces = 0; + for (size_t i = 0; i < line.size(); ++i) { + if (line[i] == ' ') { + leadingSpaces++; + } else if (line[i] == '\t') { + leadingSpaces += 4; // Count tabs as 4 spaces + } else { + break; + } + } + + // Extract the content without leading whitespace + std::string content = line.substr(line.find_first_not_of(" \t")); + if (content.empty()) { + wrapped.emplace_back(line); + return wrapped; + } + + // Calculate available width accounting for indentation and wrap indicators + // First line: original indent + content + // Continuation lines: "↳ " (2 chars) + same indent + content + const int firstLineWidth = + maxWidth - indent - static_cast(leadingSpaces); + const int contLineWidth = + maxWidth - indent - static_cast(leadingSpaces) - 2; // "↳ " + + if (firstLineWidth <= 10 || contLineWidth <= 10) { + // Not enough space to wrap intelligently, just return original + wrapped.emplace_back(line); + return wrapped; + } + + std::string currentLine; + std::string currentWord; + auto currentWidth = 0; + auto isFirstLine = true; + auto addWord = [&](const std::string& word) { + const int wordWidth = calculateDisplayWidth(word); + const int spaceWidth = currentLine.empty() ? 0 : 1; + const int effectiveWidth = isFirstLine ? firstLineWidth : contLineWidth; + + if (currentWidth + spaceWidth + wordWidth <= effectiveWidth) { + // Word fits on current line + if (!currentLine.empty()) { + currentLine += ' '; + currentWidth++; + } + currentLine += word; + currentWidth += wordWidth; + return true; + } + return false; + }; + + // Process the content word by word + for (size_t i = 0; i < content.size(); ++i) { + const char c = content[i]; + + if (c == ' ' || c == '\t') { + // End of word - try to add it to current line + if (!currentWord.empty()) { + if (!addWord(currentWord)) { + // Word doesn't fit - finalize current line and start new one + if (!currentLine.empty()) { + // Add wrap indicator to the end + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine; + if (!isFirstLine || (i < content.size() - 1)) { + lineWithIndent += " →"; + } + wrapped.push_back(lineWithIndent); + } + + // Start new continuation line + currentLine = currentWord; + currentWidth = calculateDisplayWidth(currentWord); + isFirstLine = false; + } + currentWord.clear(); + } + } else { + currentWord += c; + } + } + + // Add remaining word + if (!currentWord.empty()) { + if (!addWord(currentWord)) { + // Finalize current line + if (!currentLine.empty()) { + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine + " →"; + wrapped.push_back(lineWithIndent); + } + + // Add word on new line + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent = "↳ " + lineWithIndent + currentWord; + wrapped.push_back(lineWithIndent); + isFirstLine = false; + } else { + // Word fit, add the final line + if (!currentLine.empty()) { + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine; + wrapped.push_back(lineWithIndent); + } + } + } else if (!currentLine.empty()) { + // Add the final line + std::string lineWithIndent(leadingSpaces, ' '); + lineWithIndent += currentLine; + wrapped.push_back(lineWithIndent); + } + + // If we didn't wrap anything, return the original line + if (wrapped.empty()) { + wrapped.push_back(line); + } else if (wrapped.size() > 1) { + // Add continuation indicator to all but the first and last lines + for (size_t i = 1; i < wrapped.size(); ++i) { + if (wrapped[i].find("↳") == std::string::npos) { + std::string indentStr(leadingSpaces, ' '); + wrapped[i] = "↳ " + indentStr + wrapped[i].substr(leadingSpaces); + } + } + } + + return wrapped; +} + +void printBoxTop(llvm::raw_ostream& os) { + os << "╔"; + for (auto i = 0; i < TOTAL_WIDTH - 2; ++i) { + os << "═"; + } + os << "╗\n"; +} + +void printBoxMiddle(llvm::raw_ostream& os) { + os << "╠"; + for (auto i = 0; i < TOTAL_WIDTH - 2; ++i) { + os << "═"; + } + os << "╣\n"; +} + +void printBoxBottom(llvm::raw_ostream& os) { + os << "╚"; + for (auto i = 0; i < TOTAL_WIDTH - 2; ++i) { + os << "═"; + } + os << "╝\n"; +} + +void printBoxLine(const std::string& text, const int indent, + llvm::raw_ostream& os) { + // Content width = Total width - left border (2 chars) - right border (2 + // chars) + constexpr int contentWidth = TOTAL_WIDTH - 2 * BORDER_WIDTH; // "║ " and " ║" + + // Trim trailing whitespace before processing + const std::string trimmedText = trimTrailingWhitespace(text); + + // Wrap the line if needed + for (const auto wrappedLines = wrapLine(trimmedText, contentWidth, indent); + const auto& line : wrappedLines) { + const int displayWidth = calculateDisplayWidth(line); + const int padding = contentWidth - indent - displayWidth; + + os << "║ "; + for (auto i = 0; i < indent; ++i) { + os << " "; + } + os << line; + for (auto i = 0; i < padding; ++i) { + os << " "; + } + os << " ║\n"; + } +} + +void printBoxText(const std::string& text, const int indent, + llvm::raw_ostream& os) { + // Trim trailing newlines from the entire text + std::string trimmedText = text; + while (!trimmedText.empty() && + (trimmedText.back() == '\n' || trimmedText.back() == '\r')) { + trimmedText.pop_back(); + } + + std::istringstream stream(trimmedText); + std::string line; + + while (std::getline(stream, line)) { + printBoxLine(line, indent, os); + } +} + +} // namespace mlir diff --git a/mlir/lib/Support/TestUtils.cpp b/mlir/lib/Support/TestUtils.cpp new file mode 100644 index 0000000000..87f95df6b3 --- /dev/null +++ b/mlir/lib/Support/TestUtils.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Support/TestUtils.h" + +#include +#include + +namespace mlir { + +bool modulesAreEquivalent(ModuleOp lhs, ModuleOp rhs) { + // First verify both modules are valid + if (failed(verify(lhs)) || failed(verify(rhs))) { + return false; + } + + // Compare module attributes + if (lhs->getAttrs() != rhs->getAttrs()) { + return false; + } + + for (auto [lhsOp, rhsOp] : llvm::zip(lhs.getOps(), rhs.getOps())) { + if (!operationsAreEquivalent(&lhsOp, &rhsOp)) { + return false; + } + } + + return true; +} + +bool operationsAreEquivalent(Operation* lhs, Operation* rhs) { + // Check operation name + if (lhs->getName() != rhs->getName()) { + return false; + } + + // Check attributes + if (lhs->getAttrs() != rhs->getAttrs()) { + return false; + } + + // Check number of operands and results + if (lhs->getNumOperands() != rhs->getNumOperands() || + lhs->getNumResults() != rhs->getNumResults()) { + return false; + } + + // Check result types + for (auto [lhsResult, rhsResult] : + llvm::zip(lhs->getResults(), rhs->getResults())) { + if (lhsResult.getType() != rhsResult.getType()) { + return false; + } + } + + // Check operand types (not values, as SSA values differ) + for (auto [lhsOperand, rhsOperand] : + llvm::zip(lhs->getOperands(), rhs->getOperands())) { + if (lhsOperand.getType() != rhsOperand.getType()) { + return false; + } + } + + // Check regions + if (lhs->getNumRegions() != rhs->getNumRegions()) { + return false; + } + + for (auto [lhsRegion, rhsRegion] : + llvm::zip(lhs->getRegions(), rhs->getRegions())) { + if (!regionsAreEquivalent(&lhsRegion, &rhsRegion)) { + return false; + } + } + + return true; +} + +bool regionsAreEquivalent(Region* lhs, Region* rhs) { + // Check number of blocks + if (lhs->getBlocks().size() != rhs->getBlocks().size()) { + return false; + } + + auto lhsBlockIt = lhs->begin(); + auto rhsBlockIt = rhs->begin(); + + while (lhsBlockIt != lhs->end() && rhsBlockIt != rhs->end()) { + if (!blocksAreEquivalent(&(*lhsBlockIt), &(*rhsBlockIt))) { + return false; + } + ++lhsBlockIt; + ++rhsBlockIt; + } + + return true; +} + +bool blocksAreEquivalent(Block* lhs, Block* rhs) { + // Check number of arguments + if (lhs->getNumArguments() != rhs->getNumArguments()) { + return false; + } + + // Check argument types + for (auto [lhsArg, rhsArg] : + llvm::zip(lhs->getArguments(), rhs->getArguments())) { + if (lhsArg.getType() != rhsArg.getType()) { + return false; + } + } + + // Check operations in the block + auto lhsOpIt = lhs->begin(); + auto rhsOpIt = rhs->begin(); + + while (lhsOpIt != lhs->end() && rhsOpIt != rhs->end()) { + if (!operationsAreEquivalent(&(*lhsOpIt), &(*rhsOpIt))) { + return false; + } + ++lhsOpIt; + ++rhsOpIt; + } + + return lhsOpIt == lhs->end() && rhsOpIt == rhs->end(); +} + +} // namespace mlir diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index aaffd71e6e..7510c6fa58 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -39,10 +39,6 @@ static cl::opt outputFilename("o", cl::desc("Output filename"), cl::value_desc("filename"), cl::init("-")); -static cl::opt - runOptimization("optimize", cl::desc("Run quantum optimization passes"), - cl::init(false)); - static cl::opt convertToQIR("emit-qir", cl::desc("Convert to QIR at the end"), cl::init(false)); @@ -55,17 +51,10 @@ static cl::opt enableStatistics("mlir-statistics", cl::desc("Enable pass statistics"), cl::init(false)); -static cl::opt printIRAfterAll("mlir-print-ir-after-all", - cl::desc("Print IR after each pass"), - cl::init(false)); - -static cl::opt printIRAfterFailure("mlir-print-ir-after-failure", - cl::desc("Print IR after failures"), - cl::init(false)); - -static cl::opt verifyDiagnostics("verify-diagnostics", - cl::desc("Verify expected diagnostics"), - cl::init(false)); +static cl::opt + printIRAfterAllStages("mlir-print-ir-after-all-stages", + cl::desc("Print IR after each compiler stage"), + cl::init(false)); /** * @brief Load and parse a .mlir file @@ -131,12 +120,10 @@ int main(int argc, char** argv) { // Configure the compiler pipeline QuantumCompilerConfig config; - config.runOptimization = runOptimization; config.convertToQIR = convertToQIR; config.enableTiming = enableTiming; config.enableStatistics = enableStatistics; - config.printIRAfterAll = printIRAfterAll; - config.printIRAfterFailure = printIRAfterFailure; + config.printIRAfterAllStages = printIRAfterAllStages; // Run the compilation pipeline if (const QuantumCompilerPipeline pipeline(config); From 1a97651309a2bb9f9e5e0b00335e7be737bdc1b7 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 21:17:32 +0200 Subject: [PATCH 042/419] =?UTF-8?q?=F0=9F=9A=A7=20first=20end-to-end=20tes?= =?UTF-8?q?t=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/unittests/pipeline/CMakeLists.txt | 2 +- .../pipeline/test_compiler_pipeline.cpp | 837 ++++++++++++++---- 2 files changed, 684 insertions(+), 155 deletions(-) diff --git a/mlir/unittests/pipeline/CMakeLists.txt b/mlir/unittests/pipeline/CMakeLists.txt index 8d8586f985..48a837a691 100644 --- a/mlir/unittests/pipeline/CMakeLists.txt +++ b/mlir/unittests/pipeline/CMakeLists.txt @@ -11,6 +11,6 @@ add_executable(mqt-core-mlir-compiler-pipeline-test test_compiler_pipeline.cpp) target_link_libraries( mqt-core-mlir-compiler-pipeline-test PRIVATE GTest::gtest_main MQTCompilerPipeline MLIRQuartzTranslation MLIRQuartzProgramBuilder - MQT::CoreIR MQT::ProjectOptions) + MLIRParser MQT::CoreIR) gtest_discover_tests(mqt-core-mlir-compiler-pipeline-test) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 0c358dea69..b921d463ae 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -9,17 +9,15 @@ */ #include "ir/QuantumComputation.hpp" -#include "ir/operations/OpType.hpp" #include "mlir/Compiler/CompilerPipeline.h" -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" +#include "mlir/Support/PrettyPrinting.h" +#include "mlir/Support/TestUtils.h" #include -#include #include #include #include @@ -30,12 +28,12 @@ #include #include #include +#include #include #include namespace { -using namespace qc; using namespace mlir; /** @@ -49,6 +47,7 @@ using namespace mlir; class CompilerPipelineTest : public testing::Test { protected: std::unique_ptr context; + QuantumCompilerConfig config; void SetUp() override { // Register all dialects needed for the full compilation pipeline @@ -65,13 +64,61 @@ class CompilerPipelineTest : public testing::Test { context = std::make_unique(); context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); + + // Enable QIR conversion by default + config.convertToQIR = true; + config.recordIntermediates = true; + config.printIRAfterAllStages = true; + } + + /** + * @brief Pretty print quantum computation with ASCII art borders + * + * @param qc The quantum computation to print + */ + static void prettyPrintQuantumComputation(const qc::QuantumComputation& qc) { + llvm::errs() << "\n"; + printBoxTop(); + + // Print header + printBoxLine("Initial Quantum Computation"); + + printBoxMiddle(); + + // Print internal representation + printBoxLine("Internal Representation:"); + + // Capture the internal representation + std::ostringstream internalRepr; + internalRepr << qc; + const std::string internalStr = internalRepr.str(); + + // Print with line wrapping + printBoxText(internalStr); + + printBoxMiddle(); + + // Print OpenQASM3 representation + printBoxLine("OpenQASM3 Representation:"); + printBoxLine(""); + + const auto qasmStr = qc.toQASM(); + + // Print with line wrapping + printBoxText(qasmStr); + + printBoxBottom(); + llvm::errs().flush(); } /** * @brief Import a QuantumComputation into Quartz dialect */ OwningOpRef - importQuantumCircuit(const QuantumComputation& qc) const { + importQuantumCircuit(const qc::QuantumComputation& qc) const { + if (config.printIRAfterAllStages) { + prettyPrintQuantumComputation(qc); + } return translateQuantumComputationToQuartz(context.get(), qc); } @@ -86,279 +133,761 @@ class CompilerPipelineTest : public testing::Test { } /** - * @brief Check if IR contains a specific pattern + * @brief Clone a module for comparison purposes + * + * @details + * Creates a deep copy of the module so we can compare it later + * without worrying about in-place mutations. + */ + static OwningOpRef cloneModule(ModuleOp module) { + return module.clone(); + } + + /** + * @brief Parse IR string back into a module + * + * @details + * Useful for reconstructing modules from CompilationRecord strings. + */ + OwningOpRef parseModule(const std::string& irString) const { + return mlir::parseSourceString(irString, + ParserConfig(context.get())); + } + + /** + * @brief Check if IR contains a specific pattern (for quick checks) */ static bool irContains(const std::string& ir, const std::string& pattern) { return ir.find(pattern) != std::string::npos; } + /** + * @brief Build expected Quartz IR programmatically for comparison + */ + OwningOpRef buildExpectedQuartzIR( + const std::function& buildFunc) + const { + quartz::QuartzProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + return builder.finalize(); + } + + /** + * @brief Apply canonicalization to a module (for building expected IR) + */ + LogicalResult applyCanonicalization(ModuleOp module) const { + PassManager pm(context.get()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); + return pm.run(module); + } + + /** + * @brief Verify module at a specific stage matches expected + */ + void verifyStageMatchesExpected(const std::string& stageName, + const std::string& actualIR, + ModuleOp expectedModule) const { + const auto actualModule = parseModule(actualIR); + ASSERT_TRUE(actualModule) << "Failed to parse " << stageName << " IR"; + + EXPECT_TRUE(modulesAreEquivalent(actualModule.get(), expectedModule)) + << stageName << " IR does not match expected structure"; + } + void TearDown() override {} }; // ################################################## -// # Basic Circuit Tests +// # Empty Circuit Tests // ################################################## /** - * @brief Test: Single qubit circuit compilation + * @brief Test: Empty circuit construction * * @details - * Creates a simple circuit with H and X gates, runs through the full - * pipeline, and verifies the output. + * Verifies that an empty QuantumComputation() can be imported and compiled + * without errors. Checks multiple stages of the pipeline. */ -TEST_F(CompilerPipelineTest, SingleQubitCircuit) { - // Create a simple quantum circuit - QuantumComputation qc(1); - qc.h(0); - qc.x(0); +TEST_F(CompilerPipelineTest, EmptyCircuit) { + // Create empty circuit + const qc::QuantumComputation qc; // Import to Quartz dialect - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - // Configure compiler to run full pipeline - QuantumCompilerConfig config; - config.recordIntermediates = true; + // Build expected IR for initial Quartz import + const auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + // Empty circuit - just initialize + }); + ASSERT_TRUE(expectedQuartzInitial); // Run compilation CompilationRecord record; ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - // Verify initial Quartz IR contains the gates - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.alloc")); + // Verify Quartz import stage + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); - // Verify final IR is valid - EXPECT_FALSE(record.afterQuartzCanon.empty()); + // Verify canonicalized Quartz stage + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzInitial.get()); + + // Verify final Quartz stage (after round-trip through Flux) + verifyStageMatchesExpected("Final Quartz Canonicalization", + record.afterQuartzCanon, + expectedQuartzInitial.get()); + + // Verify the IR is valid at all stages + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterFluxConversion.empty()); + EXPECT_FALSE(record.afterQuartzConversion.empty()); + + // QIR stages should also be present + EXPECT_FALSE(record.afterQIRConversion.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); } +// ################################################## +// # Quantum Register Allocation Tests +// ################################################## + /** - * @brief Test: Two qubit entangling circuit + * @brief Test: Single qubit register allocation * * @details - * Creates a Bell state circuit (H followed by CNOT), runs through - * the pipeline, and verifies transformations. + * Tests addQubitRegister with a single qubit. Verifies import and + * canonicalized stages. */ -TEST_F(CompilerPipelineTest, BellStateCircuit) { - // Create Bell state circuit - QuantumComputation qc(2); - qc.h(0); - qc.cx(0, 1); // CNOT with control on qubit 0 +TEST_F(CompilerPipelineTest, SingleQubitRegister) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Build expected IR for initial import + const auto expectedQuartzInitial = buildExpectedQuartzIR( + [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); + ASSERT_TRUE(expectedQuartzInitial); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify multiple stages + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Final Quartz Canonicalization", + record.afterQuartzCanon, + expectedQuartzInitial.get()); +} + +/** + * @brief Test: Multi-qubit register allocation + */ +TEST_F(CompilerPipelineTest, MultiQubitRegister) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); - // Import to Quartz dialect auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - // Configure and run compiler - QuantumCompilerConfig config; - config.recordIntermediates = true; + // Build expected IR + auto expectedQuartzInitial = buildExpectedQuartzIR( + [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); +} + +/** + * @brief Test: Multiple quantum registers + */ +TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.addQubitRegister(3, "aux"); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocQubitRegister(2, "q"); + b.allocQubitRegister(3, "aux"); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); CompilationRecord record; ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - // Verify circuit structure - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.alloc")); + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); +} - // After canonicalization, IR should be simplified +/** + * @brief Test: Large qubit register allocation + */ +TEST_F(CompilerPipelineTest, LargeQubitRegister) { + qc::QuantumComputation qc; + qc.addQubitRegister(100, "q"); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify compilation succeeded and produced valid IR at all stages + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterFluxConversion.empty()); EXPECT_FALSE(record.afterQuartzCanon.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); } +// ################################################## +// # Classical Register Allocation Tests +// ################################################## + /** - * @brief Test: Circuit with measurement - * - * @details - * Creates a circuit that includes measurement operations, which - * have different semantics in Quartz (in-place) vs Flux (SSA). + * @brief Test: Single classical bit register */ -TEST_F(CompilerPipelineTest, CircuitWithMeasurement) { - // Create circuit with measurement - QuantumComputation qc(2); - qc.h(0); - qc.cx(0, 1); // CNOT - qc.measure(0, 0); - qc.measure(1, 1); +TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { + qc::QuantumComputation qc; + qc.addClassicalRegister(1, "c"); - // Import and compile auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - QuantumCompilerConfig config; - config.recordIntermediates = true; + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocClassicalBitRegister(1, "c"); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); CompilationRecord record; ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - // Verify measurement operations are present - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.measure")); + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); } /** - * @brief Test: Circuit with reset operation - * - * @details - * Tests reset operations which also have different semantics between - * reference-based (Quartz) and value-based (Flux) representations. + * @brief Test: Multi-bit classical register */ -TEST_F(CompilerPipelineTest, CircuitWithReset) { - // Create circuit with reset - QuantumComputation qc(2); - qc.h(0); - qc.reset(0); - qc.x(0); +TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { + qc::QuantumComputation qc; + qc.addClassicalRegister(5, "c"); auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - QuantumCompilerConfig config; - config.recordIntermediates = true; + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocClassicalBitRegister(5, "c"); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); CompilationRecord record; ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.reset")); + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); +} + +/** + * @brief Test: Multiple classical registers + */ +TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { + qc::QuantumComputation qc; + qc.addClassicalRegister(3, "c"); + qc.addClassicalRegister(2, "result"); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocClassicalBitRegister(3, "c"); + b.allocClassicalBitRegister(2, "result"); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); +} + +/** + * @brief Test: Large classical bit register + */ +TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { + qc::QuantumComputation qc; + qc.addClassicalRegister(128, "c"); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify compilation succeeded + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterQuartzCanon.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); } // ################################################## -// # Pipeline Stage Tests +// # Reset Operation Tests // ################################################## /** - * @brief Test: Quartz to Flux conversion - * - * @details - * Verifies that the conversion from reference semantics (Quartz) - * to value semantics (Flux) preserves circuit semantics. + * @brief Test: Single reset in single qubit circuit */ -TEST_F(CompilerPipelineTest, QuartzToFluxConversion) { - QuantumComputation qc(1); - qc.h(0); - qc.x(0); +TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { + qc::QuantumComputation qc(1); + qc.reset(0); auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - // Run only up to Flux conversion - PassManager pm(context.get()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createQuartzToFlux()); - pm.addPass(createCanonicalizerPass()); + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.staticQubit(0); + b.reset(q); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - ASSERT_TRUE(succeeded(pm.run(module.get()))); + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - // Verify Flux dialect is present - std::string ir = captureIR(module.get()); - EXPECT_TRUE(irContains(ir, "flux.alloc")); + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); + + // Verify Flux conversion contains flux.reset + EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.reset")); } /** - * @brief Test: Flux to Quartz round-trip - * - * @details - * Verifies that converting Quartz -> Flux -> Quartz produces - * semantically equivalent IR. + * @brief Test: Consecutive reset operations */ -TEST_F(CompilerPipelineTest, FluxToQuartzRoundTrip) { - QuantumComputation qc(1); - qc.h(0); +TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { + qc::QuantumComputation qc(1); + qc.reset(0); + qc.reset(0); + qc.reset(0); auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - // Capture initial state - std::string initialIR = captureIR(module.get()); + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.staticQubit(0); + b.reset(q); + b.reset(q); + b.reset(q); + }); + ASSERT_TRUE(expectedQuartzInitial); - // Run round-trip conversion - PassManager pm(context.get()); - pm.addPass(createQuartzToFlux()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createFluxToQuartz()); - pm.addPass(createCanonicalizerPass()); + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - ASSERT_TRUE(succeeded(pm.run(module.get()))); + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - // Verify we're back in Quartz dialect - std::string finalIR = captureIR(module.get()); - EXPECT_TRUE(irContains(finalIR, "quartz.alloc")); - EXPECT_FALSE(irContains(finalIR, "flux.alloc")); + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); } /** - * @brief Test: QIR conversion - * - * @details - * Tests the final lowering from Quartz to QIR (LLVM-based - * quantum intermediate representation). + * @brief Test: Separate resets in two qubit system */ -TEST_F(CompilerPipelineTest, QIRConversion) { - QuantumComputation qc(1); - qc.h(0); +TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { + qc::QuantumComputation qc(2); + qc.reset(0); + qc.reset(1); auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - QuantumCompilerConfig config; - config.convertToQIR = true; - config.recordIntermediates = true; + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.reset(q0); + b.reset(q1); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); CompilationRecord record; ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - // Verify QIR (LLVM dialect) is present in final output - EXPECT_TRUE(irContains(record.afterQIRCanon, "llvm.func")); + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); } // ################################################## -// # Canonicalization Tests +// # Measure Operation Tests // ################################################## /** - * @brief Test: Dead code elimination - * - * @details - * Verifies that dead value removal correctly eliminates unused - * operations and values from the IR. Dead value removal now always - * runs as part of the cleanup passes after each stage. + * @brief Test: Single measurement to single bit */ -TEST_F(CompilerPipelineTest, DeadCodeElimination) { - QuantumComputation qc(3); - qc.h(0); - qc.h(1); - qc.h(2); - // Only use qubit 0 in actual computation - qc.x(0); +TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { + qc::QuantumComputation qc(1); + qc.measure(0, 0); auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - QuantumCompilerConfig config; - // Dead value removal always runs automatically + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.staticQubit(0); + auto c = b.allocClassicalBitRegister(1); + b.measure(q, c, 0); + }); + ASSERT_TRUE(expectedQuartzInitial); - ASSERT_TRUE(succeeded(runCompiler(module.get(), config))); + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - // After dead code elimination, unused allocations may be removed - std::string ir = captureIR(module.get()); - EXPECT_FALSE(ir.empty()); + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); + + // Verify Flux conversion contains flux.measure + EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.measure")); +} + +/** + * @brief Test: Repeated measurement to same bit + */ +TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { + qc::QuantumComputation qc(1); + qc.measure(0, 0); + qc.reset(0); + qc.measure(0, 0); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.staticQubit(0); + auto c = b.allocClassicalBitRegister(1); + b.measure(q, c, 0); + b.reset(q); + b.measure(q, c, 0); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); +} + +/** + * @brief Test: Repeated measurement on separate bits + */ +TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { + qc::QuantumComputation qc(1); + qc.addClassicalRegister(3); + qc.measure(0, 0); + qc.reset(0); + qc.measure(0, 1); + qc.reset(0); + qc.measure(0, 2); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.staticQubit(0); + auto c = b.allocClassicalBitRegister(3); + b.measure(q, c, 0); + b.reset(q); + b.measure(q, c, 1); + b.reset(q); + b.measure(q, c, 2); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); +} + +/** + * @brief Test: Multiple classical registers with measurements + */ +TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { + qc::QuantumComputation qc(2); + qc.addClassicalRegister(2, "c1"); + qc.addClassicalRegister(2, "c2"); + qc.measure(0, 0); + qc.measure(1, 1); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify all stages completed + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterFluxConversion.empty()); + EXPECT_FALSE(record.afterQuartzCanon.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); } // ################################################## -// # Error Handling Tests +// # Combined Feature Tests // ################################################## /** - * @brief Test: Empty circuit handling - * - * @details - * Verifies that the compiler correctly handles edge cases like - * empty quantum circuits. + * @brief Test: Quantum and classical registers with operations */ -TEST_F(CompilerPipelineTest, EmptyCircuit) { - QuantumComputation qc(1); - // No operations added +TEST_F(CompilerPipelineTest, QuantumClassicalRegistersWithOperations) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.addClassicalRegister(3, "c"); + + // Reset all qubits + qc.reset(0); + qc.reset(1); + qc.reset(2); + + // Measure all qubits + qc.measure(0, 0); + qc.measure(1, 1); + qc.measure(2, 2); auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - QuantumCompilerConfig config; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config))); + // Build expected IR + auto expectedQuartzInitial = + buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(3, "q"); + auto c = b.allocClassicalBitRegister(3, "c"); + b.reset(q[0]); + b.reset(q[1]); + b.reset(q[2]); + b.measure(q[0], c, 0); + b.measure(q[1], c, 1); + b.measure(q[2], c, 2); + }); + ASSERT_TRUE(expectedQuartzInitial); + + auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); + ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, + expectedQuartzInitial.get()); + verifyStageMatchesExpected("Initial Canonicalization", + record.afterInitialCanon, + expectedQuartzCanon.get()); + + // Verify conversions to other dialects succeeded + EXPECT_TRUE(irContains(record.afterFluxConversion, "flux")); + EXPECT_TRUE(irContains(record.afterQIRConversion, "llvm")); +} + +/** + * @brief Test: End-to-end pipeline with all stages + */ +TEST_F(CompilerPipelineTest, EndToEndPipelineAllStages) { + qc::QuantumComputation qc(2); + qc.addClassicalRegister(2); + qc.reset(0); + qc.reset(1); + qc.measure(0, 0); + qc.measure(1, 1); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify each stage produces non-empty output + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterInitialCanon.empty()); + EXPECT_FALSE(record.afterFluxConversion.empty()); + EXPECT_FALSE(record.afterFluxCanon.empty()); + EXPECT_FALSE(record.afterOptimization.empty()); + EXPECT_FALSE(record.afterOptimizationCanon.empty()); + EXPECT_FALSE(record.afterQuartzConversion.empty()); + EXPECT_FALSE(record.afterQuartzCanon.empty()); + EXPECT_FALSE(record.afterQIRConversion.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); + + // Verify dialect transitions + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz")); + EXPECT_TRUE(irContains(record.afterFluxConversion, "flux")); + EXPECT_TRUE(irContains(record.afterQuartzConversion, "quartz")); + EXPECT_TRUE(irContains(record.afterQIRConversion, "llvm")); +} + +/** + * @brief Test: Complex circuit with interleaved operations + */ +TEST_F(CompilerPipelineTest, ComplexInterleavedOperations) { + qc::QuantumComputation qc; + qc.addQubitRegister(4, "q"); + qc.addClassicalRegister(4, "c1"); + qc.addClassicalRegister(2, "c2"); + + // Interleaved operations + qc.reset(0); + qc.measure(0, 0); + qc.reset(1); + qc.reset(2); + qc.measure(1, 1); + qc.measure(2, 2); + qc.reset(3); + qc.measure(3, 3); + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify all pipeline stages succeeded + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterFluxConversion.empty()); + EXPECT_FALSE(record.afterQuartzCanon.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); + + // Verify operations are present in appropriate dialects + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.reset")); + EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.measure")); + EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.reset")); + EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.measure")); +} + +/** + * @brief Test: Scalability with large mixed operations + */ +TEST_F(CompilerPipelineTest, ScalabilityLargeMixedOperations) { + constexpr size_t NUM_QUBITS = 50; + + qc::QuantumComputation qc; + qc.addQubitRegister(NUM_QUBITS, "q"); + qc.addClassicalRegister(NUM_QUBITS, "c"); + + // Add operations for all qubits + for (size_t i = 0; i < NUM_QUBITS; ++i) { + qc.reset(i); + qc.measure(i, i); + } + + auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + + CompilationRecord record; + ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); + + // Verify compilation succeeded and produced valid output at all stages + EXPECT_FALSE(record.afterQuartzImport.empty()); + EXPECT_FALSE(record.afterFluxConversion.empty()); + EXPECT_FALSE(record.afterQuartzCanon.empty()); + EXPECT_FALSE(record.afterQIRCanon.empty()); } } // namespace From 291aedd3d9b5befb6b153e91fc4baefa9870fc3f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Oct 2025 22:34:51 +0200 Subject: [PATCH 043/419] =?UTF-8?q?=F0=9F=9A=B8=20update=20alloc=20assembl?= =?UTF-8?q?y=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 8 ++++---- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 6 +++--- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 8 ++++---- mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp | 4 ++-- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 628ac95ad1..b398de953e 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -107,9 +107,9 @@ def AllocOp : FluxOp<"alloc"> { Example (qubits in a register): ```mlir - %q0 = flux.alloc q[3, 0] : !flux.qubit - %q1 = flux.alloc q[3, 1] : !flux.qubit - %q2 = flux.alloc q[3, 2] : !flux.qubit + %q0 = flux.alloc("q", 3, 0) : !flux.qubit + %q1 = flux.alloc("q", 3, 1) : !flux.qubit + %q2 = flux.alloc("q", 3, 2) : !flux.qubit ``` }]; @@ -118,7 +118,7 @@ def AllocOp : FluxOp<"alloc"> { OptionalAttr>:$register_index); let results = (outs QubitType:$result); let assemblyFormat = [{ - ($register_name^ `[` $register_size `,` $register_index `]`)? + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? attr-dict `:` type($result) }]; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 5bc9e02564..5fb620be0c 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -168,9 +168,9 @@ class QuartzProgramBuilder { * * This generates: * ```mlir - * %q0 = quartz.alloc q[3, 0] : !quartz.qubit - * %q1 = quartz.alloc q[3, 1] : !quartz.qubit - * %q2 = quartz.alloc q[3, 2] : !quartz.qubit + * %q0 = quartz.alloc("q", 3, 0) : !quartz.qubit + * %q1 = quartz.alloc("q", 3, 1) : !quartz.qubit + * %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit * ``` */ SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 36f9514b51..6497ad52d4 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -99,9 +99,9 @@ def AllocOp : QuartzOp<"alloc"> { Example (qubits in a register): ```mlir - %q0 = quartz.alloc q[3, 0] : !quartz.qubit - %q1 = quartz.alloc q[3, 1] : !quartz.qubit - %q2 = quartz.alloc q[3, 2] : !quartz.qubit + %q0 = quartz.alloc("q", 3, 0) : !quartz.qubit + %q1 = quartz.alloc("q", 3, 1) : !quartz.qubit + %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit ``` }]; @@ -110,7 +110,7 @@ def AllocOp : QuartzOp<"alloc"> { OptionalAttr>:$register_index); let results = (outs QubitType:$result); let assemblyFormat = [{ - ($register_name^ `[` $register_size `,` $register_index `]`)? + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? attr-dict `:` type($result) }]; diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index bd78620f83..4ec9be2e7b 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -74,9 +74,9 @@ class FluxToQuartzTypeConverter final : public TypeConverter { * * Example transformation: * ```mlir - * %q0 = flux.alloc q[3, 0] : !flux.qubit + * %q0 = flux.alloc("q", 3, 0) : !flux.qubit * // becomes: - * %q = quartz.alloc q[3, 0] : !quartz.qubit + * %q = quartz.alloc("q", 3, 0) : !quartz.qubit * ``` */ struct ConvertFluxAllocOp final : OpConversionPattern { diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 7777806163..ba4d7ddb27 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -144,9 +144,9 @@ class QuartzToFluxTypeConverter final : public TypeConverter { * * Example transformation: * ```mlir - * %q = quartz.alloc q[3, 0] : !quartz.qubit + * %q = quartz.alloc("q", 3, 0) : !quartz.qubit * // becomes: - * %q0 = flux.alloc q[3, 0] : !flux.qubit + * %q0 = flux.alloc("q", 3, 0) : !flux.qubit * ``` */ struct ConvertQuartzAllocOp final From 39ccf14199e274db8a6b296e353597642e6b3f25 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 12 Oct 2025 16:30:21 +0200 Subject: [PATCH 044/419] =?UTF-8?q?=F0=9F=9A=B8=20add=20explicit=20dealloc?= =?UTF-8?q?ation=20for=20qubits=20in=20QuartzProgramBuilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Quartz/Builder/QuartzProgramBuilder.h | 51 +++++++++++++++++-- .../Quartz/Builder/QuartzProgramBuilder.cpp | 35 ++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 5fb620be0c..57108ee247 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -13,6 +13,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include +#include #include #include #include @@ -290,6 +291,38 @@ class QuartzProgramBuilder { */ QuartzProgramBuilder& reset(Value qubit); + /** + * @brief Explicitly deallocate a qubit + * + * @details + * Deallocates a previously allocated qubit, releasing its resources. + * The qubit must have been allocated using allocQubit() or + * allocQubitRegister(). Attempting to deallocate the same qubit twice + * will result in an error. + * + * Note: Deallocation is automatically performed for all remaining allocated + * qubits when finalize() is called, so explicit deallocation is optional + * but can be used for precise resource management. + * + * @param qubit The qubit to deallocate + * @return A reference to this builder for method chaining + * + * @par Example: + * ```c++ + * auto q = builder.allocQubit(); + * // ... use qubit ... + * builder.dealloc(q); + * ``` + * + * This generates: + * ```mlir + * %q = quartz.alloc : !quartz.qubit + * // ... operations ... + * quartz.dealloc %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& dealloc(Value qubit); + //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// @@ -299,8 +332,14 @@ class QuartzProgramBuilder { * * @details * Completes the construction of the quantum program by: - * 1. Adding a return statement to the main function - * 2. Transferring ownership of the module to the caller + * 1. Automatically deallocating all remaining allocated qubits + * 2. Adding a return statement to the main function + * 3. Transferring ownership of the module to the caller + * + * Any qubits allocated via allocQubit() or allocQubitRegister() that have + * not been explicitly deallocated using dealloc() will be automatically + * deallocated at this point, ensuring proper resource management and + * preventing "qubit leaks". * * After calling this method, the builder is invalidated and should not be * used to add more operations. The returned OwningOpRef takes ownership of @@ -312,9 +351,10 @@ class QuartzProgramBuilder { * ```c++ * QuartzProgramBuilder builder(context); * builder.initialize(); - * auto q = builder.staticQubit(0); - * builder.h(q); + * auto q = builder.allocQubit(); + * // ... use qubit ... * auto module = builder.finalize(); + * // q is automatically deallocated here * // module now owns the MLIR module, builder is invalidated * ``` */ @@ -324,5 +364,8 @@ class QuartzProgramBuilder { OpBuilder builder; ModuleOp module; Location loc; + + /// Track allocated qubits for automatic deallocation + llvm::DenseSet allocatedQubits; }; } // namespace mlir::quartz diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index c2ce36bf6a..e0075adfd0 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -51,7 +51,12 @@ void QuartzProgramBuilder::initialize() { Value QuartzProgramBuilder::allocQubit() { // Create the AllocOp without register metadata auto allocOp = builder.create(loc); - return allocOp.getResult(); + const auto qubit = allocOp.getResult(); + + // Track the allocated qubit for automatic deallocation + allocatedQubits.insert(qubit); + + return qubit; } Value QuartzProgramBuilder::staticQubit(int64_t index) { @@ -73,7 +78,9 @@ SmallVector QuartzProgramBuilder::allocQubitRegister(int64_t size, for (int64_t i = 0; i < size; ++i) { auto indexAttr = builder.getI64IntegerAttr(i); auto allocOp = builder.create(loc, nameAttr, sizeAttr, indexAttr); - qubits.push_back(allocOp.getResult()); + const auto& qubit = qubits.emplace_back(allocOp.getResult()); + // Track the allocated qubit for automatic deallocation + allocatedQubits.insert(qubit); } return qubits; @@ -117,7 +124,31 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { return *this; } +QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { + // Check if the qubit is in the tracking set + if (allocatedQubits.erase(qubit) == 0) { + // Qubit was not found in the set - either never allocated or already + // deallocated + llvm::errs() << "Error: Attempting to deallocate a qubit that was not " + "allocated or has already been deallocated\n"; + llvm_unreachable("Double deallocation or invalid qubit deallocation"); + } + + // Create the DeallocOp + builder.create(loc, qubit); + + return *this; +} + OwningOpRef QuartzProgramBuilder::finalize() { + // Automatically deallocate all remaining allocated qubits + for (Value qubit : allocatedQubits) { + builder.create(loc, qubit); + } + + // Clear the tracking set + allocatedQubits.clear(); + // Add return statement to the main function builder.create(loc); From 2c9778e75239bd7a4346d06832b0fdda9c24d867 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:44:54 +0200 Subject: [PATCH 045/419] =?UTF-8?q?=F0=9F=93=9D=20updated=20Quartz=20progr?= =?UTF-8?q?am=20builder=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Quartz/Builder/QuartzProgramBuilder.h | 201 ++++-------------- 1 file changed, 36 insertions(+), 165 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 57108ee247..8fc3ac25d3 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -26,18 +26,10 @@ namespace mlir::quartz { * @brief Builder API for constructing quantum programs in the Quartz dialect * * @details - * The QuartzProgramBuilder provides a type-safe, ergonomic interface for - * programmatically constructing quantum circuits using reference semantics. - * Gates operate on qubit references without explicit SSA value threading, - * making it natural to express imperative quantum programs similar to how - * hardware physically transforms quantum states. - * - * The builder follows the Quartz dialect's philosophy of reference semantics, - * where operations modify qubits in place. This provides: - * - Natural mapping to hardware execution models - * - Intuitive representation for circuit descriptions - * - Direct compatibility with imperative quantum programming languages - * - Support for method chaining through fluent interface design + * The QuartzProgramBuilder provides a type-safe interface for constructing + * quantum circuits using reference semantics. Operations modify qubits in + * place without producing new SSA values, providing a natural mapping to + * hardware execution models. * * @par Example Usage: * ```c++ @@ -47,24 +39,11 @@ namespace mlir::quartz { * auto q0 = builder.staticQubit(0); * auto q1 = builder.staticQubit(1); * - * // Create Bell state using method chaining + * // Operations modify qubits in place * builder.h(q0).cx(q0, q1); * * auto module = builder.finalize(); * ``` - * - * This produces the following MLIR module with a main function: - * ```mlir - * module { - * func.func @main() attributes {passthrough = ["entry_point"]} { - * %q0 = quartz.static 0 : !quartz.qubit - * %q1 = quartz.static 1 : !quartz.qubit - * quartz.h %q0 : !quartz.qubit - * quartz.cx %q0, %q1 : !quartz.qubit, !quartz.qubit - * func.return - * } - * } - * ``` */ class QuartzProgramBuilder { public: @@ -82,17 +61,8 @@ class QuartzProgramBuilder { * @brief Initialize the builder and prepare for program construction * * @details - * This method must be called before any operations are added to the program. - * It creates a main function with an entry_point attribute and sets up the - * builder's insertion point. All subsequent operations will be added to the - * body of this main function. - * - * The generated function structure: - * ```mlir - * func.func @main() attributes {passthrough = ["entry_point"]} { - * // Operations added here - * } - * ``` + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. */ void initialize(); @@ -101,20 +71,13 @@ class QuartzProgramBuilder { //===--------------------------------------------------------------------===// /** - * @brief Dynamically allocate a single qubit - * - * @details - * Allocates a new qubit dynamically and returns a reference to it. - * The qubit is initialized to the |0⟩ state. - * - * @return A Value representing the allocated qubit + * @brief Allocate a single qubit initialized to |0⟩ + * @return A qubit reference * * @par Example: * ```c++ * auto q = builder.allocQubit(); * ``` - * - * This generates: * ```mlir * %q = quartz.alloc : !quartz.qubit * ``` @@ -122,52 +85,30 @@ class QuartzProgramBuilder { Value allocQubit(); /** - * @brief Get a static reference to a qubit by index - * - * @details - * Creates a reference to a qubit identified by a static index. This is - * useful for referring to fixed qubits in a quantum program or to - * hardware-mapped qubits. - * - * @param index The index of the qubit to reference (must be non-negative) - * @return A Value representing the qubit at the given index + * @brief Get a static qubit by index + * @param index The qubit index (must be non-negative) + * @return A qubit reference * * @par Example: * ```c++ * auto q0 = builder.staticQubit(0); - * auto q1 = builder.staticQubit(1); * ``` - * - * This generates: * ```mlir * %q0 = quartz.static 0 : !quartz.qubit - * %q1 = quartz.static 1 : !quartz.qubit * ``` */ Value staticQubit(int64_t index); /** - * @brief Allocate a qubit register as a sequence of individual qubits - * - * @details - * Allocates multiple qubits that form a logical register. Each qubit is - * allocated individually with metadata indicating its membership in the - * register, including the register name, size, and index within the register. - * - * The returned SmallVector can be used directly or converted to ValueRange - * as needed. - * - * @param size The number of qubits in the register (must be positive) - * @param name The symbolic name for the register (default: "q") - * @return A SmallVector containing the allocated qubits + * @brief Allocate a qubit register + * @param size Number of qubits (must be positive) + * @param name Register name (default: "q") + * @return Vector of qubit references * * @par Example: * ```c++ * auto q = builder.allocQubitRegister(3, "q"); - * // Use q[0], q[1], q[2] directly * ``` - * - * This generates: * ```mlir * %q0 = quartz.alloc("q", 3, 0) : !quartz.qubit * %q1 = quartz.alloc("q", 3, 1) : !quartz.qubit @@ -178,23 +119,16 @@ class QuartzProgramBuilder { /** * @brief Allocate a classical bit register - * - * @details - * Allocates a classical register for storing measurement results or other - * classical data. The register is represented as a memref of i1 elements. - * - * @param size The number of bits in the register - * @param name The symbolic name for the register (default: "c") - * @return A Value representing the allocated memref of i1 elements + * @param size Number of bits + * @param name Register name (default: "c") + * @return Memref of i1 elements * * @par Example: * ```c++ * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` - * - * This generates: * ```mlir - * %c = memref.alloc() {sym_name = "c"} : memref<3xi1> + * %c = memref.alloca() {sym_name = "c"} : memref<3xi1> * ``` */ Value allocClassicalBitRegister(int64_t size, StringRef name = "c"); @@ -204,24 +138,19 @@ class QuartzProgramBuilder { //===--------------------------------------------------------------------===// /** - * @brief Measure a qubit and return the measurement result + * @brief Measure a qubit in the computational basis * * @details - * Measures a qubit in the computational (Z) basis, collapsing the state - * and returning a classical bit result (i1). + * Measures a qubit in place and returns the classical measurement result. * * @param qubit The qubit to measure - * @return A Value representing the classical measurement result (i1) + * @return Classical measurement result (i1) * * @par Example: * ```c++ - * auto q = builder.staticQubit(0); * auto result = builder.measure(q); * ``` - * - * This generates: * ```mlir - * %q = quartz.static 0 : !quartz.qubit * %result = quartz.measure %q : !quartz.qubit -> i1 * ``` */ @@ -230,62 +159,37 @@ class QuartzProgramBuilder { /** * @brief Measure a qubit and store the result in a classical register * - * @details - * Measures a qubit in the computational basis and stores the result at - * the specified index in a classical register (memref). This is useful - * for accumulating multiple measurement results in a register. - * * @param qubit The qubit to measure - * @param memref The classical register to store the result in - * @param index The index in the register where the result should be stored - * (must be non-negative) - * @return A reference to this builder for method chaining + * @param memref Classical register + * @param index Storage index + * @return Reference to this builder for method chaining * * @par Example: * ```c++ - * auto q0 = builder.staticQubit(0); - * auto q1 = builder.staticQubit(1); - * auto c = builder.allocClassicalBitRegister(2); - * - * builder.measure(q0, c, 0) - * .measure(q1, c, 1); + * builder.measure(q0, c, 0); * ``` - * - * This generates (within the main function body): * ```mlir - * %q0 = quartz.static 0 : !quartz.qubit - * %q1 = quartz.static 1 : !quartz.qubit - * %c = memref.alloc() {sym_name = "c"} : memref<2xi1> * %r0 = quartz.measure %q0 : !quartz.qubit -> i1 * %c0 = arith.constant 0 : index * memref.store %r0, %c[%c0] : memref<2xi1> - * %r1 = quartz.measure %q1 : !quartz.qubit -> i1 - * %c1 = arith.constant 1 : index - * memref.store %r1, %c[%c1] : memref<2xi1> * ``` */ QuartzProgramBuilder& measure(Value qubit, Value memref, int64_t index); /** - * @brief Reset a qubit to the |0⟩ state + * @brief Reset a qubit to |0⟩ state * * @details - * Resets a qubit to the |0⟩ state, regardless of its current state. - * This operation is often used to recycle qubits in quantum algorithms - * or to initialize qubits to a known state. + * Resets a qubit to the |0⟩ state in place. * * @param qubit The qubit to reset - * @return A reference to this builder for method chaining + * @return Reference to this builder for method chaining * * @par Example: * ```c++ - * auto q = builder.staticQubit(0); * builder.reset(q); * ``` - * - * This generates: * ```mlir - * %q = quartz.static 0 : !quartz.qubit * quartz.reset %q : !quartz.qubit * ``` */ @@ -295,29 +199,17 @@ class QuartzProgramBuilder { * @brief Explicitly deallocate a qubit * * @details - * Deallocates a previously allocated qubit, releasing its resources. - * The qubit must have been allocated using allocQubit() or - * allocQubitRegister(). Attempting to deallocate the same qubit twice - * will result in an error. - * - * Note: Deallocation is automatically performed for all remaining allocated - * qubits when finalize() is called, so explicit deallocation is optional - * but can be used for precise resource management. + * Deallocates a qubit and removes it from tracking. Optional, finalize() + * automatically deallocates all remaining allocated qubits. * * @param qubit The qubit to deallocate - * @return A reference to this builder for method chaining + * @return Reference to this builder for method chaining * * @par Example: * ```c++ - * auto q = builder.allocQubit(); - * // ... use qubit ... * builder.dealloc(q); * ``` - * - * This generates: * ```mlir - * %q = quartz.alloc : !quartz.qubit - * // ... operations ... * quartz.dealloc %q : !quartz.qubit * ``` */ @@ -331,32 +223,11 @@ class QuartzProgramBuilder { * @brief Finalize the program and return the constructed module * * @details - * Completes the construction of the quantum program by: - * 1. Automatically deallocating all remaining allocated qubits - * 2. Adding a return statement to the main function - * 3. Transferring ownership of the module to the caller - * - * Any qubits allocated via allocQubit() or allocQubitRegister() that have - * not been explicitly deallocated using dealloc() will be automatically - * deallocated at this point, ensuring proper resource management and - * preventing "qubit leaks". - * - * After calling this method, the builder is invalidated and should not be - * used to add more operations. The returned OwningOpRef takes ownership of - * the module. + * Automatically deallocates all remaining allocated qubits, adds a return + * statement, and transfers ownership of the module to the caller. + * The builder should not be used after calling this method. * * @return OwningOpRef containing the constructed quantum program module - * - * @par Example: - * ```c++ - * QuartzProgramBuilder builder(context); - * builder.initialize(); - * auto q = builder.allocQubit(); - * // ... use qubit ... - * auto module = builder.finalize(); - * // q is automatically deallocated here - * // module now owns the MLIR module, builder is invalidated - * ``` */ OwningOpRef finalize(); From 254c230a7e30da4e5592eb7a59b397bf77701cbc Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:45:22 +0200 Subject: [PATCH 046/419] =?UTF-8?q?=F0=9F=9A=B8=20add=20custom=20builder?= =?UTF-8?q?=20for=20alloc=20in=20Flux=20dialect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index b398de953e..035d024a86 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -122,6 +122,18 @@ def AllocOp : FluxOp<"alloc"> { attr-dict `:` type($result) }]; + let builders = [ + OpBuilder<(ins), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); + }]>, + OpBuilder<(ins "::mlir::StringAttr":$register_name, + "::mlir::IntegerAttr":$register_size, + "::mlir::IntegerAttr":$register_index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), + register_name, register_size, register_index); + }]> + ]; + let hasVerifier = 1; } From e591bf727e07b23108e60a701ae02f14eb99a204 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:45:49 +0200 Subject: [PATCH 047/419] =?UTF-8?q?=E2=9C=A8=20add=20Flux=20program=20buil?= =?UTF-8?q?der?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 273 ++++++++++++++++++ mlir/lib/Dialect/Flux/Builder/CMakeLists.txt | 27 ++ .../Flux/Builder/FluxProgramBuilder.cpp | 193 +++++++++++++ mlir/lib/Dialect/Flux/CMakeLists.txt | 1 + 4 files changed, 494 insertions(+) create mode 100644 mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h create mode 100644 mlir/lib/Dialect/Flux/Builder/CMakeLists.txt create mode 100644 mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h new file mode 100644 index 0000000000..5f5f03fdb1 --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::flux { + +/** + * @brief Builder API for constructing quantum programs in the Flux dialect + * + * @details + * The FluxProgramBuilder provides a type-safe interface for constructing + * quantum circuits using value semantics. Operations consume input qubit + * SSA values and produce new output values, following the functional + * programming paradigm. + * + * @par Linear Type Enforcement: + * The builder enforces linear type semantics by tracking valid qubit SSA + * values. Once a qubit is consumed by an operation producing a new version + * (e.g., reset, measure), the old SSA value is invalidated. This prevents + * use-after-consume errors and mirrors quantum computing's no-cloning theorem. + * + * @par Example Usage: + * ```c++ + * FluxProgramBuilder builder(context); + * builder.initialize(); + * + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * + * // Operations return updated values + * q0 = builder.h(q0); + * std::tie(q0, q1) = builder.cx(q0, q1); + * + * auto module = builder.finalize(); + * ``` + */ +class FluxProgramBuilder { +public: + /** + * @brief Construct a new FluxProgramBuilder + * @param context The MLIR context to use for building operations + */ + explicit FluxProgramBuilder(MLIRContext* context); + + //===--------------------------------------------------------------------===// + // Initialization + //===--------------------------------------------------------------------===// + + /** + * @brief Initialize the builder and prepare for program construction + * + * @details + * Creates a main function with an entry_point attribute. Must be called + * before adding operations. + */ + void initialize(); + + //===--------------------------------------------------------------------===// + // Memory Management + //===--------------------------------------------------------------------===// + + /** + * @brief Allocate a single qubit initialized to |0⟩ + * @return A tracked, valid qubit SSA value + * + * @par Example: + * ```c++ + * auto q = builder.allocQubit(); + * ``` + * ```mlir + * %q = flux.alloc : !flux.qubit + * ``` + */ + Value allocQubit(); + + /** + * @brief Get a static qubit by index + * @param index The qubit index (must be non-negative) + * @return A tracked, valid qubit SSA value + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * ``` + * ```mlir + * %q0 = flux.static 0 : !flux.qubit + * ``` + */ + Value staticQubit(int64_t index); + + /** + * @brief Allocate a qubit register + * @param size Number of qubits (must be positive) + * @param name Register name (default: "q") + * @return Vector of tracked, valid qubit SSA values + * + * @par Example: + * ```c++ + * auto q = builder.allocQubitRegister(3, "q"); + * ``` + * ```mlir + * %q0 = flux.alloc("q", 3, 0) : !flux.qubit + * %q1 = flux.alloc("q", 3, 1) : !flux.qubit + * %q2 = flux.alloc("q", 3, 2) : !flux.qubit + * ``` + */ + SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); + + /** + * @brief Allocate a classical bit register + * @param size Number of bits + * @param name Register name (default: "c") + * @return Memref of i1 elements + * + * @par Example: + * ```c++ + * auto c = builder.allocClassicalBitRegister(3, "c"); + * ``` + * ```mlir + * %c = memref.alloca() {sym_name = "c"} : memref<3xi1> + * ``` + */ + Value allocClassicalBitRegister(int64_t size, StringRef name = "c"); + + //===--------------------------------------------------------------------===// + // Measurement and Reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measure a qubit in the computational basis + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value + * along with the measurement result (i1). The input is validated and + * tracking is updated to reflect the new output value. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Pair of (output_qubit, measurement_result) + * + * @par Example: + * ```c++ + * auto [q_out, result] = builder.measure(q); + * ``` + * ```mlir + * %q_out, %result = flux.measure %q : !flux.qubit + * ``` + */ + std::pair measure(Value qubit); + + /** + * @brief Measure a qubit and store result in a classical register + * + * @param qubit Input qubit (must be valid/unconsumed) + * @param memref Classical register + * @param index Storage index + * @return Output qubit value + * + * @par Example: + * ```c++ + * q0 = builder.measure(q0, c, 0); + * ``` + * ```mlir + * %q0_out, %r0 = flux.measure %q0 : !flux.qubit + * %c0 = arith.constant 0 : index + * memref.store %r0, %c[%c0] : memref<2xi1> + * ``` + */ + Value measure(Value qubit, Value memref, int64_t index); + + /** + * @brief Reset a qubit to |0⟩ state + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value + * in the |0⟩ state. The input is validated and tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit value + * + * @par Example: + * ```c++ + * q = builder.reset(q); + * ``` + * ```mlir + * %q_out = flux.reset %q : !flux.qubit -> !flux.qubit + * ``` + */ + Value reset(Value qubit); + + /** + * @brief Explicitly deallocate a qubit + * + * @details + * Validates and removes the qubit from tracking. Optional, finalize() + * automatically deallocates all remaining qubits. + * + * @param qubit Qubit to deallocate (must be valid/unconsumed) + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.dealloc(q); + * ``` + * ```mlir + * flux.dealloc %q : !flux.qubit + * ``` + */ + FluxProgramBuilder& dealloc(Value qubit); + + //===--------------------------------------------------------------------===// + // Finalization + //===--------------------------------------------------------------------===// + + /** + * @brief Finalize the program and return the constructed module + * + * @details + * Automatically deallocates all remaining valid qubits, adds a return + * statement, and transfers ownership of the module to the caller. + * The builder should not be used after calling this method. + * + * @return OwningOpRef containing the constructed quantum program module + */ + OwningOpRef finalize(); + +private: + //===--------------------------------------------------------------------===// + // Linear Type Tracking Helpers + //===--------------------------------------------------------------------===// + + /** + * @brief Validate that a qubit value is valid and unconsumed + * @param qubit Qubit value to validate + * @throws Aborts if qubit is not tracked (consumed or never created) + */ + void validateQubitValue(Value qubit) const; + + /** + * @brief Update tracking when an operation consumes and produces a qubit + * @param inputQubit Input qubit being consumed (must be valid) + * @param outputQubit New output qubit being produced + */ + void updateQubitTracking(Value inputQubit, Value outputQubit); + + OpBuilder builder; + ModuleOp module; + Location loc; + + /// Track valid (unconsumed) qubit SSA values for linear type enforcement. + /// Only values present in this set are valid for use in operations. + /// When an operation consumes a qubit and produces a new one, the old value + /// is removed and the new output is added. + llvm::DenseSet validQubits; +}; +} // namespace mlir::flux diff --git a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt b/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt new file mode 100644 index 0000000000..352d0f0da0 --- /dev/null +++ b/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_library( + MLIRFluxProgramBuilder + FluxProgramBuilder.cpp + LINK_LIBS + PUBLIC + MLIRArithDialect + MLIRFuncDialect + MLIRMemRefDialect + MLIRSCFDialect + MLIRFluxDialect) + +# collect header files +file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Flux/Builder/*.h) + +# add public headers using file sets +target_sources( + MLIRFluxProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${TRANSLATION_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp new file mode 100644 index 0000000000..1be8c8d601 --- /dev/null +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::flux { + +FluxProgramBuilder::FluxProgramBuilder(MLIRContext* context) + : builder(context), + module(builder.create(UnknownLoc::get(context))), + loc(UnknownLoc::get(context)) {} + +void FluxProgramBuilder::initialize() { + // Ensure the Flux dialect is loaded + builder.getContext()->loadDialect(); + + // Set insertion point to the module body + builder.setInsertionPointToStart(module.getBody()); + + // Create main function as entry point + auto funcType = builder.getFunctionType({}, {}); + auto mainFunc = builder.create(loc, "main", funcType); + + // Add entry_point attribute to identify the main function + auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); + mainFunc->setAttr("passthrough", + ArrayAttr::get(builder.getContext(), {entryPointAttr})); + + // Create entry block and set insertion point + auto& entryBlock = mainFunc.getBody().emplaceBlock(); + builder.setInsertionPointToStart(&entryBlock); +} + +Value FluxProgramBuilder::allocQubit() { + auto allocOp = builder.create(loc); + const auto qubit = allocOp.getResult(); + + // Track the allocated qubit as valid + validQubits.insert(qubit); + + return qubit; +} + +Value FluxProgramBuilder::staticQubit(int64_t index) { + auto indexAttr = builder.getI64IntegerAttr(index); + auto staticOp = builder.create(loc, indexAttr); + const auto qubit = staticOp.getQubit(); + + // Track the static qubit as valid + validQubits.insert(qubit); + + return qubit; +} + +SmallVector +FluxProgramBuilder::allocQubitRegister(const int64_t size, + const StringRef name) { + SmallVector qubits; + qubits.reserve(static_cast(size)); + + auto nameAttr = builder.getStringAttr(name); + auto sizeAttr = builder.getI64IntegerAttr(size); + + for (int64_t i = 0; i < size; ++i) { + auto indexAttr = builder.getI64IntegerAttr(i); + auto allocOp = builder.create(loc, nameAttr, sizeAttr, indexAttr); + const auto& qubit = qubits.emplace_back(allocOp.getResult()); + // Track the allocated qubit as valid + validQubits.insert(qubit); + } + + return qubits; +} + +Value FluxProgramBuilder::allocClassicalBitRegister(int64_t size, + StringRef name) { + // Create memref type + auto memrefType = MemRefType::get({size}, builder.getI1Type()); + + // Allocate the memref + auto allocOp = builder.create(loc, memrefType); + + allocOp->setAttr("sym_name", builder.getStringAttr(name)); + + return allocOp.getResult(); +} + +//===----------------------------------------------------------------------===// +// Linear Type Tracking Helpers +//===----------------------------------------------------------------------===// + +void FluxProgramBuilder::validateQubitValue(const Value qubit) const { + if (!validQubits.contains(qubit)) { + llvm::errs() << "Error: Attempting to use an invalid qubit SSA value. " + << "The value may have been consumed by a previous operation " + << "or was never created through this builder.\n"; + llvm_unreachable( + "Invalid qubit value used (either consumed or not tracked)"); + } +} + +void FluxProgramBuilder::updateQubitTracking(const Value inputQubit, + const Value outputQubit) { + // Validate the input qubit + validateQubitValue(inputQubit); + + // Remove the input (consumed) value from tracking + validQubits.erase(inputQubit); + + // Add the output (new) value to tracking + validQubits.insert(outputQubit); +} + +//===----------------------------------------------------------------------===// +// Measurement and Reset +//===----------------------------------------------------------------------===// + +std::pair FluxProgramBuilder::measure(Value qubit) { + auto measureOp = builder.create(loc, qubit); + auto qubitOut = measureOp.getQubitOut(); + auto result = measureOp.getResult(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return {qubitOut, result}; +} + +Value FluxProgramBuilder::measure(const Value qubit, Value memref, + int64_t index) { + // Measure the qubit + auto [qubitOut, result] = measure(qubit); + + // Create constant index for the store operation + auto indexValue = builder.create(loc, index); + + // Store the result in the memref at the given index + builder.create(loc, result, memref, + ValueRange{indexValue.getResult()}); + + return qubitOut; +} + +Value FluxProgramBuilder::reset(Value qubit) { + auto resetOp = builder.create(loc, qubit); + const auto qubitOut = resetOp.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + +FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { + validateQubitValue(qubit); + validQubits.erase(qubit); + + builder.create(loc, qubit); + + return *this; +} + +OwningOpRef FluxProgramBuilder::finalize() { + // Automatically deallocate all remaining valid qubits + for (Value qubit : validQubits) { + builder.create(loc, qubit); + } + + validQubits.clear(); + + builder.create(loc); + + return module; +} + +} // namespace mlir::flux diff --git a/mlir/lib/Dialect/Flux/CMakeLists.txt b/mlir/lib/Dialect/Flux/CMakeLists.txt index ace971aa36..77b183713f 100644 --- a/mlir/lib/Dialect/Flux/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/CMakeLists.txt @@ -6,4 +6,5 @@ # # Licensed under the MIT License +add_subdirectory(Builder) add_subdirectory(IR) From 6f0f8f013904ff3ba46055d8f43806552a1ea60c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:50:45 +0200 Subject: [PATCH 048/419] =?UTF-8?q?=E2=9C=A8=20add=20QIR=20program=20build?= =?UTF-8?q?er=20and=20utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 249 ++++++++++++++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 136 ++++++++++ mlir/lib/Dialect/CMakeLists.txt | 3 +- mlir/lib/Dialect/QIR/Builder/CMakeLists.txt | 26 ++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 242 +++++++++++++++++ mlir/lib/Dialect/QIR/CMakeLists.txt | 10 + mlir/lib/Dialect/QIR/Utils/CMakeLists.txt | 15 ++ mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 157 +++++++++++ 8 files changed, 837 insertions(+), 1 deletion(-) create mode 100644 mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h create mode 100644 mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h create mode 100644 mlir/lib/Dialect/QIR/Builder/CMakeLists.txt create mode 100644 mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp create mode 100644 mlir/lib/Dialect/QIR/CMakeLists.txt create mode 100644 mlir/lib/Dialect/QIR/Utils/CMakeLists.txt create mode 100644 mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h new file mode 100644 index 0000000000..871e47c500 --- /dev/null +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qir { + +/** + * @brief Builder API for constructing QIR (Quantum Intermediate + * Representation) programs + * + * @details + * The QIRProgramBuilder provides a type-safe interface for constructing + * quantum programs in QIR format. Like Quartz, QIR uses reference semantics + * where operations modify qubits in place, but QIR programs require specific + * boilerplate structure including proper block organization and metadata + * attributes. + * + * @par QIR Structure: + * QIR programs follow a specific structure with: + * - Entry block: Constants and initialization (__quantum__rt__initialize) + * - Main block: Reversible quantum operations (gates) + * - Irreversible block: Measurements, resets, deallocations + * - End block: Return statement + * + * @par Example Usage: + * ```c++ + * QIRProgramBuilder builder(context); + * builder.initialize(); + * + * auto q0 = builder.staticQubit(0); + * auto q1 = builder.staticQubit(1); + * + * // Operations use QIR function calls + * builder.h(q0).cx(q0, q1); + * auto result = builder.measure(q0, 0); + * + * auto module = builder.finalize(); + * ``` + */ +class QIRProgramBuilder { +public: + /** + * @brief Construct a new QIRProgramBuilder + * @param context The MLIR context to use for building operations + */ + explicit QIRProgramBuilder(MLIRContext* context); + + //===--------------------------------------------------------------------===// + // Initialization + //===--------------------------------------------------------------------===// + + /** + * @brief Initialize the builder and prepare for program construction + * + * @details + * Creates the main function with proper QIR structure (4-block layout), + * adds __quantum__rt__initialize call, and sets up the builder's insertion + * points. Must be called before adding operations. + */ + void initialize(); + + //===--------------------------------------------------------------------===// + // Memory Management + //===--------------------------------------------------------------------===// + + /** + * @brief Allocate a single qubit dynamically + * @return An LLVM pointer representing the qubit + * + * @par Example: + * ```c++ + * auto q = builder.allocQubit(); + * ``` + * ```mlir + * %q = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * ``` + */ + Value allocQubit(); + + /** + * @brief Get a static qubit by index + * @param index The qubit index (must be non-negative) + * @return An LLVM pointer representing the qubit + * + * @par Example: + * ```c++ + * auto q0 = builder.staticQubit(0); + * ``` + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * ``` + */ + Value staticQubit(int64_t index); + + /** + * @brief Allocate a qubit register dynamically + * @param size Number of qubits (must be positive) + * @return Vector of LLVM pointers representing the qubits + * + * @par Example: + * ```c++ + * auto q = builder.allocQubitRegister(3); + * ``` + * ```mlir + * %q0 = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * %q1 = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * %q2 = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * ``` + */ + SmallVector allocQubitRegister(int64_t size); + + //===--------------------------------------------------------------------===// + // Measurement and Reset + //===--------------------------------------------------------------------===// + + /** + * @brief Measure a qubit and record the result + * + * @details + * Performs a Z-basis measurement using __quantum__qis__mz__body and + * records the result using __quantum__rt__result_record_output. The + * result is labeled with the provided index (e.g., "r0", "r1"). + * + * @param qubit The qubit to measure + * @param resultIndex The classical bit index for result labeling + * @return An LLVM pointer to the measurement result + * + * @par Example: + * ```c++ + * auto result = builder.measure(q0, 0); + * ``` + * ```mlir + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %r = llvm.inttoptr %c0 : i64 to !llvm.ptr + * llvm.call @__quantum__qis__mz__body(%q0, %r) : (!llvm.ptr, !llvm.ptr) -> + * () llvm.call @__quantum__rt__result_record_output(%r, %label0) : + * (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + Value measure(Value qubit, size_t resultIndex); + + /** + * @brief Reset a qubit to |0⟩ state + * + * @details + * Resets a qubit using __quantum__qis__reset__body. + * + * @param qubit The qubit to reset + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.reset(q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& reset(Value qubit); + + /** + * @brief Explicitly deallocate a qubit + * + * @details + * Deallocates a qubit using __quantum__rt__qubit_release. Optional - + * finalize() automatically deallocates all remaining allocated qubits. + * + * @param qubit The qubit to deallocate + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.dealloc(q); + * ``` + * ```mlir + * llvm.call @__quantum__rt__qubit_release(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& dealloc(Value qubit); + + //===--------------------------------------------------------------------===// + // Finalization + //===--------------------------------------------------------------------===// + + /** + * @brief Finalize the program and return the constructed module + * + * @details + * Automatically deallocates all remaining allocated qubits, ensures proper + * QIR metadata attributes are set, and transfers ownership of the module to + * the caller. The builder should not be used after calling this method. + * + * @return OwningOpRef containing the constructed QIR program module + */ + OwningOpRef finalize(); + +private: + OpBuilder builder; + ModuleOp module; + LLVM::LLVMFuncOp mainFunc; + Location loc; + + /// Entry block: constants and initialization + Block* entryBlock{}; + /// Main block: reversible operations (gates) + Block* mainBlock{}; + /// Irreversible block: measurements, resets, deallocations + Block* irreversibleBlock{}; + /// End block: return statement + Block* endBlock{}; + + /// Track allocated qubits for automatic deallocation + llvm::DenseSet allocatedQubits; + + /// Cache static qubit pointers for reuse + llvm::DenseMap staticQubitCache; + + /// Cache result pointers for reuse (separate from qubits) + llvm::DenseMap resultPointerCache; + + /// Track result labels to avoid duplicates + llvm::DenseMap resultLabelCache; + + /// Track qubit and result counts for QIR metadata + QIRMetadata metadata_; +}; + +} // namespace mlir::qir diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h new file mode 100644 index 0000000000..abd882e7fc --- /dev/null +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include +#include + +namespace mlir::qir { + +// QIR function name constants +static constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; +static constexpr auto QIR_QUBIT_ALLOCATE = "__quantum__rt__qubit_allocate"; +static constexpr auto QIR_QUBIT_RELEASE = "__quantum__rt__qubit_release"; +static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; +static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; +static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; + +/** + * @brief State object for tracking QIR metadata during conversion + * + * @details + * This struct maintains metadata about the QIR program being built: + * - Qubit and result counts for QIR metadata + * - Whether dynamic memory management is needed + */ +struct QIRMetadata { + /// Number of qubits used in the module + size_t numQubits{0}; + /// Number of measurement results stored in the module + size_t numResults{0}; + /// Whether the module uses dynamic qubit management + bool useDynamicQubit{false}; + /// Whether the module uses dynamic result management + bool useDynamicResult{false}; +}; + +/** + * @brief Find the main LLVM function with entry_point attribute + * + * @details + * Searches for the LLVM function marked with the "entry_point" attribute in + * the passthrough attributes. + * + * @param op The module operation to search in + * @return The main LLVM function, or nullptr if not found + */ +LLVM::LLVMFuncOp getMainFunction(Operation* op); + +/** + * @brief Set QIR base profile metadata attributes on the main function + * + * @details + * Adds the required metadata attributes for QIR base profile compliance: + * - `entry_point`: Marks the main entry point function + * - `output_labeling_schema`: schema_id + * - `qir_profiles`: base_profile + * - `required_num_qubits`: Number of qubits used + * - `required_num_results`: Number of measurement results + * - `qir_major_version`: 1 + * - `qir_minor_version`: 0 + * - `dynamic_qubit_management`: true/false + * - `dynamic_result_management`: true/false + * + * These attributes are required by the QIR specification and inform QIR + * consumers about the module's resource requirements and capabilities. + * + * @param main The main LLVM function to annotate + * @param metadata The QIR metadata containing qubit/result counts + */ +void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata); + +/** + * @brief Get or create a QIR function declaration + * + * @details + * Searches for an existing function declaration in the symbol table. If not + * found, creates a new function declaration at the end of the module. + * + * For QIR functions that are irreversible (measurement, reset, deallocation), + * the "irreversible" attribute is added automatically. + * + * @param builder The builder to use for creating operations + * @param op The operation requesting the function (for context) + * @param fnName The name of the QIR function + * @param fnType The LLVM function type signature + * @return The LLVM function declaration + */ +LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, + Operation* op, StringRef fnName, + Type fnType); + +/** + * @brief Create a global string constant for result labeling + * + * @details + * Creates a global string constant at module level and returns an addressOf + * operation pointing to it. The global is created at the start of the module, + * and the addressOf is inserted at the builder's current insertion point. + * + * @param builder The builder to use for creating operations + * @param op The operation requesting the label (for context/location) + * @param label The label string (e.g., "r0") + * @param symbolPrefix The prefix for the symbol name (default: + * "qir.result_label") + * @return AddressOf operation for the global constant + */ +LLVM::AddressOfOp +createResultLabel(OpBuilder& builder, Operation* op, StringRef label, + StringRef symbolPrefix = "qir.result_label"); + +/** + * @brief Create a pointer value from an integer index + * + * @details + * Creates a constant operation with the given index and converts it to a + * pointer using inttoptr. This is used for static qubit/result references in + * QIR. + * + * @param builder The builder to use for creating operations + * @param loc The location for the operations + * @param index The integer index + * @return The pointer value + */ +Value createPointerFromIndex(OpBuilder& builder, Location loc, int64_t index); + +} // namespace mlir::qir diff --git a/mlir/lib/Dialect/CMakeLists.txt b/mlir/lib/Dialect/CMakeLists.txt index 5cf34f8170..79232ed264 100644 --- a/mlir/lib/Dialect/CMakeLists.txt +++ b/mlir/lib/Dialect/CMakeLists.txt @@ -8,5 +8,6 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) -add_subdirectory(Quartz) add_subdirectory(Flux) +add_subdirectory(QIR) +add_subdirectory(Quartz) diff --git a/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt b/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt new file mode 100644 index 0000000000..aab1a2caad --- /dev/null +++ b/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_library( + MLIRQIRProgramBuilder + QIRProgramBuilder.cpp + LINK_LIBS + PUBLIC + MLIRLLVMDialect + MLIRQIRUtils + MLIRIR + MLIRSupport) + +# collect header files +file(GLOB_RECURSE QIR_BUILDER_HEADERS_SOURCE + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QIR/Builder/*.h) + +# add public headers using file sets +target_sources( + MLIRQIRProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${QIR_BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp new file mode 100644 index 0000000000..564abbfa58 --- /dev/null +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" + +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qir { + +QIRProgramBuilder::QIRProgramBuilder(MLIRContext* context) + : builder(context), + module(builder.create(UnknownLoc::get(context))), + loc(UnknownLoc::get(context)) {} + +void QIRProgramBuilder::initialize() { + // Ensure LLVM dialect is loaded + builder.getContext()->loadDialect(); + + // Set insertion point to the module body + builder.setInsertionPointToStart(module.getBody()); + + // Create main function: () -> void + auto funcType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {}); + mainFunc = builder.create(loc, "main", funcType); + + // Create the 4-block structure for QIR base profile + entryBlock = mainFunc.addEntryBlock(builder); + mainBlock = mainFunc.addBlock(); + irreversibleBlock = mainFunc.addBlock(); + endBlock = mainFunc.addBlock(); + + // Add unconditional branches between blocks + builder.setInsertionPointToEnd(entryBlock); + builder.create(loc, mainBlock); + + builder.setInsertionPointToEnd(mainBlock); + builder.create(loc, irreversibleBlock); + + builder.setInsertionPointToEnd(irreversibleBlock); + builder.create(loc, endBlock); + + builder.setInsertionPointToEnd(endBlock); + builder.create(loc, ValueRange{}); + + // Add QIR initialization call in entry block + builder.setInsertionPointToStart(entryBlock); + auto zeroOp = builder.create( + loc, LLVM::LLVMPointerType::get(builder.getContext())); + + // Move to before the branch + builder.setInsertionPoint(entryBlock->getTerminator()); + + const auto initType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto initFunc = getOrCreateFunctionDeclaration(builder, module.getOperation(), + QIR_INITIALIZE, initType); + builder.create(loc, initFunc, ValueRange{zeroOp.getResult()}); + + // Set insertion point to main block for user operations + builder.setInsertionPointToStart(mainBlock); +} + +Value QIRProgramBuilder::allocQubit() { + // Create function signature: () -> ptr + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMPointerType::get(builder.getContext()), {}); + + auto fnDecl = getOrCreateFunctionDeclaration( + builder, module.getOperation(), QIR_QUBIT_ALLOCATE, qirSignature); + + // Call qubit_allocate + auto callOp = builder.create(loc, fnDecl, ValueRange{}); + const auto qubit = callOp.getResult(); + + // Track for automatic deallocation + allocatedQubits.insert(qubit); + + // Update counts + metadata_.numQubits++; + metadata_.useDynamicQubit = true; + + return qubit; +} + +Value QIRProgramBuilder::staticQubit(const int64_t index) { + // Check cache + if (staticQubitCache.contains(static_cast(index))) { + return staticQubitCache.at(static_cast(index)); + } + + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert at start of entry block (after initialize but before branch) + builder.setInsertionPoint(entryBlock->getTerminator()); + + // Use common utility function to create pointer from index + const auto qubit = createPointerFromIndex(builder, loc, index); + + // Cache for reuse + staticQubitCache[static_cast(index)] = qubit; + + // Update qubit count + if (static_cast(index) >= metadata_.numQubits) { + metadata_.numQubits = static_cast(index) + 1; + } + + return qubit; +} + +SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { + SmallVector qubits; + qubits.reserve(static_cast(size)); + + for (int64_t i = 0; i < size; ++i) { + qubits.push_back(allocQubit()); + } + + return qubits; +} + +Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in irreversible block (before branch) + builder.setInsertionPoint(irreversibleBlock->getTerminator()); + + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + + // Get or create result pointer (separate from qubit pointers) + Value resultValue = nullptr; + if (resultPointerCache.contains(resultIndex)) { + resultValue = resultPointerCache.at(resultIndex); + } else { + // Create at start of entry block using common utility + builder.setInsertionPoint(entryBlock->getTerminator()); + resultValue = + createPointerFromIndex(builder, loc, static_cast(resultIndex)); + resultPointerCache[resultIndex] = resultValue; + + // Restore to irreversible block + builder.setInsertionPoint(irreversibleBlock->getTerminator()); + } + + // Create mz call + const auto mzSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); + auto mzDecl = getOrCreateFunctionDeclaration(builder, module.getOperation(), + QIR_MEASURE, mzSignature); + builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); + + // Get or create result label + LLVM::AddressOfOp labelOp; + if (resultLabelCache.contains(resultIndex)) { + labelOp = resultLabelCache.at(resultIndex); + } else { + // Use common utility function to create result label + labelOp = createResultLabel(builder, module.getOperation(), + "r" + std::to_string(resultIndex)); + resultLabelCache.try_emplace(resultIndex, labelOp); + metadata_.numResults++; + } + + // Create record_output call + const auto recordSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); + auto recordDecl = getOrCreateFunctionDeclaration( + builder, module.getOperation(), QIR_RECORD_OUTPUT, recordSignature); + builder.create(loc, recordDecl, + ValueRange{resultValue, labelOp.getResult()}); + + return resultValue; +} + +QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in irreversible block (before branch) + builder.setInsertionPoint(irreversibleBlock->getTerminator()); + + // Create reset call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto fnDecl = getOrCreateFunctionDeclaration(builder, module.getOperation(), + QIR_RESET, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); + + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { + allocatedQubits.erase(qubit); + + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in irreversible block (before branch) + builder.setInsertionPoint(irreversibleBlock->getTerminator()); + + // Create release call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto fnDecl = getOrCreateFunctionDeclaration(builder, module.getOperation(), + QIR_QUBIT_RELEASE, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); + + return *this; +} + +OwningOpRef QIRProgramBuilder::finalize() { + for (const Value qubit : allocatedQubits) { + dealloc(qubit); + } + allocatedQubits.clear(); + + setQIRAttributes(mainFunc, metadata_); + + return module; +} + +} // namespace mlir::qir diff --git a/mlir/lib/Dialect/QIR/CMakeLists.txt b/mlir/lib/Dialect/QIR/CMakeLists.txt new file mode 100644 index 0000000000..319b90b05a --- /dev/null +++ b/mlir/lib/Dialect/QIR/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(Utils) +add_subdirectory(Builder) diff --git a/mlir/lib/Dialect/QIR/Utils/CMakeLists.txt b/mlir/lib/Dialect/QIR/Utils/CMakeLists.txt new file mode 100644 index 0000000000..4280d86d1e --- /dev/null +++ b/mlir/lib/Dialect/QIR/Utils/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB QIR_UTILS_SOURCES *.cpp) + +add_mlir_library(MLIRQIRUtils ${QIR_UTILS_SOURCES} LINK_LIBS PUBLIC MLIRLLVMDialect MLIRIR) + +target_include_directories( + MLIRQIRUtils PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QIR/Utils/QIRUtils.h) diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp new file mode 100644 index 0000000000..d87792d222 --- /dev/null +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qir { + +LLVM::LLVMFuncOp getMainFunction(Operation* op) { + auto module = dyn_cast(op); + if (!module) { + return nullptr; + } + + // Search for function with entry_point attribute + for (const auto funcOp : module.getOps()) { + auto passthrough = funcOp->getAttrOfType("passthrough"); + if (!passthrough) { + continue; + } + if (llvm::any_of(passthrough, [](Attribute attr) { + const auto strAttr = dyn_cast(attr); + return strAttr && strAttr.getValue() == "entry_point"; + })) { + return funcOp; + } + } + return nullptr; +} + +void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata) { + OpBuilder builder(main.getBody()); + SmallVector attributes; + + // Core QIR attributes + attributes.emplace_back(builder.getStringAttr("entry_point")); + attributes.emplace_back( + builder.getStrArrayAttr({"output_labeling_schema", "schema_id"})); + attributes.emplace_back( + builder.getStrArrayAttr({"qir_profiles", "base_profile"})); + + // Resource requirements + attributes.emplace_back(builder.getStrArrayAttr( + {"required_num_qubits", std::to_string(metadata.numQubits)})); + attributes.emplace_back(builder.getStrArrayAttr( + {"required_num_results", std::to_string(metadata.numResults)})); + + // QIR version + attributes.emplace_back(builder.getStrArrayAttr({"qir_major_version", "1"})); + attributes.emplace_back(builder.getStrArrayAttr({"qir_minor_version", "0"})); + + // Management model + attributes.emplace_back( + builder.getStrArrayAttr({"dynamic_qubit_management", + metadata.useDynamicQubit ? "true" : "false"})); + attributes.emplace_back( + builder.getStrArrayAttr({"dynamic_result_management", + metadata.useDynamicResult ? "true" : "false"})); + + main->setAttr("passthrough", builder.getArrayAttr(attributes)); +} + +LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, + Operation* op, StringRef fnName, + Type fnType) { + // Check if the function already exists + auto* fnDecl = + SymbolTable::lookupNearestSymbolFrom(op, builder.getStringAttr(fnName)); + + if (fnDecl == nullptr) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Create the declaration at the end of the module + if (auto module = dyn_cast(op)) { + builder.setInsertionPointToEnd(module.getBody()); + } else { + module = op->getParentOfType(); + builder.setInsertionPointToEnd(module.getBody()); + } + + fnDecl = builder.create(op->getLoc(), fnName, fnType); + + // Add irreversible attribute to irreversible quantum operations + if (fnName == QIR_MEASURE || fnName == QIR_QUBIT_RELEASE || + fnName == QIR_RESET) { + fnDecl->setAttr("passthrough", builder.getStrArrayAttr({"irreversible"})); + } + } + + return cast(fnDecl); +} + +LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, + const StringRef label, + const StringRef symbolPrefix) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Create the declaration at the start of the module + if (auto module = dyn_cast(op)) { + builder.setInsertionPointToStart(module.getBody()); + } else { + module = op->getParentOfType(); + builder.setInsertionPointToStart(module.getBody()); + } + + const auto symbolName = + builder.getStringAttr((symbolPrefix + "_" + label).str()); + const auto llvmArrayType = LLVM::LLVMArrayType::get( + builder.getIntegerType(8), static_cast(label.size() + 1)); + const auto stringInitializer = builder.getStringAttr(label.str() + '\0'); + + const auto globalOp = builder.create( + op->getLoc(), llvmArrayType, /*isConstant=*/true, LLVM::Linkage::Internal, + symbolName, stringInitializer); + globalOp->setAttr("addr_space", builder.getI32IntegerAttr(0)); + globalOp->setAttr("dso_local", builder.getUnitAttr()); + + // Create addressOf operation + // Shall be added to the first block of the `main` function in the module + auto main = getMainFunction(op); + auto& firstBlock = *(main.getBlocks().begin()); + builder.setInsertionPointToStart(&firstBlock); + + const auto addressOfOp = builder.create( + op->getLoc(), LLVM::LLVMPointerType::get(builder.getContext()), + symbolName); + + return addressOfOp; +} + +Value createPointerFromIndex(OpBuilder& builder, const Location loc, + const int64_t index) { + auto constantOp = + builder.create(loc, builder.getI64IntegerAttr(index)); + auto intToPtrOp = builder.create( + loc, LLVM::LLVMPointerType::get(builder.getContext()), + constantOp.getResult()); + return intToPtrOp.getResult(); +} + +} // namespace mlir::qir From 2ecc93374f4f0effedabfaa65c4d8a8631b29abe Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:51:41 +0200 Subject: [PATCH 049/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20QIRUtils=20i?= =?UTF-8?q?n=20qir=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../lib/Conversion/QuartzToQIR/CMakeLists.txt | 3 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 249 ++---------------- 2 files changed, 21 insertions(+), 231 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt b/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt index 3442693da8..78b2929f91 100644 --- a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt +++ b/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt @@ -15,8 +15,9 @@ add_mlir_library( QuartzToQIRIncGen LINK_LIBS PUBLIC + MLIRQIRUtils MLIRLLVMDialect - MLIRFluxDialect + MLIRQuartzDialect MLIRArithDialect MLIRFuncDialect MLIRFuncToLLVM diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 0bafde365f..bf4dc409d5 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -10,6 +10,7 @@ #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" +#include "mlir/Dialect/QIR/Utils/QIRUtils.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include @@ -45,55 +46,13 @@ namespace mlir { using namespace quartz; +using namespace qir; #define GEN_PASS_DEF_QUARTZTOQIR #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" namespace { -/** - * @brief Looks up or creates a QIR function declaration - * - * @details - * Searches for an existing function declaration in the symbol table. If not - * found, creates a new function declaration at the end of the module. - * - * For QIR functions that are irreversible (measurement, reset, deallocation), - * the "irreversible" attribute is added automatically. - * - * @param rewriter The pattern rewriter to use - * @param op The operation requesting the function - * @param fnName The name of the QIR function - * @param fnType The LLVM function type signature - * @return The LLVM function declaration - */ -LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, - Operation* op, StringRef fnName, - Type fnType) { - // Check if the function already exists - auto* fnDecl = - SymbolTable::lookupNearestSymbolFrom(op, rewriter.getStringAttr(fnName)); - - if (fnDecl == nullptr) { - // Create the declaration at the end of the module - const PatternRewriter::InsertionGuard insertGuard(rewriter); - auto module = op->getParentOfType(); - rewriter.setInsertionPointToEnd(module.getBody()); - - fnDecl = rewriter.create(op->getLoc(), fnName, fnType); - - // Add irreversible attribute to irreversible quantum operations - if (fnName == "__quantum__qis__mz__body" || - fnName == "__quantum__rt__qubit_release" || - fnName == "__quantum__qis__reset__body") { - fnDecl->setAttr("passthrough", - rewriter.getStrArrayAttr({"irreversible"})); - } - } - - return cast(fnDecl); -} - /** * @brief State object for tracking lowering information during QIR conversion * @@ -105,23 +64,13 @@ LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, * - Result output mapping * - Whether dynamic memory management is needed */ -struct LoweringState { +struct LoweringState : QIRMetadata { /// Map from qubit index to pointer value for reuse DenseMap ptrMap; /// Map from result index to addressOf operation for output recording DenseMap outputMap; /// Index for the next measure operation label size_t index{}; - /// Number of qubits used in the module - size_t numQubits{}; - /// Number of measurement results stored in the module - size_t numResults{}; - /// Whether the module uses dynamic qubit management (true when `quartz.alloc` - /// is used, false when only `quartz.static` is used) - bool useDynamicQubit{}; - /// Whether the module uses dynamic result management (expected: false for - /// Quartz at the moment) - bool useDynamicResult{}; }; /** @@ -244,9 +193,6 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; - static constexpr StringLiteral FN_NAME_ALLOCATE = - "__quantum__rt__qubit_allocate"; - LogicalResult matchAndRewrite(AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { @@ -257,8 +203,8 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { LLVM::LLVMFunctionType::get(LLVM::LLVMPointerType::get(ctx), {}); // Get or create function declaration - const auto fnDecl = - getFunctionDeclaration(rewriter, op, FN_NAME_ALLOCATE, qirSignature); + const auto fnDecl = getOrCreateFunctionDeclaration( + rewriter, op, QIR_QUBIT_ALLOCATE, qirSignature); // Replace with call to qubit_allocate rewriter.replaceOpWithNewOp(op, fnDecl, ValueRange{}); @@ -291,9 +237,6 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { struct ConvertQuartzDeallocQIR final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; - static constexpr StringLiteral FN_NAME_QUBIT_RELEASE = - "__quantum__rt__qubit_release"; - LogicalResult matchAndRewrite(DeallocOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { @@ -304,8 +247,8 @@ struct ConvertQuartzDeallocQIR final : OpConversionPattern { LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); // Get or create function declaration - const auto fnDecl = getFunctionDeclaration( - rewriter, op, FN_NAME_QUBIT_RELEASE, qirSignature); + const auto fnDecl = getOrCreateFunctionDeclaration( + rewriter, op, QIR_QUBIT_RELEASE, qirSignature); // Replace with call to qubit_release rewriter.replaceOpWithNewOp(op, fnDecl, @@ -333,8 +276,6 @@ struct ConvertQuartzDeallocQIR final : OpConversionPattern { struct ConvertQuartzResetQIR final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; - static constexpr StringLiteral FN_NAME_RESET = "__quantum__qis__reset__body"; - LogicalResult matchAndRewrite(ResetOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { @@ -346,7 +287,7 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { // Get or create function declaration const auto fnDecl = - getFunctionDeclaration(rewriter, op, FN_NAME_RESET, qirSignature); + getOrCreateFunctionDeclaration(rewriter, op, QIR_RESET, qirSignature); // Replace with call to reset rewriter.replaceOpWithNewOp(op, fnDecl, @@ -392,67 +333,6 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; - static constexpr StringLiteral FN_NAME_MEASURE = "__quantum__qis__mz__body"; - static constexpr StringLiteral FN_NAME_RECORD_OUTPUT = - "__quantum__rt__result_record_output"; - - /** - * @brief Creates a global constant and addressOf for result labeling - * - * @details - * Creates a global string constant at module level with the label "rN\0" - * where N is the index, and returns an addressOf operation pointing to it. - * The addressOf is placed at the start of the main function. - * - * @param op The measure operation - * @param rewriter The rewriter - * @param state The lowering state - * @return The addressOf operation for the global constant - */ - static Operation* getAddressOfOp(Operation* op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { - // Create global at module level - auto module = op->getParentOfType(); - rewriter.setInsertionPointToStart(module.getBody()); - - // Calculate string length for array size - auto num = state.index; - auto digits = 1; - while (num >= 10) { - num /= 10; - ++digits; - } - - // Create global with label "r0\0", "r1\0", etc. - const auto symbolName = rewriter.getStringAttr( - "mlir.llvm.nameless_global_" + std::to_string(state.index)); - const auto llvmArrayType = - LLVM::LLVMArrayType::get(rewriter.getIntegerType(8), digits + 2); - const auto stringInitializer = - rewriter.getStringAttr("r" + std::to_string(state.index) + '\0'); - - auto globalOp = rewriter.create( - op->getLoc(), llvmArrayType, /*isConstant=*/true, - LLVM::Linkage::Internal, symbolName, stringInitializer); - globalOp->setAttr("addr_space", rewriter.getI32IntegerAttr(0)); - globalOp->setAttr("dso_local", rewriter.getUnitAttr()); - - // Create addressOf at start of main function - auto main = op->getParentOfType(); - auto& firstBlock = *(main.getBlocks().begin()); - rewriter.setInsertionPointToStart(&firstBlock); - const auto addressOfOp = rewriter.create( - op->getLoc(), LLVM::LLVMPointerType::get(rewriter.getContext()), - symbolName); - - // Restore insertion point - rewriter.setInsertionPoint(op); - state.index++; - - return addressOfOp; - } - LogicalResult matchAndRewrite(MeasureOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { @@ -466,7 +346,7 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { if (ptrMap.contains(getState().numResults)) { resultValue = ptrMap.at(getState().numResults); } else { - auto constantOp = rewriter.create( + const auto constantOp = rewriter.create( op.getLoc(), rewriter.getI64IntegerAttr( static_cast(getState().numResults))); resultValue = rewriter @@ -480,7 +360,7 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { const auto mzSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); const auto mzDecl = - getFunctionDeclaration(rewriter, op, FN_NAME_MEASURE, mzSignature); + getOrCreateFunctionDeclaration(rewriter, op, QIR_MEASURE, mzSignature); rewriter.create(op.getLoc(), mzDecl, ValueRange{adaptor.getQubit(), resultValue}); @@ -497,7 +377,7 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { return failure(); } - auto integerAttr = dyn_cast(constantOp.getValue()); + const auto integerAttr = dyn_cast(constantOp.getValue()); if (!integerAttr) { op.emitError("Measurement index could not be resolved. Index cannot " "be cast to IntegerAttr."); @@ -527,8 +407,8 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { // Create result_record_output call const auto recordSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); - const auto recordDecl = getFunctionDeclaration( - rewriter, op, FN_NAME_RECORD_OUTPUT, recordSignature); + const auto recordDecl = getOrCreateFunctionDeclaration( + rewriter, op, QIR_RECORD_OUTPUT, recordSignature); // Get or create output label addressOf for this index Operation* labelOp = nullptr; @@ -538,7 +418,8 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { labelOp = outputMap.at(index); } else { // Create new label - labelOp = getAddressOfOp(op, rewriter, getState()); + labelOp = createResultLabel(rewriter, op, + "r" + std::to_string(getState().index)); outputMap.try_emplace(index, labelOp); // Only increment result count for new labels getState().numResults++; @@ -581,43 +462,6 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { struct QuartzToQIR final : impl::QuartzToQIRBase { using QuartzToQIRBase::QuartzToQIRBase; - /// QIR runtime initialization function name - static constexpr StringLiteral FN_NAME_INITIALIZE = - "__quantum__rt__initialize"; - - /** - * @brief Finds the main entry point function in the module - * - * @details - * Searches for the LLVM function marked with the "entry_point" attribute in - * the passthrough attributes. This is the main entry point created by the - * QuartzProgramBuilder. - * - * @param op The module operation to search in - * @return The main LLVM function, or nullptr if not found - */ - static LLVM::LLVMFuncOp getMainFunction(Operation* op) { - auto module = dyn_cast(op); - if (!module) { - return nullptr; - } - - // Search for function with entry_point attribute - for (auto funcOp : module.getOps()) { - auto passthrough = funcOp->getAttrOfType("passthrough"); - if (!passthrough) { - continue; - } - if (llvm::any_of(passthrough, [](Attribute attr) { - auto strAttr = dyn_cast(attr); - return strAttr && strAttr.getValue() == "entry_point"; - })) { - return funcOp; - } - } - return nullptr; - } - /** * @brief Ensures proper block structure for QIR base profile * @@ -722,14 +566,14 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { // Get or create the initialize function declaration auto* fnDecl = SymbolTable::lookupNearestSymbolFrom( - main, builder.getStringAttr(FN_NAME_INITIALIZE)); + main, builder.getStringAttr(QIR_INITIALIZE)); if (fnDecl == nullptr) { const PatternRewriter::InsertionGuard insertGuard(builder); builder.setInsertionPointToEnd(moduleOp.getBody()); auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); - fnDecl = builder.create( - main->getLoc(), FN_NAME_INITIALIZE, fnSignature); + fnDecl = builder.create(main->getLoc(), QIR_INITIALIZE, + fnSignature); } // Create the initialization call @@ -737,61 +581,6 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { ValueRange{zeroOp->getResult(0)}); } - /** - * @brief Sets QIR base profile metadata attributes on the main function - * - * @details - * Adds the required metadata attributes for QIR base profile compliance: - * - `entry_point`: Marks the main entry point function - * - `output_labeling_schema`: schema_id - * - `qir_profiles`: base_profile - * - `required_num_qubits`: Number of qubits used - * - `required_num_results`: Number of measurement results - * - `qir_major_version`: 1 - * - `qir_minor_version`: 0 - * - `dynamic_qubit_management`: true/false - * - `dynamic_result_management`: true/false - * - * These attributes are required by the QIR specification and inform QIR - * consumers about the module's resource requirements and capabilities. - * - * @param main The main LLVM function to annotate - * @param state The lowering state containing qubit/result counts - */ - static void setAttributes(LLVM::LLVMFuncOp& main, LoweringState* state) { - OpBuilder builder(main.getBody()); - SmallVector attributes; - - // Core QIR attributes - attributes.emplace_back(builder.getStringAttr("entry_point")); - attributes.emplace_back( - builder.getStrArrayAttr({"output_labeling_schema", "schema_id"})); - attributes.emplace_back( - builder.getStrArrayAttr({"qir_profiles", "base_profile"})); - - // Resource requirements - attributes.emplace_back(builder.getStrArrayAttr( - {"required_num_qubits", std::to_string(state->numQubits)})); - attributes.emplace_back(builder.getStrArrayAttr( - {"required_num_results", std::to_string(state->numResults)})); - - // QIR version - attributes.emplace_back( - builder.getStrArrayAttr({"qir_major_version", "1"})); - attributes.emplace_back( - builder.getStrArrayAttr({"qir_minor_version", "0"})); - - // Management model - attributes.emplace_back( - builder.getStrArrayAttr({"dynamic_qubit_management", - state->useDynamicQubit ? "true" : "false"})); - attributes.emplace_back( - builder.getStrArrayAttr({"dynamic_result_management", - state->useDynamicResult ? "true" : "false"})); - - main->setAttr("passthrough", builder.getArrayAttr(attributes)); - } - /** * @brief Executes the Quartz to QIR conversion pass * @@ -877,7 +666,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { } // Stage 4: Set QIR metadata attributes - setAttributes(main, &state); + setQIRAttributes(main, state); // Stage 5: Convert standard dialects to LLVM { From 7d51e7233131a2e09bf6157a38aa4e51f2ada1d4 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:52:09 +0200 Subject: [PATCH 050/419] =?UTF-8?q?=F0=9F=A9=B9=20relax=20operation=20comp?= =?UTF-8?q?arison=20conditions=20for=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Support/TestUtils.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Support/TestUtils.cpp b/mlir/lib/Support/TestUtils.cpp index 87f95df6b3..1fc6e48d17 100644 --- a/mlir/lib/Support/TestUtils.cpp +++ b/mlir/lib/Support/TestUtils.cpp @@ -10,6 +10,7 @@ #include "mlir/Support/TestUtils.h" +#include #include #include @@ -41,9 +42,11 @@ bool operationsAreEquivalent(Operation* lhs, Operation* rhs) { return false; } - // Check attributes - if (lhs->getAttrs() != rhs->getAttrs()) { - return false; + // Check attributes (skip for LLVMFuncOp) + if (!llvm::isa(lhs) && !llvm::isa(rhs)) { + if (lhs->getAttrs() != rhs->getAttrs()) { + return false; + } } // Check number of operands and results From ea51fa6a9acdc1ec5bdcdd031235d38f6d8e397f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 13:52:40 +0200 Subject: [PATCH 051/419] =?UTF-8?q?=E2=9C=85=20rework=20end-to-end=20testi?= =?UTF-8?q?ng=20infrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/unittests/pipeline/CMakeLists.txt | 10 +- .../pipeline/test_compiler_pipeline.cpp | 1174 +++++++++-------- 2 files changed, 636 insertions(+), 548 deletions(-) diff --git a/mlir/unittests/pipeline/CMakeLists.txt b/mlir/unittests/pipeline/CMakeLists.txt index 48a837a691..0362fc8751 100644 --- a/mlir/unittests/pipeline/CMakeLists.txt +++ b/mlir/unittests/pipeline/CMakeLists.txt @@ -10,7 +10,13 @@ add_executable(mqt-core-mlir-compiler-pipeline-test test_compiler_pipeline.cpp) target_link_libraries( mqt-core-mlir-compiler-pipeline-test - PRIVATE GTest::gtest_main MQTCompilerPipeline MLIRQuartzTranslation MLIRQuartzProgramBuilder - MLIRParser MQT::CoreIR) + PRIVATE GTest::gtest_main + MQTCompilerPipeline + MLIRQuartzTranslation + MLIRQuartzProgramBuilder + MLIRFluxProgramBuilder + MLIRQIRProgramBuilder + MLIRParser + MQT::CoreIR) gtest_discover_tests(mqt-core-mlir-compiler-pipeline-test) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index b921d463ae..ebb525702a 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -10,7 +10,9 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" #include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" @@ -30,12 +32,105 @@ #include #include #include +#include #include namespace { using namespace mlir; +//===----------------------------------------------------------------------===// +// Stage Verification Utility +//===----------------------------------------------------------------------===// + +/** + * @brief Helper to verify a compilation stage matches expected IR + * + * @details + * Provides detailed error messages including actual and expected IR when + * verification fails. + */ +class StageVerifier { +public: + explicit StageVerifier(MLIRContext* ctx) : context(ctx) {} + + /** + * @brief Verify a stage matches expected module + * + * @param stageName Human-readable stage name for error messages + * @param actualIR String IR from CompilationRecord + * @param expectedModule Expected module to compare against + * @return True if modules match, false otherwise with diagnostic output + */ + [[nodiscard]] ::testing::AssertionResult + verify(const std::string& stageName, const std::string& actualIR, + ModuleOp expectedModule) const { + // Parse actual IR + const auto actualModule = + parseSourceString(actualIR, ParserConfig(context)); + if (!actualModule) { + return ::testing::AssertionFailure() + << "Failed to parse " << stageName << " IR\n" + << "Raw IR string:\n" + << actualIR; + } + + // Compare modules + if (!modulesAreEquivalent(actualModule.get(), expectedModule)) { + std::ostringstream msg; + msg << stageName << " IR does not match expected structure\n\n"; + + msg << "=== EXPECTED IR ===\n"; + std::string expectedStr; + llvm::raw_string_ostream expectedStream(expectedStr); + expectedModule.print(expectedStream); + msg << expectedStr << "\n\n"; + + msg << "=== ACTUAL IR ===\n"; + msg << actualIR << "\n\n"; + + msg << "=== DIFFERENCE ===\n"; + msg << "Modules are structurally different. "; + msg << "Check operation types, attributes, and structure.\n"; + + return ::testing::AssertionFailure() << msg.str(); + } + + return ::testing::AssertionSuccess(); + } + +private: + MLIRContext* context; +}; + +//===----------------------------------------------------------------------===// +// Stage Expectation Builder +//===----------------------------------------------------------------------===// + +/** + * @brief Helper to build expected IR for multiple stages at once + * + * @details + * Reduces boilerplate by allowing specification of which stages should + * match which expected IR. + */ +struct StageExpectations { + ModuleOp quartzImport; + ModuleOp initialCanon; + ModuleOp fluxConversion; + ModuleOp fluxCanon; + ModuleOp optimization; + ModuleOp optimizationCanon; + ModuleOp quartzConversion; + ModuleOp quartzCanon; + ModuleOp qirConversion; + ModuleOp qirCanon; +}; + +//===----------------------------------------------------------------------===// +// Base Test Fixture +//===----------------------------------------------------------------------===// + /** * @brief Base test fixture for end-to-end compiler pipeline tests * @@ -47,30 +142,43 @@ using namespace mlir; class CompilerPipelineTest : public testing::Test { protected: std::unique_ptr context; + std::unique_ptr verifier; QuantumCompilerConfig config; + CompilationRecord record; + + OwningOpRef emptyQuartz; + OwningOpRef emptyFlux; + OwningOpRef emptyQIR; void SetUp() override { // Register all dialects needed for the full compilation pipeline DialectRegistry registry; - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); - registry.insert(); + registry + .insert(); context = std::make_unique(); context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); - // Enable QIR conversion by default + verifier = std::make_unique(context.get()); + + // Enable QIR conversion and recording by default config.convertToQIR = true; config.recordIntermediates = true; - config.printIRAfterAllStages = true; + config.printIRAfterAllStages = + false; /// TODO: Change back after everything is running + + emptyQuartz = buildQuartzIR([](quartz::QuartzProgramBuilder&) {}); + emptyFlux = buildFluxIR([](flux::FluxProgramBuilder&) {}); + emptyQIR = buildQIR([](qir::QIRProgramBuilder&) {}); } + //===--------------------------------------------------------------------===// + // Quantum Circuit Construction and Import + //===--------------------------------------------------------------------===// + /** * @brief Pretty print quantum computation with ASCII art borders * @@ -114,7 +222,7 @@ class CompilerPipelineTest : public testing::Test { /** * @brief Import a QuantumComputation into Quartz dialect */ - OwningOpRef + [[nodiscard]] OwningOpRef importQuantumCircuit(const qc::QuantumComputation& qc) const { if (config.printIRAfterAllStages) { prettyPrintQuantumComputation(qc); @@ -123,92 +231,170 @@ class CompilerPipelineTest : public testing::Test { } /** - * @brief Run the compiler pipeline with specified configuration + * @brief Run the compiler pipeline with the current configuration */ - static LogicalResult runCompiler(ModuleOp module, - const QuantumCompilerConfig& config, - CompilationRecord* record = nullptr) { + [[nodiscard]] LogicalResult runPipeline(const ModuleOp module) { const QuantumCompilerPipeline pipeline(config); - return pipeline.runPipeline(module, record); + return pipeline.runPipeline(module, &record); } - /** - * @brief Clone a module for comparison purposes - * - * @details - * Creates a deep copy of the module so we can compare it later - * without worrying about in-place mutations. - */ - static OwningOpRef cloneModule(ModuleOp module) { - return module.clone(); - } + //===--------------------------------------------------------------------===// + // Expected IR Builder Methods + //===--------------------------------------------------------------------===// /** - * @brief Parse IR string back into a module - * - * @details - * Useful for reconstructing modules from CompilationRecord strings. + * @brief Build expected Quartz IR programmatically */ - OwningOpRef parseModule(const std::string& irString) const { - return mlir::parseSourceString(irString, - ParserConfig(context.get())); + [[nodiscard]] OwningOpRef buildQuartzIR( + const std::function& buildFunc) + const { + quartz::QuartzProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + return builder.finalize(); } /** - * @brief Check if IR contains a specific pattern (for quick checks) + * @brief Build expected Flux IR programmatically */ - static bool irContains(const std::string& ir, const std::string& pattern) { - return ir.find(pattern) != std::string::npos; + [[nodiscard]] OwningOpRef buildFluxIR( + const std::function& buildFunc) const { + flux::FluxProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + return builder.finalize(); } /** - * @brief Build expected Quartz IR programmatically for comparison + * @brief Build expected QIR programmatically */ - OwningOpRef buildExpectedQuartzIR( - const std::function& buildFunc) - const { - quartz::QuartzProgramBuilder builder(context.get()); + [[nodiscard]] OwningOpRef buildQIR( + const std::function& buildFunc) const { + qir::QIRProgramBuilder builder(context.get()); builder.initialize(); buildFunc(builder); return builder.finalize(); } + //===--------------------------------------------------------------------===// + // Stage Verification Methods + //===--------------------------------------------------------------------===// + /** - * @brief Apply canonicalization to a module (for building expected IR) + * @brief Verify all stages match their expected IR + * + * @details + * Simplifies test writing by checking all stages with one call. + * Stages without expectations are skipped. */ - LogicalResult applyCanonicalization(ModuleOp module) const { - PassManager pm(context.get()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createRemoveDeadValuesPass()); - return pm.run(module); + void verifyAllStages(const StageExpectations& expectations) const { + if (expectations.quartzImport) { + EXPECT_TRUE(verifier->verify("Quartz Import", record.afterQuartzImport, + expectations.quartzImport)); + } + + if (expectations.initialCanon) { + EXPECT_TRUE(verifier->verify("Initial Canonicalization", + record.afterInitialCanon, + expectations.initialCanon)); + } + + if (expectations.fluxConversion) { + EXPECT_TRUE(verifier->verify("Flux Conversion", + record.afterFluxConversion, + expectations.fluxConversion)); + } + + if (expectations.fluxCanon) { + EXPECT_TRUE(verifier->verify("Flux Canonicalization", + record.afterFluxCanon, + expectations.fluxCanon)); + } + + if (expectations.optimization) { + EXPECT_TRUE(verifier->verify("Optimization", record.afterOptimization, + expectations.optimization)); + } + + if (expectations.optimizationCanon) { + EXPECT_TRUE(verifier->verify("Optimization Canonicalization", + record.afterOptimizationCanon, + expectations.optimizationCanon)); + } + + if (expectations.quartzConversion) { + EXPECT_TRUE(verifier->verify("Quartz Conversion", + record.afterQuartzConversion, + expectations.quartzConversion)); + } + + if (expectations.quartzCanon) { + EXPECT_TRUE(verifier->verify("Quartz Canonicalization", + record.afterQuartzCanon, + expectations.quartzCanon)); + } + + if (expectations.qirConversion) { + EXPECT_TRUE(verifier->verify("QIR Conversion", record.afterQIRConversion, + expectations.qirConversion)); + } + + if (expectations.qirCanon) { + EXPECT_TRUE(verifier->verify("QIR Canonicalization", record.afterQIRCanon, + expectations.qirCanon)); + } } /** - * @brief Verify module at a specific stage matches expected + * @brief Verify a single stage */ - void verifyStageMatchesExpected(const std::string& stageName, - const std::string& actualIR, - ModuleOp expectedModule) const { - const auto actualModule = parseModule(actualIR); - ASSERT_TRUE(actualModule) << "Failed to parse " << stageName << " IR"; - - EXPECT_TRUE(modulesAreEquivalent(actualModule.get(), expectedModule)) - << stageName << " IR does not match expected structure"; + void verifyStage(const std::string& stageName, const std::string& actualIR, + const ModuleOp expectedModule) const { + EXPECT_TRUE(verifier->verify(stageName, actualIR, expectedModule)); } - void TearDown() override {} + void TearDown() override { + // Verify all stages were recorded (basic sanity check) + EXPECT_FALSE(record.afterQuartzImport.empty()) + << "Quartz import stage was not recorded"; + EXPECT_FALSE(record.afterInitialCanon.empty()) + << "Initial canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterFluxConversion.empty()) + << "Flux conversion stage was not recorded"; + EXPECT_FALSE(record.afterFluxCanon.empty()) + << "Flux canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterOptimization.empty()) + << "Optimization stage was not recorded"; + EXPECT_FALSE(record.afterOptimizationCanon.empty()) + << "Optimization canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterQuartzConversion.empty()) + << "Quartz conversion stage was not recorded"; + EXPECT_FALSE(record.afterQuartzCanon.empty()) + << "Quartz canonicalization stage was not recorded"; + + if (config.convertToQIR) { + EXPECT_FALSE(record.afterQIRConversion.empty()) + << "QIR conversion stage was not recorded"; + EXPECT_FALSE(record.afterQIRCanon.empty()) + << "QIR canonicalization stage was not recorded"; + } + } }; +//===----------------------------------------------------------------------===// +// Test Cases +//===----------------------------------------------------------------------===// + // ################################################## // # Empty Circuit Tests // ################################################## /** - * @brief Test: Empty circuit construction + * @brief Test: Empty circuit compilation * * @details - * Verifies that an empty QuantumComputation() can be imported and compiled - * without errors. Checks multiple stages of the pipeline. + * Verifies that an empty circuit compiles correctly through all stages, + * producing empty but valid IR at each stage. */ TEST_F(CompilerPipelineTest, EmptyCircuit) { // Create empty circuit @@ -218,39 +404,22 @@ TEST_F(CompilerPipelineTest, EmptyCircuit) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - // Build expected IR for initial Quartz import - const auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - // Empty circuit - just initialize - }); - ASSERT_TRUE(expectedQuartzInitial); - // Run compilation - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify Quartz import stage - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - - // Verify canonicalized Quartz stage - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzInitial.get()); - - // Verify final Quartz stage (after round-trip through Flux) - verifyStageMatchesExpected("Final Quartz Canonicalization", - record.afterQuartzCanon, - expectedQuartzInitial.get()); - - // Verify the IR is valid at all stages - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterFluxConversion.empty()); - EXPECT_FALSE(record.afterQuartzConversion.empty()); - - // QIR stages should also be present - EXPECT_FALSE(record.afterQIRConversion.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + // Verify all stages + verifyAllStages({ + .quartzImport = emptyQuartz.get(), + .initialCanon = emptyQuartz.get(), + .fluxConversion = emptyFlux.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } // ################################################## @@ -261,8 +430,8 @@ TEST_F(CompilerPipelineTest, EmptyCircuit) { * @brief Test: Single qubit register allocation * * @details - * Tests addQubitRegister with a single qubit. Verifies import and - * canonicalized stages. + * Since the register is unused, it should be removed during canonicalization + * in the Flux dialect. */ TEST_F(CompilerPipelineTest, SingleQubitRegister) { qc::QuantumComputation qc; @@ -270,24 +439,25 @@ TEST_F(CompilerPipelineTest, SingleQubitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); - // Build expected IR for initial import - const auto expectedQuartzInitial = buildExpectedQuartzIR( + const auto quartzExpected = buildQuartzIR( [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); - ASSERT_TRUE(expectedQuartzInitial); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify multiple stages - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Final Quartz Canonicalization", - record.afterQuartzCanon, - expectedQuartzInitial.get()); + const auto fluxExpected = buildFluxIR( + [](flux::FluxProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .initialCanon = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** @@ -297,25 +467,27 @@ TEST_F(CompilerPipelineTest, MultiQubitRegister) { qc::QuantumComputation qc; qc.addQubitRegister(3, "q"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); - // Build expected IR - auto expectedQuartzInitial = buildExpectedQuartzIR( + const auto quartzExpected = buildQuartzIR( [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + const auto fluxExpected = buildFluxIR( + [](flux::FluxProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .initialCanon = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** @@ -326,28 +498,32 @@ TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { qc.addQubitRegister(2, "q"); qc.addQubitRegister(3, "aux"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(2, "q"); b.allocQubitRegister(3, "aux"); }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + b.allocQubitRegister(2, "q"); + b.allocQubitRegister(3, "aux"); + }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .initialCanon = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** @@ -357,17 +533,9 @@ TEST_F(CompilerPipelineTest, LargeQubitRegister) { qc::QuantumComputation qc; qc.addQubitRegister(100, "q"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify compilation succeeded and produced valid IR at all stages - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterFluxConversion.empty()); - EXPECT_FALSE(record.afterQuartzCanon.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); } // ################################################## @@ -376,94 +544,101 @@ TEST_F(CompilerPipelineTest, LargeQubitRegister) { /** * @brief Test: Single classical bit register + * + * @details + * Since the register is unused, it should be removed during canonicalization. */ TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { qc::QuantumComputation qc; qc.addClassicalRegister(1, "c"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocClassicalBitRegister(1, "c"); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocClassicalBitRegister(1, "c"); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = emptyQuartz.get(), + .fluxConversion = emptyFlux.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** * @brief Test: Multi-bit classical register + * + * @details + * Since the register is unused, it should be removed during canonicalization. */ TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { qc::QuantumComputation qc; qc.addClassicalRegister(5, "c"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocClassicalBitRegister(5, "c"); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocClassicalBitRegister(5, "c"); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = emptyQuartz.get(), + .fluxConversion = emptyFlux.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** * @brief Test: Multiple classical registers + * + * @details + * Since the registers are unused, they should be removed during + * canonicalization. */ TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { qc::QuantumComputation qc; qc.addClassicalRegister(3, "c"); - qc.addClassicalRegister(2, "result"); + qc.addClassicalRegister(2, "d"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocClassicalBitRegister(3, "c"); - b.allocClassicalBitRegister(2, "result"); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + b.allocClassicalBitRegister(3, "c"); + b.allocClassicalBitRegister(2, "d"); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = emptyQuartz.get(), + .fluxConversion = emptyFlux.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** @@ -473,16 +648,9 @@ TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { qc::QuantumComputation qc; qc.addClassicalRegister(128, "c"); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify compilation succeeded - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterQuartzCanon.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); } // ################################################## @@ -491,40 +659,49 @@ TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { /** * @brief Test: Single reset in single qubit circuit + * + * @details + * Since the reset directly follows an allocation, it should be removed during + * canonicalization. */ TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { qc::QuantumComputation qc(1); qc.reset(0); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.staticQubit(0); - b.reset(q); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); - - // Verify Flux conversion contains flux.reset - EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.reset")); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto q = b.allocQubitRegister(1, "q"); + b.reset(q[0]); + }); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto q = b.allocQubitRegister(1, "q"); + b.reset(q[0]); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** * @brief Test: Consecutive reset operations + * + * @details + * Since reset is idempotent, consecutive resets should be reduced to a single + * reset during canonicalization. Since that single reset directly follows an + * allocation, it should be removed as well. */ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { qc::QuantumComputation qc(1); @@ -532,30 +709,36 @@ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { qc.reset(0); qc.reset(0); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.staticQubit(0); - b.reset(q); - b.reset(q); - b.reset(q); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto q = b.allocQubitRegister(1, "q"); + b.reset(q[0]); + b.reset(q[0]); + b.reset(q[0]); + }); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + q[0] = b.reset(q[0]); + q[0] = b.reset(q[0]); + q[0] = b.reset(q[0]); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } /** @@ -566,30 +749,34 @@ TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { qc.reset(0); qc.reset(1); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q0 = b.staticQubit(0); - auto q1 = b.staticQubit(1); - b.reset(q0); - b.reset(q1); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto q = b.allocQubitRegister(2, "q"); + b.reset(q[0]); + b.reset(q[1]); + }); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(2, "q"); + q[0] = b.reset(q[0]); + q[1] = b.reset(q[1]); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = emptyFlux.get(), + .optimization = emptyFlux.get(), + .optimizationCanon = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .quartzCanon = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + .qirCanon = emptyQIR.get(), + }); } // ################################################## @@ -600,35 +787,42 @@ TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { * @brief Test: Single measurement to single bit */ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { - qc::QuantumComputation qc(1); + qc::QuantumComputation qc(1, 1); qc.measure(0, 0); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.staticQubit(0); - auto c = b.allocClassicalBitRegister(1); - b.measure(q, c, 0); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); - - // Verify Flux conversion contains flux.measure - EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.measure")); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto c = b.allocClassicalBitRegister(1); + b.measure(q[0], c, 0); + }); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto c = b.allocClassicalBitRegister(1); + b.measure(q[0], c, 0); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.measure(q[0], 0); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = fluxExpected.get(), + .optimization = fluxExpected.get(), + .optimizationCanon = fluxExpected.get(), + .quartzConversion = expected.get(), + .quartzCanon = expected.get(), + .qirConversion = qirExpected.get(), + .qirCanon = qirExpected.get(), + }); } /** @@ -637,34 +831,44 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { qc::QuantumComputation qc(1); qc.measure(0, 0); - qc.reset(0); qc.measure(0, 0); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.staticQubit(0); - auto c = b.allocClassicalBitRegister(1); - b.measure(q, c, 0); - b.reset(q); - b.measure(q, c, 0); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto c = b.allocClassicalBitRegister(1); + b.measure(q[0], c, 0); + b.measure(q[0], c, 0); + }); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto c = b.allocClassicalBitRegister(1); + q[0] = b.measure(q[0], c, 0); + q[0] = b.measure(q[0], c, 0); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.measure(q[0], 0); + b.measure(q[0], 0); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = fluxExpected.get(), + .optimization = fluxExpected.get(), + .optimizationCanon = fluxExpected.get(), + .quartzConversion = expected.get(), + .quartzCanon = expected.get(), + .qirConversion = qirExpected.get(), + .qirCanon = qirExpected.get(), + }); } /** @@ -674,38 +878,48 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { qc::QuantumComputation qc(1); qc.addClassicalRegister(3); qc.measure(0, 0); - qc.reset(0); qc.measure(0, 1); - qc.reset(0); qc.measure(0, 2); - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.staticQubit(0); - auto c = b.allocClassicalBitRegister(3); - b.measure(q, c, 0); - b.reset(q); - b.measure(q, c, 1); - b.reset(q); - b.measure(q, c, 2); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto c = b.allocClassicalBitRegister(3); + b.measure(q[0], c, 0); + b.measure(q[0], c, 1); + b.measure(q[0], c, 2); + }); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + const auto c = b.allocClassicalBitRegister(3); + q[0] = b.measure(q[0], c, 0); + q[0] = b.measure(q[0], c, 1); + q[0] = b.measure(q[0], c, 2); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.measure(q[0], 0); + b.measure(q[0], 1); + b.measure(q[0], 2); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = fluxExpected.get(), + .optimization = fluxExpected.get(), + .optimizationCanon = fluxExpected.get(), + .quartzConversion = expected.get(), + .quartzCanon = expected.get(), + .qirConversion = qirExpected.get(), + .qirCanon = qirExpected.get(), + }); } /** @@ -713,181 +927,49 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { */ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { qc::QuantumComputation qc(2); - qc.addClassicalRegister(2, "c1"); - qc.addClassicalRegister(2, "c2"); - qc.measure(0, 0); - qc.measure(1, 1); - - auto module = importQuantumCircuit(qc); - ASSERT_TRUE(module); + auto& c1 = qc.addClassicalRegister(1, "c1"); + auto& c2 = qc.addClassicalRegister(1, "c2"); + qc.measure(0, c1[0]); + qc.measure(1, c2[0]); - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify all stages completed - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterFluxConversion.empty()); - EXPECT_FALSE(record.afterQuartzCanon.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); -} - -// ################################################## -// # Combined Feature Tests -// ################################################## - -/** - * @brief Test: Quantum and classical registers with operations - */ -TEST_F(CompilerPipelineTest, QuantumClassicalRegistersWithOperations) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.addClassicalRegister(3, "c"); - - // Reset all qubits - qc.reset(0); - qc.reset(1); - qc.reset(2); - - // Measure all qubits - qc.measure(0, 0); - qc.measure(1, 1); - qc.measure(2, 2); - - auto module = importQuantumCircuit(qc); - ASSERT_TRUE(module); - - // Build expected IR - auto expectedQuartzInitial = - buildExpectedQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.allocQubitRegister(3, "q"); - auto c = b.allocClassicalBitRegister(3, "c"); - b.reset(q[0]); - b.reset(q[1]); - b.reset(q[2]); - b.measure(q[0], c, 0); - b.measure(q[1], c, 1); - b.measure(q[2], c, 2); - }); - ASSERT_TRUE(expectedQuartzInitial); - - auto expectedQuartzCanon = cloneModule(expectedQuartzInitial.get()); - ASSERT_TRUE(succeeded(applyCanonicalization(expectedQuartzCanon.get()))); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - verifyStageMatchesExpected("Quartz Import", record.afterQuartzImport, - expectedQuartzInitial.get()); - verifyStageMatchesExpected("Initial Canonicalization", - record.afterInitialCanon, - expectedQuartzCanon.get()); - - // Verify conversions to other dialects succeeded - EXPECT_TRUE(irContains(record.afterFluxConversion, "flux")); - EXPECT_TRUE(irContains(record.afterQIRConversion, "llvm")); -} - -/** - * @brief Test: End-to-end pipeline with all stages - */ -TEST_F(CompilerPipelineTest, EndToEndPipelineAllStages) { - qc::QuantumComputation qc(2); - qc.addClassicalRegister(2); - qc.reset(0); - qc.reset(1); - qc.measure(0, 0); - qc.measure(1, 1); - - auto module = importQuantumCircuit(qc); - ASSERT_TRUE(module); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify each stage produces non-empty output - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterInitialCanon.empty()); - EXPECT_FALSE(record.afterFluxConversion.empty()); - EXPECT_FALSE(record.afterFluxCanon.empty()); - EXPECT_FALSE(record.afterOptimization.empty()); - EXPECT_FALSE(record.afterOptimizationCanon.empty()); - EXPECT_FALSE(record.afterQuartzConversion.empty()); - EXPECT_FALSE(record.afterQuartzCanon.empty()); - EXPECT_FALSE(record.afterQIRConversion.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); - - // Verify dialect transitions - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz")); - EXPECT_TRUE(irContains(record.afterFluxConversion, "flux")); - EXPECT_TRUE(irContains(record.afterQuartzConversion, "quartz")); - EXPECT_TRUE(irContains(record.afterQIRConversion, "llvm")); -} - -/** - * @brief Test: Complex circuit with interleaved operations - */ -TEST_F(CompilerPipelineTest, ComplexInterleavedOperations) { - qc::QuantumComputation qc; - qc.addQubitRegister(4, "q"); - qc.addClassicalRegister(4, "c1"); - qc.addClassicalRegister(2, "c2"); - - // Interleaved operations - qc.reset(0); - qc.measure(0, 0); - qc.reset(1); - qc.reset(2); - qc.measure(1, 1); - qc.measure(2, 2); - qc.reset(3); - qc.measure(3, 3); - - auto module = importQuantumCircuit(qc); - ASSERT_TRUE(module); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify all pipeline stages succeeded - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterFluxConversion.empty()); - EXPECT_FALSE(record.afterQuartzCanon.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); - - // Verify operations are present in appropriate dialects - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.reset")); - EXPECT_TRUE(irContains(record.afterQuartzImport, "quartz.measure")); - EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.reset")); - EXPECT_TRUE(irContains(record.afterFluxConversion, "flux.measure")); -} - -/** - * @brief Test: Scalability with large mixed operations - */ -TEST_F(CompilerPipelineTest, ScalabilityLargeMixedOperations) { - constexpr size_t NUM_QUBITS = 50; - - qc::QuantumComputation qc; - qc.addQubitRegister(NUM_QUBITS, "q"); - qc.addClassicalRegister(NUM_QUBITS, "c"); - - // Add operations for all qubits - for (size_t i = 0; i < NUM_QUBITS; ++i) { - qc.reset(i); - qc.measure(i, i); - } - - auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - - CompilationRecord record; - ASSERT_TRUE(succeeded(runCompiler(module.get(), config, &record))); - - // Verify compilation succeeded and produced valid output at all stages - EXPECT_FALSE(record.afterQuartzImport.empty()); - EXPECT_FALSE(record.afterFluxConversion.empty()); - EXPECT_FALSE(record.afterQuartzCanon.empty()); - EXPECT_FALSE(record.afterQIRCanon.empty()); + ASSERT_TRUE(succeeded(runPipeline(module.get()))); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + const auto creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto creg2 = b.allocClassicalBitRegister(1, "c2"); + b.measure(q[0], creg1, 0); + b.measure(q[1], creg2, 0); + }); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + const auto creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto creg2 = b.allocClassicalBitRegister(1, "c2"); + q[0] = b.measure(q[0], creg1, 0); + q[1] = b.measure(q[1], creg2, 0); + }); + + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.measure(q[0], 0); + b.measure(q[1], 1); + }); + + verifyAllStages({ + .quartzImport = expected.get(), + .initialCanon = expected.get(), + .fluxConversion = fluxExpected.get(), + .fluxCanon = fluxExpected.get(), + .optimization = fluxExpected.get(), + .optimizationCanon = fluxExpected.get(), + .quartzConversion = expected.get(), + .quartzCanon = expected.get(), + .qirConversion = qirExpected.get(), + .qirCanon = qirExpected.get(), + }); } } // namespace From 4070622ab2b1f473831e9d006b5eeb4651609169 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 14:35:46 +0200 Subject: [PATCH 052/419] =?UTF-8?q?=F0=9F=A9=B9=20small=20QIR-related=20fi?= =?UTF-8?q?xes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 27 +++++++++++-------- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 564abbfa58..e067ae1237 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -39,6 +39,11 @@ void QIRProgramBuilder::initialize() { LLVM::LLVMVoidType::get(builder.getContext()), {}); mainFunc = builder.create(loc, "main", funcType); + // Add entry_point attribute to identify the main function + auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); + mainFunc->setAttr("passthrough", + ArrayAttr::get(builder.getContext(), {entryPointAttr})); + // Create the 4-block structure for QIR base profile entryBlock = mainFunc.addEntryBlock(builder); mainBlock = mainFunc.addBlock(); @@ -69,8 +74,8 @@ void QIRProgramBuilder::initialize() { const auto initType = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())); - auto initFunc = getOrCreateFunctionDeclaration(builder, module.getOperation(), - QIR_INITIALIZE, initType); + auto initFunc = + getOrCreateFunctionDeclaration(builder, module, QIR_INITIALIZE, initType); builder.create(loc, initFunc, ValueRange{zeroOp.getResult()}); // Set insertion point to main block for user operations @@ -83,7 +88,7 @@ Value QIRProgramBuilder::allocQubit() { LLVM::LLVMPointerType::get(builder.getContext()), {}); auto fnDecl = getOrCreateFunctionDeclaration( - builder, module.getOperation(), QIR_QUBIT_ALLOCATE, qirSignature); + builder, module, QIR_QUBIT_ALLOCATE, qirSignature); // Call qubit_allocate auto callOp = builder.create(loc, fnDecl, ValueRange{}); @@ -163,8 +168,8 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { // Create mz call const auto mzSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); - auto mzDecl = getOrCreateFunctionDeclaration(builder, module.getOperation(), - QIR_MEASURE, mzSignature); + auto mzDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); // Get or create result label @@ -173,8 +178,8 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { labelOp = resultLabelCache.at(resultIndex); } else { // Use common utility function to create result label - labelOp = createResultLabel(builder, module.getOperation(), - "r" + std::to_string(resultIndex)); + labelOp = + createResultLabel(builder, module, "r" + std::to_string(resultIndex)); resultLabelCache.try_emplace(resultIndex, labelOp); metadata_.numResults++; } @@ -183,7 +188,7 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { const auto recordSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); auto recordDecl = getOrCreateFunctionDeclaration( - builder, module.getOperation(), QIR_RECORD_OUTPUT, recordSignature); + builder, module, QIR_RECORD_OUTPUT, recordSignature); builder.create(loc, recordDecl, ValueRange{resultValue, labelOp.getResult()}); @@ -201,8 +206,8 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())); - auto fnDecl = getOrCreateFunctionDeclaration(builder, module.getOperation(), - QIR_RESET, qirSignature); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_RESET, qirSignature); builder.create(loc, fnDecl, ValueRange{qubit}); return *this; @@ -221,7 +226,7 @@ QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())); - auto fnDecl = getOrCreateFunctionDeclaration(builder, module.getOperation(), + auto fnDecl = getOrCreateFunctionDeclaration(builder, module, QIR_QUBIT_RELEASE, qirSignature); builder.create(loc, fnDecl, ValueRange{qubit}); diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index d87792d222..93202a0bbe 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -23,7 +23,7 @@ namespace mlir::qir { LLVM::LLVMFuncOp getMainFunction(Operation* op) { auto module = dyn_cast(op); if (!module) { - return nullptr; + module = op->getParentOfType(); } // Search for function with entry_point attribute From 61b181754021ee508d58e76597023cd66e5cf788 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 14:36:15 +0200 Subject: [PATCH 053/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20equality=20o?= =?UTF-8?q?f=20text-based=20IR=20as=20verification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Support/TestUtils.h | 64 -------- mlir/lib/Support/CMakeLists.txt | 1 - mlir/lib/Support/TestUtils.cpp | 138 ----------------- .../pipeline/test_compiler_pipeline.cpp | 142 ++++++------------ 4 files changed, 49 insertions(+), 296 deletions(-) delete mode 100644 mlir/include/mlir/Support/TestUtils.h delete mode 100644 mlir/lib/Support/TestUtils.cpp diff --git a/mlir/include/mlir/Support/TestUtils.h b/mlir/include/mlir/Support/TestUtils.h deleted file mode 100644 index 034e56be78..0000000000 --- a/mlir/include/mlir/Support/TestUtils.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -namespace mlir { -class Block; -class ModuleOp; -class Operation; -class Region; - -/** - * @brief Compare two modules for structural equivalence using MLIR's APIs - * - * @details - * Uses MLIR's IR walking and comparison mechanisms to verify that two - * modules are structurally equivalent. This is more robust than string - * comparison as it is insensitive to formatting differences. - * - * @param lhs First module to compare - * @param rhs Second module to compare - * @return true if modules are structurally equivalent, false otherwise - */ -bool modulesAreEquivalent(ModuleOp lhs, ModuleOp rhs); - -/** - * @brief Compare two operations for structural equivalence - * - * @details - * Recursively compares operations, their attributes, operands, results, - * and nested regions. This implements MLIR's structural equivalence check. - * - * @param lhs First operation to compare - * @param rhs Second operation to compare - * @return true if operations are structurally equivalent, false otherwise - */ -bool operationsAreEquivalent(Operation* lhs, Operation* rhs); - -/** - * @brief Compare two regions for structural equivalence - * - * @param lhs First region to compare - * @param rhs Second region to compare - * @return true if regions are structurally equivalent, false otherwise - */ -bool regionsAreEquivalent(Region* lhs, Region* rhs); - -/** - * @brief Compare two blocks for structural equivalence - * - * @param lhs First block to compare - * @param rhs Second block to compare - * @return true if blocks are structurally equivalent, false otherwise - */ -bool blocksAreEquivalent(Block* lhs, Block* rhs); - -} // namespace mlir diff --git a/mlir/lib/Support/CMakeLists.txt b/mlir/lib/Support/CMakeLists.txt index 5e7c709984..cd368aad5e 100644 --- a/mlir/lib/Support/CMakeLists.txt +++ b/mlir/lib/Support/CMakeLists.txt @@ -7,7 +7,6 @@ add_mlir_library( MLIRSupportMQT PrettyPrinting.cpp - TestUtils.cpp ADDITIONAL_HEADER_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Support LINK_LIBS diff --git a/mlir/lib/Support/TestUtils.cpp b/mlir/lib/Support/TestUtils.cpp deleted file mode 100644 index 1fc6e48d17..0000000000 --- a/mlir/lib/Support/TestUtils.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Support/TestUtils.h" - -#include -#include -#include - -namespace mlir { - -bool modulesAreEquivalent(ModuleOp lhs, ModuleOp rhs) { - // First verify both modules are valid - if (failed(verify(lhs)) || failed(verify(rhs))) { - return false; - } - - // Compare module attributes - if (lhs->getAttrs() != rhs->getAttrs()) { - return false; - } - - for (auto [lhsOp, rhsOp] : llvm::zip(lhs.getOps(), rhs.getOps())) { - if (!operationsAreEquivalent(&lhsOp, &rhsOp)) { - return false; - } - } - - return true; -} - -bool operationsAreEquivalent(Operation* lhs, Operation* rhs) { - // Check operation name - if (lhs->getName() != rhs->getName()) { - return false; - } - - // Check attributes (skip for LLVMFuncOp) - if (!llvm::isa(lhs) && !llvm::isa(rhs)) { - if (lhs->getAttrs() != rhs->getAttrs()) { - return false; - } - } - - // Check number of operands and results - if (lhs->getNumOperands() != rhs->getNumOperands() || - lhs->getNumResults() != rhs->getNumResults()) { - return false; - } - - // Check result types - for (auto [lhsResult, rhsResult] : - llvm::zip(lhs->getResults(), rhs->getResults())) { - if (lhsResult.getType() != rhsResult.getType()) { - return false; - } - } - - // Check operand types (not values, as SSA values differ) - for (auto [lhsOperand, rhsOperand] : - llvm::zip(lhs->getOperands(), rhs->getOperands())) { - if (lhsOperand.getType() != rhsOperand.getType()) { - return false; - } - } - - // Check regions - if (lhs->getNumRegions() != rhs->getNumRegions()) { - return false; - } - - for (auto [lhsRegion, rhsRegion] : - llvm::zip(lhs->getRegions(), rhs->getRegions())) { - if (!regionsAreEquivalent(&lhsRegion, &rhsRegion)) { - return false; - } - } - - return true; -} - -bool regionsAreEquivalent(Region* lhs, Region* rhs) { - // Check number of blocks - if (lhs->getBlocks().size() != rhs->getBlocks().size()) { - return false; - } - - auto lhsBlockIt = lhs->begin(); - auto rhsBlockIt = rhs->begin(); - - while (lhsBlockIt != lhs->end() && rhsBlockIt != rhs->end()) { - if (!blocksAreEquivalent(&(*lhsBlockIt), &(*rhsBlockIt))) { - return false; - } - ++lhsBlockIt; - ++rhsBlockIt; - } - - return true; -} - -bool blocksAreEquivalent(Block* lhs, Block* rhs) { - // Check number of arguments - if (lhs->getNumArguments() != rhs->getNumArguments()) { - return false; - } - - // Check argument types - for (auto [lhsArg, rhsArg] : - llvm::zip(lhs->getArguments(), rhs->getArguments())) { - if (lhsArg.getType() != rhsArg.getType()) { - return false; - } - } - - // Check operations in the block - auto lhsOpIt = lhs->begin(); - auto rhsOpIt = rhs->begin(); - - while (lhsOpIt != lhs->end() && rhsOpIt != rhs->end()) { - if (!operationsAreEquivalent(&(*lhsOpIt), &(*rhsOpIt))) { - return false; - } - ++lhsOpIt; - ++rhsOpIt; - } - - return lhsOpIt == lhs->end() && rhsOpIt == rhs->end(); -} - -} // namespace mlir diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index ebb525702a..421073b07a 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -17,7 +17,6 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" #include "mlir/Support/PrettyPrinting.h" -#include "mlir/Support/TestUtils.h" #include #include @@ -44,64 +43,37 @@ using namespace mlir; //===----------------------------------------------------------------------===// /** - * @brief Helper to verify a compilation stage matches expected IR + * @brief Verify a stage matches expected module * - * @details - * Provides detailed error messages including actual and expected IR when - * verification fails. + * @param stageName Human-readable stage name for error messages + * @param actualIR String IR from CompilationRecord + * @param expectedModule Expected module to compare against + * @return True if modules match, false otherwise with diagnostic output */ -class StageVerifier { -public: - explicit StageVerifier(MLIRContext* ctx) : context(ctx) {} - - /** - * @brief Verify a stage matches expected module - * - * @param stageName Human-readable stage name for error messages - * @param actualIR String IR from CompilationRecord - * @param expectedModule Expected module to compare against - * @return True if modules match, false otherwise with diagnostic output - */ - [[nodiscard]] ::testing::AssertionResult - verify(const std::string& stageName, const std::string& actualIR, - ModuleOp expectedModule) const { - // Parse actual IR - const auto actualModule = - parseSourceString(actualIR, ParserConfig(context)); - if (!actualModule) { - return ::testing::AssertionFailure() - << "Failed to parse " << stageName << " IR\n" - << "Raw IR string:\n" - << actualIR; - } - - // Compare modules - if (!modulesAreEquivalent(actualModule.get(), expectedModule)) { - std::ostringstream msg; - msg << stageName << " IR does not match expected structure\n\n"; - - msg << "=== EXPECTED IR ===\n"; - std::string expectedStr; - llvm::raw_string_ostream expectedStream(expectedStr); - expectedModule.print(expectedStream); - msg << expectedStr << "\n\n"; - - msg << "=== ACTUAL IR ===\n"; - msg << actualIR << "\n\n"; - - msg << "=== DIFFERENCE ===\n"; - msg << "Modules are structurally different. "; - msg << "Check operation types, attributes, and structure.\n"; - - return ::testing::AssertionFailure() << msg.str(); - } - - return ::testing::AssertionSuccess(); +[[nodiscard]] testing::AssertionResult verify(const std::string& stageName, + const std::string& actualIR, + ModuleOp expectedModule) { + // dump expected module to text-based IR + std::string expectedStr; + llvm::raw_string_ostream expectedStream(expectedStr); + expectedModule.print(expectedStream); + + // Compare modules + if (actualIR != expectedStr) { + std::ostringstream msg; + msg << stageName << " IR does not match expected structure\n\n"; + + msg << "=== EXPECTED IR ===\n"; + msg << expectedStr << "\n\n"; + + msg << "=== ACTUAL IR ===\n"; + msg << actualIR << "\n"; + + return testing::AssertionFailure() << msg.str(); } -private: - MLIRContext* context; -}; + return testing::AssertionSuccess(); +} //===----------------------------------------------------------------------===// // Stage Expectation Builder @@ -142,7 +114,6 @@ struct StageExpectations { class CompilerPipelineTest : public testing::Test { protected: std::unique_ptr context; - std::unique_ptr verifier; QuantumCompilerConfig config; CompilationRecord record; @@ -162,8 +133,6 @@ class CompilerPipelineTest : public testing::Test { context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); - verifier = std::make_unique(context.get()); - // Enable QIR conversion and recording by default config.convertToQIR = true; config.recordIntermediates = true; @@ -289,70 +258,57 @@ class CompilerPipelineTest : public testing::Test { */ void verifyAllStages(const StageExpectations& expectations) const { if (expectations.quartzImport) { - EXPECT_TRUE(verifier->verify("Quartz Import", record.afterQuartzImport, - expectations.quartzImport)); + EXPECT_TRUE(verify("Quartz Import", record.afterQuartzImport, + expectations.quartzImport)); } if (expectations.initialCanon) { - EXPECT_TRUE(verifier->verify("Initial Canonicalization", - record.afterInitialCanon, - expectations.initialCanon)); + EXPECT_TRUE(verify("Initial Canonicalization", record.afterInitialCanon, + expectations.initialCanon)); } if (expectations.fluxConversion) { - EXPECT_TRUE(verifier->verify("Flux Conversion", - record.afterFluxConversion, - expectations.fluxConversion)); + EXPECT_TRUE(verify("Flux Conversion", record.afterFluxConversion, + expectations.fluxConversion)); } if (expectations.fluxCanon) { - EXPECT_TRUE(verifier->verify("Flux Canonicalization", - record.afterFluxCanon, - expectations.fluxCanon)); + EXPECT_TRUE(verify("Flux Canonicalization", record.afterFluxCanon, + expectations.fluxCanon)); } if (expectations.optimization) { - EXPECT_TRUE(verifier->verify("Optimization", record.afterOptimization, - expectations.optimization)); + EXPECT_TRUE(verify("Optimization", record.afterOptimization, + expectations.optimization)); } if (expectations.optimizationCanon) { - EXPECT_TRUE(verifier->verify("Optimization Canonicalization", - record.afterOptimizationCanon, - expectations.optimizationCanon)); + EXPECT_TRUE(verify("Optimization Canonicalization", + record.afterOptimizationCanon, + expectations.optimizationCanon)); } if (expectations.quartzConversion) { - EXPECT_TRUE(verifier->verify("Quartz Conversion", - record.afterQuartzConversion, - expectations.quartzConversion)); + EXPECT_TRUE(verify("Quartz Conversion", record.afterQuartzConversion, + expectations.quartzConversion)); } if (expectations.quartzCanon) { - EXPECT_TRUE(verifier->verify("Quartz Canonicalization", - record.afterQuartzCanon, - expectations.quartzCanon)); + EXPECT_TRUE(verify("Quartz Canonicalization", record.afterQuartzCanon, + expectations.quartzCanon)); } if (expectations.qirConversion) { - EXPECT_TRUE(verifier->verify("QIR Conversion", record.afterQIRConversion, - expectations.qirConversion)); + EXPECT_TRUE(verify("QIR Conversion", record.afterQIRConversion, + expectations.qirConversion)); } if (expectations.qirCanon) { - EXPECT_TRUE(verifier->verify("QIR Canonicalization", record.afterQIRCanon, - expectations.qirCanon)); + EXPECT_TRUE(verify("QIR Canonicalization", record.afterQIRCanon, + expectations.qirCanon)); } } - /** - * @brief Verify a single stage - */ - void verifyStage(const std::string& stageName, const std::string& actualIR, - const ModuleOp expectedModule) const { - EXPECT_TRUE(verifier->verify(stageName, actualIR, expectedModule)); - } - void TearDown() override { // Verify all stages were recorded (basic sanity check) EXPECT_FALSE(record.afterQuartzImport.empty()) @@ -829,7 +785,7 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { * @brief Test: Repeated measurement to same bit */ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { - qc::QuantumComputation qc(1); + qc::QuantumComputation qc(1, 1); qc.measure(0, 0); qc.measure(0, 0); From 9626746c38e69fa63561970c0460683e92aa8e2e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 16:01:56 +0200 Subject: [PATCH 054/419] =?UTF-8?q?=E2=9C=A8=20add=20canonicalization=20pa?= =?UTF-8?q?tterns=20for=20dealloc=20and=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 + mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 57 ++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 035d024a86..4b65c60566 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -150,6 +150,7 @@ def DeallocOp : FluxOp<"dealloc"> { let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; + let hasCanonicalizer = 1; } def StaticOp : FluxOp<"static"> { @@ -205,6 +206,7 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { let arguments = (ins QubitType:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let hasCanonicalizer = 1; } #endif // FluxOPS diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 137c799093..7ca3478443 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -15,6 +15,7 @@ #include #include #include +#include // IWYU pragma: end_keep using namespace mlir; @@ -81,3 +82,59 @@ LogicalResult AllocOp::verify() { } return success(); } + +//===----------------------------------------------------------------------===// +// Canonicalization Patterns +//===----------------------------------------------------------------------===// + +namespace { +/** + * @class RemoveAllocDeallocPair + * @brief A class designed to identify and remove matching allocation and + * deallocation pairs without operations between them. + */ +struct RemoveAllocDeallocPair final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(DeallocOp deallocOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an AllocOp + auto allocOp = deallocOp.getQubit().getDefiningOp(); + if (!allocOp) { + return failure(); + } + + // Remove the AllocOp and the DeallocOp + rewriter.eraseOp(allocOp); + rewriter.eraseOp(deallocOp); + return success(); + } +}; + +struct RemoveResetAfterAlloc final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ResetOp resetOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an AllocOp + if (auto allocOp = resetOp.getQubitIn().getDefiningOp(); + !allocOp) { + return failure(); + } + + // Remove the ResetOp + rewriter.replaceOp(resetOp, resetOp.getQubitIn()); + return success(); + } +}; +} // namespace + +void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} From a85f9e605430644514a7e30fd7ba28844081b8d9 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 13 Oct 2025 16:47:54 +0200 Subject: [PATCH 055/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20enhance=20IR=20ver?= =?UTF-8?q?ification=20by=20parsing=20actual=20IR=20and=20comparing=20modu?= =?UTF-8?q?les?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../pipeline/test_compiler_pipeline.cpp | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 421073b07a..1b3ec4fb39 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -53,21 +54,30 @@ using namespace mlir; [[nodiscard]] testing::AssertionResult verify(const std::string& stageName, const std::string& actualIR, ModuleOp expectedModule) { - // dump expected module to text-based IR - std::string expectedStr; - llvm::raw_string_ostream expectedStream(expectedStr); - expectedModule.print(expectedStream); + // Parse the actual IR string into a ModuleOp + const auto actualModule = + parseSourceString(actualIR, expectedModule.getContext()); + if (!actualModule) { + return testing::AssertionFailure() + << stageName << " failed to parse actual IR"; + } - // Compare modules - if (actualIR != expectedStr) { + if (!OperationEquivalence::isEquivalentTo( + actualModule.get(), expectedModule, + OperationEquivalence::ignoreValueEquivalence, nullptr, + OperationEquivalence::IgnoreLocations | + OperationEquivalence::IgnoreDiscardableAttrs | + OperationEquivalence::IgnoreProperties)) { std::ostringstream msg; msg << stageName << " IR does not match expected structure\n\n"; - msg << "=== EXPECTED IR ===\n"; - msg << expectedStr << "\n\n"; + std::string expectedStr; + llvm::raw_string_ostream expectedStream(expectedStr); + expectedModule.print(expectedStream); + expectedStream.flush(); - msg << "=== ACTUAL IR ===\n"; - msg << actualIR << "\n"; + msg << "=== EXPECTED IR ===\n" << expectedStr << "\n\n"; + msg << "=== ACTUAL IR ===\n" << actualIR << "\n"; return testing::AssertionFailure() << msg.str(); } From b4d3df5d9bfc85bab8327dd642edd6adb47d4847 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 14 Oct 2025 22:35:03 +0200 Subject: [PATCH 056/419] =?UTF-8?q?=E2=9C=A8=20implement=20classical=20reg?= =?UTF-8?q?ister=20structures=20and=20update=20measurement=20operations=20?= =?UTF-8?q?in=20Quartz=20dialect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Quartz/Builder/QuartzProgramBuilder.h | 64 ++++++++++++++----- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 33 ++++++++-- .../Quartz/Builder/QuartzProgramBuilder.cpp | 55 +++++++--------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 23 +++++++ .../TranslateQuantumComputationToQuartz.cpp | 39 ++++++----- 5 files changed, 145 insertions(+), 69 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 8fc3ac25d3..22b923f3d5 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -12,7 +12,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include +#include #include #include #include @@ -117,21 +117,53 @@ class QuartzProgramBuilder { */ SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); + /** + * @brief A small structure representing a single classical bit within a + * classical register. + */ + struct Bit { + /// Name of the register containing this bit + StringRef registerName; + /// Size of the register containing this bit + int64_t registerSize{}; + /// Index of this bit within the register + int64_t registerIndex{}; + }; + + /** + * @brief A small structure representing a classical bit register. + */ + struct ClassicalRegister { + /// Name of the classical register + StringRef name; + /// Size of the classical register + int64_t size; + + /** + * @brief Access a specific bit in the classical register + * @param index The index of the bit to access (must be less than size) + * @return A Bit structure representing the specified bit + */ + Bit operator[](const int64_t index) const { + assert(0 <= index && index < size); + return { + .registerName = name, .registerSize = size, .registerIndex = index}; + } + }; + /** * @brief Allocate a classical bit register * @param size Number of bits * @param name Register name (default: "c") - * @return Memref of i1 elements + * @return A reference to a ClassicalRegister structure * * @par Example: * ```c++ * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` - * ```mlir - * %c = memref.alloca() {sym_name = "c"} : memref<3xi1> - * ``` */ - Value allocClassicalBitRegister(int64_t size, StringRef name = "c"); + ClassicalRegister& allocClassicalBitRegister(int64_t size, + StringRef name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset @@ -157,24 +189,20 @@ class QuartzProgramBuilder { Value measure(Value qubit); /** - * @brief Measure a qubit and store the result in a classical register + * @brief Measure a qubit and store the result in a bit of a register * * @param qubit The qubit to measure - * @param memref Classical register - * @param index Storage index - * @return Reference to this builder for method chaining + * @param bit The classical bit to store the result * * @par Example: * ```c++ - * builder.measure(q0, c, 0); + * builder.measure(q0, c[0]); * ``` * ```mlir - * %r0 = quartz.measure %q0 : !quartz.qubit -> i1 - * %c0 = arith.constant 0 : index - * memref.store %r0, %c[%c0] : memref<2xi1> + * %r0 = quartz.measure("c", 3, 0) %q0 : !quartz.qubit -> i1 * ``` */ - QuartzProgramBuilder& measure(Value qubit, Value memref, int64_t index); + QuartzProgramBuilder& measure(Value qubit, const Bit& bit); /** * @brief Reset a qubit to |0⟩ state @@ -224,7 +252,8 @@ class QuartzProgramBuilder { * * @details * Automatically deallocates all remaining allocated qubits, adds a return - * statement, and transfers ownership of the module to the caller. + * statement with exit code 0 (indicating successful execution), and + * transfers ownership of the module to the caller. * The builder should not be used after calling this method. * * @return OwningOpRef containing the constructed quantum program module @@ -238,5 +267,8 @@ class QuartzProgramBuilder { /// Track allocated qubits for automatic deallocation llvm::DenseSet allocatedQubits; + + /// Track allocated classical Registers + SmallVector allocatedClassicalRegisters; }; } // namespace mlir::quartz diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 6497ad52d4..ce3a4bbac8 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -172,15 +172,40 @@ def MeasureOp : QuartzOp<"measure"> { Measures a qubit in the computational (Z) basis, collapsing the state and returning a classical bit result. - Example: + Optionally, the measurement can be recorded to an output register by + specifying: + - `register_name`: Name of the classical register (e.g., "c") + - `register_size`: Total size of the register + - `register_index`: Index within the register for this measurement + + Example (simple measurement): + ```mlir + %result = quartz.measure %q : !quartz.qubit -> i1 + ``` + + Example (measurement with output recording): ```mlir - %c = quartz.measure %q : !quartz.qubit -> i1 + %result = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 ``` }]; - let arguments = (ins QubitType:$qubit); + let arguments = (ins QubitType:$qubit, + OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); let results = (outs I1:$result); - let assemblyFormat = "$qubit attr-dict `:` type($qubit) `->` type($result)"; + let assemblyFormat = [{ + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? + $qubit `:` type($qubit) `->` type($result) attr-dict + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit), [{ + build($_builder, $_state, $_builder.getI1Type(), qubit, nullptr, nullptr, nullptr); + }]> + ]; + + let hasVerifier = 1; } def ResetOp : QuartzOp<"reset"> { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index e0075adfd0..fcf8176c2a 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -35,7 +35,7 @@ void QuartzProgramBuilder::initialize() { builder.setInsertionPointToStart(module.getBody()); // Create main function as entry point - auto funcType = builder.getFunctionType({}, {}); + auto funcType = builder.getFunctionType({}, {builder.getI64Type()}); auto mainFunc = builder.create(loc, "main", funcType); // Add entry_point attribute to identify the main function @@ -59,18 +59,19 @@ Value QuartzProgramBuilder::allocQubit() { return qubit; } -Value QuartzProgramBuilder::staticQubit(int64_t index) { +Value QuartzProgramBuilder::staticQubit(const int64_t index) { // Create the StaticOp with the given index auto indexAttr = builder.getI64IntegerAttr(index); auto staticOp = builder.create(loc, indexAttr); return staticOp.getQubit(); } -SmallVector QuartzProgramBuilder::allocQubitRegister(int64_t size, - StringRef name) { +SmallVector +QuartzProgramBuilder::allocQubitRegister(const int64_t size, + const StringRef name) { // Allocate a sequence of qubits with register metadata SmallVector qubits; - qubits.reserve(static_cast(size)); + qubits.reserve(size); auto nameAttr = builder.getStringAttr(name); auto sizeAttr = builder.getI64IntegerAttr(size); @@ -86,17 +87,9 @@ SmallVector QuartzProgramBuilder::allocQubitRegister(int64_t size, return qubits; } -Value QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, - StringRef name) { - // Create memref type - auto memrefType = MemRefType::get({size}, builder.getI1Type()); - - // Allocate the memref - auto allocOp = builder.create(loc, memrefType); - - allocOp->setAttr("sym_name", builder.getStringAttr(name)); - - return allocOp.getResult(); +QuartzProgramBuilder::ClassicalRegister& +QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { + return allocatedClassicalRegisters.emplace_back(name, size); } Value QuartzProgramBuilder::measure(Value qubit) { @@ -104,18 +97,12 @@ Value QuartzProgramBuilder::measure(Value qubit) { return measureOp.getResult(); } -QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, Value memref, - int64_t index) { - // Measure the qubit - auto result = measure(qubit); - - // Create constant index for the store operation - auto indexValue = builder.create(loc, index); - - // Store the result in the memref at the given index - builder.create(loc, result, memref, - ValueRange{indexValue.getResult()}); - +QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, + const Bit& bit) { + auto nameAttr = builder.getStringAttr(bit.registerName); + auto sizeAttr = builder.getI64IntegerAttr(bit.registerSize); + auto indexAttr = builder.getI64IntegerAttr(bit.registerIndex); + builder.create(loc, qubit, nameAttr, sizeAttr, indexAttr); return *this; } @@ -126,7 +113,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { // Check if the qubit is in the tracking set - if (allocatedQubits.erase(qubit) == 0) { + if (!allocatedQubits.erase(qubit)) { // Qubit was not found in the set - either never allocated or already // deallocated llvm::errs() << "Error: Attempting to deallocate a qubit that was not " @@ -142,15 +129,19 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all remaining allocated qubits - for (Value qubit : allocatedQubits) { + for (const Value qubit : allocatedQubits) { builder.create(loc, qubit); } // Clear the tracking set allocatedQubits.clear(); - // Add return statement to the main function - builder.create(loc); + // Create constant 0 for successful exit code + auto exitCode = + builder.create(loc, builder.getI64IntegerAttr(0)); + + // Add return statement with exit code 0 to the main function + builder.create(loc, ValueRange{exitCode}); // Transfer ownership to the caller return module; diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 9828e00a53..eedd79ed05 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -81,3 +81,26 @@ LogicalResult AllocOp::verify() { } return success(); } + +LogicalResult MeasureOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 66b521f399..2dd82d030a 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -46,7 +46,8 @@ struct QregInfo { SmallVector qubits; }; -using BitMemInfo = std::pair; // (memref, localIdx) +using BitMemInfo = std::pair; // (register ref, localIdx) using BitIndexVec = SmallVector; /** @@ -128,13 +129,13 @@ buildQubitMap(const qc::QuantumComputation& quantumComputation, * @brief Allocates classical registers using the QuartzProgramBuilder * * @details - * Creates classical bit registers (memrefs) and builds a mapping from - * global classical bit indices to (memref, local_index) pairs. This is - * used for measurement result storage. + * Creates classical bit registers and builds a mapping from global classical + * bit indices to (register, local_index) pairs. This is used for measurement + * result storage. * * @param builder The QuartzProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate - * @return Vector mapping global bit indices to memref and local indices + * @return Vector mapping global bit indices to register and local indices */ BitIndexVec allocateClassicalRegisters(QuartzProgramBuilder& builder, @@ -157,11 +158,11 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, BitIndexVec bitMap; bitMap.resize(quantumComputation.getNcbits()); for (const auto* reg : cregPtrs) { - auto mem = + auto& mem = builder.allocClassicalBitRegister(reg->getSize(), reg->getName()); for (std::size_t i = 0; i < reg->getSize(); ++i) { const auto globalIdx = static_cast(reg->getStartIndex() + i); - bitMap[globalIdx] = {mem, i}; + bitMap[globalIdx] = {&mem, i}; } } @@ -178,7 +179,7 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, * @param builder The QuartzProgramBuilder used to create operations * @param operation The measurement operation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index - * @param bitMap Mapping from global bit index to (memref, local_index) + * @param bitMap Mapping from global bit index to (register, local_index) */ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const SmallVector& qubits, const BitIndexVec& bitMap) { @@ -192,8 +193,8 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const auto bitIdx = static_cast(classics[i]); const auto& [mem, localIdx] = bitMap[bitIdx]; - // Use builder's measure method which stores to memref - builder.measure(qubit, mem, localIdx); + // Use builder's measure method which keeps output record + builder.measure(qubit, (*mem)[localIdx]); } } @@ -231,7 +232,7 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param builder The QuartzProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index - * @param bitMap Mapping from global bit index to (memref, local_index) + * @param bitMap Mapping from global bit index to (register, local_index) * @return Success if all supported operations were translated */ LogicalResult @@ -270,11 +271,15 @@ translateOperations(QuartzProgramBuilder& builder, * translation. * * The translation process: - * 1. Creates a QuartzProgramBuilder and initializes it (creates main function) + * 1. Creates a QuartzProgramBuilder and initializes it (creates main function + * with signature () -> i64) * 2. Allocates quantum registers using quartz.alloc with register metadata - * 3. Allocates classical registers using memref.alloc + * 3. Tracks classical registers for measurement results * 4. Translates operations (currently: measure, reset) - * 5. Finalizes the module (adds return statement) + * 5. Finalizes the module (adds return statement with exit code 0) + * + * The generated main function returns exit code 0 to indicate successful + * execution of the quantum program. * * Currently supported operations: * - Measurement (quartz.measure) @@ -294,13 +299,13 @@ OwningOpRef translateQuantumComputationToQuartz( builder.initialize(); // Allocate quantum registers using the builder - auto qregs = allocateQregs(builder, quantumComputation); + const auto qregs = allocateQregs(builder, quantumComputation); // Build flat qubit mapping for easy lookup - auto qubits = buildQubitMap(quantumComputation, qregs); + const auto qubits = buildQubitMap(quantumComputation, qregs); // Allocate classical registers using the builder - auto bitMap = allocateClassicalRegisters(builder, quantumComputation); + const auto bitMap = allocateClassicalRegisters(builder, quantumComputation); // Translate operations if (translateOperations(builder, quantumComputation, qubits, bitMap) From 953b8c1d11a6b55f1351d143b8503d91f2895e0a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 14 Oct 2025 22:35:35 +0200 Subject: [PATCH 057/419] =?UTF-8?q?=E2=9C=A8=20implement=20classical=20reg?= =?UTF-8?q?ister=20structures=20and=20enhance=20measurement=20operations?= =?UTF-8?q?=20in=20Flux=20dialect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 63 ++++++++++++++----- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 33 +++++++++- .../Flux/Builder/FluxProgramBuilder.cpp | 47 +++++++------- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 23 +++++++ 4 files changed, 123 insertions(+), 43 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 5f5f03fdb1..e8b17730aa 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -12,7 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include +#include #include #include #include @@ -124,21 +124,53 @@ class FluxProgramBuilder { */ SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); + /** + * @brief A small structure representing a single classical bit within a + * classical register. + */ + struct Bit { + /// Name of the register containing this bit + StringRef registerName; + /// Size of the register containing this bit + int64_t registerSize{}; + /// Index of this bit within the register + int64_t registerIndex{}; + }; + + /** + * @brief A small structure representing a classical bit register. + */ + struct ClassicalRegister { + /// Name of the classical register + StringRef name; + /// Size of the classical register + int64_t size; + + /** + * @brief Access a specific bit in the classical register + * @param index The index of the bit to access (must be less than size) + * @return A Bit structure representing the specified bit + */ + Bit operator[](const int64_t index) const { + assert(index < size); + return { + .registerName = name, .registerSize = size, .registerIndex = index}; + } + }; + /** * @brief Allocate a classical bit register * @param size Number of bits * @param name Register name (default: "c") - * @return Memref of i1 elements + * @return A reference to a ClassicalRegister structure * * @par Example: * ```c++ * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` - * ```mlir - * %c = memref.alloca() {sym_name = "c"} : memref<3xi1> - * ``` */ - Value allocClassicalBitRegister(int64_t size, StringRef name = "c"); + ClassicalRegister& allocClassicalBitRegister(int64_t size, + StringRef name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset @@ -166,24 +198,21 @@ class FluxProgramBuilder { std::pair measure(Value qubit); /** - * @brief Measure a qubit and store result in a classical register + * @brief Measure a qubit and record the result in a bit of a register * * @param qubit Input qubit (must be valid/unconsumed) - * @param memref Classical register - * @param index Storage index + * @param bit The classical bit to record the result * @return Output qubit value * * @par Example: * ```c++ - * q0 = builder.measure(q0, c, 0); + * q0 = builder.measure(q0, c[0]); * ``` * ```mlir - * %q0_out, %r0 = flux.measure %q0 : !flux.qubit - * %c0 = arith.constant 0 : index - * memref.store %r0, %c[%c0] : memref<2xi1> + * %q0_out, %r0 = flux.measure("c", 3, 0) %q0 : !flux.qubit * ``` */ - Value measure(Value qubit, Value memref, int64_t index); + Value measure(Value qubit, const Bit& bit); /** * @brief Reset a qubit to |0⟩ state @@ -234,7 +263,8 @@ class FluxProgramBuilder { * * @details * Automatically deallocates all remaining valid qubits, adds a return - * statement, and transfers ownership of the module to the caller. + * statement with exit code 0 (indicating successful execution), and + * transfers ownership of the module to the caller. * The builder should not be used after calling this method. * * @return OwningOpRef containing the constructed quantum program module @@ -269,5 +299,8 @@ class FluxProgramBuilder { /// When an operation consumes a qubit and produces a new one, the old value /// is removed and the new output is added. llvm::DenseSet validQubits; + + /// Track allocated classical Registers + SmallVector allocatedClassicalRegisters; }; } // namespace mlir::flux diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 4b65c60566..014296c5a3 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -181,14 +181,41 @@ def MeasureOp : FluxOp<"measure"> { Measures a qubit in the computational (Z) basis, collapsing the state and returning both the output qubit and a classical bit result. - Example: + Optionally, the measurement can be recorded to an output register by + specifying: + - `register_name`: Name of the classical register (e.g., "c") + - `register_size`: Total size of the register + - `register_index`: Index within the register for this measurement + + Example (simple measurement): + ```mlir + %q_out, %result = flux.measure %q_in : !flux.qubit + ``` + + Example (measurement with output recording): ```mlir - %q_out, %c = flux.measure %q_in : !flux.qubit + %q_out, %result = flux.measure("c", 2, 0) %q_in : !flux.qubit ``` }]; - let arguments = (ins QubitType:$qubit_in); + let arguments = (ins QubitType:$qubit_in, + OptionalAttr:$register_name, + OptionalAttr>:$register_size, + OptionalAttr>:$register_index); let results = (outs QubitType:$qubit_out, I1:$result); + let assemblyFormat = [{ + (`(` $register_name^ `,` $register_size `,` $register_index `)`)? + $qubit_in `:` type($qubit_in) attr-dict + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), $_builder.getI1Type(), + qubit_in, nullptr, nullptr, nullptr); + }]> + ]; + + let hasVerifier = 1; } def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 1be8c8d601..edfd0f46f5 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include +#include #include #include #include @@ -35,7 +36,7 @@ void FluxProgramBuilder::initialize() { builder.setInsertionPointToStart(module.getBody()); // Create main function as entry point - auto funcType = builder.getFunctionType({}, {}); + auto funcType = builder.getFunctionType({}, {builder.getI64Type()}); auto mainFunc = builder.create(loc, "main", funcType); // Add entry_point attribute to identify the main function @@ -58,7 +59,7 @@ Value FluxProgramBuilder::allocQubit() { return qubit; } -Value FluxProgramBuilder::staticQubit(int64_t index) { +Value FluxProgramBuilder::staticQubit(const int64_t index) { auto indexAttr = builder.getI64IntegerAttr(index); auto staticOp = builder.create(loc, indexAttr); const auto qubit = staticOp.getQubit(); @@ -89,17 +90,9 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, return qubits; } -Value FluxProgramBuilder::allocClassicalBitRegister(int64_t size, - StringRef name) { - // Create memref type - auto memrefType = MemRefType::get({size}, builder.getI1Type()); - - // Allocate the memref - auto allocOp = builder.create(loc, memrefType); - - allocOp->setAttr("sym_name", builder.getStringAttr(name)); - - return allocOp.getResult(); +FluxProgramBuilder::ClassicalRegister& +FluxProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { + return allocatedClassicalRegisters.emplace_back(name, size); } //===----------------------------------------------------------------------===// @@ -143,17 +136,16 @@ std::pair FluxProgramBuilder::measure(Value qubit) { return {qubitOut, result}; } -Value FluxProgramBuilder::measure(const Value qubit, Value memref, - int64_t index) { - // Measure the qubit - auto [qubitOut, result] = measure(qubit); - - // Create constant index for the store operation - auto indexValue = builder.create(loc, index); +Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { + auto nameAttr = builder.getStringAttr(bit.registerName); + auto sizeAttr = builder.getI64IntegerAttr(bit.registerSize); + auto indexAttr = builder.getI64IntegerAttr(bit.registerIndex); + auto measureOp = + builder.create(loc, qubit, nameAttr, sizeAttr, indexAttr); + const auto qubitOut = measureOp.getQubitOut(); - // Store the result in the memref at the given index - builder.create(loc, result, memref, - ValueRange{indexValue.getResult()}); + // Update tracking + updateQubitTracking(qubit, qubitOut); return qubitOut; } @@ -179,13 +171,18 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { OwningOpRef FluxProgramBuilder::finalize() { // Automatically deallocate all remaining valid qubits - for (Value qubit : validQubits) { + for (const auto qubit : validQubits) { builder.create(loc, qubit); } validQubits.clear(); - builder.create(loc); + // Create constant 0 for successful exit code + auto exitCode = + builder.create(loc, builder.getI64IntegerAttr(0)); + + // Add return statement with exit code 0 to the main function + builder.create(loc, ValueRange{exitCode}); return module; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 7ca3478443..648320fb26 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -83,6 +83,29 @@ LogicalResult AllocOp::verify() { return success(); } +LogicalResult MeasureOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// From d0deb0ec7a638d68038505ed0c9071f1dc60fbf3 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 14 Oct 2025 22:36:16 +0200 Subject: [PATCH 058/419] =?UTF-8?q?=E2=9C=A8=20enhance=20QIR=20program=20s?= =?UTF-8?q?tructure=20and=20measurement=20operations=20with=20register=20s?= =?UTF-8?q?upport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 120 +++++++--- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 + .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 209 +++++++++++++----- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 6 +- 4 files changed, 255 insertions(+), 82 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 871e47c500..0a187fc317 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -35,12 +35,12 @@ namespace mlir::qir { * boilerplate structure including proper block organization and metadata * attributes. * - * @par QIR Structure: - * QIR programs follow a specific structure with: + * @par QIR Base Profile Structure: + * QIR Base Profile compliant programs follow a specific 4-block structure: * - Entry block: Constants and initialization (__quantum__rt__initialize) - * - Main block: Reversible quantum operations (gates) - * - Irreversible block: Measurements, resets, deallocations - * - End block: Return statement + * - Body block: Reversible quantum operations (gates) + * - Measurements block: Measurements, resets, deallocations + * - Output block: Output recording calls (array-based, grouped by register) * * @par Example Usage: * ```c++ @@ -52,7 +52,10 @@ namespace mlir::qir { * * // Operations use QIR function calls * builder.h(q0).cx(q0, q1); - * auto result = builder.measure(q0, 0); + * + * // Measure with register info for proper output recording + * builder.measure(q0, "c", 0); + * builder.measure(q1, "c", 1); * * auto module = builder.finalize(); * ``` @@ -135,15 +138,16 @@ class QIRProgramBuilder { //===--------------------------------------------------------------------===// /** - * @brief Measure a qubit and record the result + * @brief Measure a qubit and record the result (simple version) * * @details - * Performs a Z-basis measurement using __quantum__qis__mz__body and - * records the result using __quantum__rt__result_record_output. The - * result is labeled with the provided index (e.g., "r0", "r1"). + * Performs a Z-basis measurement using __quantum__qis__mz__body. The + * result is tracked for deferred output recording in the output block. + * This version does NOT include register information, so output will + * not be grouped by register. * * @param qubit The qubit to measure - * @param resultIndex The classical bit index for result labeling + * @param resultIndex The classical bit index for result pointer * @return An LLVM pointer to the measurement result * * @par Example: @@ -151,15 +155,55 @@ class QIRProgramBuilder { * auto result = builder.measure(q0, 0); * ``` * ```mlir + * // In measurements block: * %c0 = llvm.mlir.constant(0 : i64) : i64 * %r = llvm.inttoptr %c0 : i64 to !llvm.ptr - * llvm.call @__quantum__qis__mz__body(%q0, %r) : (!llvm.ptr, !llvm.ptr) -> - * () llvm.call @__quantum__rt__result_record_output(%r, %label0) : - * (!llvm.ptr, !llvm.ptr) -> () + * llvm.call @__quantum__qis__mz__body(%q0, %r) : (!llvm.ptr, !llvm.ptr) -> () + * + * // Output recording deferred to output block * ``` */ Value measure(Value qubit, size_t resultIndex); + /** + * @brief Measure a qubit into a classical register + * + * @details + * Performs a Z-basis measurement using __quantum__qis__mz__body and tracks + * the measurement with register information for array-based output recording. + * Output recording is deferred to the output block during finalize(), where + * measurements are grouped by register and recorded using: + * 1. __quantum__rt__array_record_output for each register + * 2. __quantum__rt__result_record_output for each measurement in the register + * + * @param qubit The qubit to measure + * @param registerName The name of the classical register (e.g., "c") + * @param registerIndex The index within the register for this measurement + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.measure(q0, "c", 0); + * builder.measure(q1, "c", 1); + * ``` + * ```mlir + * // In measurements block: + * llvm.call @__quantum__qis__mz__body(%q0, %r0) : (!llvm.ptr, !llvm.ptr) -> + * () llvm.call @__quantum__qis__mz__body(%q1, %r1) : (!llvm.ptr, !llvm.ptr) + * -> () + * + * // In output block (generated during finalize): + * @0 = internal constant [3 x i8] c"c\00" + * @1 = internal constant [5 x i8] c"c0r\00" + * @2 = internal constant [5 x i8] c"c1r\00" + * llvm.call @__quantum__rt__array_record_output(i64 2, ptr @0) + * llvm.call @__quantum__rt__result_record_output(ptr %r0, ptr @1) + * llvm.call @__quantum__rt__result_record_output(ptr %r1, ptr @2) + * ``` + */ + QIRProgramBuilder& measure(Value qubit, StringRef registerName, + size_t registerIndex); + /** * @brief Reset a qubit to |0⟩ state * @@ -207,9 +251,11 @@ class QIRProgramBuilder { * @brief Finalize the program and return the constructed module * * @details - * Automatically deallocates all remaining allocated qubits, ensures proper - * QIR metadata attributes are set, and transfers ownership of the module to - * the caller. The builder should not be used after calling this method. + * Automatically deallocates all remaining allocated qubits, generates + * array-based output recording in the output block (grouped by register), + * ensures proper QIR metadata attributes are set, and transfers ownership + * of the module to the caller. The builder should not be used after calling + * this method. * * @return OwningOpRef containing the constructed QIR program module */ @@ -223,27 +269,45 @@ class QIRProgramBuilder { /// Entry block: constants and initialization Block* entryBlock{}; - /// Main block: reversible operations (gates) - Block* mainBlock{}; - /// Irreversible block: measurements, resets, deallocations - Block* irreversibleBlock{}; - /// End block: return statement - Block* endBlock{}; + /// Body block: reversible operations (gates) + Block* bodyBlock{}; + /// Measurements block: measurements, resets, deallocations + Block* measurementsBlock{}; + /// Output block: output recording calls + Block* outputBlock{}; + + /// Exit code constant (created in entry block, used in output block) + LLVM::ConstantOp exitCode; /// Track allocated qubits for automatic deallocation - llvm::DenseSet allocatedQubits; + DenseSet allocatedQubits; /// Cache static qubit pointers for reuse - llvm::DenseMap staticQubitCache; + DenseMap staticQubitCache; /// Cache result pointers for reuse (separate from qubits) - llvm::DenseMap resultPointerCache; + DenseMap resultPointerCache; - /// Track result labels to avoid duplicates - llvm::DenseMap resultLabelCache; + /// Map from (register_name, register_index) to result pointer + DenseMap, Value> registerResultMap; + + /// Sequence of measurements to record in output block + /// Each entry: (result_ptr, register_name, register_index) + SmallVector> measurementSequence; /// Track qubit and result counts for QIR metadata QIRMetadata metadata_; + + /** + * @brief Generate array-based output recording in the output block + * + * @details + * Called by finalize() to generate output recording calls for all tracked + * measurements. Groups measurements by register and generates: + * 1. array_record_output for each register + * 2. result_record_output for each measurement in the register + */ + void generateOutputRecording(); }; } // namespace mlir::qir diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index abd882e7fc..98aa280d78 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -23,6 +23,8 @@ static constexpr auto QIR_QUBIT_ALLOCATE = "__quantum__rt__qubit_allocate"; static constexpr auto QIR_QUBIT_RELEASE = "__quantum__rt__qubit_release"; static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; +static constexpr auto QIR_ARRAY_RECORD_OUTPUT = + "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; /** diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index e067ae1237..f19d5e8259 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace mlir::qir { @@ -34,9 +35,8 @@ void QIRProgramBuilder::initialize() { // Set insertion point to the module body builder.setInsertionPointToStart(module.getBody()); - // Create main function: () -> void - auto funcType = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), {}); + // Create main function: () -> i64 + auto funcType = LLVM::LLVMFunctionType::get(builder.getI64Type(), {}); mainFunc = builder.create(loc, "main", funcType); // Add entry_point attribute to identify the main function @@ -44,33 +44,19 @@ void QIRProgramBuilder::initialize() { mainFunc->setAttr("passthrough", ArrayAttr::get(builder.getContext(), {entryPointAttr})); - // Create the 4-block structure for QIR base profile + // Create the 4-block structure for QIR Base Profile entryBlock = mainFunc.addEntryBlock(builder); - mainBlock = mainFunc.addBlock(); - irreversibleBlock = mainFunc.addBlock(); - endBlock = mainFunc.addBlock(); + bodyBlock = mainFunc.addBlock(); + measurementsBlock = mainFunc.addBlock(); + outputBlock = mainFunc.addBlock(); - // Add unconditional branches between blocks - builder.setInsertionPointToEnd(entryBlock); - builder.create(loc, mainBlock); - - builder.setInsertionPointToEnd(mainBlock); - builder.create(loc, irreversibleBlock); - - builder.setInsertionPointToEnd(irreversibleBlock); - builder.create(loc, endBlock); - - builder.setInsertionPointToEnd(endBlock); - builder.create(loc, ValueRange{}); - - // Add QIR initialization call in entry block + // Create exit code constant in entry block (where constants belong) and add + // QIR initialization call in entry block (after exit code constant) builder.setInsertionPointToStart(entryBlock); auto zeroOp = builder.create( loc, LLVM::LLVMPointerType::get(builder.getContext())); - - // Move to before the branch - builder.setInsertionPoint(entryBlock->getTerminator()); - + exitCode = + builder.create(loc, builder.getI64IntegerAttr(0)); const auto initType = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())); @@ -78,8 +64,22 @@ void QIRProgramBuilder::initialize() { getOrCreateFunctionDeclaration(builder, module, QIR_INITIALIZE, initType); builder.create(loc, initFunc, ValueRange{zeroOp.getResult()}); - // Set insertion point to main block for user operations - builder.setInsertionPointToStart(mainBlock); + // Add unconditional branches between blocks + builder.setInsertionPointToEnd(entryBlock); + builder.create(loc, bodyBlock); + + builder.setInsertionPointToEnd(bodyBlock); + builder.create(loc, measurementsBlock); + + builder.setInsertionPointToEnd(measurementsBlock); + builder.create(loc, outputBlock); + + // Return the exit code (success) in output block + builder.setInsertionPointToEnd(outputBlock); + builder.create(loc, ValueRange{exitCode.getResult()}); + + // Set insertion point to body block for user operations + builder.setInsertionPointToStart(bodyBlock); } Value QIRProgramBuilder::allocQubit() { @@ -123,7 +123,7 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { staticQubitCache[static_cast(index)] = qubit; // Update qubit count - if (static_cast(index) >= metadata_.numQubits) { + if (std::cmp_greater_equal(index, metadata_.numQubits)) { metadata_.numQubits = static_cast(index) + 1; } @@ -145,8 +145,8 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); - // Insert in irreversible block (before branch) - builder.setInsertionPoint(irreversibleBlock->getTerminator()); + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); @@ -161,8 +161,11 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { createPointerFromIndex(builder, loc, static_cast(resultIndex)); resultPointerCache[resultIndex] = resultValue; - // Restore to irreversible block - builder.setInsertionPoint(irreversibleBlock->getTerminator()); + // Track for output recording + metadata_.numResults++; + + // Restore to measurements block + builder.setInsertionPoint(measurementsBlock->getTerminator()); } // Create mz call @@ -172,35 +175,63 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); - // Get or create result label - LLVM::AddressOfOp labelOp; - if (resultLabelCache.contains(resultIndex)) { - labelOp = resultLabelCache.at(resultIndex); + // Track this measurement for deferred output recording (without register + // info) + measurementSequence.emplace_back(resultValue, "c", resultIndex); + + return resultValue; +} + +QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, + const StringRef registerName, + const size_t registerIndex) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); + + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + + // Check if we already have a result pointer for this register slot + const auto key = std::make_pair(registerName, registerIndex); + + Value resultValue = nullptr; + if (const auto it = registerResultMap.find(key); + it != registerResultMap.end()) { + resultValue = it->second; } else { - // Use common utility function to create result label - labelOp = - createResultLabel(builder, module, "r" + std::to_string(resultIndex)); - resultLabelCache.try_emplace(resultIndex, labelOp); + // Create at start of entry block + builder.setInsertionPoint(entryBlock->getTerminator()); + resultValue = createPointerFromIndex( + builder, loc, static_cast(metadata_.numResults)); + // Cache for reuse + resultPointerCache[metadata_.numResults] = resultValue; + registerResultMap.insert({key, resultValue}); metadata_.numResults++; + // Restore to measurements block + builder.setInsertionPoint(measurementsBlock->getTerminator()); } - // Create record_output call - const auto recordSignature = LLVM::LLVMFunctionType::get( + // Create mz call + const auto mzSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); - auto recordDecl = getOrCreateFunctionDeclaration( - builder, module, QIR_RECORD_OUTPUT, recordSignature); - builder.create(loc, recordDecl, - ValueRange{resultValue, labelOp.getResult()}); + auto mzDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); + builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); - return resultValue; + // Track this measurement for deferred output recording (with register info) + measurementSequence.emplace_back(resultValue, registerName, registerIndex); + + return *this; } QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); - // Insert in irreversible block (before branch) - builder.setInsertionPoint(irreversibleBlock->getTerminator()); + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); // Create reset call const auto qirSignature = LLVM::LLVMFunctionType::get( @@ -219,8 +250,8 @@ QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); - // Insert in irreversible block (before branch) - builder.setInsertionPoint(irreversibleBlock->getTerminator()); + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); // Create release call const auto qirSignature = LLVM::LLVMFunctionType::get( @@ -233,12 +264,88 @@ QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { return *this; } +void QIRProgramBuilder::generateOutputRecording() { + if (measurementSequence.empty()) { + return; // No measurements to record + } + + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in output block (before return) + builder.setInsertionPoint(outputBlock->getTerminator()); + + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + + // Group measurements by register + llvm::StringMap>> registerGroups; + for (const auto& [resultValue, regName, regIdx] : measurementSequence) { + if (!regName.empty()) { + registerGroups[regName].emplace_back(regIdx, resultValue); + } + } + + // Sort registers by name for deterministic output + SmallVector>>> + sortedRegisters; + for (auto& [name, measurements] : registerGroups) { + sortedRegisters.emplace_back(name, std::move(measurements)); + } + llvm::sort(sortedRegisters, + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Create array_record_output call + const auto arrayRecordSig = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(builder.getContext()), + {builder.getI64Type(), ptrType}); + const auto arrayRecordDecl = getOrCreateFunctionDeclaration( + builder, module, QIR_ARRAY_RECORD_OUTPUT, arrayRecordSig); + + // Create result_record_output calls for each measurement + const auto resultRecordSig = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); + const auto resultRecordDecl = getOrCreateFunctionDeclaration( + builder, module, QIR_RECORD_OUTPUT, resultRecordSig); + + // Generate output recording for each register + for (auto& [registerName, measurements] : sortedRegisters) { + // Sort measurements by register index + llvm::sort(measurements, + [](const auto& a, const auto& b) { return a.first < b.first; }); + + const auto arraySize = measurements.size(); + auto arrayLabelOp = createResultLabel(builder, module, registerName); + auto arraySizeConst = builder.create( + loc, builder.getI64IntegerAttr(static_cast(arraySize))); + + builder.create( + loc, arrayRecordDecl, + ValueRange{arraySizeConst.getResult(), arrayLabelOp.getResult()}); + + for (size_t i = 0; i < measurements.size(); ++i) { + const auto [regIdx, resultPtr] = measurements[i]; + + // Create label for result: "{registerName}{i}r" + const std::string resultLabel = + registerName.str() + std::to_string(i) + "r"; + auto resultLabelOp = createResultLabel(builder, module, resultLabel); + + builder.create( + loc, resultRecordDecl, + ValueRange{resultPtr, resultLabelOp.getResult()}); + } + } +} + OwningOpRef QIRProgramBuilder::finalize() { for (const Value qubit : allocatedQubits) { dealloc(qubit); } allocatedQubits.clear(); + // Generate output recording in the output block + generateOutputRecording(); + setQIRAttributes(mainFunc, metadata_); return module; diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index 93202a0bbe..ee7da1acd1 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -49,7 +49,7 @@ void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata) { // Core QIR attributes attributes.emplace_back(builder.getStringAttr("entry_point")); attributes.emplace_back( - builder.getStrArrayAttr({"output_labeling_schema", "schema_id"})); + builder.getStrArrayAttr({"output_labeling_schema", "labeled"})); attributes.emplace_back( builder.getStrArrayAttr({"qir_profiles", "base_profile"})); @@ -59,8 +59,8 @@ void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata) { attributes.emplace_back(builder.getStrArrayAttr( {"required_num_results", std::to_string(metadata.numResults)})); - // QIR version - attributes.emplace_back(builder.getStrArrayAttr({"qir_major_version", "1"})); + // QIR version (Base Profile spec requires version 2.0) + attributes.emplace_back(builder.getStrArrayAttr({"qir_major_version", "2"})); attributes.emplace_back(builder.getStrArrayAttr({"qir_minor_version", "0"})); // Management model From feac898936342609b9a88242113c3a2a1174c6fc Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 14 Oct 2025 22:36:38 +0200 Subject: [PATCH 059/419] =?UTF-8?q?=E2=9C=A8=20enhance=20dialect=20convers?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 11 +- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 17 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 344 +++++++++++------- 3 files changed, 225 insertions(+), 147 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 4ec9be2e7b..790bfd73e4 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -163,11 +163,14 @@ struct ConvertFluxStaticOp final : OpConversionPattern { * alongside the measurement bit. MLIR's conversion infrastructure automatically * routes subsequent uses of the Flux output qubit to this Quartz reference. * + * Register metadata (name, size, index) for output recording is preserved + * during conversion. + * * Example transformation: * ```mlir - * %q_out, %c = flux.measure %q_in : !flux.qubit -> (!flux.qubit, i1) + * %q_out, %c = flux.measure("c", 2, 0) %q_in : !flux.qubit * // becomes: - * %c = quartz.measure %q : !quartz.qubit -> i1 + * %c = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 * // %q_out uses are replaced with %q (the adaptor-converted input) * ``` */ @@ -181,8 +184,10 @@ struct ConvertFluxMeasureOp final : OpConversionPattern { const auto& quartzQubit = adaptor.getQubitIn(); // Create quartz.measure (in-place operation, returns only bit) + // Preserve register metadata for output recording auto quartzOp = rewriter.create( - op.getLoc(), op.getResult().getType(), quartzQubit); + op.getLoc(), quartzQubit, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); auto measureBit = quartzOp.getResult(); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index ba4d7ddb27..13a012149d 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -265,11 +265,14 @@ struct ConvertQuartzStaticOp final * performs the measurement, updates the mapping with the output qubit, * and returns the classical bit result. * + * Register metadata (name, size, index) for output recording is preserved + * during conversion. + * * Example transformation: * ```mlir - * %c = quartz.measure %q : !quartz.qubit -> i1 + * %c = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 * // becomes (where %q maps to %q_in): - * %q_out, %c = flux.measure %q_in : !flux.qubit -> (!flux.qubit, i1) + * %q_out, %c = flux.measure("c", 2, 0) %q_in : !flux.qubit * // state updated: %q now maps to %q_out * ``` */ @@ -281,9 +284,6 @@ struct ConvertQuartzMeasureOp final matchAndRewrite(quartz::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - // Prepare result type - const auto& qubitType = flux::QubitType::get(rewriter.getContext()); - const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map @@ -291,10 +291,11 @@ struct ConvertQuartzMeasureOp final // Create flux.measure operation (returns both output qubit and bit result) auto fluxOp = rewriter.create( - op.getLoc(), qubitType, op.getResult().getType(), fluxQubit); + op.getLoc(), fluxQubit, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - auto outFluxQubit = fluxOp.getQubitOut(); - auto newBit = fluxOp.getResult(); + const auto outFluxQubit = fluxOp.getQubitOut(); + const auto newBit = fluxOp.getResult(); // Update mapping: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = outFluxQubit; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index bf4dc409d5..6df981ba5c 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -61,16 +61,23 @@ namespace { * operations to QIR (Quantum Intermediate Representation). It tracks: * - Qubit and result counts for QIR metadata * - Pointer value caching for reuse - * - Result output mapping * - Whether dynamic memory management is needed + * - Sequence of measurements for output recording */ struct LoweringState : QIRMetadata { /// Map from qubit index to pointer value for reuse DenseMap ptrMap; - /// Map from result index to addressOf operation for output recording - DenseMap outputMap; - /// Index for the next measure operation label - size_t index{}; + + /// Map from classical result index to pointer value for reuse + DenseMap resultPtrMap; + + /// Map from (register_name, register_index) to result pointer + /// This allows caching result pointers for measurements with register info + DenseMap, Value> registerResultMap; + + /// Sequence of measurements to record in output block + /// Each entry: (result_ptr, register_name, register_index) + SmallVector> measurementSequence; }; /** @@ -297,37 +304,28 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { }; /** - * @brief Converts quartz.measure operation to QIR measurement and output - * recording + * @brief Converts quartz.measure operation to QIR measurement * * @details - * Converts qubit measurement to two QIR calls: - * 1. `__quantum__qis__mz__body`: Performs the measurement - * 2. `__quantum__rt__result_record_output`: Records the result for output + * Converts qubit measurement to a QIR call to `__quantum__qis__mz__body`. + * Unlike the previous implementation, this does NOT immediately record output. + * Instead, it tracks measurements in the lowering state for deferred output + * recording in a separate output block, as required by the QIR Base Profile. * - * The converter examines the users of the measurement result to find - * memref.store operations, extracting the classical register index from them. - * This ensures faithful translation of which classical bit receives the - * measurement result, including cases where multiple measurements target the - * same bit. + * For measurements with register information, the result pointer is mapped + * to (register_name, register_index) for later retrieval. For measurements + * without register information, a sequential result pointer is assigned. * - * The result pointer is created using inttoptr and cached for reuse. A global - * constant is created for the result label (r0, r1, etc.) based on the - * classical register index. - * - * @par Example: + * @par Example (with register): * ```mlir - * %result = quartz.measure %q : !quartz.qubit -> i1 - * %c0 = arith.constant 0 : index - * memref.store %result, %creg[%c0] : memref<2xi1> + * %result = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 * ``` * becomes: * ```mlir * %c0_i64 = llvm.mlir.constant(0 : i64) : i64 * %result_ptr = llvm.inttoptr %c0_i64 : i64 to !llvm.ptr * llvm.call @__quantum__qis__mz__body(%q, %result_ptr) : (!llvm.ptr, !llvm.ptr) - * -> () llvm.call @__quantum__rt__result_record_output(%result_ptr, %label0) : - * (!llvm.ptr, !llvm.ptr) -> () + * -> () * ``` */ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { @@ -337,23 +335,53 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { matchAndRewrite(MeasureOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); - auto& ptrMap = getState().ptrMap; - auto& outputMap = getState().outputMap; const auto ptrType = LLVM::LLVMPointerType::get(ctx); + auto& state = getState(); + auto& numResults = state.numResults; + auto& resultPtrMap = state.resultPtrMap; + auto& registerResultMap = state.registerResultMap; + auto& measurementSequence = state.measurementSequence; + + // Get or create result pointer value + Value resultValue; + if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { + const auto registerName = op.getRegisterName().value(); + const auto registerIndex = + static_cast(op.getRegisterIndex().value()); + const auto key = std::make_pair(registerName, registerIndex); + + if (const auto it = registerResultMap.find(key); + it != registerResultMap.end()) { + resultValue = it->second; + } else { + const auto constantOp = rewriter.create( + op.getLoc(), + rewriter.getI64IntegerAttr( + static_cast(numResults))); // Sequential result index + resultValue = rewriter + .create(op.getLoc(), ptrType, + constantOp->getResult(0)) + .getResult(); + resultPtrMap[numResults] = resultValue; + registerResultMap.insert({key, resultValue}); + numResults++; + } - // Get or create result pointer - Value resultValue = nullptr; - if (ptrMap.contains(getState().numResults)) { - resultValue = ptrMap.at(getState().numResults); + // Track this measurement for output recording + measurementSequence.emplace_back(resultValue, registerName, + registerIndex); } else { + // No register info - assign sequential result pointer const auto constantOp = rewriter.create( - op.getLoc(), rewriter.getI64IntegerAttr( - static_cast(getState().numResults))); + op.getLoc(), rewriter.getI64IntegerAttr(static_cast( + numResults))); // Sequential result index resultValue = rewriter .create(op.getLoc(), ptrType, constantOp->getResult(0)) .getResult(); - ptrMap.try_emplace(getState().numResults, resultValue); + resultPtrMap[numResults] = resultValue; + measurementSequence.emplace_back(resultValue, "c", numResults); + numResults++; } // Create mz (measure) call: mz(qubit, result) @@ -364,72 +392,6 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { rewriter.create(op.getLoc(), mzDecl, ValueRange{adaptor.getQubit(), resultValue}); - // Find the classical register index by examining store operations - size_t index = 0; - for (const auto* user : op->getResult(0).getUsers()) { - if (auto storeOp = dyn_cast(user)) { - // Extract the index from the store operation - auto constantOp = - storeOp.getIndices()[0].getDefiningOp(); - if (!constantOp) { - op.emitError("Measurement index could not be resolved. Index is not " - "a ConstantOp."); - return failure(); - } - - const auto integerAttr = dyn_cast(constantOp.getValue()); - if (!integerAttr) { - op.emitError("Measurement index could not be resolved. Index cannot " - "be cast to IntegerAttr."); - return failure(); - } - index = integerAttr.getInt(); - - const auto allocaOp = storeOp.getMemref(); - - // Clean up the store operation and related ops - storeOp->dropAllReferences(); - rewriter.eraseOp(storeOp); - - // Erase the alloca if all stores are removed - if (allocaOp.use_empty()) { - rewriter.eraseOp(allocaOp.getDefiningOp()); - } - - // Delete the constant if there are no users left - if (constantOp->use_empty()) { - rewriter.eraseOp(constantOp); - } - break; - } - } - - // Create result_record_output call - const auto recordSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); - const auto recordDecl = getOrCreateFunctionDeclaration( - rewriter, op, QIR_RECORD_OUTPUT, recordSignature); - - // Get or create output label addressOf for this index - Operation* labelOp = nullptr; - if (outputMap.contains(index)) { - // Reuse existing label for this index (handles multiple measurements to - // same bit) - labelOp = outputMap.at(index); - } else { - // Create new label - labelOp = createResultLabel(rewriter, op, - "r" + std::to_string(getState().index)); - outputMap.try_emplace(index, labelOp); - // Only increment result count for new labels - getState().numResults++; - } - - rewriter.create( - op.getLoc(), recordDecl, - ValueRange{resultValue, labelOp->getResult(0)}); - - // Remove the original operation rewriter.eraseOp(op); return success(); @@ -468,14 +430,14 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * @details * The QIR base profile requires a specific 4-block structure: * 1. **Entry block**: Contains constant operations and initialization - * 2. **Main block**: Contains reversible quantum operations (gates) - * 3. **Irreversible block**: Contains irreversible operations (measure, + * 2. **Body block**: Contains reversible quantum operations (gates) + * 3. **Measurements block**: Contains irreversible operations (measure, * reset, dealloc) - * 4. **End block**: Contains return operation + * 4. **Output block**: Contains output recording calls * - * Blocks are connected with unconditional jumps (entry → main → - * irreversible → end). This structure ensures proper QIR semantics and - * enables optimizations. + * Blocks are connected with unconditional jumps (entry → body → + * measurements → output). This structure ensures proper QIR Base + * Profile semantics. * * If the function already has multiple blocks, this function does nothing. * @@ -488,52 +450,50 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { } // Get the existing block - auto* mainBlock = &main.front(); + auto* bodyBlock = &main.front(); OpBuilder builder(main.getBody()); // Create the required blocks auto* entryBlock = builder.createBlock(&main.getBody()); - // Move the entry block before the main block - main.getBlocks().splice(Region::iterator(mainBlock), main.getBlocks(), + // Move the entry block before the body block + main.getBlocks().splice(Region::iterator(bodyBlock), main.getBlocks(), entryBlock); - Block* irreversibleBlock = builder.createBlock(&main.getBody()); - Block* endBlock = builder.createBlock(&main.getBody()); + Block* measurementsBlock = builder.createBlock(&main.getBody()); + Block* outputBlock = builder.createBlock(&main.getBody()); - auto& mainBlockOps = mainBlock->getOperations(); - auto& endBlockOps = endBlock->getOperations(); - auto& irreversibleBlockOps = irreversibleBlock->getOperations(); + auto& bodyBlockOps = bodyBlock->getOperations(); + auto& outputBlockOps = outputBlock->getOperations(); + auto& measurementsBlockOps = measurementsBlock->getOperations(); // Move operations to appropriate blocks - for (auto it = mainBlock->begin(); it != mainBlock->end();) { + for (auto it = bodyBlock->begin(); it != bodyBlock->end();) { // Ensure iterator remains valid after potential move - auto& op = *it++; - - // Check for irreversible operations - if (op.getDialect()->getNamespace() == "memref") { - // Keep memref operations for classical bits in place (they're not - // quantum operations) - continue; - } - if (isa(op) || isa(op) || isa(op)) { - // Move irreversible quantum operations to irreversible block - irreversibleBlockOps.splice(irreversibleBlock->end(), mainBlockOps, + if (auto& op = *it++; + isa(op) || isa(op) || isa(op)) { + // Move irreversible quantum operations to measurements block + measurementsBlockOps.splice(measurementsBlock->end(), bodyBlockOps, Block::iterator(op)); } else if (isa(op)) { - // Move return to end block - endBlockOps.splice(endBlock->end(), mainBlockOps, Block::iterator(op)); + // Move return to output block + outputBlockOps.splice(outputBlock->end(), bodyBlockOps, + Block::iterator(op)); + } else if (op.hasTrait()) { + // Move constant like operations to the entry block + entryBlock->getOperations().splice(entryBlock->end(), bodyBlockOps, + Block::iterator(op)); } - // All other operations (gates, etc.) stay in main block + // All other operations (gates, etc.) stay in body block } // Add unconditional jumps between blocks builder.setInsertionPointToEnd(entryBlock); - builder.create(main->getLoc(), mainBlock); + builder.create(main->getLoc(), bodyBlock); - builder.setInsertionPointToEnd(mainBlock); - builder.create(main->getLoc(), irreversibleBlock); + builder.setInsertionPointToEnd(bodyBlock); + builder.create(main->getLoc(), measurementsBlock); - builder.setInsertionPointToEnd(irreversibleBlock); - builder.create(main->getLoc(), endBlock); + builder.setInsertionPointToEnd(measurementsBlock); + builder.create(main->getLoc(), outputBlock); } /** @@ -547,7 +507,6 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * * @param main The main LLVM function * @param ctx The MLIR context - * @param state The lowering state (unused but kept for consistency) */ static void addInitialize(LLVM::LLVMFuncOp& main, MLIRContext* ctx, LoweringState* /*state*/) { @@ -581,6 +540,117 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { ValueRange{zeroOp->getResult(0)}); } + /** + * @brief Adds output recording calls to the output block + * + * @details + * Generates output recording calls in the output block based on the + * measurements tracked during conversion. Follows the QIR Base Profile + * specification for labeled output schema. + * + * For each classical register, creates: + * 1. An array_record_output call with the register size and label + * 2. Individual result_record_output calls for each measurement in the + * register + * + * Labels follow the format: "{registerName}{resultIndex}r" + * - registerName: Name of the classical register (e.g., "c") + * - resultIndex: Index within the array + * - 'r' suffix: Indicates this is a result record + * + * Example output: + * ``` + * @0 = internal constant [3 x i8] c"c\00" + * @1 = internal constant [5 x i8] c"c0r\00" + * @2 = internal constant [5 x i8] c"c1r\00" + * call void @__quantum__rt__array_record_output(i64 2, ptr @0) + * call void @__quantum__rt__result_record_output(ptr %result0, ptr @1) + * call void @__quantum__rt__result_record_output(ptr %result1, ptr @2) + * ``` + * + * Any output recording calls that are not part of registers (i.e., + * measurements without register info) are grouped under a default label + * "c" and recorded similarly. + * + * @param main The main LLVM function + * @param ctx The MLIR context + * @param state The lowering state containing measurement information + */ + static void addOutputRecording(LLVM::LLVMFuncOp& main, MLIRContext* ctx, + LoweringState* state) { + if (state->measurementSequence.empty()) { + return; // No measurements to record + } + + OpBuilder builder(ctx); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + + // Find the output block (4th block: entry, body, measurements, output, end) + auto& outputBlock = main.getBlocks().back(); + + // Insert before the branch to end block + builder.setInsertionPoint(&outputBlock.back()); + + // Group measurements by register + llvm::StringMap>> registerGroups; + for (const auto& [resultPtr, regName, regIdx] : + state->measurementSequence) { + if (!regName.empty()) { + registerGroups[regName].emplace_back(regIdx, resultPtr); + } + } + + // Sort registers by name for deterministic output + SmallVector>>> + sortedRegisters; + for (auto& [name, measurements] : registerGroups) { + sortedRegisters.emplace_back(name, std::move(measurements)); + } + llvm::sort(sortedRegisters, + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // create function declarations for output recording + const auto arrayRecordSig = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {builder.getI64Type(), ptrType}); + const auto arrayRecordDecl = getOrCreateFunctionDeclaration( + builder, main, QIR_ARRAY_RECORD_OUTPUT, arrayRecordSig); + + const auto resultRecordSig = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); + const auto resultRecordDecl = getOrCreateFunctionDeclaration( + builder, main, QIR_RECORD_OUTPUT, resultRecordSig); + + // Generate output recording for each register + for (auto& [registerName, measurements] : sortedRegisters) { + // Sort measurements by register index + llvm::sort(measurements, [](const auto& a, const auto& b) { + return a.first < b.first; + }); + + const auto arraySize = measurements.size(); + auto arrayLabelOp = createResultLabel(builder, main, registerName); + auto arraySizeConst = builder.create( + main->getLoc(), + builder.getI64IntegerAttr(static_cast(arraySize))); + + builder.create( + main->getLoc(), arrayRecordDecl, + ValueRange{arraySizeConst.getResult(), arrayLabelOp.getResult()}); + + // Create result_record_output calls for each measurement + for (auto [regIdx, resultPtr] : measurements) { + // Create label for result: "{arrayCounter+1+i}_{registerName}{i}r" + const std::string resultLabel = + registerName.str() + std::to_string(regIdx) + "r"; + auto resultLabelOp = createResultLabel(builder, main, resultLabel); + + builder.create( + main->getLoc(), resultRecordDecl, + ValueRange{resultPtr, resultLabelOp.getResult()}); + } + } + } + /** * @brief Executes the Quartz to QIR conversion pass * @@ -598,7 +668,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * * **Stage 3: Quartz to LLVM** * Convert Quartz dialect operations to QIR calls (static, alloc, dealloc, - * measure, reset). + * measure, reset) and add output recording to the output block. * * **Stage 4: QIR attributes** * Add QIR base profile metadata to the main function, including qubit/result @@ -663,6 +733,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { signalPassFailure(); return; } + + addOutputRecording(main, ctx, &state); } // Stage 4: Set QIR metadata attributes From f6db960ac98bcda2a75b7f226152b527a510ade4 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 14 Oct 2025 22:36:57 +0200 Subject: [PATCH 060/419] =?UTF-8?q?=E2=9C=A8=20update=20measurement=20oper?= =?UTF-8?q?ations=20to=20use=20classical=20bit=20register=20indexing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../pipeline/test_compiler_pipeline.cpp | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 1b3ec4fb39..16aa2e702f 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -762,14 +762,14 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(1); - const auto c = b.allocClassicalBitRegister(1); - b.measure(q[0], c, 0); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); }); const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto q = b.allocQubitRegister(1); - const auto c = b.allocClassicalBitRegister(1); - b.measure(q[0], c, 0); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); }); const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { @@ -805,16 +805,16 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(1); - const auto c = b.allocClassicalBitRegister(1); - b.measure(q[0], c, 0); - b.measure(q[0], c, 0); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + b.measure(q[0], c[0]); }); const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto q = b.allocQubitRegister(1); - const auto c = b.allocClassicalBitRegister(1); - q[0] = b.measure(q[0], c, 0); - q[0] = b.measure(q[0], c, 0); + const auto& c = b.allocClassicalBitRegister(1); + q[0] = b.measure(q[0], c[0]); + q[0] = b.measure(q[0], c[0]); }); const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { @@ -853,18 +853,18 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(1); - const auto c = b.allocClassicalBitRegister(3); - b.measure(q[0], c, 0); - b.measure(q[0], c, 1); - b.measure(q[0], c, 2); + const auto& c = b.allocClassicalBitRegister(3); + b.measure(q[0], c[0]); + b.measure(q[0], c[1]); + b.measure(q[0], c[2]); }); const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto q = b.allocQubitRegister(1); - const auto c = b.allocClassicalBitRegister(3); - q[0] = b.measure(q[0], c, 0); - q[0] = b.measure(q[0], c, 1); - q[0] = b.measure(q[0], c, 2); + const auto& c = b.allocClassicalBitRegister(3); + q[0] = b.measure(q[0], c[0]); + q[0] = b.measure(q[0], c[1]); + q[0] = b.measure(q[0], c[2]); }); const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { @@ -904,18 +904,18 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(2); - const auto creg1 = b.allocClassicalBitRegister(1, "c1"); - const auto creg2 = b.allocClassicalBitRegister(1, "c2"); - b.measure(q[0], creg1, 0); - b.measure(q[1], creg2, 0); + const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); + b.measure(q[0], creg1[0]); + b.measure(q[1], creg2[0]); }); const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto q = b.allocQubitRegister(2); - const auto creg1 = b.allocClassicalBitRegister(1, "c1"); - const auto creg2 = b.allocClassicalBitRegister(1, "c2"); - q[0] = b.measure(q[0], creg1, 0); - q[1] = b.measure(q[1], creg2, 0); + const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); + q[0] = b.measure(q[0], creg1[0]); + q[1] = b.measure(q[1], creg2[0]); }); const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { From 83f1e77749a88495c8d6e8959f2a3e9bca016e48 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 14 Oct 2025 22:39:16 +0200 Subject: [PATCH 061/419] =?UTF-8?q?=F0=9F=94=A5=20remove=20two=20leftover?= =?UTF-8?q?=20files=20from=20iteration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/mlir/UnitaryExprDesign.md | 909 ----------------------------- docs/mlir/implementation-prompt.md | 520 ----------------- 2 files changed, 1429 deletions(-) delete mode 100644 docs/mlir/UnitaryExprDesign.md delete mode 100644 docs/mlir/implementation-prompt.md diff --git a/docs/mlir/UnitaryExprDesign.md b/docs/mlir/UnitaryExprDesign.md deleted file mode 100644 index 3909456cc2..0000000000 --- a/docs/mlir/UnitaryExprDesign.md +++ /dev/null @@ -1,909 +0,0 @@ -# Unitary Expression Support Library - -**Purpose:** Provide efficient unitary matrix computation and symbolic reasoning for transformation and optimization passes in the MQT MLIR compilation infrastructure. - -## Overview - -The `UnitaryExpr` support library is a lightweight C++20 framework that enables transformation passes to efficiently reason about, compose, and manipulate quantum gate unitaries. -It serves as the computational backbone for operations like static matrix extraction, gate fusion, equivalence checking, and optimization within the MQT MLIR dialects. - -This library integrates seamlessly with the unified `UnitaryOpInterface` defined in the quantum dialect architecture, providing the underlying computational machinery for extracting and manipulating unitary matrices from quantum operations. - -## Design Philosophy - -The library balances performance, expressiveness, and integration with MLIR infrastructure through the following principles: - -### Core Principles - -- **Performance-Critical:** Optimized for small matrices common in quantum computing (2×2 single-qubit, 4×4 two-qubit gates) -- **MLIR-Native:** Leverages MLIR/LLVM support types (`mlir::ArrayRef`, `mlir::SmallVector`, `llvm::APFloat`) over standard library equivalents where appropriate -- **Allocation-Aware:** Stack-based storage for fixed-size matrices; minimal heap allocations for dynamic cases -- **Inline-Friendly:** Header-only implementations for critical paths to enable aggressive compiler optimization -- **Type-Safe:** Exploits C++20 concepts and the type system to prevent dimension mismatches at compile time -- **Symbolic When Needed:** Supports both concrete materialized matrices and symbolic expression trees for deferred evaluation -- **Zero External Dependencies:** Relies exclusively on LLVM/MLIR infrastructure—no Eigen, BLAS, or quantum simulation libraries - -### Design Goals - -The library addresses three key use cases in quantum compilation: - -1. **Static Analysis:** Enable passes to query matrix properties (hermiticity, diagonality, unitarity) without full materialization -2. **Gate Fusion:** Efficiently compose sequences of gates to identify fusion opportunities -3. **Equivalence Checking:** Compare unitary operations for semantic equivalence during optimization - -## Qubit Ordering and Endianness - -A fundamental challenge in multi-qubit gate composition is correctly handling qubit ordering and endianness conventions. The matrix representation of a quantum gate depends on which physical qubits it operates on and in what order they appear in the tensor product basis. - -### The Problem - -Consider a controlled-X gate applied to qubits `q0` and `q1`: - -```mlir -quartz.ctrl(%q0) { quartz.x %q1 } // q0 controls, q1 is target -``` - -versus - -```mlir -quartz.ctrl(%q1) { quartz.x %q0 } // q1 controls, q0 is target -``` - -Even though both operations have the same SSA values, their matrix representations differ because the computational basis ordering changes: - -- First case: basis ordered as `|q0⟩ ⊗ |q1⟩` → states `|00⟩, |01⟩, |10⟩, |11⟩` -- Second case: basis ordered as `|q1⟩ ⊗ |q0⟩` → states `|00⟩, |01⟩, |10⟩, |11⟩` (but q1 is now the high-order bit) - -This becomes especially critical when composing sequences of multi-qubit gates where qubits appear in different positions. - -### Endianness Convention - -The library adopts **big-endian** qubit ordering convention, which aligns with standard quantum computing literature and notation: - -- **Qubit position 0** (first in the list) corresponds to the **most significant bit** in the basis state -- **Qubit position n-1** (last in the list) corresponds to the **least significant bit** in the basis state -- For n qubits at positions [q₀, q₁, ..., qₙ₋₁], the basis state index is: 2^(n-1)·q₀ + 2^(n-2)·q₁ + ... + 2^0·qₙ₋₁ -- Basis states are enumerated in standard binary order: `|00⟩, |01⟩, |10⟩, |11⟩` for two qubits - -**Example:** For `ctrl(%q0) { x %q1 }` where the qubit order is `[q0, q1]`: - -- `q0` at position 0 (MSB) → controls on bit 1 (high-order bit) -- `q1` at position 1 (LSB) → target is bit 0 (low-order bit) -- Tensor product: `|q0⟩ ⊗ |q1⟩` -- Matrix acts as: `|00⟩→|00⟩, |01⟩→|01⟩, |10⟩→|11⟩, |11⟩→|10⟩` (standard CNOT with q0 control, q1 target) - -**Rationale:** Big-endian ordering is the standard convention in quantum computing papers and textbooks, where multi-qubit states are written left-to-right with the first qubit as the leftmost tensor factor: `|ψ⟩ = |q₀⟩ ⊗ |q₁⟩ ⊗ ... ⊗ |qₙ₋₁⟩`. This convention ensures that the library's matrix representations match published results and established quantum algorithms. - -### Design Principles - -1. **Explicit Qubit Ordering:** All multi-qubit operations must explicitly track which qubits they operate on and in what order -2. **Permutation Tracking:** When composing gates with different qubit orderings, explicit permutation matrices handle reordering -3. **Canonical Forms:** Operations on the same qubits in the same order can be directly composed; otherwise, reordering is required -4. **Efficient Common Case:** Same-order composition (the common case) has zero overhead; reordering only when necessary - -### Qubit Order Representation - -```cpp -namespace mqt::unitary { - -/// Represents an ordered list of qubits for a multi-qubit operation -/// Uses MLIR Value to represent SSA values (for Flux) or qubit references (for Quartz) -using QubitOrder = mlir::SmallVector; - -/// Check if two qubit orders are identical (same qubits in same positions) -[[nodiscard]] inline bool isSameOrder(const QubitOrder& a, const QubitOrder& b) { - return a.size() == b.size() && - std::equal(a.begin(), a.end(), b.begin()); -} - -/// Check if two qubit orders operate on the same qubits (possibly different order) -[[nodiscard]] bool isSameQubits(const QubitOrder& a, const QubitOrder& b); - -/// Compute permutation mapping from order 'from' to order 'to' -/// Returns std::nullopt if they don't contain the same qubits -[[nodiscard]] std::optional> - computePermutation(const QubitOrder& from, const QubitOrder& to); - -} // namespace mqt::unitary -``` - -## Core Data Structures - -### Fixed-Size Matrix Types - -The library provides specialized matrix types for the most common quantum operations, leveraging stack allocation and cache-friendly memory layouts. - -#### Mat2: Single-Qubit Matrices - -The `Mat2` class represents 2×2 complex matrices for single-qubit gates: - -```cpp -namespace mqt::unitary { - -/// 2×2 complex matrix for single-qubit gates (stack-allocated, 64 bytes) -class Mat2 { -public: - using Complex = std::complex; - - /// Stack storage: 4 complex values = 8 doubles = 64 bytes - /// Fits in a single CPU cache line - std::array data; - - /// Constructors - constexpr Mat2() = default; - constexpr Mat2(Complex a00, Complex a01, Complex a10, Complex a11); - - /// Static factory methods for common gates - static constexpr Mat2 identity() noexcept; - static constexpr Mat2 pauliX() noexcept; - static constexpr Mat2 pauliY() noexcept; - static constexpr Mat2 pauliZ() noexcept; - static constexpr Mat2 hadamard() noexcept; - static constexpr Mat2 phaseS() noexcept; - static constexpr Mat2 phaseT() noexcept; - - /// Element access (row-major ordering) - constexpr Complex& operator()(size_t row, size_t col) noexcept; - constexpr const Complex& operator()(size_t row, size_t col) const noexcept; - - /// Matrix operations - [[nodiscard]] Mat2 operator*(const Mat2& other) const noexcept; - [[nodiscard]] Mat2 adjoint() const noexcept; - [[nodiscard]] Mat2 power(double exponent) const; - - /// Numerical queries - [[nodiscard]] bool isApproxEqual(const Mat2& other, - double tolerance = 1e-14) const noexcept; - [[nodiscard]] bool isUnitary(double tolerance = 1e-14) const noexcept; - [[nodiscard]] bool isHermitian(double tolerance = 1e-14) const noexcept; - [[nodiscard]] bool isDiagonal(double tolerance = 1e-14) const noexcept; -}; - -} // namespace mqt::unitary -``` - -**Design Rationale:** - -- **Cache-Friendly:** 64-byte alignment fits exactly in one cache line on most architectures -- **constexpr Support:** Common gates can be computed at compile time, eliminating runtime overhead -- **Row-Major Storage:** Matches typical access patterns in matrix operations -- **Numerical Tolerance:** Machine precision (≈10⁻¹⁴) used for floating-point comparisons -- **Qubit-Order Independent:** Single-qubit gates have no ordering ambiguity - -#### Mat4: Two-Qubit Matrices - -The `Mat4` class represents 4×4 complex matrices for two-qubit gates: - -```cpp -namespace mqt::unitary { - -/// 4×4 complex matrix for two-qubit gates (stack-allocated, 256 bytes) -/// -/// Qubit Ordering Convention: -/// - Qubits are ordered as [q0, q1] where q0 is the LSB (position 0) -/// - Basis states: |00⟩, |01⟩, |10⟩, |11⟩ (binary: 0, 1, 2, 3) -/// - Matrix element M[i][j] represents amplitude from basis state j to state i -class Mat4 { -public: - using Complex = std::complex; - - /// Stack storage: 16 complex values = 32 doubles = 256 bytes - /// Fits in four cache lines - std::array data; - - /// Qubit order for this matrix (which qubits, in what positions) - /// Empty for anonymous matrices; populated for gate-derived matrices - QubitOrder qubits; - - /// Constructors - constexpr Mat4() = default; - explicit constexpr Mat4(std::array values); - explicit Mat4(std::array values, QubitOrder order); - - /// Static factory methods for common gates - /// These assume canonical two-qubit ordering [q0, q1] - static constexpr Mat4 identity() noexcept; - static constexpr Mat4 cnot() noexcept; // Control on q0, target on q1 - static constexpr Mat4 cz() noexcept; // Control on q0, target on q1 - static constexpr Mat4 swap() noexcept; // Symmetric: swaps q0 and q1 - - /// Element access (row-major ordering) - constexpr Complex& operator()(size_t row, size_t col) noexcept; - constexpr const Complex& operator()(size_t row, size_t col) const noexcept; - - /// Matrix operations - [[nodiscard]] Mat4 operator*(const Mat4& other) const; - [[nodiscard]] Mat4 adjoint() const noexcept; - [[nodiscard]] Mat4 power(double exponent) const; - - /// Construction from smaller matrices - [[nodiscard]] static Mat4 tensorProduct(const Mat2& left, const Mat2& right); - [[nodiscard]] static Mat4 controlled(const Mat2& target, - bool positiveControl = true); - - /// Qubit reordering - /// Permute the qubit order of this matrix to match a target ordering - /// Example: if this matrix is for [q0, q1] and target is [q1, q0], - /// returns a new matrix with basis reordered accordingly - [[nodiscard]] Mat4 permuteQubits(const QubitOrder& targetOrder) const; - - /// Apply a permutation directly (permutation[i] = position of target qubit i in source) - [[nodiscard]] Mat4 applyPermutation(mlir::ArrayRef permutation) const; - - /// Numerical queries - [[nodiscard]] bool isApproxEqual(const Mat4& other, - double tolerance = 1e-14) const noexcept; - [[nodiscard]] bool isUnitary(double tolerance = 1e-14) const noexcept; - [[nodiscard]] bool isHermitian(double tolerance = 1e-14) const noexcept; - [[nodiscard]] bool isDiagonal(double tolerance = 1e-14) const noexcept; -}; - -} // namespace mqt::unitary -``` - -**Design Rationale:** - -- **Controlled Gate Construction:** Efficient expansion from single-qubit gates to controlled two-qubit gates -- **Tensor Product Support:** Enables construction of product states and parallel gate application -- **Consistent Interface:** Mirrors `Mat2` API for uniform usage patterns -- **Explicit Qubit Tracking:** `qubits` field maintains ordering information for composition correctness -- **Permutation Support:** Efficient basis reordering when composing gates with different qubit orders - -#### Permutation Algorithm - -The basis permutation algorithm reorders matrix elements according to the qubit permutation: - -```cpp -namespace mqt::unitary { - -/// Apply a qubit permutation to a 2^n × 2^n matrix -/// permutation[i] specifies the source position of target qubit i -/// -/// Algorithm: For each basis state |b⟩: -/// 1. Interpret b as n-bit number with qubits in target order -/// 2. Remap bits according to permutation to get source state |b'⟩ -/// 3. Copy M[b'][c'] → M_out[b][c] for all b, c -/// -/// Time complexity: O(4^n) for n-qubit gates -/// Space complexity: O(4^n) for output matrix (input unchanged) -template -[[nodiscard]] std::array, N * N> - permuteMatrixBasis(const std::array, N * N>& matrix, - mlir::ArrayRef permutation, - size_t numQubits); - -} // namespace mqt::unitary -``` - -**Example:** Consider `CNOT[q0, q1]` (control q0, target q1) and we want `CNOT[q1, q0]`: - -- Permutation: `[1, 0]` (target position 0 gets source position 1; target position 1 gets source position 0) -- Basis mapping: `|00⟩→|00⟩, |01⟩→|10⟩, |10⟩→|01⟩, |11⟩→|11⟩` -- Result: CNOT with swapped control/target roles - -### Symbolic Expression Framework - -For gates with dynamic parameters, complex compositions, or deferred evaluation, the library provides a symbolic expression tree system. - -#### Base Expression Interface - -```cpp -namespace mqt::unitary { - -/// Base class for symbolic unitary expressions -class UnitaryExpr { -public: - virtual ~UnitaryExpr() = default; - - /// Query expression structure - [[nodiscard]] virtual size_t getNumQubits() const = 0; - [[nodiscard]] virtual bool canMaterialize() const = 0; - [[nodiscard]] virtual bool isFullyStatic() const = 0; - - /// Get the qubits this expression operates on (in order) - [[nodiscard]] virtual QubitOrder getQubitOrder() const = 0; - - /// Materialization (returns std::nullopt if not possible) - [[nodiscard]] virtual std::optional materializeMat2() const; - [[nodiscard]] virtual std::optional materializeMat4() const; - [[nodiscard]] virtual mlir::DenseElementsAttr - materializeDense(mlir::MLIRContext* ctx) const; - - /// Symbolic property queries - [[nodiscard]] virtual bool isHermitian() const = 0; - [[nodiscard]] virtual bool isDiagonal() const = 0; - - /// Smart pointer for ownership - using Ptr = std::unique_ptr; -}; - -} // namespace mqt::unitary -``` - -**Design Philosophy:** - -- **Lazy Evaluation:** Build expression trees without immediate computation -- **Partial Information:** Query properties without full materialization -- **Optimization Opportunities:** Simplify expressions symbolically before materializing - -#### Expression Node Types - -##### ConstExpr: Concrete Matrices - -```cpp -/// Fully materialized matrix expression -class ConstExpr : public UnitaryExpr { - std::variant matrix; - QubitOrder qubits; - -public: - explicit ConstExpr(Mat2 m); - explicit ConstExpr(Mat4 m); - explicit ConstExpr(Mat2 m, QubitOrder order); - explicit ConstExpr(Mat4 m, QubitOrder order); - - [[nodiscard]] size_t getNumQubits() const override; - [[nodiscard]] QubitOrder getQubitOrder() const override { return qubits; } - [[nodiscard]] bool canMaterialize() const override { return true; } - [[nodiscard]] bool isFullyStatic() const override { return true; } - - [[nodiscard]] std::optional materializeMat2() const override; - [[nodiscard]] std::optional materializeMat4() const override; - - [[nodiscard]] bool isHermitian() const override; - [[nodiscard]] bool isDiagonal() const override; -}; -``` - -##### MulExpr: Matrix Composition with Qubit Reordering - -```cpp -/// Matrix multiplication (composition of unitaries) -/// Handles automatic qubit reordering when composing gates with different orderings -class MulExpr : public UnitaryExpr { - UnitaryExpr::Ptr left; - UnitaryExpr::Ptr right; - QubitOrder resultOrder; // Cached result qubit order - -public: - MulExpr(UnitaryExpr::Ptr l, UnitaryExpr::Ptr r); - - [[nodiscard]] size_t getNumQubits() const override; - [[nodiscard]] QubitOrder getQubitOrder() const override { return resultOrder; } - [[nodiscard]] bool canMaterialize() const override; - [[nodiscard]] bool isFullyStatic() const override; - - [[nodiscard]] std::optional materializeMat2() const override; - [[nodiscard]] std::optional materializeMat4() const override; - - [[nodiscard]] bool isHermitian() const override; - [[nodiscard]] bool isDiagonal() const override; - -private: - /// Compute the result qubit order from left and right expressions - /// Uses left's ordering as the canonical target - static QubitOrder computeResultOrder(const UnitaryExpr& left, - const UnitaryExpr& right); -}; -``` - -**Materialization Strategy with Reordering:** - -When materializing a `MulExpr`: - -1. Check if both children have the same qubit order → direct composition (fast path) -2. If orders differ, align them: - - Use `left`'s qubit order as canonical - - Permute `right`'s matrix to match `left`'s order - - Compose the aligned matrices -3. Result inherits `left`'s qubit order - -**Example:** - -```cpp -// Left: CNOT with qubits [q0, q1] -// Right: CZ with qubits [q1, q0] (opposite order) -// -// Composition process: -// 1. Identify qubit order mismatch -// 2. Permute right's matrix: CZ[q1,q0] → CZ[q0,q1] -// 3. Compose: CNOT[q0,q1] * CZ[q0,q1] -// 4. Result has order [q0, q1] -``` - -##### AdjExpr: Adjoint (Inverse) - -```cpp -/// Adjoint (conjugate transpose) of a unitary -class AdjExpr : public UnitaryExpr { - UnitaryExpr::Ptr child; - -public: - explicit AdjExpr(UnitaryExpr::Ptr c); - - [[nodiscard]] size_t getNumQubits() const override; - [[nodiscard]] bool canMaterialize() const override; - [[nodiscard]] bool isFullyStatic() const override; - - [[nodiscard]] std::optional materializeMat2() const override; - [[nodiscard]] std::optional materializeMat4() const override; - - [[nodiscard]] bool isHermitian() const override; - [[nodiscard]] bool isDiagonal() const override; -}; -``` - -**Property Propagation:** If child is Hermitian, adjoint equals child; diagonal property preserved. - -##### PowExpr: Matrix Exponentiation - -```cpp -/// Power of a unitary (U^exponent) -class PowExpr : public UnitaryExpr { - UnitaryExpr::Ptr child; - double exponent; - -public: - PowExpr(UnitaryExpr::Ptr c, double exp); - - [[nodiscard]] size_t getNumQubits() const override; - [[nodiscard]] bool canMaterialize() const override; - [[nodiscard]] bool isFullyStatic() const override; - - [[nodiscard]] std::optional materializeMat2() const override; - [[nodiscard]] std::optional materializeMat4() const override; - - [[nodiscard]] bool isHermitian() const override; - [[nodiscard]] bool isDiagonal() const override; -}; -``` - -**Exponentiation Strategy:** - -- **Integer powers:** Repeated multiplication -- **Fractional powers:** Eigendecomposition and diagonal exponentiation -- **Special cases:** Identity for exponent 0, adjoint for exponent -1 - -##### CtrlExpr: Control Expansion - -```cpp -/// Controlled unitary expansion -class CtrlExpr : public UnitaryExpr { - UnitaryExpr::Ptr target; - mlir::SmallVector posControls; - mlir::SmallVector negControls; - QubitOrder resultOrder; // Control qubits followed by target qubits - -public: - CtrlExpr(UnitaryExpr::Ptr t, - mlir::ArrayRef posCtrl, - mlir::ArrayRef negCtrl); - - [[nodiscard]] size_t getNumQubits() const override; - [[nodiscard]] QubitOrder getQubitOrder() const override { return resultOrder; } - [[nodiscard]] bool canMaterialize() const override; - [[nodiscard]] bool isFullyStatic() const override; - - [[nodiscard]] mlir::DenseElementsAttr - materializeDense(mlir::MLIRContext* ctx) const override; - - [[nodiscard]] bool isHermitian() const override; - [[nodiscard]] bool isDiagonal() const override; - -private: - /// Compute canonical qubit ordering: controls first, then targets - static QubitOrder computeResultOrder(mlir::ArrayRef posCtrl, - mlir::ArrayRef negCtrl, - const UnitaryExpr& target); -}; -``` - -**Qubit Ordering for Controlled Gates:** - -- Control qubits appear first in the qubit order (positive controls, then negative controls) -- Target qubits follow controls -- Example: `ctrl(%q2, %q3) { gate %q0, %q1 }` → order is `[q2, q3, q0, q1]` - -##### ParamExpr: Parametrized Gates - -```cpp -/// Parametrized expression with symbolic parameters -class ParamExpr : public UnitaryExpr { - mlir::SmallVector paramIndices; - std::function)> generator; - -public: - ParamExpr(mlir::ArrayRef indices, - std::function)> gen); - - [[nodiscard]] size_t getNumQubits() const override { return 1; } - [[nodiscard]] bool canMaterialize() const override { return false; } - [[nodiscard]] bool isFullyStatic() const override { return false; } - - /// Partial evaluation: given parameter values, materialize - [[nodiscard]] Mat2 evaluate(mlir::ArrayRef paramValues) const; - - [[nodiscard]] bool isHermitian() const override; - [[nodiscard]] bool isDiagonal() const override; -}; -``` - -**Design Notes:** - -- **Multiple Parameters:** Supports gates with arbitrary parameter counts (e.g., `u(θ, φ, λ)` has three parameters) -- **Parameter Tracking:** Maintains indices for parameter identification across expressions -- **Generator Function:** Callable that produces the matrix given parameter values - -## Core Operations and Algorithms - -### Composition - -Matrix composition represents sequential application of quantum gates (right-to-left convention): - -```cpp -namespace mqt::unitary { - -/// Compose two unitaries (matrix multiplication: b then a) -/// Automatically handles qubit reordering if necessary -[[nodiscard]] UnitaryExpr::Ptr compose(UnitaryExpr::Ptr a, UnitaryExpr::Ptr b); - -/// Fast path for static 2×2 composition (no qubit ordering issues) -[[nodiscard]] Mat2 compose(const Mat2& a, const Mat2& b) noexcept; - -/// Fast path for static 4×4 composition with explicit qubit ordering -/// If qubit orders match, performs direct multiplication -/// If orders differ, permutes b's matrix before multiplication -[[nodiscard]] Mat4 compose(const Mat4& a, const Mat4& b); - -/// Compose sequence of unitaries (applied left-to-right) -/// Maintains consistent qubit ordering throughout composition -[[nodiscard]] UnitaryExpr::Ptr - composeSequence(mlir::ArrayRef unitaries); - -} // namespace mqt::unitary -``` - -**Optimization Strategy:** - -- **Same-order fast path:** When gates operate on the same qubits in the same order, skip permutation logic entirely -- **Reordering path:** When orders differ, apply permutation then compose -- **Single-qubit gates:** No ordering issues; always use fast path -- **Symbolic expressions:** Build `MulExpr` nodes that defer reordering until materialization - -#### Composition Algorithm Details - -```cpp -namespace mqt::unitary { - -/// Detailed composition algorithm for two 4×4 matrices -/// -/// Given: matrices A (qubits [qa0, qa1]) and B (qubits [qb0, qb1]) -/// Goal: Compute A * B correctly accounting for qubit ordering -/// -/// Cases: -/// 1. Same order ([qa0, qa1] == [qb0, qb1]): Direct multiplication -/// 2. Different order: -/// a. Check if qubits overlap: must have same qubit set -/// b. Compute permutation: π such that qb[π[i]] = qa[i] -/// c. Apply permutation to B: B' = permute(B, π) -/// d. Multiply: A * B' -/// e. Result has A's qubit order -/// -[[nodiscard]] Mat4 composeWithReordering(const Mat4& a, const Mat4& b); - -} // namespace mqt::unitary -``` - -**Example Scenario:** - -```cpp -// Gate sequence: H⊗H on [q0,q1], then CNOT on [q1,q0], then CZ on [q0,q1] - -// Step 1: H⊗H with order [q0, q1] -Mat2 h = Mat2::hadamard(); -Mat4 hh = Mat4::tensorProduct(h, h); -hh.qubits = {q0, q1}; - -// Step 2: CNOT with order [q1, q0] (control=q1, target=q0) -Mat4 cnot = Mat4::cnot(); // Assumes [control, target] -cnot.qubits = {q1, q0}; - -// Step 3: Compose HH with CNOT -// - HH has order [q0, q1] -// - CNOT has order [q1, q0] -// - Permutation: [1, 0] (swap qubits) -// - Permute CNOT: CNOT' with order [q0, q1] -// - Result: HH * CNOT' with order [q0, q1] -Mat4 intermediate = compose(hh, cnot); - -// Step 4: CZ with order [q0, q1] -Mat4 cz = Mat4::cz(); -cz.qubits = {q0, q1}; - -// Step 5: Compose with CZ -// - intermediate has order [q0, q1] -// - CZ has order [q0, q1] -// - Same order → direct multiplication (fast path) -Mat4 result = compose(intermediate, cz); -``` - -### Mixed Single-Qubit and Multi-Qubit Composition - -A particularly important case is composing sequences where single-qubit and two-qubit gates are interleaved: - -```cpp -namespace mqt::unitary { - -/// Compose a single-qubit gate into a two-qubit context -/// -/// Given: 2×2 matrix G for single-qubit gate on qubit q -/// Qubit context [q0, q1] for the two-qubit space -/// Result: 4×4 matrix representing G ⊗ I or I ⊗ G depending on which qubit -/// -/// Example: X gate on q1 in context [q0, q1] → I ⊗ X -/// X gate on q0 in context [q0, q1] → X ⊗ I -/// -[[nodiscard]] Mat4 embedSingleQubit(const Mat2& gate, - mlir::Value targetQubit, - const QubitOrder& context); - -/// Compose a sequence with mixed single and two-qubit gates -/// Automatically expands single-qubit gates to the appropriate two-qubit space -[[nodiscard]] UnitaryExpr::Ptr - composeMixedSequence(mlir::ArrayRef gates, - const QubitOrder& targetContext); - -} // namespace mqt::unitary -``` - -**Example:** - -```mlir -// Sequence: CNOT %q0, %q1; H %q0; CZ %q0, %q1 -// Context: [q0, q1] (two-qubit space [q0, q1]) -// -// Step 1: CNOT on [q0, q1] → 4×4 matrix -// Step 2: H on q0 → expand to (H ⊗ I) in context [q0, q1] → 4×4 matrix -// Step 3: CZ on [q0, q1] → 4×4 matrix -// Step 4: Compose all three 4×4 matrices with consistent [q0, q1] ordering -``` - -### Example 6: Symbolic Expression with Reordering - -Build symbolic expression trees that track qubit ordering: - -```cpp -using namespace mqt::unitary; - -// Build expression: CNOT[q0,q1] · (CZ[q1,q0])† - -// CNOT with order [q0, q1] -Mat4 cnot = Mat4::cnot(); -cnot.qubits = {q0, q1}; -auto cnotExpr = std::make_unique(cnot, QubitOrder{q0, q1}); - -// CZ with order [q1, q0] -Mat4 cz = Mat4::cz(); -cz.qubits = {q1, q0}; -auto czExpr = std::make_unique(cz, QubitOrder{q1, q0}); - -// Adjoint of CZ -auto czAdj = std::make_unique(std::move(czExpr)); - -// Compose (handles reordering automatically) -auto composed = std::make_unique(std::move(cnotExpr), - std::move(czAdj)); - -// Query result order (inherits from left operand) -QubitOrder resultOrder = composed->getQubitOrder(); -assert(resultOrder[0] == q0 && resultOrder[1] == q1); - -// Materialize when needed -if (auto result = composed->materializeMat4()) { - // Matrix is correctly computed with [q0, q1] ordering - assert(result->qubits[0] == q0); - assert(result->qubits[1] == q1); -} -``` - -### Example 7: Parametrized Gates - -Handle gates with multiple parameters: - -```cpp -using namespace mqt::unitary; - -// Generator for U gate with three parameters: u(θ, φ, λ) -auto uGenerator = [](mlir::ArrayRef params) -> Mat2 { - assert(params.size() == 3); - double theta = params[0]; - double phi = params[1]; - double lambda = params[2]; - - // Matrix for u(θ, φ, λ) - // [[cos(θ/2), -exp(iλ)sin(θ/2)], - // [exp(iφ)sin(θ/2), exp(i(φ+λ))cos(θ/2)]] - // ... implementation ... -}; - -// Create parametrized expression -mlir::SmallVector paramIndices = {0, 1, 2}; -auto uExpr = std::make_unique(paramIndices, uGenerator); - -// Evaluate with concrete values -mlir::SmallVector values = {M_PI / 2, 0.0, M_PI}; -Mat2 concrete = uExpr->evaluate(values); -``` - -## Testing Strategy - -### Unit Tests - -**Correctness Tests:** - -- Mathematical properties: unitarity (U†U = I), adjoint correctness, power consistency -- Gate identities: Pauli relations, Hadamard properties, rotation periodicity -- Composition associativity: (A·B)·C = A·(B·C) -- Control expansion: verify controlled-unitary structure -- **Qubit ordering:** Verify different qubit orderings produce different matrices -- **Reordering correctness:** Compose A·B with explicit reordering matches permuted composition -- **Mixed sequences:** Single-qubit gates embedded correctly in multi-qubit contexts - -**Precision Tests:** - -- Machine precision validation: ≈10⁻¹⁴ relative error -- Numerical stability: condition number analysis for matrix operations -- Edge cases: identity matrices, diagonal matrices, Hermitian matrices -- **Permutation accuracy:** Basis reordering preserves matrix unitarity and determinant - -### Performance Tests - -**Microbenchmarks:** - -- Individual operation latencies (multiplication, adjoint, power) -- **Same-order vs different-order composition:** Measure fast-path effectiveness -- **Qubit order detection:** Measure comparison overhead -- **Permutation cost:** Measure basis reordering overhead -- Comparison against reference implementations -- Regression detection across compiler versions - -**Allocation Tracking:** - -- Verify zero heap allocations for fixed-size operations -- Profile expression tree memory usage -- Detect allocation regressions - -**Cache Behavior:** - -- Profile L1/L2/L3 cache hit rates -- Measure memory bandwidth utilization -- Optimize for cache line alignment - -### Integration Tests - -**MLIR Interoperability:** - -- Round-trip conversion: Mat2 ↔ DenseElementsAttr -- Attribute preservation across transformations -- Context lifetime management -- **Qubit order preservation:** Verify SSA values maintained through conversions - -**Interface Usage:** - -- `UnitaryOpInterface` implementation correctness -- Modifier chain materialization with qubit tracking -- Pass infrastructure integration -- **Gate fusion:** Verify qubit order compatibility checks in fusion passes - -## Implementation Structure - -### Dependencies - -The library relies exclusively on LLVM/MLIR infrastructure and C++20 standard library: - -- **C++20 Standard Library:** - - ``: Complex number arithmetic - - ``: Fixed-size containers - - ``: Optional return types - - ``: Type-safe unions - - ``: Function objects - - ``: Type constraints - - ``: Standard algorithms (std::equal, std::find) - -- **LLVM ADT:** - - `llvm/ADT/ArrayRef.h`: Non-owning array reference - - `llvm/ADT/SmallVector.h`: Small-size optimized vector - - `llvm/ADT/APFloat.h`: Arbitrary precision floating point - -- **MLIR Infrastructure:** - - `mlir/IR/Attributes.h`: MLIR attribute system - - `mlir/IR/Builders.h`: Attribute construction utilities - - `mlir/IR/BuiltinTypes.h`: Tensor and complex types - - `mlir/IR/MLIRContext.h`: MLIR context management - -### Header Organization - -``` -include/mqt/unitary/ - Mat2.h // 2×2 matrix class - Mat4.h // 4×4 matrix class - QubitOrder.h // Qubit ordering utilities - Permutation.h // Basis permutation algorithms - UnitaryExpr.h // Symbolic expression framework - Operations.h // Composition, adjoint, power, control - Conversion.h // MLIR attribute interop - Numerical.h // Eigendecomposition, exponentiation - Utils.h // Numerical utilities, tolerances -``` - -### Namespace Structure - -```cpp -namespace mqt::unitary { - // Core matrix types - class Mat2; - class Mat4; - - // Qubit ordering - using QubitOrder = mlir::SmallVector; - [[nodiscard]] bool isSameOrder(const QubitOrder&, const QubitOrder&); - [[nodiscard]] bool isSameQubits(const QubitOrder&, const QubitOrder&); - [[nodiscard]] std::optional> - computePermutation(const QubitOrder&, const QubitOrder&); - - // Symbolic expressions - class UnitaryExpr; - class ConstExpr; - class MulExpr; - class AdjExpr; - class PowExpr; - class CtrlExpr; - class ParamExpr; - - // Operations - [[nodiscard]] UnitaryExpr::Ptr compose(UnitaryExpr::Ptr, UnitaryExpr::Ptr); - [[nodiscard]] UnitaryExpr::Ptr adjoint(UnitaryExpr::Ptr); - [[nodiscard]] UnitaryExpr::Ptr power(UnitaryExpr::Ptr, double); - [[nodiscard]] Mat4 controlled(const Mat2&, mlir::Value, mlir::Value, bool); - - // Mixed composition - [[nodiscard]] Mat4 embedSingleQubit(const Mat2&, mlir::Value, const QubitOrder&); - [[nodiscard]] UnitaryExpr::Ptr - composeMixedSequence(mlir::ArrayRef, const QubitOrder&); - - // Materialization - [[nodiscard]] bool canMaterialize(const UnitaryExpr&); - [[nodiscard]] std::optional materializeMat2(const UnitaryExpr&); - [[nodiscard]] std::optional materializeMat4(const UnitaryExpr&); - [[nodiscard]] mlir::DenseElementsAttr materializeAttr(const UnitaryExpr&, - mlir::MLIRContext*); - - // MLIR conversion - [[nodiscard]] mlir::DenseElementsAttr toAttr(const Mat2&, mlir::MLIRContext*); - [[nodiscard]] mlir::DenseElementsAttr toAttr(const Mat4&, mlir::MLIRContext*); - [[nodiscard]] std::optional fromAttr(mlir::DenseElementsAttr); - [[nodiscard]] std::optional fromAttr(mlir::DenseElementsAttr); -} -``` - -## Relationship to Quantum Dialect - -The `UnitaryExpr` library provides the computational substrate for the unified unitary interface defined in the MQT quantum dialects (Quartz and Flux): - -**Integration Points:** - -1. **Matrix Extraction:** Operations implementing `UnitaryOpInterface::tryGetStaticMatrix()` use this library to compute concrete matrices with correct qubit ordering -2. **Symbolic Composition:** Modifier operations (`ctrl`, `inv`, `pow`) build `UnitaryExpr` trees that track qubit ordering through transformations -3. **Equivalence Checking:** Optimization passes use matrix comparison to identify equivalent gate sequences, accounting for qubit order differences -4. **Gate Fusion:** Canonicalization passes compose matrices with automatic reordering to detect fusion opportunities across different qubit orderings - -**Design Separation:** - -The library maintains clean separation from MLIR dialect definitions: - -- **No dialect dependencies:** Can be used standalone for matrix computations -- **Generic interface:** Not tied to specific operation types -- **Reusable infrastructure:** Applicable to future dialects or external tools -- **Qubit-order agnostic core:** Matrix operations work with any ordering convention - -This architectural choice enables the library to serve as a foundational component across the entire MQT MLIR infrastructure while maintaining modularity and testability. The explicit handling of qubit ordering ensures correctness when composing gates from different sources or with different qubit arrangements, which is essential for robust optimization passes. diff --git a/docs/mlir/implementation-prompt.md b/docs/mlir/implementation-prompt.md deleted file mode 100644 index dd68f92d32..0000000000 --- a/docs/mlir/implementation-prompt.md +++ /dev/null @@ -1,520 +0,0 @@ -# MLIR Quantum Compilation Infrastructure Implementation Prompt - -You are an expert MLIR compiler developer tasked with implementing a comprehensive MLIR-based quantum compilation infrastructure for the Munich Quantum Toolkit (MQT). Your implementation will follow the detailed technical specification provided in the `docs/mlir/mlir-compilation-infrastructure-technical-concept.md` document. - -## Project Context - -**Location:** The implementation lives in the top-level `mlir/` directory of the MQT Core project. - -**Approach:** You should build on top of the existing infrastructure where beneficial, but **backwards compatibility is NOT a concern**. Full refactors are preferred if they better achieve the design goals. The existing code can serve as inspiration but should not constrain the implementation. - -**Build Configuration:** - -```bash -cmake -S . -B build-mlir-copilot \ - -DLLVM_EXTERNAL_LIT=/home/lburgholzer/Documents/mqt-core/.venv/bin/lit \ - -DBUILD_MQT_CORE_MLIR=ON \ - -DMLIR_DIR=/usr/lib/llvm-21/lib/cmake/mlir \ - --config Release -``` - -**Build Targets:** - -- Build MLIR tests: `cmake --build build-mlir-copilot --target mqt-core-mlir-lit-test-build-only` -- Run MLIR lit tests: `cmake --build build-mlir-copilot --target mqt-core-mlir-lit-test` -- Build translation tests: `cmake --build build-mlir-copilot --target mqt-core-mlir-translation-test` -- Run translation tests: `./build-mlir-copilot/mlir/unittests/translation/mqt-core-mlir-translation-test` - -**Code Quality:** - -- Lint the project: `pre-commit run -a` - -## Implementation Phases - -Your implementation should proceed through the following phases, implementing the design systematically: - -### Phase 1: Core Type System and Dialect Infrastructure - -**Goal:** Establish the foundation for both Quartz and Flux dialects. - -**Tasks:** - -1. Define `!quartz.qubit` and `!flux.qubit` types in their respective dialects -2. Implement the `UnitaryOpInterface` with all methods specified in Section 3.5: - -- Qubit accessors (targets, positive/negative controls) -- Value semantics threading (inputs/outputs) -- Parameter handling with `ParameterDescriptor` -- Matrix extraction (`hasStaticUnitary()`, `tryGetStaticMatrix()`) -- Modifier state (`isInverted()`, `getPower()`) -- Identification methods (`getBaseSymbol()`, `getCanonicalDescriptor()`) - -3. Set up dialect infrastructure with proper CMake integration -4. Implement basic verification framework - -**Acceptance Criteria:** - -- Both dialects register successfully with MLIR -- `UnitaryOpInterface` compiles and can be attached to operations -- CMake build succeeds without errors - -### Phase 2: Resource and Measurement Operations - -**Goal:** Implement non-unitary operations for qubit management and measurement. - -**Tasks:** - -1. **Resource Operations (Section 3.3):** - -- `quartz.alloc` / `flux.alloc` -- `quartz.dealloc` / `flux.dealloc` -- `quartz.qubit` / `flux.qubit` (static qubit references) -- Integration with `memref` for quantum and classical registers - -2. **Measurement and Reset (Section 3.4):** - -- `quartz.measure` / `flux.measure` (computational basis only) -- `quartz.reset` / `flux.reset` -- Proper type signatures for both reference and value semantics - -3. Implement canonicalization patterns for these operations -4. Add verification rules - -**Acceptance Criteria:** - -- All resource operations parse, verify, and print correctly -- Canonicalization patterns trigger appropriately -- Unit tests validate each operation's behavior - -### Phase 3: Base Gate Operations - Part 1 (Zero/Single-Qubit Gates) - -**Goal:** Implement all single-qubit base gates with full interface support. - -**Tasks:** - -1. Implement gates from Section 4.3: - -- **Zero-qubit:** `gphase` (4.3.1) -- **Pauli gates:** `id` (4.3.2), `x` (4.3.3), `y` (4.3.4), `z` (4.3.5) -- **Fixed gates:** `h` (4.3.6), `s` (4.3.7), `sdg` (4.3.8), `t` (4.3.9), `tdg` (4.3.10), `sx` (4.3.11), `sxdg` (4.3.12) -- **Rotation gates:** `rx` (4.3.13), `ry` (4.3.14), `rz` (4.3.15), `p` (4.3.16), `r` (4.3.17) -- **Universal gates:** `u` (4.3.18), `u2` (4.3.19) - -2. For each gate: - -- Implement proper traits (`OneTarget`, `NoParameter`, `Hermitian`, etc.) -- Implement `UnitaryOpInterface` methods -- Define static matrix representation where applicable -- Implement all specified canonicalization patterns - -3. Support both static (attribute) and dynamic (SSA value) parameters -4. Add comprehensive unit tests for each gate - -**Acceptance Criteria:** - -- All single-qubit gates parse and verify correctly in both dialects -- Static matrix extraction works for all parameter-free gates -- Dynamic matrix computation works for parameterized gates with static parameters -- All canonicalization patterns trigger correctly -- Global phase preservation semantics are respected - -### Phase 4: Base Gate Operations - Part 2 (Two-Qubit Gates) - -**Goal:** Implement all two-qubit base gates. - -**Tasks:** - -1. Implement gates from Section 4.3: - -- **Entangling gates:** `swap` (4.3.20), `iswap` (4.3.21), `dcx` (4.3.22), `ecr` (4.3.23) -- **Ising-type gates:** `rxx` (4.3.24), `ryy` (4.3.25), `rzx` (4.3.26), `rzz` (4.3.27) -- **General gates:** `xx_plus_yy` (4.3.28), `xx_minus_yy` (4.3.29) -- **Utility gates:** `barrier` (4.3.30) - -2. For each gate: - -- Implement proper traits (`TwoTarget`, parameter arities) -- Implement `UnitaryOpInterface` with 4×4 matrices -- Implement canonicalization patterns - -3. Special handling for `barrier`: - -- Implements `UnitaryOpInterface` but acts as compiler directive -- Prevents optimization reordering -- Scope-based global phase aggregation - -**Acceptance Criteria:** - -- All two-qubit gates verify and print correctly -- 4×4 matrix extraction works for all gates -- Canonicalization patterns function correctly -- Barrier semantics are properly enforced - -### Phase 5: Modifier Operations - -**Goal:** Implement the complete modifier system for composable gate transformations. - -**Tasks:** - -1. **Control Modifiers (Section 5.2):** - -- Implement `quartz.ctrl` / `flux.ctrl` -- Implement `quartz.negctrl` / `flux.negctrl` -- Support arbitrary nesting and flattening -- Implement control qubit exclusivity verification -- Handle value threading in Flux dialect - -2. **Inverse Modifier (Section 5.3):** - -- Implement `quartz.inv` / `flux.inv` -- Double inverse cancellation -- Hermitian gate simplification -- Parametric rotation inversion - -3. **Power Modifier (Section 5.4):** - -- Implement `quartz.pow` / `flux.pow` -- Support static and dynamic exponents -- Handle negative powers (convert to `inv(pow(+exp))`) -- Integer and real/rational exponent handling -- Matrix exponentiation via eigendecomposition - -4. **Canonical Ordering:** - -- Implement canonicalization rules to enforce `negctrl → ctrl → pow → inv` ordering -- Nested modifier flattening - -5. **Unitary Computation:** - -- Extend matrices to control spaces -- Conjugate transpose for inversion -- Matrix exponentiation for powers - -**Acceptance Criteria:** - -- All modifiers parse, verify, and execute correctly -- Canonical ordering is enforced through canonicalization -- Nested modifiers flatten appropriately -- Control modifier exclusivity is verified (no qubit in both `ctrl` and `negctrl`) -- Matrix extraction works through modifier layers -- Unit tests cover all canonicalization patterns - -### Phase 6: Box Operation (Scoped Sequences) - -**Goal:** Implement the `box` operation for scoped unitary composition. - -**Tasks:** - -1. Implement `quartz.box` / `flux.box` (Section 6) -2. Handle value threading for Flux dialect (region arguments and yields) -3. Implement canonicalization: - -- Empty box elimination -- Single-operation inlining -- Nested box flattening - -4. Composite unitary computation (product of constituent unitaries) -5. Verification rules - -**Acceptance Criteria:** - -- Box operations construct and verify correctly in both dialects -- Flux dialect properly threads values through region -- Canonicalization patterns trigger appropriately -- Composite matrix multiplication works for static sequences - -### Phase 7: Builder APIs - -**Goal:** Provide ergonomic programmatic APIs for circuit construction. - -**Tasks:** - -1. **QuartzProgramBuilder (Section 8.2):** - -- Resource management methods -- All base gate methods (single and two-qubit) -- Convenience methods (`cx`, `mcx`, multi-controlled gates) -- Modifier methods (lambdas for body construction) -- Box scoping -- Gate definition and application placeholders -- Finalization with optional default pass application - -2. **FluxProgramBuilder (Section 8.3):** - -- Similar API but with SSA value returns -- Proper value threading -- Tuple returns for multi-qubit gates - -3. **Integration:** - -- Initialize MLIR context -- Create module and function -- Provide fluent chaining interface -- Apply default passes on finalization (optional) - -**Acceptance Criteria:** - -- Both builders construct valid MLIR modules -- Fluent chaining works correctly -- Generated IR matches hand-written IR structurally -- Example circuits (Bell state, GHZ) build successfully - -### Phase 8: Testing Infrastructure Refactor - -**Goal:** Establish robust, maintainable testing following Section 9. - -**Tasks:** - -1. **Unit Testing Framework (Section 9.2):** - -- Set up GoogleTest infrastructure -- Test categories: - - Operation construction - - Canonicalization patterns - - Interface implementation - - Matrix extraction - - Modifier composition - - Dialect conversion -- Structural equivalence checks (not SSA name matching) -- Numerical matrix comparison with tolerances - -2. **Minimal FileCheck Tests (Section 9.3):** - -- Parser/printer round-trip validation -- Focus on structural properties, not formatting -- Avoid brittle SSA name checks - -3. **Integration Tests (Section 9.4):** - -- Frontend translation tests (Qiskit, OpenQASM 3, `qc::QuantumComputation`) -- Compiler pipeline tests -- Backend translation tests (QIR) -- End-to-end algorithm scenarios - -4. **Default Pass Pipeline (Section 9.5):** - -- Integrate `-canonicalize` and `-remove-dead-values` as defaults -- Configure builder API to optionally apply on finalization -- Document testing patterns using default pipeline - -**Acceptance Criteria:** - -- Comprehensive unit test coverage for all operations -- FileCheck tests are minimal and focused -- Integration tests cover key scenarios -- All tests pass reliably -- Test execution time is reasonable (<5 minutes for full suite) - -### Phase 9: User-Defined Gates (Deferred) - -**Note:** Section 7 explicitly defers detailed specification of user-defined gates. This phase is a placeholder for future work once core infrastructure stabilizes. - -**Future Tasks:** - -- Design concrete syntax for `define_matrix_gate` and `define_composite_gate` -- Implement symbol table integration -- Define verification rules for unitarity and consistency -- Create `apply` operation for gate instantiation -- Integrate with modifier system -- Design builder API convenience methods - -## Implementation Guidelines - -### Code Organization - -``` -mlir/ -├── include/ -│ └── mqt/ -│ └── Dialect/ -│ ├── Quartz/ -│ │ ├── IR/ -│ │ │ ├── QuartzDialect.h -│ │ │ ├── QuartzOps.h -│ │ │ ├── QuartzTypes.h -│ │ │ └── QuartzInterfaces.h -│ │ └── Transforms/ -│ │ └── Passes.h -│ └── Flux/ -│ ├── IR/ -│ │ ├── FluxDialect.h -│ │ ├── FluxOps.h -│ │ ├── FluxTypes.h -│ │ └── FluxInterfaces.h -│ └── Transforms/ -│ └── Passes.h -├── lib/ -│ └── Dialect/ -│ ├── Quartz/ -│ │ ├── IR/ -│ │ │ ├── QuartzDialect.cpp -│ │ │ ├── QuartzOps.cpp -│ │ │ └── QuartzTypes.cpp -│ │ └── Transforms/ -│ │ └── Canonicalize.cpp -│ └── Flux/ -│ ├── IR/ -│ │ ├── FluxDialect.cpp -│ │ ├── FluxOps.cpp -│ │ └── FluxTypes.cpp -│ └── Transforms/ -│ └── Canonicalize.cpp -├── tools/ -│ └── mqt-opt/ -│ └── mqt-opt.cpp -├── test/ -│ ├── Dialect/ -│ │ ├── Quartz/ -│ │ └── Flux/ -│ └── Integration/ -└── unittests/ - ├── Dialect/ - │ ├── Quartz/ - │ └── Flux/ - └── Builder/ -``` - -### TableGen Usage - -- Define operations using ODS (Operation Definition Specification) -- Define interfaces using TableGen interface definitions -- Use traits to express operation properties -- Generate boilerplate code where possible - -### Naming Conventions - -- **Dialects:** `quartz`, `flux` (lowercase) -- **Operations:** `quartz.h`, `flux.ctrl`, etc. (lowercase, dialect prefix) -- **Types:** `!quartz.qubit`, `!flux.qubit` -- **C++ Classes:** `QuartzDialect`, `HadamardOp`, `UnitaryOpInterface` (PascalCase) -- **Methods:** `getNumTargets()`, `tryGetStaticMatrix()` (camelCase) - -### Verification Philosophy - -- Verify type correctness -- Verify qubit uniqueness constraints -- Verify control/target disjointness -- Verify region structure (single block, proper terminator) -- Verify interface implementation consistency -- **Do not** verify unitary correctness (too expensive, user responsibility) - -### Canonicalization Strategy - -- Implement patterns as separate classes inheriting from `OpRewritePattern` -- Register patterns in `getCanonicalizationPatterns()` -- Patterns should be confluent (order-independent) -- Use pattern benefit values to prioritize important patterns -- Document pattern purpose and examples - -### Matrix Computation - -- Use `mlir::DenseElementsAttr` for static matrices -- Store as `tensor>` where N = 2^(num_qubits) -- Use numerical libraries (Eigen, if available) for: - - Matrix multiplication - - Eigendecomposition - - Exponentiation -- Handle numerical precision: machine epsilon for comparisons -- Return `std::nullopt` for symbolic/dynamic cases - -### Testing Best Practices - -1. **Unit Tests:** - -- One test fixture per operation type -- Test construction with valid and invalid parameters -- Test each canonicalization pattern independently -- Test interface methods return correct values -- Use builders for construction, not raw IR strings - -2. **Integration Tests:** - -- Use realistic circuit examples -- Verify unitary equivalence numerically -- Test complete compilation pipelines -- Include performance benchmarks for large circuits - -3. **FileCheck Tests:** - -- Minimal use, only for round-trip parsing -- Check operation presence, not SSA names -- Use `CHECK-LABEL` for test separation -- Keep tests small and focused - -## Implementation Checklist - -For each phase, ensure: - -- [ ] Code compiles without warnings -- [ ] All operations have TableGen definitions -- [ ] All operations implement required interfaces -- [ ] Verification logic is comprehensive -- [ ] Canonicalization patterns are implemented and tested -- [ ] Unit tests achieve high coverage -- [ ] Documentation comments are complete -- [ ] Code passes `pre-commit run -a` -- [ ] CMake integration is correct -- [ ] All specified tests pass - -## Key Design Principles to Preserve - -1. **Separation of Concerns:** Base gates are modifier-free; use wrappers for extensions -2. **Canonical Forms:** Enforce consistent representation through canonicalization -3. **Interface Uniformity:** All unitary operations expose the same introspection API -4. **Semantic Clarity:** Reference semantics (Quartz) vs. value semantics (Flux) are distinct -5. **Extensibility:** Design accommodates future dialects and abstraction levels -6. **Global Phase Preservation:** Treat global phases carefully; only eliminate via explicit passes -7. **Numerical Robustness:** Use appropriate tolerances and stable algorithms - -## Success Criteria - -The implementation is successful when: - -1. All operations in Sections 3-6 are fully implemented and tested -2. Both Quartz and Flux dialects function correctly with proper semantics -3. Builder APIs enable ergonomic circuit construction -4. Testing infrastructure provides confidence through structural and semantic validation -5. Default pass pipeline normalizes IR consistently -6. Documentation is complete and accurate -7. Code quality meets project standards (linting passes) -8. All build targets succeed -9. Integration tests demonstrate end-to-end functionality - -## Getting Started - -**Step 1:** Read the complete specification in `docs/mlir/mlir-compilation-infrastructure-technical-concept.md` - -**Step 2:** Set up your build environment using the configuration command - -**Step 3:** Explore the existing `mlir/` directory to understand current structure - -**Step 4:** Begin with Phase 1, implementing core types and interfaces - -**Step 5:** Incrementally build up functionality, testing at each stage - -**Step 6:** Use the existing translation tests as inspiration for test structure - -**Step 7:** Iterate on design decisions, documenting deviations from the spec - -## Questions and Clarifications - -If you encounter ambiguities or need clarifications: - -1. Check if the specification explicitly defers the decision (e.g., user-defined gates) -2. Look for related patterns in existing MLIR dialects (e.g., `arith`, `tensor`, `scf`) -3. Choose the simplest correct implementation that satisfies the requirements -4. Document your decision for future reference - -## Deliverables - -At the end of each phase, provide: - -1. **Code:** Fully implemented operations, types, interfaces, and passes -2. **Tests:** Comprehensive unit tests and targeted integration tests -3. **Documentation:** Inline code comments and any necessary updates to the specification -4. **Build Validation:** Confirmation that all build and test targets pass -5. **Summary:** Brief description of what was implemented and any notable decisions - ---- - -**Remember:** This is a comprehensive implementation project that requires attention to detail, adherence to MLIR best practices, and systematic testing. Focus on correctness and clarity over premature optimization. The goal is to create a robust foundation for quantum compilation that can evolve with the field. From efb5539290ee5b0350f08cd164f2b67bf6693307 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 15 Oct 2025 09:12:39 +0200 Subject: [PATCH 062/419] =?UTF-8?q?=E2=9C=A8=20use=20int64=5Ft=20for=20ind?= =?UTF-8?q?exing=20and=20enhance=20register=20handling=20in=20QIRProgramBu?= =?UTF-8?q?ilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 15 ++--- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 62 +++++-------------- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 0a187fc317..e7e4ede835 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -12,7 +12,6 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" -#include #include #include #include @@ -163,7 +162,7 @@ class QIRProgramBuilder { * // Output recording deferred to output block * ``` */ - Value measure(Value qubit, size_t resultIndex); + Value measure(Value qubit, int64_t resultIndex); /** * @brief Measure a qubit into a classical register @@ -202,7 +201,7 @@ class QIRProgramBuilder { * ``` */ QIRProgramBuilder& measure(Value qubit, StringRef registerName, - size_t registerIndex); + int64_t registerIndex); /** * @brief Reset a qubit to |0⟩ state @@ -283,17 +282,13 @@ class QIRProgramBuilder { DenseSet allocatedQubits; /// Cache static qubit pointers for reuse - DenseMap staticQubitCache; + DenseMap staticQubitCache; /// Cache result pointers for reuse (separate from qubits) - DenseMap resultPointerCache; + DenseMap resultPointerCache; /// Map from (register_name, register_index) to result pointer - DenseMap, Value> registerResultMap; - - /// Sequence of measurements to record in output block - /// Each entry: (result_ptr, register_name, register_index) - SmallVector> measurementSequence; + DenseMap, Value> registerResultMap; /// Track qubit and result counts for QIR metadata QIRMetadata metadata_; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index f19d5e8259..6b308fd86c 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -12,7 +12,6 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" -#include #include #include #include @@ -106,21 +105,15 @@ Value QIRProgramBuilder::allocQubit() { Value QIRProgramBuilder::staticQubit(const int64_t index) { // Check cache - if (staticQubitCache.contains(static_cast(index))) { - return staticQubitCache.at(static_cast(index)); + if (staticQubitCache.contains(index)) { + return staticQubitCache.at(index); } - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert at start of entry block (after initialize but before branch) - builder.setInsertionPoint(entryBlock->getTerminator()); - // Use common utility function to create pointer from index const auto qubit = createPointerFromIndex(builder, loc, index); // Cache for reuse - staticQubitCache[static_cast(index)] = qubit; + staticQubitCache[index] = qubit; // Update qubit count if (std::cmp_greater_equal(index, metadata_.numQubits)) { @@ -132,7 +125,7 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { SmallVector qubits; - qubits.reserve(static_cast(size)); + qubits.reserve(size); for (int64_t i = 0; i < size; ++i) { qubits.push_back(allocQubit()); @@ -141,7 +134,7 @@ SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { return qubits; } -Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { +Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -155,17 +148,10 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { if (resultPointerCache.contains(resultIndex)) { resultValue = resultPointerCache.at(resultIndex); } else { - // Create at start of entry block using common utility - builder.setInsertionPoint(entryBlock->getTerminator()); - resultValue = - createPointerFromIndex(builder, loc, static_cast(resultIndex)); + resultValue = createPointerFromIndex(builder, loc, resultIndex); resultPointerCache[resultIndex] = resultValue; - - // Track for output recording + registerResultMap.insert({{"c", resultIndex}, resultValue}); metadata_.numResults++; - - // Restore to measurements block - builder.setInsertionPoint(measurementsBlock->getTerminator()); } // Create mz call @@ -175,16 +161,12 @@ Value QIRProgramBuilder::measure(const Value qubit, const size_t resultIndex) { getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); - // Track this measurement for deferred output recording (without register - // info) - measurementSequence.emplace_back(resultValue, "c", resultIndex); - return resultValue; } QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, const StringRef registerName, - const size_t registerIndex) { + const int64_t registerIndex) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -201,16 +183,12 @@ QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, it != registerResultMap.end()) { resultValue = it->second; } else { - // Create at start of entry block - builder.setInsertionPoint(entryBlock->getTerminator()); resultValue = createPointerFromIndex( builder, loc, static_cast(metadata_.numResults)); // Cache for reuse resultPointerCache[metadata_.numResults] = resultValue; registerResultMap.insert({key, resultValue}); metadata_.numResults++; - // Restore to measurements block - builder.setInsertionPoint(measurementsBlock->getTerminator()); } // Create mz call @@ -220,9 +198,6 @@ QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, getOrCreateFunctionDeclaration(builder, module, QIR_MEASURE, mzSignature); builder.create(loc, mzDecl, ValueRange{qubit, resultValue}); - // Track this measurement for deferred output recording (with register info) - measurementSequence.emplace_back(resultValue, registerName, registerIndex); - return *this; } @@ -265,7 +240,7 @@ QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { } void QIRProgramBuilder::generateOutputRecording() { - if (measurementSequence.empty()) { + if (registerResultMap.empty()) { return; // No measurements to record } @@ -278,15 +253,14 @@ void QIRProgramBuilder::generateOutputRecording() { auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); // Group measurements by register - llvm::StringMap>> registerGroups; - for (const auto& [resultValue, regName, regIdx] : measurementSequence) { - if (!regName.empty()) { - registerGroups[regName].emplace_back(regIdx, resultValue); - } + llvm::StringMap>> registerGroups; + for (const auto& [key, resultPtr] : registerResultMap) { + const auto& [regName, regIdx] = key; + registerGroups[regName].emplace_back(regIdx, resultPtr); } // Sort registers by name for deterministic output - SmallVector>>> + SmallVector>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); @@ -322,12 +296,10 @@ void QIRProgramBuilder::generateOutputRecording() { loc, arrayRecordDecl, ValueRange{arraySizeConst.getResult(), arrayLabelOp.getResult()}); - for (size_t i = 0; i < measurements.size(); ++i) { - const auto [regIdx, resultPtr] = measurements[i]; - - // Create label for result: "{registerName}{i}r" + for (const auto [regIdx, resultPtr] : measurements) { + // Create label for result: "{registerName}{regIdx}r" const std::string resultLabel = - registerName.str() + std::to_string(i) + "r"; + registerName.str() + std::to_string(regIdx) + "r"; auto resultLabelOp = createResultLabel(builder, module, resultLabel); builder.create( From 26fe26ab4e2c8de471ef9c0b6eaad5987db437e3 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 15 Oct 2025 09:12:53 +0200 Subject: [PATCH 063/419] =?UTF-8?q?=E2=9C=A8=20update=20QuartzToQIR=20to?= =?UTF-8?q?=20use=20int64=5Ft=20for=20indexing=20and=20enhance=20register?= =?UTF-8?q?=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 6df981ba5c..191ab12922 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -66,18 +66,14 @@ namespace { */ struct LoweringState : QIRMetadata { /// Map from qubit index to pointer value for reuse - DenseMap ptrMap; + DenseMap ptrMap; /// Map from classical result index to pointer value for reuse - DenseMap resultPtrMap; + DenseMap resultPtrMap; /// Map from (register_name, register_index) to result pointer /// This allows caching result pointers for measurements with register info - DenseMap, Value> registerResultMap; - - /// Sequence of measurements to record in output block - /// Each entry: (result_ptr, register_name, register_index) - SmallVector> measurementSequence; + DenseMap, Value> registerResultMap; }; /** @@ -152,7 +148,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); - const auto index = static_cast(op.getIndex()); + const auto index = op.getIndex(); // Get or create a pointer to the qubit if (getState().ptrMap.contains(index)) { @@ -161,7 +157,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { } else { // Create constant and inttoptr operations const auto constantOp = rewriter.create( - op.getLoc(), rewriter.getI64IntegerAttr(static_cast(index))); + op.getLoc(), rewriter.getI64IntegerAttr(index)); const auto intToPtrOp = rewriter.replaceOpWithNewOp( op, LLVM::LLVMPointerType::get(ctx), constantOp->getResult(0)); @@ -337,17 +333,15 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { auto* ctx = getContext(); const auto ptrType = LLVM::LLVMPointerType::get(ctx); auto& state = getState(); - auto& numResults = state.numResults; + const auto numResults = static_cast(state.numResults); auto& resultPtrMap = state.resultPtrMap; auto& registerResultMap = state.registerResultMap; - auto& measurementSequence = state.measurementSequence; // Get or create result pointer value Value resultValue; if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { const auto registerName = op.getRegisterName().value(); - const auto registerIndex = - static_cast(op.getRegisterIndex().value()); + const auto registerIndex = op.getRegisterIndex().value(); const auto key = std::make_pair(registerName, registerIndex); if (const auto it = registerResultMap.find(key); @@ -364,24 +358,20 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { .getResult(); resultPtrMap[numResults] = resultValue; registerResultMap.insert({key, resultValue}); - numResults++; + state.numResults++; } - - // Track this measurement for output recording - measurementSequence.emplace_back(resultValue, registerName, - registerIndex); } else { // No register info - assign sequential result pointer const auto constantOp = rewriter.create( - op.getLoc(), rewriter.getI64IntegerAttr(static_cast( - numResults))); // Sequential result index + op.getLoc(), + rewriter.getI64IntegerAttr(numResults)); // Sequential result index resultValue = rewriter .create(op.getLoc(), ptrType, constantOp->getResult(0)) .getResult(); resultPtrMap[numResults] = resultValue; - measurementSequence.emplace_back(resultValue, "c", numResults); - numResults++; + registerResultMap.insert({{"c", numResults}, resultValue}); + state.numResults++; } // Create mz (measure) call: mz(qubit, result) @@ -578,7 +568,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { */ static void addOutputRecording(LLVM::LLVMFuncOp& main, MLIRContext* ctx, LoweringState* state) { - if (state->measurementSequence.empty()) { + if (state->registerResultMap.empty()) { return; // No measurements to record } @@ -592,16 +582,14 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { builder.setInsertionPoint(&outputBlock.back()); // Group measurements by register - llvm::StringMap>> registerGroups; - for (const auto& [resultPtr, regName, regIdx] : - state->measurementSequence) { - if (!regName.empty()) { - registerGroups[regName].emplace_back(regIdx, resultPtr); - } + llvm::StringMap>> registerGroups; + for (const auto& [key, resultPtr] : state->registerResultMap) { + const auto& [registerName, registerIndex] = key; + registerGroups[registerName].emplace_back(registerIndex, resultPtr); } // Sort registers by name for deterministic output - SmallVector>>> + SmallVector>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); From 80cfa5c6947d5d5cf6f8e4cd7e43bda8b07aa731 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 15 Oct 2025 09:13:07 +0200 Subject: [PATCH 064/419] =?UTF-8?q?=E2=9C=A8=20enhance=20compiler=20pipeli?= =?UTF-8?q?ne=20tests=20by=20adding=20canonicalization=20passes=20and=20up?= =?UTF-8?q?dating=20measurement=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../pipeline/test_compiler_pipeline.cpp | 161 +++++------------- 1 file changed, 39 insertions(+), 122 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 16aa2e702f..c48ddd5209 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -98,15 +98,10 @@ using namespace mlir; */ struct StageExpectations { ModuleOp quartzImport; - ModuleOp initialCanon; ModuleOp fluxConversion; - ModuleOp fluxCanon; ModuleOp optimization; - ModuleOp optimizationCanon; ModuleOp quartzConversion; - ModuleOp quartzCanon; ModuleOp qirConversion; - ModuleOp qirCanon; }; //===----------------------------------------------------------------------===// @@ -147,7 +142,7 @@ class CompilerPipelineTest : public testing::Test { config.convertToQIR = true; config.recordIntermediates = true; config.printIRAfterAllStages = - false; /// TODO: Change back after everything is running + true; /// TODO: Change back after everything is running emptyQuartz = buildQuartzIR([](quartz::QuartzProgramBuilder&) {}); emptyFlux = buildFluxIR([](flux::FluxProgramBuilder&) {}); @@ -222,7 +217,19 @@ class CompilerPipelineTest : public testing::Test { //===--------------------------------------------------------------------===// /** - * @brief Build expected Quartz IR programmatically + * @brief Run canonicalization + */ + void runCanonicalizationPasses(ModuleOp module) const { + PassManager pm(module.getContext()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createCSEPass()); + if (failed(pm.run(module))) { + llvm::errs() << "Failed to run canonicalization passes\n"; + } + } + + /** + * @brief Build expected Quartz IR programmatically and run canonicalization */ [[nodiscard]] OwningOpRef buildQuartzIR( const std::function& buildFunc) @@ -230,29 +237,35 @@ class CompilerPipelineTest : public testing::Test { quartz::QuartzProgramBuilder builder(context.get()); builder.initialize(); buildFunc(builder); - return builder.finalize(); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; } /** - * @brief Build expected Flux IR programmatically + * @brief Build expected Flux IR programmatically and run canonicalization */ [[nodiscard]] OwningOpRef buildFluxIR( const std::function& buildFunc) const { flux::FluxProgramBuilder builder(context.get()); builder.initialize(); buildFunc(builder); - return builder.finalize(); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; } /** - * @brief Build expected QIR programmatically + * @brief Build expected QIR programmatically and run canonicalization */ [[nodiscard]] OwningOpRef buildQIR( const std::function& buildFunc) const { qir::QIRProgramBuilder builder(context.get()); builder.initialize(); buildFunc(builder); - return builder.finalize(); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; } //===--------------------------------------------------------------------===// @@ -267,56 +280,30 @@ class CompilerPipelineTest : public testing::Test { * Stages without expectations are skipped. */ void verifyAllStages(const StageExpectations& expectations) const { - if (expectations.quartzImport) { - EXPECT_TRUE(verify("Quartz Import", record.afterQuartzImport, + if (expectations.quartzImport != nullptr) { + EXPECT_TRUE(verify("Quartz Import", record.afterInitialCanon, expectations.quartzImport)); } - if (expectations.initialCanon) { - EXPECT_TRUE(verify("Initial Canonicalization", record.afterInitialCanon, - expectations.initialCanon)); - } - - if (expectations.fluxConversion) { - EXPECT_TRUE(verify("Flux Conversion", record.afterFluxConversion, + if (expectations.fluxConversion != nullptr) { + EXPECT_TRUE(verify("Flux Conversion", record.afterFluxCanon, expectations.fluxConversion)); } - if (expectations.fluxCanon) { - EXPECT_TRUE(verify("Flux Canonicalization", record.afterFluxCanon, - expectations.fluxCanon)); - } - - if (expectations.optimization) { - EXPECT_TRUE(verify("Optimization", record.afterOptimization, + if (expectations.optimization != nullptr) { + EXPECT_TRUE(verify("Optimization", record.afterOptimizationCanon, expectations.optimization)); } - if (expectations.optimizationCanon) { - EXPECT_TRUE(verify("Optimization Canonicalization", - record.afterOptimizationCanon, - expectations.optimizationCanon)); - } - - if (expectations.quartzConversion) { - EXPECT_TRUE(verify("Quartz Conversion", record.afterQuartzConversion, + if (expectations.quartzConversion != nullptr) { + EXPECT_TRUE(verify("Quartz Conversion", record.afterQuartzCanon, expectations.quartzConversion)); } - if (expectations.quartzCanon) { - EXPECT_TRUE(verify("Quartz Canonicalization", record.afterQuartzCanon, - expectations.quartzCanon)); - } - - if (expectations.qirConversion) { - EXPECT_TRUE(verify("QIR Conversion", record.afterQIRConversion, + if (expectations.qirConversion != nullptr) { + EXPECT_TRUE(verify("QIR Conversion", record.afterQIRCanon, expectations.qirConversion)); } - - if (expectations.qirCanon) { - EXPECT_TRUE(verify("QIR Canonicalization", record.afterQIRCanon, - expectations.qirCanon)); - } } void TearDown() override { @@ -376,15 +363,10 @@ TEST_F(CompilerPipelineTest, EmptyCircuit) { // Verify all stages verifyAllStages({ .quartzImport = emptyQuartz.get(), - .initialCanon = emptyQuartz.get(), .fluxConversion = emptyFlux.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -414,15 +396,10 @@ TEST_F(CompilerPipelineTest, SingleQubitRegister) { verifyAllStages({ .quartzImport = quartzExpected.get(), - .initialCanon = quartzExpected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -444,15 +421,10 @@ TEST_F(CompilerPipelineTest, MultiQubitRegister) { verifyAllStages({ .quartzImport = quartzExpected.get(), - .initialCanon = quartzExpected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -480,15 +452,10 @@ TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { verifyAllStages({ .quartzImport = quartzExpected.get(), - .initialCanon = quartzExpected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -528,15 +495,10 @@ TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = emptyQuartz.get(), .fluxConversion = emptyFlux.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -560,15 +522,10 @@ TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = emptyQuartz.get(), .fluxConversion = emptyFlux.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -595,15 +552,10 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = emptyQuartz.get(), .fluxConversion = emptyFlux.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -649,15 +601,10 @@ TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -695,15 +642,10 @@ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -733,15 +675,10 @@ TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = emptyFlux.get(), .optimization = emptyFlux.get(), - .optimizationCanon = emptyFlux.get(), .quartzConversion = emptyQuartz.get(), - .quartzCanon = emptyQuartz.get(), .qirConversion = emptyQIR.get(), - .qirCanon = emptyQIR.get(), }); } @@ -779,15 +716,10 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = fluxExpected.get(), .optimization = fluxExpected.get(), - .optimizationCanon = fluxExpected.get(), .quartzConversion = expected.get(), - .quartzCanon = expected.get(), .qirConversion = qirExpected.get(), - .qirCanon = qirExpected.get(), }); } @@ -825,15 +757,10 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = fluxExpected.get(), .optimization = fluxExpected.get(), - .optimizationCanon = fluxExpected.get(), .quartzConversion = expected.get(), - .quartzCanon = expected.get(), .qirConversion = qirExpected.get(), - .qirCanon = qirExpected.get(), }); } @@ -876,15 +803,10 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = fluxExpected.get(), .optimization = fluxExpected.get(), - .optimizationCanon = fluxExpected.get(), .quartzConversion = expected.get(), - .quartzCanon = expected.get(), .qirConversion = qirExpected.get(), - .qirCanon = qirExpected.get(), }); } @@ -893,8 +815,8 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { */ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { qc::QuantumComputation qc(2); - auto& c1 = qc.addClassicalRegister(1, "c1"); - auto& c2 = qc.addClassicalRegister(1, "c2"); + const auto& c1 = qc.addClassicalRegister(1, "c1"); + const auto& c2 = qc.addClassicalRegister(1, "c2"); qc.measure(0, c1[0]); qc.measure(1, c2[0]); @@ -920,21 +842,16 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.measure(q[0], 0); - b.measure(q[1], 1); + b.measure(q[0], "c1", 0); + b.measure(q[1], "c2", 0); }); verifyAllStages({ .quartzImport = expected.get(), - .initialCanon = expected.get(), .fluxConversion = fluxExpected.get(), - .fluxCanon = fluxExpected.get(), .optimization = fluxExpected.get(), - .optimizationCanon = fluxExpected.get(), .quartzConversion = expected.get(), - .quartzCanon = expected.get(), .qirConversion = qirExpected.get(), - .qirCanon = qirExpected.get(), }); } From 2cdfcc203c8b2cbcc231b660e73426eb5498ff98 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 15 Oct 2025 10:26:18 +0200 Subject: [PATCH 065/419] =?UTF-8?q?=E2=9C=A8=20add=20memory=20effect=20tra?= =?UTF-8?q?its=20to=20both=20dialects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 10 +++++----- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 014296c5a3..0365347945 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -89,7 +89,7 @@ class FluxOp traits = []> : // Resource Operations //===----------------------------------------------------------------------===// -def AllocOp : FluxOp<"alloc"> { +def AllocOp : FluxOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns an SSA value representing it. @@ -137,7 +137,7 @@ def AllocOp : FluxOp<"alloc"> { let hasVerifier = 1; } -def DeallocOp : FluxOp<"dealloc"> { +def DeallocOp : FluxOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. @@ -153,7 +153,7 @@ def DeallocOp : FluxOp<"dealloc"> { let hasCanonicalizer = 1; } -def StaticOp : FluxOp<"static"> { +def StaticOp : FluxOp<"static", [Pure]> { let summary = "Retrieve a static qubit by index"; let description = [{ The `flux.static` operation produces an SSA value representing a qubit @@ -198,7 +198,7 @@ def MeasureOp : FluxOp<"measure"> { ``` }]; - let arguments = (ins QubitType:$qubit_in, + let arguments = (ins Arg:$qubit_in, OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); @@ -230,7 +230,7 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { ``` }]; - let arguments = (ins QubitType:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let hasCanonicalizer = 1; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index ce3a4bbac8..a5ca111308 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -81,7 +81,7 @@ class QuartzOp traits = []> : // Resource Operations //===----------------------------------------------------------------------===// -def AllocOp : QuartzOp<"alloc"> { +def AllocOp : QuartzOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns a reference to it. @@ -129,7 +129,7 @@ def AllocOp : QuartzOp<"alloc"> { let hasVerifier = 1; } -def DeallocOp : QuartzOp<"dealloc"> { +def DeallocOp : QuartzOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. @@ -189,7 +189,7 @@ def MeasureOp : QuartzOp<"measure"> { ``` }]; - let arguments = (ins QubitType:$qubit, + let arguments = (ins Arg:$qubit, OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); @@ -219,7 +219,7 @@ def ResetOp : QuartzOp<"reset"> { ``` }]; - let arguments = (ins QubitType:$qubit); + let arguments = (ins Arg:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; } From 36b6748119486588d36626b5aefe6137a06cd8e1 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 23 Oct 2025 21:44:33 +0200 Subject: [PATCH 066/419] =?UTF-8?q?=F0=9F=9A=A7=20wip=20QuartzInterface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Quartz/IR/QuartzInterfaces.td | 75 +++++++------------ 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index de5b7b2f37..9b66bdc56d 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -19,7 +19,7 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let description = [{ This interface provides a unified API for all operations that apply or produce a unitary transformation. This includes base gates, user-defined - gates, modified operations (control, inverse, power), and sequences. + gates, modifier operations (control, inverse, power), and sequences. The interface enables uniform introspection and composition capabilities across all unitary operations in the Quartz dialect. @@ -29,10 +29,18 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let methods = [ // Qubit accessors + InterfaceMethod< + "Returns the number of qubits acted on by the unitary operation.", + "size_t", "getNumQubits", (ins) + >, InterfaceMethod< "Returns the number of target qubits (excluding control qubits).", "size_t", "getNumTargets", (ins) >, + InterfaceMethod< + "Returns the number of control qubits (both positive and negative).", + "size_t", "getNumControls", (ins) + >, InterfaceMethod< "Returns the number of positive control qubits.", "size_t", "getNumPosControls", (ins) @@ -41,6 +49,10 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the number of negative control qubits.", "size_t", "getNumNegControls", (ins) >, + InterfaceMethod< + "Returns the i-th qubit (targets + controls combined).", + "::mlir::Value", "getQubit", (ins "size_t":$i) + >, InterfaceMethod< "Returns the i-th target qubit.", "::mlir::Value", "getTarget", (ins "size_t":$i) @@ -54,41 +66,25 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "::mlir::Value", "getNegControl", (ins "size_t":$i) >, - // Value semantics threading (identity in reference semantics) - InterfaceMethod< - "Returns the i-th input qubit (targets + controls combined).", - "::mlir::Value", "getInput", (ins "size_t":$i) - >, - InterfaceMethod< - "Returns the i-th output qubit (targets + controls combined). " - "In reference semantics, returns the same as getInput.", - "::mlir::Value", "getOutput", (ins "size_t":$i), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return $_op.getInput(i); - }] - >, + // Parameter handling InterfaceMethod< - "Returns the output qubit corresponding to the given input qubit. " - "In reference semantics, returns the input itself.", - "::mlir::Value", "getOutputForInput", (ins "::mlir::Value":$input), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return input; - }] + "Returns the number of parameters.", + "size_t", "getNumParams", (ins) >, + /// TODO: This is missing many convenience methods for handling parameters. Some inspiration is in the RFC + + // Convenience methods InterfaceMethod< - "Returns the input qubit corresponding to the given output qubit. " - "In reference semantics, returns the output itself.", - "::mlir::Value", "getInputForOutput", (ins "::mlir::Value":$output), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return output; - }] + "Returns true if the operation has any control qubits, otherwise false.", + "bool", "isControlled", (ins), + [{ return getNumControls() > 0; }] >, - - // Parameter handling InterfaceMethod< - "Returns the number of parameters.", - "size_t", "getNumParams", (ins) + "Returns true if the operation only acts on a single qubit.", + "bool", "isSingleQubit", (ins), + [{ return getNumQubits() == 1; }] >, + /// TODO: I am fairly sure that there are quite some further convenience methods that would be helpful here, e.g., whether it is a two-qubit gate // Matrix extraction InterfaceMethod< @@ -100,28 +96,15 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns std::nullopt if the operation is symbolic or dynamic.", "std::optional<::mlir::DenseElementsAttr>", "tryGetStaticMatrix", (ins) >, - - // Modifier state - InterfaceMethod< - "Returns true if the operation is inverted.", - "bool", "isInverted", (ins), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return false; - }] - >, - InterfaceMethod< - "Returns the power exponent if applicable, otherwise std::nullopt.", - "std::optional", "getPower", (ins), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return std::nullopt; - }] - >, + /// TODO: probably also worth to check with the RFC and compare // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", "::llvm::StringRef", "getBaseSymbol", (ins) >, + + /// TODO: I am fairly sure the RFC had some more functions here. ]; } From 49461f8efa76e0935722cc548c88fe9a93b9cec4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:41:37 +0200 Subject: [PATCH 067/419] Fix linter errors --- mlir/lib/Compiler/CompilerPipeline.cpp | 44 ++++++++++------- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 5 -- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 6 --- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 +- .../Flux/Builder/FluxProgramBuilder.cpp | 6 +++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 3 ++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 12 ++++- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 9 +++- .../Quartz/Builder/QuartzProgramBuilder.cpp | 11 +++-- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 2 + .../TranslateQuantumComputationToQuartz.cpp | 29 ++++++----- mlir/lib/Support/PrettyPrinting.cpp | 14 ++++-- mlir/tools/mqt-cc/mqt-cc.cpp | 49 +++++++++++-------- .../pipeline/test_compiler_pipeline.cpp | 8 ++- 14 files changed, 125 insertions(+), 77 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 150097b5b5..b762f39c3a 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -15,31 +15,17 @@ #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" #include "mlir/Support/PrettyPrinting.h" +#include #include #include #include +#include #include #include namespace mlir { -void QuantumCompilerPipeline::addCleanupPasses(PassManager& pm) { - // Always run canonicalization and dead value removal - pm.addPass(createCanonicalizerPass()); - pm.addPass(createRemoveDeadValuesPass()); -} - -void QuantumCompilerPipeline::configurePassManager(PassManager& pm) const { - // Enable timing statistics if requested - if (config_.enableTiming) { - pm.enableTiming(); - } - - // Enable pass statistics if requested - if (config_.enableStatistics) { - pm.enableStatistics(); - } -} +namespace { /** * @brief Pretty print IR with ASCII art borders and stage identifier @@ -75,6 +61,26 @@ static void prettyPrintStage(ModuleOp module, const StringRef stageName, llvm::errs().flush(); } +} // namespace + +void QuantumCompilerPipeline::addCleanupPasses(PassManager& pm) { + // Always run canonicalization and dead value removal + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); +} + +void QuantumCompilerPipeline::configurePassManager(PassManager& pm) const { + // Enable timing statistics if requested + if (config_.enableTiming) { + pm.enableTiming(); + } + + // Enable pass statistics if requested + if (config_.enableStatistics) { + pm.enableStatistics(); + } +} + LogicalResult QuantumCompilerPipeline::runPipeline(ModuleOp module, CompilationRecord* record) const { @@ -103,7 +109,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, // Stage 1: Initial canonicalization addCleanupPasses(pm); - if (failed(pm.run(module))) { + if (pm.run(module).failed()) { return failure(); } if (record != nullptr && config_.recordIntermediates) { @@ -236,7 +242,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, printBoxLine("✓ Compilation Complete"); - std::string summaryLine = + const std::string summaryLine = "Successfully processed " + std::to_string(currentStage) + " stages"; printBoxLine(summaryLine, 1); // Indent by 1 space diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 790bfd73e4..0639df7648 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -13,17 +13,12 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include #include #include -#include -#include #include -#include #include #include #include -#include #include #include #include diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 13a012149d..2ab2d24f77 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -13,14 +13,9 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include #include -#include #include #include -#include -#include -#include #include #include #include @@ -30,7 +25,6 @@ #include #include #include -#include namespace mlir { using namespace flux; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 191ab12922..9a2732477b 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -13,11 +13,11 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include #include #include #include #include +#include #include #include #include @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include namespace mlir { diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index edfd0f46f5..db430f12f7 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -15,11 +15,17 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#include +#include +#include namespace mlir::flux { diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 648320fb26..506aaa7e7b 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -18,6 +18,9 @@ #include // IWYU pragma: end_keep +#include +#include + using namespace mlir; using namespace mlir::flux; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 6b308fd86c..46bf535659 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -12,11 +12,18 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" +#include +#include +#include #include +#include #include #include #include #include +#include +#include +#include #include #include @@ -123,8 +130,9 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { return qubit; } -SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { - SmallVector qubits; +llvm::SmallVector +QIRProgramBuilder::allocQubitRegister(const int64_t size) { + llvm::SmallVector qubits; qubits.reserve(size); for (int64_t i = 0; i < size; ++i) { diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index ee7da1acd1..95cebf49d0 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -10,12 +10,19 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" +#include +#include #include +#include #include #include #include #include +#include +#include #include +#include +#include #include namespace mlir::qir { @@ -44,7 +51,7 @@ LLVM::LLVMFuncOp getMainFunction(Operation* op) { void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata) { OpBuilder builder(main.getBody()); - SmallVector attributes; + llvm::SmallVector attributes; // Core QIR attributes attributes.emplace_back(builder.getStringAttr("entry_point")); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index fcf8176c2a..2b87ecf354 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -12,13 +12,18 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include +#include #include +#include +#include #include #include #include #include #include +#include +#include +#include namespace mlir::quartz { @@ -66,11 +71,11 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { return staticOp.getQubit(); } -SmallVector +llvm::SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { // Allocate a sequence of qubits with register metadata - SmallVector qubits; + llvm::SmallVector qubits; qubits.reserve(size); auto nameAttr = builder.getStringAttr(name); diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index eedd79ed05..bda38ae3b7 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -17,6 +17,8 @@ #include // IWYU pragma: end_keep +#include + using namespace mlir; using namespace mlir::quartz; diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 2dd82d030a..79a964bd6c 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -21,12 +21,14 @@ #include #include #include -#include +#include #include #include #include #include +#include #include +#include namespace mlir { @@ -43,12 +45,12 @@ namespace { */ struct QregInfo { const qc::QuantumRegister* qregPtr; - SmallVector qubits; + llvm::SmallVector qubits; }; using BitMemInfo = std::pair; // (register ref, localIdx) -using BitIndexVec = SmallVector; +using BitIndexVec = llvm::SmallVector; /** * @brief Allocates quantum registers using the QuartzProgramBuilder @@ -63,11 +65,11 @@ using BitIndexVec = SmallVector; * @param quantumComputation The quantum computation to translate * @return Vector containing information about all quantum registers */ -SmallVector +llvm::SmallVector allocateQregs(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - SmallVector qregPtrs; + llvm::SmallVector qregPtrs; qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + quantumComputation.getAncillaRegisters().size()); for (const auto& qreg : @@ -86,7 +88,7 @@ allocateQregs(QuartzProgramBuilder& builder, }); // Allocate quantum registers using the builder - SmallVector qregs; + llvm::SmallVector qregs; for (const auto* qregPtr : qregPtrs) { auto qubits = builder.allocQubitRegister(qregPtr->getSize(), qregPtr->getName()); @@ -108,10 +110,10 @@ allocateQregs(QuartzProgramBuilder& builder, * @param qregs Vector containing information about all quantum registers * @return Flat vector of qubit values indexed by physical qubit index */ -SmallVector +llvm::SmallVector buildQubitMap(const qc::QuantumComputation& quantumComputation, - const SmallVector& qregs) { - SmallVector flatQubits; + const llvm::SmallVector& qregs) { + llvm::SmallVector flatQubits; const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); flatQubits.resize(static_cast(maxPhys) + 1); @@ -141,7 +143,7 @@ BitIndexVec allocateClassicalRegisters(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - SmallVector cregPtrs; + llvm::SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); for (const auto& reg : quantumComputation.getClassicalRegisters() | std::views::values) { @@ -182,7 +184,8 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, * @param bitMap Mapping from global bit index to (register, local_index) */ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits, const BitIndexVec& bitMap) { + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -210,7 +213,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits) { + const llvm::SmallVector& qubits) { for (const auto& target : operation.getTargets()) { const Value qubit = qubits[target]; builder.reset(qubit); @@ -238,7 +241,7 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, LogicalResult translateOperations(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation, - const SmallVector& qubits, + const llvm::SmallVector& qubits, const BitIndexVec& bitMap) { for (const auto& operation : quantumComputation) { switch (operation->getType()) { diff --git a/mlir/lib/Support/PrettyPrinting.cpp b/mlir/lib/Support/PrettyPrinting.cpp index ce6c0c5c9a..e0471b2cf0 100644 --- a/mlir/lib/Support/PrettyPrinting.cpp +++ b/mlir/lib/Support/PrettyPrinting.cpp @@ -10,6 +10,7 @@ #include "mlir/Support/PrettyPrinting.h" +#include #include #include #include @@ -17,8 +18,7 @@ namespace mlir { -constexpr auto TOTAL_WIDTH = 120; -constexpr auto BORDER_WIDTH = 2; // "║ " on each side +namespace { /** * @brief Trim trailing whitespace from a string @@ -28,6 +28,11 @@ static std::string trimTrailingWhitespace(const std::string& str) { return (end == std::string::npos) ? "" : str.substr(0, end + 1); } +} // namespace + +constexpr auto TOTAL_WIDTH = 120; +constexpr auto BORDER_WIDTH = 2; // "║ " on each side + int calculateDisplayWidth(const std::string& str) { auto displayWidth = 0; for (size_t i = 0; i < str.size();) { @@ -187,7 +192,7 @@ std::vector wrapLine(const std::string& line, const int maxWidth, // Add continuation indicator to all but the first and last lines for (size_t i = 1; i < wrapped.size(); ++i) { if (wrapped[i].find("↳") == std::string::npos) { - std::string indentStr(leadingSpaces, ' '); + const std::string indentStr(leadingSpaces, ' '); wrapped[i] = "↳ " + indentStr + wrapped[i].substr(leadingSpaces); } } @@ -224,7 +229,8 @@ void printBoxLine(const std::string& text, const int indent, llvm::raw_ostream& os) { // Content width = Total width - left border (2 chars) - right border (2 // chars) - constexpr int contentWidth = TOTAL_WIDTH - 2 * BORDER_WIDTH; // "║ " and " ║" + constexpr int contentWidth = + TOTAL_WIDTH - (2 * BORDER_WIDTH); // "║ " and " ║" // Trim trailing whitespace before processing const std::string trimmedText = trimTrailingWhitespace(text); diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 7510c6fa58..6d7e257d48 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -26,32 +27,37 @@ #include #include #include +#include +#include +#include using namespace llvm; using namespace mlir; +namespace { + // Command-line options -static cl::opt inputFilename(cl::Positional, - cl::desc(""), - cl::init("-")); +const cl::opt inputFilename(cl::Positional, + cl::desc(""), + cl::init("-")); -static cl::opt outputFilename("o", cl::desc("Output filename"), - cl::value_desc("filename"), - cl::init("-")); +const cl::opt outputFilename("o", cl::desc("Output filename"), + cl::value_desc("filename"), + cl::init("-")); -static cl::opt convertToQIR("emit-qir", - cl::desc("Convert to QIR at the end"), - cl::init(false)); +const cl::opt convertToQIR("emit-qir", + cl::desc("Convert to QIR at the end"), + cl::init(false)); -static cl::opt enableTiming("mlir-timing", - cl::desc("Enable pass timing statistics"), - cl::init(false)); +const cl::opt enableTiming("mlir-timing", + cl::desc("Enable pass timing statistics"), + cl::init(false)); -static cl::opt enableStatistics("mlir-statistics", - cl::desc("Enable pass statistics"), - cl::init(false)); +const cl::opt enableStatistics("mlir-statistics", + cl::desc("Enable pass statistics"), + cl::init(false)); -static cl::opt +const cl::opt printIRAfterAllStages("mlir-print-ir-after-all-stages", cl::desc("Print IR after each compiler stage"), cl::init(false)); @@ -59,8 +65,7 @@ static cl::opt /** * @brief Load and parse a .mlir file */ -static OwningOpRef loadMLIRFile(StringRef filename, - MLIRContext* context) { +OwningOpRef loadMLIRFile(StringRef filename, MLIRContext* context) { // Set up the input file std::string errorMessage; auto file = openInputFile(filename, &errorMessage); @@ -78,7 +83,7 @@ static OwningOpRef loadMLIRFile(StringRef filename, /** * @brief Write the module to the output file */ -static LogicalResult writeOutput(ModuleOp module, const StringRef filename) { +LogicalResult writeOutput(ModuleOp module, const StringRef filename) { std::string errorMessage; const auto output = openOutputFile(filename, &errorMessage); if (!output) { @@ -91,8 +96,10 @@ static LogicalResult writeOutput(ModuleOp module, const StringRef filename) { return success(); } +} // namespace + int main(int argc, char** argv) { - InitLLVM y(argc, argv); + const InitLLVM y(argc, argv); // Parse command-line options cl::ParseCommandLineOptions(argc, argv, "MQT Core Compiler Driver\n"); @@ -127,7 +134,7 @@ int main(int argc, char** argv) { // Run the compilation pipeline if (const QuantumCompilerPipeline pipeline(config); - failed(pipeline.runPipeline(module.get()))) { + pipeline.runPipeline(module.get()).failed()) { errs() << "Compilation pipeline failed\n"; return 1; } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index c48ddd5209..640e0b14c9 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -18,7 +18,9 @@ #include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" #include "mlir/Support/PrettyPrinting.h" +#include #include +#include #include #include #include @@ -31,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -223,7 +227,7 @@ class CompilerPipelineTest : public testing::Test { PassManager pm(module.getContext()); pm.addPass(createCanonicalizerPass()); pm.addPass(createCSEPass()); - if (failed(pm.run(module))) { + if (pm.run(module).failed()) { llvm::errs() << "Failed to run canonicalization passes\n"; } } @@ -358,7 +362,7 @@ TEST_F(CompilerPipelineTest, EmptyCircuit) { ASSERT_TRUE(module); // Run compilation - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); // Verify all stages verifyAllStages({ From e642e0c8622e782b0146adfe4e2bb5647712a7e3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:42:04 +0200 Subject: [PATCH 068/419] Remove memref as dependent dialect so that build succeeds --- mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td | 1 - mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td | 1 - mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td index ca438f8ec3..0225698a71 100644 --- a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td +++ b/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td @@ -20,7 +20,6 @@ def FluxToQuartz : Pass<"flux-to-quartz"> { // Define dependent dialects let dependentDialects = [ - "mlir::memref::MemRefDialect", "mlir::flux::FluxDialect", "mlir::quartz::QuartzDialect" ]; diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td index bf124704d7..ff311bc0a4 100644 --- a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td +++ b/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td @@ -20,7 +20,6 @@ def QuartzToFlux : Pass<"quartz-to-flux"> { // Define dependent dialects let dependentDialects = [ - "mlir::memref::MemRefDialect", "mlir::quartz::QuartzDialect", "mlir::flux::FluxDialect" ]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 9b66bdc56d..c2d9fddc3a 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -77,12 +77,12 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { InterfaceMethod< "Returns true if the operation has any control qubits, otherwise false.", "bool", "isControlled", (ins), - [{ return getNumControls() > 0; }] + [{ return getNumControls(impl, tablegen_opaque_val) > 0; }] >, InterfaceMethod< "Returns true if the operation only acts on a single qubit.", "bool", "isSingleQubit", (ins), - [{ return getNumQubits() == 1; }] + [{ return getNumQubits(impl, tablegen_opaque_val) == 1; }] >, /// TODO: I am fairly sure that there are quite some further convenience methods that would be helpful here, e.g., whether it is a two-qubit gate From 2eee064f8cd3de4eb905baa32746295e3b8ff95c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:02:25 +0200 Subject: [PATCH 069/419] Fix more linter errors --- mlir/lib/Compiler/CompilerPipeline.cpp | 4 +- .../Flux/Builder/FluxProgramBuilder.cpp | 4 +- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 5 +- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 2 +- .../TranslateQuantumComputationToQuartz.cpp | 1 - mlir/lib/Support/PrettyPrinting.cpp | 8 +-- mlir/tools/mqt-cc/mqt-cc.cpp | 59 ++++++++++--------- .../pipeline/test_compiler_pipeline.cpp | 2 +- 8 files changed, 43 insertions(+), 42 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index b762f39c3a..8497a69cdc 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -35,8 +35,8 @@ namespace { * @param stageNumber Current stage number * @param totalStages Total number of stages (for progress indication) */ -static void prettyPrintStage(ModuleOp module, const StringRef stageName, - const int stageNumber, const int totalStages) { +void prettyPrintStage(ModuleOp module, const llvm::StringRef stageName, + const int stageNumber, const int totalStages) { llvm::errs() << "\n"; printBoxTop(); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index db430f12f7..8e390559c9 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -76,10 +76,10 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { return qubit; } -SmallVector +llvm::SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { - SmallVector qubits; + llvm::SmallVector qubits; qubits.reserve(static_cast(size)); auto nameAttr = builder.getStringAttr(name); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 46bf535659..e48d53e2b9 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -261,14 +261,15 @@ void QIRProgramBuilder::generateOutputRecording() { auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); // Group measurements by register - llvm::StringMap>> registerGroups; + llvm::StringMap>> registerGroups; for (const auto& [key, resultPtr] : registerResultMap) { const auto& [regName, regIdx] = key; registerGroups[regName].emplace_back(regIdx, resultPtr); } // Sort registers by name for deterministic output - SmallVector>>> + llvm::SmallVector< + std::pair>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index 95cebf49d0..e3cbf5a8c3 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 79a964bd6c..a9f23d1925 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -16,7 +16,6 @@ #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" #include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include #include diff --git a/mlir/lib/Support/PrettyPrinting.cpp b/mlir/lib/Support/PrettyPrinting.cpp index e0471b2cf0..fe1563096b 100644 --- a/mlir/lib/Support/PrettyPrinting.cpp +++ b/mlir/lib/Support/PrettyPrinting.cpp @@ -23,7 +23,7 @@ namespace { /** * @brief Trim trailing whitespace from a string */ -static std::string trimTrailingWhitespace(const std::string& str) { +std::string trimTrailingWhitespace(const std::string& str) { const size_t end = str.find_last_not_of(" \t\r\n"); return (end == std::string::npos) ? "" : str.substr(0, end + 1); } @@ -71,10 +71,10 @@ std::vector wrapLine(const std::string& line, const int maxWidth, // Detect leading whitespace (indentation) in the original line size_t leadingSpaces = 0; - for (size_t i = 0; i < line.size(); ++i) { - if (line[i] == ' ') { + for (const char c : line) { + if (c == ' ') { leadingSpaces++; - } else if (line[i] == '\t') { + } else if (c == '\t') { leadingSpaces += 4; // Count tabs as 4 spaces } else { break; diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 6d7e257d48..f363ed4d47 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -37,30 +38,30 @@ using namespace mlir; namespace { // Command-line options -const cl::opt inputFilename(cl::Positional, - cl::desc(""), - cl::init("-")); - -const cl::opt outputFilename("o", cl::desc("Output filename"), - cl::value_desc("filename"), +const cl::opt INPUT_FILENAME(cl::Positional, + cl::desc(""), cl::init("-")); -const cl::opt convertToQIR("emit-qir", - cl::desc("Convert to QIR at the end"), - cl::init(false)); +const cl::opt OUTPUT_FILENAME("o", cl::desc("Output filename"), + cl::value_desc("filename"), + cl::init("-")); + +const cl::opt CONVERT_TO_QIR("emit-qir", + cl::desc("Convert to QIR at the end"), + cl::init(false)); -const cl::opt enableTiming("mlir-timing", - cl::desc("Enable pass timing statistics"), - cl::init(false)); +const cl::opt ENABLE_TIMING("mlir-timing", + cl::desc("Enable pass timing statistics"), + cl::init(false)); -const cl::opt enableStatistics("mlir-statistics", - cl::desc("Enable pass statistics"), - cl::init(false)); +const cl::opt ENABLE_STATISTICS("mlir-statistics", + cl::desc("Enable pass statistics"), + cl::init(false)); const cl::opt - printIRAfterAllStages("mlir-print-ir-after-all-stages", - cl::desc("Print IR after each compiler stage"), - cl::init(false)); + PRINT_IR_AFTER_ALL_STAGES("mlir-print-ir-after-all-stages", + cl::desc("Print IR after each compiler stage"), + cl::init(false)); /** * @brief Load and parse a .mlir file @@ -83,17 +84,17 @@ OwningOpRef loadMLIRFile(StringRef filename, MLIRContext* context) { /** * @brief Write the module to the output file */ -LogicalResult writeOutput(ModuleOp module, const StringRef filename) { +mlir::LogicalResult writeOutput(ModuleOp module, const StringRef filename) { std::string errorMessage; const auto output = openOutputFile(filename, &errorMessage); if (!output) { errs() << errorMessage << "\n"; - return failure(); + return mlir::failure(); } module.print(output->os()); output->keep(); - return success(); + return mlir::success(); } } // namespace @@ -119,18 +120,18 @@ int main(int argc, char** argv) { context.loadAllAvailableDialects(); // Load the input .mlir file - const auto module = loadMLIRFile(inputFilename, &context); + const auto module = loadMLIRFile(INPUT_FILENAME, &context); if (!module) { - errs() << "Failed to load input file: " << inputFilename << "\n"; + errs() << "Failed to load input file: " << INPUT_FILENAME << "\n"; return 1; } // Configure the compiler pipeline QuantumCompilerConfig config; - config.convertToQIR = convertToQIR; - config.enableTiming = enableTiming; - config.enableStatistics = enableStatistics; - config.printIRAfterAllStages = printIRAfterAllStages; + config.convertToQIR = CONVERT_TO_QIR; + config.enableTiming = ENABLE_TIMING; + config.enableStatistics = ENABLE_STATISTICS; + config.printIRAfterAllStages = PRINT_IR_AFTER_ALL_STAGES; // Run the compilation pipeline if (const QuantumCompilerPipeline pipeline(config); @@ -140,8 +141,8 @@ int main(int argc, char** argv) { } // Write the output - if (failed(writeOutput(module.get(), outputFilename))) { - errs() << "Failed to write output file: " << outputFilename << "\n"; + if (writeOutput(module.get(), OUTPUT_FILENAME).failed()) { + errs() << "Failed to write output file: " << OUTPUT_FILENAME << "\n"; return 1; } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 640e0b14c9..2a11142ade 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -223,7 +223,7 @@ class CompilerPipelineTest : public testing::Test { /** * @brief Run canonicalization */ - void runCanonicalizationPasses(ModuleOp module) const { + static void runCanonicalizationPasses(ModuleOp module) { PassManager pm(module.getContext()); pm.addPass(createCanonicalizerPass()); pm.addPass(createCSEPass()); From dd59932ee559a597600bd8fe796f0937f3151d1d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:11:19 +0200 Subject: [PATCH 070/419] Also run new pipeline tests --- .github/workflows/reusable-mlir-tests.yml | 2 +- mlir/unittests/CMakeLists.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-mlir-tests.yml b/.github/workflows/reusable-mlir-tests.yml index 3a322dc86b..5deda38b4a 100644 --- a/.github/workflows/reusable-mlir-tests.yml +++ b/.github/workflows/reusable-mlir-tests.yml @@ -104,7 +104,7 @@ jobs: run: cmake --build build --config ${{ matrix.coverage && 'Debug' || 'Release' }} --target mqt-core-mlir-lit-test-build-only - name: Build MLIR unittests - run: cmake --build build --config ${{ matrix.coverage && 'Debug' || 'Release' }} --target mqt-core-mlir-translation-test + run: cmake --build build --config ${{ matrix.coverage && 'Debug' || 'Release' }} --target mqt-core-mlir-unittests # Test - name: Run lit tests diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt index 7f7e458e15..e8d512a6ed 100644 --- a/mlir/unittests/CMakeLists.txt +++ b/mlir/unittests/CMakeLists.txt @@ -8,3 +8,8 @@ add_subdirectory(translation) add_subdirectory(pipeline) + +add_custom_target(mqt-core-mlir-unittests) + +add_dependencies(mqt-core-mlir-unittests mqt-core-mlir-translation-test + mqt-core-mlir-compiler-pipeline-test) From fed75465e2d948f8f7837fc61df4034ff64fa432 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:30:54 +0200 Subject: [PATCH 071/419] Use .succeeded() in pipeline tests --- .../pipeline/test_compiler_pipeline.cpp | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 2a11142ade..1b47770be9 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -391,7 +391,8 @@ TEST_F(CompilerPipelineTest, SingleQubitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + ; const auto quartzExpected = buildQuartzIR( [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); @@ -416,7 +417,8 @@ TEST_F(CompilerPipelineTest, MultiQubitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + ; const auto quartzExpected = buildQuartzIR( [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); @@ -442,7 +444,7 @@ TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzExpected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { @@ -472,7 +474,7 @@ TEST_F(CompilerPipelineTest, LargeQubitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); } // ################################################## @@ -491,7 +493,7 @@ TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { b.allocClassicalBitRegister(1, "c"); @@ -518,7 +520,7 @@ TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { b.allocClassicalBitRegister(5, "c"); @@ -547,7 +549,7 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { b.allocClassicalBitRegister(3, "c"); @@ -572,7 +574,7 @@ TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); } // ################################################## @@ -592,7 +594,7 @@ TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { const auto q = b.allocQubitRegister(1, "q"); @@ -628,7 +630,7 @@ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { const auto q = b.allocQubitRegister(1, "q"); @@ -663,7 +665,7 @@ TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { const auto q = b.allocQubitRegister(2, "q"); @@ -699,7 +701,7 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(1); @@ -737,7 +739,7 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(1); @@ -780,7 +782,7 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(1); @@ -826,7 +828,7 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(runPipeline(module.get()))); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto q = b.allocQubitRegister(2); From 749a67dcadfbc32bb821a5a6b10430ee2a1fb6a0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:00:13 +0200 Subject: [PATCH 072/419] Finalize QuartzInterface (for now) --- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 18 ++++++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzInterfaces.td | 16 ++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 0b712e018a..afb0f299a7 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -11,7 +11,11 @@ #pragma once #include +#include +#include #include +#include +#include // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) @@ -43,6 +47,20 @@ // Interfaces //===----------------------------------------------------------------------===// +struct ParameterDescriptor { + bool isStatic; + std::optional constantValue; + mlir::Value valueOperand; +}; + +struct CanonicalDescriptor { + std::string baseSymbol; + mlir::ValueRange orderedParams; + mlir::ValueRange posControls; + mlir::ValueRange negControls; + // TODO: Add modifier states +}; + #include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index c2d9fddc3a..f827d497c4 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -71,7 +71,11 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the number of parameters.", "size_t", "getNumParams", (ins) >, - /// TODO: This is missing many convenience methods for handling parameters. Some inspiration is in the RFC + InterfaceMethod< + "Returns the i-th parameter.", + "ParameterDescriptor", "getParameter", (ins "size_t":$i) + >, + /// TODO: Add convenience methods as necessary // Convenience methods InterfaceMethod< @@ -96,15 +100,19 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns std::nullopt if the operation is symbolic or dynamic.", "std::optional<::mlir::DenseElementsAttr>", "tryGetStaticMatrix", (ins) >, - /// TODO: probably also worth to check with the RFC and compare + + // Modifier state + /// TODO: Implement interface methods to query modifier state // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", "::llvm::StringRef", "getBaseSymbol", (ins) >, - - /// TODO: I am fairly sure the RFC had some more functions here. + InterfaceMethod< + "Returns the canonical descriptor of the operation. This can be used for equivalence testing.", + "CanonicalDescriptor", "getCanonicalDescriptor", (ins) + >, ]; } From 3f1acc0f65044e647678c5c8d3fc67f455b20733 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:07:41 +0200 Subject: [PATCH 073/419] Align FluxInterface --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 22 ++++++++ .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 51 +++++++++++++------ .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 4 ++ .../Dialect/Quartz/IR/QuartzInterfaces.td | 4 +- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 7576288641..f97e6c6288 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -11,7 +11,11 @@ #pragma once #include +#include +#include #include +#include +#include // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) @@ -43,6 +47,24 @@ // Interfaces //===----------------------------------------------------------------------===// +namespace mlir::flux { + +struct ParameterDescriptor { + bool isStatic; + std::optional constantValue; + mlir::Value valueOperand; +}; + +struct CanonicalDescriptor { + std::string baseSymbol; + mlir::ValueRange orderedParams; + mlir::ValueRange posControls; + mlir::ValueRange negControls; + // TODO: Add modifier states +}; + +} // namespace mlir::flux + #include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index e6f523a877..56987e3a38 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -19,7 +19,7 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let description = [{ This interface provides a unified API for all operations that apply or produce a unitary transformation in the Flux dialect. This includes base - gates, user-defined gates, modified operations (control, inverse, power), + gates, user-defined gates, modifier operations (control, inverse, power), and sequences. The interface enables uniform introspection and composition capabilities @@ -30,10 +30,18 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let methods = [ // Qubit accessors + InterfaceMethod< + "Returns the number of qubits acted on by the unitary operation.", + "size_t", "getNumQubits", (ins) + >, InterfaceMethod< "Returns the number of target qubits (excluding control qubits).", "size_t", "getNumTargets", (ins) >, + InterfaceMethod< + "Returns the number of control qubits (both positive and negative).", + "size_t", "getNumControls", (ins) + >, InterfaceMethod< "Returns the number of positive control qubits.", "size_t", "getNumPosControls", (ins) @@ -42,6 +50,10 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the number of negative control qubits.", "size_t", "getNumNegControls", (ins) >, + InterfaceMethod< + "Returns the i-th qubit (targets + controls combined).", + "::mlir::Value", "getQubit", (ins "size_t":$i) + >, InterfaceMethod< "Returns the i-th target input qubit.", "::mlir::Value", "getTarget", (ins "size_t":$i) @@ -78,6 +90,24 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the number of parameters.", "size_t", "getNumParams", (ins) >, + InterfaceMethod< + "Returns the i-th parameter.", + "::mlir::flux::ParameterDescriptor", "getParameter", (ins "size_t":$i) + >, + /// TODO: Add convenience methods as necessary + + // Convenience methods + InterfaceMethod< + "Returns true if the operation has any control qubits, otherwise false.", + "bool", "isControlled", (ins), + [{ return getNumControls(impl, tablegen_opaque_val) > 0; }] + >, + InterfaceMethod< + "Returns true if the operation only acts on a single qubit.", + "bool", "isSingleQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 1; }] + >, + /// TODO: Add more convenience methods as necessary // Matrix extraction InterfaceMethod< @@ -91,26 +121,17 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, // Modifier state - InterfaceMethod< - "Returns true if the operation is inverted.", - "bool", "isInverted", (ins), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return false; - }] - >, - InterfaceMethod< - "Returns the power exponent if applicable, otherwise std::nullopt.", - "std::optional", "getPower", (ins), - /*methodBody=*/"", /*defaultImplementation=*/[{ - return std::nullopt; - }] - >, + /// TODO: Implement interface methods to query modifier state // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", "::llvm::StringRef", "getBaseSymbol", (ins) >, + InterfaceMethod< + "Returns the canonical descriptor of the operation. This can be used for equivalence testing.", + "::mlir::flux::CanonicalDescriptor", "getCanonicalDescriptor", (ins) + >, ]; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index afb0f299a7..260bce18af 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -47,6 +47,8 @@ // Interfaces //===----------------------------------------------------------------------===// +namespace mlir::quartz { + struct ParameterDescriptor { bool isStatic; std::optional constantValue; @@ -61,6 +63,8 @@ struct CanonicalDescriptor { // TODO: Add modifier states }; +} // namespace mlir::quartz + #include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index f827d497c4..d3949a4dfb 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -73,7 +73,7 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "ParameterDescriptor", "getParameter", (ins "size_t":$i) + "::mlir::quartz::ParameterDescriptor", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary @@ -111,7 +111,7 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the canonical descriptor of the operation. This can be used for equivalence testing.", - "CanonicalDescriptor", "getCanonicalDescriptor", (ins) + "::mlir::quartz::CanonicalDescriptor", "getCanonicalDescriptor", (ins) >, ]; } From 7e6d6ef24194afb1c6ca2997bfaeeff333d15fa0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:00:18 +0100 Subject: [PATCH 074/419] Remove superfluous semicolons --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 1b47770be9..b81c443d57 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -392,7 +392,6 @@ TEST_F(CompilerPipelineTest, SingleQubitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - ; const auto quartzExpected = buildQuartzIR( [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); @@ -418,7 +417,6 @@ TEST_F(CompilerPipelineTest, MultiQubitRegister) { const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - ; const auto quartzExpected = buildQuartzIR( [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); From d1c740edb3e74993e5e15a8b10fb7557f13f96c3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:10:36 +0100 Subject: [PATCH 075/419] Initial implementation of XOp in QuartzDialect --- .../Quartz/Builder/QuartzProgramBuilder.h | 24 ++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 3 +- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 29 ++++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 21 ++++++++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 38 +++++++++++++++++++ 5 files changed, 113 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 22b923f3d5..cb5e35ba58 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -223,6 +223,30 @@ class QuartzProgramBuilder { */ QuartzProgramBuilder& reset(Value qubit); + //===--------------------------------------------------------------------===// + // Unitry Operations + //===--------------------------------------------------------------------===// + + /** + * @brief Apply the X gate to a qubit + * + * @param qubit The target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.x(q); + * ``` + * ```mlir + * quartz.x %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& x(Value qubit); + + //===--------------------------------------------------------------------===// + // Deallocation + //===--------------------------------------------------------------------===// + /** * @brief Explicitly deallocate a qubit * diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 260bce18af..378a1cb448 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -58,8 +58,7 @@ struct ParameterDescriptor { struct CanonicalDescriptor { std::string baseSymbol; mlir::ValueRange orderedParams; - mlir::ValueRange posControls; - mlir::ValueRange negControls; + // TODO: Add controls // TODO: Add modifier states }; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index a5ca111308..92529d2ac9 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -9,6 +9,7 @@ #ifndef QUARTZ_OPS #define QUARTZ_OPS +include "mlir/Dialect/Quartz/IR/QuartzInterfaces.td" include "mlir/IR/BuiltinTypeInterfaces.td" include "mlir/IR/DialectBase.td" include "mlir/IR/EnumAttr.td" @@ -223,4 +224,32 @@ def ResetOp : QuartzOp<"reset"> { let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; } +//===----------------------------------------------------------------------===// +// Unitray Operations +//===----------------------------------------------------------------------===// + +def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { + let arguments = (ins QubitType:$qubit_in); + let results = (outs); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + size_t getNumTargets() { return 1; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + size_t getNumPosControls(); + size_t getNumNegControls(); + ::mlir::Value getQubit(size_t i); + ::mlir::Value getTarget(size_t i); + ::mlir::Value getPosControl(size_t i); + ::mlir::Value getNegControl(size_t i); + size_t getNumParams() { return 0; } + ::mlir::quartz::ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary() { return true; } + ::mlir::DenseElementsAttr tryGetStaticMatrix(); + ::llvm::StringRef getBaseSymbol() { return "x"; } + ::mlir::quartz::CanonicalDescriptor getCanonicalDescriptor(); + }]; +} + #endif // QUARTZ_OPS diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 2b87ecf354..cce0b289ef 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -97,6 +97,10 @@ QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { return allocatedClassicalRegisters.emplace_back(name, size); } +//===----------------------------------------------------------------------===// +// Measurement and Reset +//===----------------------------------------------------------------------===// + Value QuartzProgramBuilder::measure(Value qubit) { auto measureOp = builder.create(loc, qubit); return measureOp.getResult(); @@ -116,6 +120,19 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { return *this; } +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { + builder.create(loc, qubit); + return *this; +} + +//===----------------------------------------------------------------------===// +// Deallocation +//===----------------------------------------------------------------------===// + QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { // Check if the qubit is in the tracking set if (!allocatedQubits.erase(qubit)) { @@ -132,6 +149,10 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { return *this; } +//===----------------------------------------------------------------------===// +// Finalization +//===----------------------------------------------------------------------===// + OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all remaining allocated qubits for (const Value qubit : allocatedQubits) { diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index bda38ae3b7..ddebb6a6cb 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -106,3 +106,41 @@ LogicalResult MeasureOp::verify() { } return success(); } + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +size_t XOp::getNumPosControls() { llvm_unreachable("Not implemented yet"); } + +size_t XOp::getNumNegControls() { llvm_unreachable("Not implemented yet"); } + +Value XOp::getQubit(size_t i) { llvm_unreachable("Not implemented yet"); } + +Value XOp::getTarget(size_t i) { + if (i != 0) { + llvm_unreachable("XOp has only one target qubit"); + } + return getQubitIn(); +} + +Value XOp::getPosControl(size_t i) { llvm_unreachable("Not implemented yet"); } + +Value XOp::getNegControl(size_t i) { llvm_unreachable("Not implemented yet"); } + +ParameterDescriptor XOp::getParameter(size_t i) { + llvm_unreachable("XOp has no parameters"); +} + +DenseElementsAttr XOp::tryGetStaticMatrix() { + auto* ctx = getContext(); + auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); +} + +CanonicalDescriptor XOp::getCanonicalDescriptor() { + return CanonicalDescriptor{ + .baseSymbol = "x", + .orderedParams = {}, + }; +} From a3162ce9eced4fa2bef64681ac442880d2e0ac85 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:28:59 +0100 Subject: [PATCH 076/419] Initial implementation of XOp in FluxDialect --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 28 ++++++++ .../mlir/Dialect/Flux/IR/FluxDialect.h | 3 +- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 8 +-- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 33 ++++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 22 +++++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 66 +++++++++++++++++++ 6 files changed, 154 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index e8b17730aa..c5254ae8b7 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -234,6 +234,34 @@ class FluxProgramBuilder { */ Value reset(Value qubit); + //===--------------------------------------------------------------------===// + // Unitary Operations + //===--------------------------------------------------------------------===// + + /** + * @brief Apply the X gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit value + * + * @par Example: + * ```c++ + * q = builder.x(q); + * ``` + * ```mlir + * %q_out = flux.x %q : !flux.qubit -> !flux.qubit + * ``` + */ + Value x(Value qubit); + + //===--------------------------------------------------------------------===// + // Deallocation + //===--------------------------------------------------------------------===// + /** * @brief Explicitly deallocate a qubit * diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index f97e6c6288..56c3212787 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -58,8 +58,7 @@ struct ParameterDescriptor { struct CanonicalDescriptor { std::string baseSymbol; mlir::ValueRange orderedParams; - mlir::ValueRange posControls; - mlir::ValueRange negControls; + // TODO: Add controls // TODO: Add modifier states }; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 56987e3a38..a2601620b1 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -76,14 +76,14 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the i-th output qubit (targets + controls combined).", "::mlir::Value", "getOutput", (ins "size_t":$i) >, - InterfaceMethod< - "Returns the output qubit corresponding to the given input qubit.", - "::mlir::Value", "getOutputForInput", (ins "::mlir::Value":$input) - >, InterfaceMethod< "Returns the input qubit corresponding to the given output qubit.", "::mlir::Value", "getInputForOutput", (ins "::mlir::Value":$output) >, + InterfaceMethod< + "Returns the output qubit corresponding to the given input qubit.", + "::mlir::Value", "getOutputForInput", (ins "::mlir::Value":$input) + >, // Parameter handling InterfaceMethod< diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 0365347945..f891e55226 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -9,6 +9,7 @@ #ifndef FluxOPS #define FluxOPS +include "mlir/Dialect/Flux/IR/FluxInterfaces.td" include "mlir/IR/BuiltinTypeInterfaces.td" include "mlir/IR/DialectBase.td" include "mlir/IR/EnumAttr.td" @@ -236,4 +237,36 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { let hasCanonicalizer = 1; } +//===----------------------------------------------------------------------===// +// Unitray Operations +//===----------------------------------------------------------------------===// + +def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { + let arguments = (ins QubitType:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + size_t getNumTargets() { return 1; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + size_t getNumPosControls(); + size_t getNumNegControls(); + ::mlir::Value getQubit(size_t i); + ::mlir::Value getTarget(size_t i); + ::mlir::Value getPosControl(size_t i); + ::mlir::Value getNegControl(size_t i); + ::mlir::Value getInput(size_t i); + ::mlir::Value getOutput(size_t i); + ::mlir::Value getInputForOutput(::mlir::Value output); + ::mlir::Value getOutputForInput(::mlir::Value input); + size_t getNumParams() { return 0; } + ::mlir::flux::ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary() { return true; } + ::mlir::DenseElementsAttr tryGetStaticMatrix(); + ::llvm::StringRef getBaseSymbol() { return "x"; } + ::mlir::flux::CanonicalDescriptor getCanonicalDescriptor(); + }]; +} + #endif // FluxOPS diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 8e390559c9..901673c964 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -166,6 +166,24 @@ Value FluxProgramBuilder::reset(Value qubit) { return qubitOut; } +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +Value FluxProgramBuilder::x(Value qubit) { + auto xOp = builder.create(loc, qubit); + const auto qubitOut = xOp.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + +//===----------------------------------------------------------------------===// +// Deallocation +//===----------------------------------------------------------------------===// + FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { validateQubitValue(qubit); validQubits.erase(qubit); @@ -175,6 +193,10 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { return *this; } +//===----------------------------------------------------------------------===// +// Finalization +//===----------------------------------------------------------------------===// + OwningOpRef FluxProgramBuilder::finalize() { // Automatically deallocate all remaining valid qubits for (const auto qubit : validQubits) { diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 506aaa7e7b..ae5e9dc6e9 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -109,6 +109,72 @@ LogicalResult MeasureOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +size_t XOp::getNumPosControls() { llvm_unreachable("Not implemented yet"); } + +size_t XOp::getNumNegControls() { llvm_unreachable("Not implemented yet"); } + +Value XOp::getQubit(size_t i) { llvm_unreachable("Not implemented yet"); } + +Value XOp::getTarget(size_t i) { + if (i != 0) { + llvm_unreachable("XOp has only one target qubit"); + } + return getQubitIn(); +} + +Value XOp::getPosControl(size_t i) { llvm_unreachable("Not implemented yet"); } + +Value XOp::getNegControl(size_t i) { llvm_unreachable("Not implemented yet"); } + +Value XOp::getInput(size_t i) { + if (i != 0) { + llvm_unreachable("XOp has only one input qubit"); + } + return getQubitIn(); +} + +Value XOp::getOutput(size_t i) { + if (i != 0) { + llvm_unreachable("XOp has only one output qubit"); + } + return getQubitOut(); +} + +Value XOp::getInputForOutput(Value output) { + if (output != getQubitOut()) { + llvm_unreachable("Given output is not the XOp's output"); + } + return getQubitIn(); +} + +Value XOp::getOutputForInput(Value input) { + if (input != getQubitIn()) { + llvm_unreachable("Given input is not the XOp's input"); + } + return getQubitOut(); +} + +ParameterDescriptor XOp::getParameter(size_t i) { + llvm_unreachable("XOp has no parameters"); +} + +DenseElementsAttr XOp::tryGetStaticMatrix() { + auto* ctx = getContext(); + auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); +} + +CanonicalDescriptor XOp::getCanonicalDescriptor() { + return CanonicalDescriptor{ + .baseSymbol = "x", + .orderedParams = {}, + }; +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// From 88eade6a1aef36743146fca37fddfb2037266cf6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:44:28 +0100 Subject: [PATCH 077/419] Add (temporary) translation and conversions for XOp --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 21 +++++++++++++++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 26 +++++++++++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 12 +++++++++ 3 files changed, 59 insertions(+) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 0639df7648..64f73724be 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -233,6 +233,26 @@ struct ConvertFluxResetOp final : OpConversionPattern { } }; +// Temporary implementation of XOp conversion +struct ConvertFluxXOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::XOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create quartz.x (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit); + + // Replace the output qubit with the same quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); + } +}; + /** * @brief Pass implementation for Flux-to-Quartz conversion * @@ -282,6 +302,7 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 2ab2d24f77..3fa5309d4a 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -353,6 +353,31 @@ struct ConvertQuartzResetOp final } }; +// Temporary implementation of XOp conversion +struct ConvertQuartzXOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto& quartzQubit = op.getQubitIn(); + + // Get the latest Flux qubit value from the state map + const Value fluxQubit = getState().qubitMap[quartzQubit]; + + // Create flux.x operation (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + + // Update mapping: the Quartz qubit now corresponds to the output qubit + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getResult()); + + return success(); + } +}; + /** * @brief Pass implementation for Quartz-to-Flux conversion * @@ -396,6 +421,7 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index a9f23d1925..958b5abcb9 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -219,6 +219,15 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +// Temporary implementation of XOp translation +void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + for (const auto& target : operation.getTargets()) { + const Value qubit = qubits[target]; + builder.x(qubit); + } +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -250,6 +259,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::Reset: addResetOp(builder, *operation, qubits); break; + case qc::OpType::X: + addXOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported From e482a8e891d98af55891dd5205976ef8d664f0dd Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:59:07 +0100 Subject: [PATCH 078/419] Add XOp to QIR builder and conversion --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 24 +++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 1 + .../Quartz/Builder/QuartzProgramBuilder.h | 2 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 165 ++++++++++-------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 30 ++++ 5 files changed, 151 insertions(+), 71 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index e7e4ede835..b262b62a73 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -222,6 +222,30 @@ class QIRProgramBuilder { */ QIRProgramBuilder& reset(Value qubit); + //===--------------------------------------------------------------------===// + // Unitary Operations + //===--------------------------------------------------------------------===// + + /** + * @brief Apply the X gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.x(q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& x(const Value qubit); + + //===--------------------------------------------------------------------===// + // Deallocation + //===--------------------------------------------------------------------===// + /** * @brief Explicitly deallocate a qubit * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 98aa280d78..b9419ab827 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -26,6 +26,7 @@ static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; static constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; +static constexpr auto QIR_X = "__quantum__qis__x__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index cb5e35ba58..dbadd96538 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -230,7 +230,7 @@ class QuartzProgramBuilder { /** * @brief Apply the X gate to a qubit * - * @param qubit The target qubit + * @param qubit Input qubit * @return Reference to this builder for method chaining * * @par Example: diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 9a2732477b..5845aa5abd 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -125,57 +125,6 @@ struct QuartzToQIRTypeConverter final : LLVMTypeConverter { namespace { -/** - * @brief Converts quartz.static operation to QIR inttoptr - * - * @details - * Converts a static qubit reference to an LLVM pointer by creating a constant - * with the qubit index and converting it to a pointer. The pointer is cached - * in the lowering state for reuse. - * - * @par Example: - * ```mlir - * %q0 = quartz.static 0 : !quartz.qubit - * ``` - * becomes: - * ```mlir - * %c0 = llvm.mlir.constant(0 : i64) : i64 - * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr - * ``` - */ -struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); - const auto index = op.getIndex(); - - // Get or create a pointer to the qubit - if (getState().ptrMap.contains(index)) { - // Reuse existing pointer - rewriter.replaceOp(op, getState().ptrMap.at(index)); - } else { - // Create constant and inttoptr operations - const auto constantOp = rewriter.create( - op.getLoc(), rewriter.getI64IntegerAttr(index)); - const auto intToPtrOp = rewriter.replaceOpWithNewOp( - op, LLVM::LLVMPointerType::get(ctx), constantOp->getResult(0)); - - // Cache for reuse - getState().ptrMap.try_emplace(index, intToPtrOp->getResult(0)); - } - - // Track maximum qubit index - if (index >= getState().numQubits) { - getState().numQubits = index + 1; - } - - return success(); - } -}; - /** * @brief Converts quartz.alloc operation to QIR qubit_allocate * @@ -263,40 +212,52 @@ struct ConvertQuartzDeallocQIR final : OpConversionPattern { }; /** - * @brief Converts quartz.reset operation to QIR reset + * @brief Converts quartz.static operation to QIR inttoptr * * @details - * Converts qubit reset to a call to the QIR __quantum__qis__reset__body - * function, which resets a qubit to the |0⟩ state. + * Converts a static qubit reference to an LLVM pointer by creating a constant + * with the qubit index and converting it to a pointer. The pointer is cached + * in the lowering state for reuse. * * @par Example: * ```mlir - * quartz.reset %q : !quartz.qubit + * %q0 = quartz.static 0 : !quartz.qubit * ``` * becomes: * ```mlir - * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr * ``` */ -struct ConvertQuartzResetQIR final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(ResetOp op, OpAdaptor adaptor, + matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); + const auto index = op.getIndex(); - // Create QIR function signature: (ptr) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + // Get or create a pointer to the qubit + if (getState().ptrMap.contains(index)) { + // Reuse existing pointer + rewriter.replaceOp(op, getState().ptrMap.at(index)); + } else { + // Create constant and inttoptr operations + const auto constantOp = rewriter.create( + op.getLoc(), rewriter.getI64IntegerAttr(index)); + const auto intToPtrOp = rewriter.replaceOpWithNewOp( + op, LLVM::LLVMPointerType::get(ctx), constantOp->getResult(0)); - // Get or create function declaration - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_RESET, qirSignature); + // Cache for reuse + getState().ptrMap.try_emplace(index, intToPtrOp->getResult(0)); + } + + // Track maximum qubit index + if (index >= getState().numQubits) { + getState().numQubits = index + 1; + } - // Replace with call to reset - rewriter.replaceOpWithNewOp(op, fnDecl, - adaptor.getOperands()); return success(); } }; @@ -390,6 +351,69 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.reset operation to QIR reset + * + * @details + * Converts qubit reset to a call to the QIR __quantum__qis__reset__body + * function, which resets a qubit to the |0⟩ state. + * + * @par Example: + * ```mlir + * quartz.reset %q : !quartz.qubit + * ``` + * becomes: + * ```mlir + * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzResetQIR final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (ptr) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + + // Get or create function declaration + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_RESET, qirSignature); + + // Replace with call to reset + rewriter.replaceOpWithNewOp(op, fnDecl, + adaptor.getOperands()); + return success(); + } +}; + +// Temporary implementation of XOp conversion +struct ConvertQuartzXQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(XOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (ptr) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + + // Get or create function declaration + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_X, qirSignature); + + // Replace with call to X + rewriter.replaceOpWithNewOp(op, fnDecl, + adaptor.getOperands()); + return success(); + } +}; + } // namespace /** @@ -710,11 +734,12 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { target.addIllegalDialect(); // Add conversion patterns for Quartz operations - quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx); - quartzPatterns.add(typeConverter, ctx); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx); + quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index e48d53e2b9..1b267c620b 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -227,6 +227,32 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { return *this; } +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create x call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_X, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); + + return *this; +} + +//===----------------------------------------------------------------------===// +// Deallocation +//===----------------------------------------------------------------------===// + QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { allocatedQubits.erase(qubit); @@ -247,6 +273,10 @@ QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { return *this; } +//===----------------------------------------------------------------------===// +// Finalization +//===----------------------------------------------------------------------===// + void QIRProgramBuilder::generateOutputRecording() { if (registerResultMap.empty()) { return; // No measurements to record From 37a121ce88dbd1053518c459b0aa0e0438b8c135 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:19:33 +0100 Subject: [PATCH 079/419] Add temporary unit test --- .../pipeline/test_compiler_pipeline.cpp | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index b81c443d57..d2c9030961 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -859,4 +859,40 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { }); } +// ################################################## +// # Temporary Unit Tests +// ################################################## + +TEST_F(CompilerPipelineTest, X) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.x(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + b.x(q[0]); + }); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + b.x(q[0]); + }); + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.x(q[0]); + }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .optimization = fluxExpected.get(), + .quartzConversion = quartzExpected.get(), + .qirConversion = qirExpected.get(), + }); +} + } // namespace From 6b35e6f1341b86bf8bf3a6f46c704c57e09d6986 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:47:02 +0100 Subject: [PATCH 080/419] Fix some linter errors --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 17 +++++++++++++---- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index ae5e9dc6e9..343495aa33 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -18,7 +18,12 @@ #include // IWYU pragma: end_keep +#include +#include +#include #include +#include +#include #include using namespace mlir; @@ -117,7 +122,7 @@ size_t XOp::getNumPosControls() { llvm_unreachable("Not implemented yet"); } size_t XOp::getNumNegControls() { llvm_unreachable("Not implemented yet"); } -Value XOp::getQubit(size_t i) { llvm_unreachable("Not implemented yet"); } +Value XOp::getQubit(size_t /*i*/) { llvm_unreachable("Not implemented yet"); } Value XOp::getTarget(size_t i) { if (i != 0) { @@ -126,9 +131,13 @@ Value XOp::getTarget(size_t i) { return getQubitIn(); } -Value XOp::getPosControl(size_t i) { llvm_unreachable("Not implemented yet"); } +Value XOp::getPosControl(size_t /*i*/) { + llvm_unreachable("Not implemented yet"); +} -Value XOp::getNegControl(size_t i) { llvm_unreachable("Not implemented yet"); } +Value XOp::getNegControl(size_t /*i*/) { + llvm_unreachable("Not implemented yet"); +} Value XOp::getInput(size_t i) { if (i != 0) { @@ -158,7 +167,7 @@ Value XOp::getOutputForInput(Value input) { return getQubitOut(); } -ParameterDescriptor XOp::getParameter(size_t i) { +ParameterDescriptor XOp::getParameter(size_t /*i*/) { llvm_unreachable("XOp has no parameters"); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index ddebb6a6cb..5ff4c7732e 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -17,6 +17,11 @@ #include // IWYU pragma: end_keep +#include +#include +#include +#include +#include #include using namespace mlir; @@ -115,7 +120,7 @@ size_t XOp::getNumPosControls() { llvm_unreachable("Not implemented yet"); } size_t XOp::getNumNegControls() { llvm_unreachable("Not implemented yet"); } -Value XOp::getQubit(size_t i) { llvm_unreachable("Not implemented yet"); } +Value XOp::getQubit(size_t /*i*/) { llvm_unreachable("Not implemented yet"); } Value XOp::getTarget(size_t i) { if (i != 0) { @@ -124,11 +129,15 @@ Value XOp::getTarget(size_t i) { return getQubitIn(); } -Value XOp::getPosControl(size_t i) { llvm_unreachable("Not implemented yet"); } +Value XOp::getPosControl(size_t /*i*/) { + llvm_unreachable("Not implemented yet"); +} -Value XOp::getNegControl(size_t i) { llvm_unreachable("Not implemented yet"); } +Value XOp::getNegControl(size_t /*i*/) { + llvm_unreachable("Not implemented yet"); +} -ParameterDescriptor XOp::getParameter(size_t i) { +ParameterDescriptor XOp::getParameter(size_t /*i*/) { llvm_unreachable("XOp has no parameters"); } From 6ead19ac77b9529c10a2411b6753148d81778633 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:00:06 +0100 Subject: [PATCH 081/419] Mark some interface methods as const --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 22 +++++++++--------- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 23 +++++++++---------- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 20 ++++++++++------ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 20 ++++++++++------ 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index f891e55226..7f1f9b2391 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -248,24 +248,24 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() { return 1; } + size_t getNumTargets() const { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls(); - size_t getNumNegControls(); - ::mlir::Value getQubit(size_t i); + size_t getNumPosControls() const; + size_t getNumNegControls() const; + ::mlir::Value getQubit(size_t i) const; ::mlir::Value getTarget(size_t i); - ::mlir::Value getPosControl(size_t i); - ::mlir::Value getNegControl(size_t i); + ::mlir::Value getPosControl(size_t i) const; + ::mlir::Value getNegControl(size_t i) const; ::mlir::Value getInput(size_t i); ::mlir::Value getOutput(size_t i); ::mlir::Value getInputForOutput(::mlir::Value output); ::mlir::Value getOutputForInput(::mlir::Value input); - size_t getNumParams() { return 0; } - ::mlir::flux::ParameterDescriptor getParameter(size_t i); - bool hasStaticUnitary() { return true; } + size_t getNumParams() const { return 0; } + ::mlir::flux::ParameterDescriptor getParameter(size_t i) const; + bool hasStaticUnitary() const { return true; } ::mlir::DenseElementsAttr tryGetStaticMatrix(); - ::llvm::StringRef getBaseSymbol() { return "x"; } - ::mlir::flux::CanonicalDescriptor getCanonicalDescriptor(); + ::llvm::StringRef getBaseSymbol() const { return "x"; } + ::mlir::flux::CanonicalDescriptor getCanonicalDescriptor() const; }]; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 92529d2ac9..4b6d2b60c4 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -230,25 +230,24 @@ def ResetOp : QuartzOp<"reset"> { def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { let arguments = (ins QubitType:$qubit_in); - let results = (outs); let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() { return 1; } + size_t getNumTargets() const { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls(); - size_t getNumNegControls(); - ::mlir::Value getQubit(size_t i); + size_t getNumPosControls() const; + size_t getNumNegControls() const; + ::mlir::Value getQubit(size_t i) const; ::mlir::Value getTarget(size_t i); - ::mlir::Value getPosControl(size_t i); - ::mlir::Value getNegControl(size_t i); - size_t getNumParams() { return 0; } - ::mlir::quartz::ParameterDescriptor getParameter(size_t i); - bool hasStaticUnitary() { return true; } + ::mlir::Value getPosControl(size_t i) const; + ::mlir::Value getNegControl(size_t i) const; + size_t getNumParams() const { return 0; } + ::mlir::quartz::ParameterDescriptor getParameter(size_t i) const; + bool hasStaticUnitary() const { return true; } ::mlir::DenseElementsAttr tryGetStaticMatrix(); - ::llvm::StringRef getBaseSymbol() { return "x"; } - ::mlir::quartz::CanonicalDescriptor getCanonicalDescriptor(); + ::llvm::StringRef getBaseSymbol() const { return "x"; } + ::mlir::quartz::CanonicalDescriptor getCanonicalDescriptor() const; }]; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 343495aa33..05205845ba 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -118,11 +118,17 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// -size_t XOp::getNumPosControls() { llvm_unreachable("Not implemented yet"); } +size_t XOp::getNumPosControls() const { + llvm_unreachable("Not implemented yet"); +} -size_t XOp::getNumNegControls() { llvm_unreachable("Not implemented yet"); } +size_t XOp::getNumNegControls() const { + llvm_unreachable("Not implemented yet"); +} -Value XOp::getQubit(size_t /*i*/) { llvm_unreachable("Not implemented yet"); } +Value XOp::getQubit(size_t /*i*/) const { + llvm_unreachable("Not implemented yet"); +} Value XOp::getTarget(size_t i) { if (i != 0) { @@ -131,11 +137,11 @@ Value XOp::getTarget(size_t i) { return getQubitIn(); } -Value XOp::getPosControl(size_t /*i*/) { +Value XOp::getPosControl(size_t /*i*/) const { llvm_unreachable("Not implemented yet"); } -Value XOp::getNegControl(size_t /*i*/) { +Value XOp::getNegControl(size_t /*i*/) const { llvm_unreachable("Not implemented yet"); } @@ -167,7 +173,7 @@ Value XOp::getOutputForInput(Value input) { return getQubitOut(); } -ParameterDescriptor XOp::getParameter(size_t /*i*/) { +ParameterDescriptor XOp::getParameter(size_t /*i*/) const { llvm_unreachable("XOp has no parameters"); } @@ -177,7 +183,7 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); } -CanonicalDescriptor XOp::getCanonicalDescriptor() { +CanonicalDescriptor XOp::getCanonicalDescriptor() const { return CanonicalDescriptor{ .baseSymbol = "x", .orderedParams = {}, diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 5ff4c7732e..d00752d12a 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -116,11 +116,17 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// -size_t XOp::getNumPosControls() { llvm_unreachable("Not implemented yet"); } +size_t XOp::getNumPosControls() const { + llvm_unreachable("Not implemented yet"); +} -size_t XOp::getNumNegControls() { llvm_unreachable("Not implemented yet"); } +size_t XOp::getNumNegControls() const { + llvm_unreachable("Not implemented yet"); +} -Value XOp::getQubit(size_t /*i*/) { llvm_unreachable("Not implemented yet"); } +Value XOp::getQubit(size_t /*i*/) const { + llvm_unreachable("Not implemented yet"); +} Value XOp::getTarget(size_t i) { if (i != 0) { @@ -129,15 +135,15 @@ Value XOp::getTarget(size_t i) { return getQubitIn(); } -Value XOp::getPosControl(size_t /*i*/) { +Value XOp::getPosControl(size_t /*i*/) const { llvm_unreachable("Not implemented yet"); } -Value XOp::getNegControl(size_t /*i*/) { +Value XOp::getNegControl(size_t /*i*/) const { llvm_unreachable("Not implemented yet"); } -ParameterDescriptor XOp::getParameter(size_t /*i*/) { +ParameterDescriptor XOp::getParameter(size_t /*i*/) const { llvm_unreachable("XOp has no parameters"); } @@ -147,7 +153,7 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); } -CanonicalDescriptor XOp::getCanonicalDescriptor() { +CanonicalDescriptor XOp::getCanonicalDescriptor() const { return CanonicalDescriptor{ .baseSymbol = "x", .orderedParams = {}, From 289dba00ea9b166209ee9f065bf38cb18903085b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:31:01 +0100 Subject: [PATCH 082/419] Apply suggestion from @burgholzer Co-authored-by: Lukas Burgholzer Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> --- mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt index 34065fac73..ee38d53a7f 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License set(DIALECT_NAME "Quartz") -set(DIALECT_NAME_UPPER "FLUX") +set(DIALECT_NAME_UPPER "QUARTZ") add_mlir_dialect(QuartzOps quartz) add_mlir_interface(QuartzInterfaces) From 2395feb605bfcfe564567ad0469bf9a4407d1fc0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:31:19 +0100 Subject: [PATCH 083/419] Apply suggestion from @burgholzer Co-authored-by: Lukas Burgholzer Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> --- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 4b6d2b60c4..0f37dab8ae 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -225,7 +225,7 @@ def ResetOp : QuartzOp<"reset"> { } //===----------------------------------------------------------------------===// -// Unitray Operations +// Unitary Operations //===----------------------------------------------------------------------===// def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { From 2145245cda9b7a5708599101a381aa2a185792e2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:31:46 +0100 Subject: [PATCH 084/419] Fix typo --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 7f1f9b2391..af76a77884 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -238,7 +238,7 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { } //===----------------------------------------------------------------------===// -// Unitray Operations +// Unitary Operations //===----------------------------------------------------------------------===// def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { From aa884bf281e7e7d1250226d675956ff02da3c34d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:34:21 +0100 Subject: [PATCH 085/419] Remove CanonicalDescriptor again --- mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h | 7 ------- mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td | 4 ---- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 1 - mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h | 7 ------- mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td | 4 ---- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 1 - mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 7 ------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 7 ------- 8 files changed, 38 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 56c3212787..12e2ecf8c6 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -55,13 +55,6 @@ struct ParameterDescriptor { mlir::Value valueOperand; }; -struct CanonicalDescriptor { - std::string baseSymbol; - mlir::ValueRange orderedParams; - // TODO: Add controls - // TODO: Add modifier states -}; - } // namespace mlir::flux #include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index a2601620b1..8c433b77c0 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -128,10 +128,6 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the base symbol/mnemonic of the operation.", "::llvm::StringRef", "getBaseSymbol", (ins) >, - InterfaceMethod< - "Returns the canonical descriptor of the operation. This can be used for equivalence testing.", - "::mlir::flux::CanonicalDescriptor", "getCanonicalDescriptor", (ins) - >, ]; } diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index af76a77884..d26ee77143 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -265,7 +265,6 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { bool hasStaticUnitary() const { return true; } ::mlir::DenseElementsAttr tryGetStaticMatrix(); ::llvm::StringRef getBaseSymbol() const { return "x"; } - ::mlir::flux::CanonicalDescriptor getCanonicalDescriptor() const; }]; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 378a1cb448..537fddfa97 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -55,13 +55,6 @@ struct ParameterDescriptor { mlir::Value valueOperand; }; -struct CanonicalDescriptor { - std::string baseSymbol; - mlir::ValueRange orderedParams; - // TODO: Add controls - // TODO: Add modifier states -}; - } // namespace mlir::quartz #include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index d3949a4dfb..805553260e 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -109,10 +109,6 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the base symbol/mnemonic of the operation.", "::llvm::StringRef", "getBaseSymbol", (ins) >, - InterfaceMethod< - "Returns the canonical descriptor of the operation. This can be used for equivalence testing.", - "::mlir::quartz::CanonicalDescriptor", "getCanonicalDescriptor", (ins) - >, ]; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 0f37dab8ae..aa687cca20 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -247,7 +247,6 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { bool hasStaticUnitary() const { return true; } ::mlir::DenseElementsAttr tryGetStaticMatrix(); ::llvm::StringRef getBaseSymbol() const { return "x"; } - ::mlir::quartz::CanonicalDescriptor getCanonicalDescriptor() const; }]; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 05205845ba..1ba58955a9 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -183,13 +183,6 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); } -CanonicalDescriptor XOp::getCanonicalDescriptor() const { - return CanonicalDescriptor{ - .baseSymbol = "x", - .orderedParams = {}, - }; -} - //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index d00752d12a..e212998d12 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -152,10 +152,3 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); } - -CanonicalDescriptor XOp::getCanonicalDescriptor() const { - return CanonicalDescriptor{ - .baseSymbol = "x", - .orderedParams = {}, - }; -} From 6ab1b7fda40a67a25ba98e6b1e5a8fe4b42d1412 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:36:56 +0100 Subject: [PATCH 086/419] Remove namespace specifiers --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 22 +++++++++---------- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 14 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index d26ee77143..414c85361e 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -252,19 +252,19 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } size_t getNumPosControls() const; size_t getNumNegControls() const; - ::mlir::Value getQubit(size_t i) const; - ::mlir::Value getTarget(size_t i); - ::mlir::Value getPosControl(size_t i) const; - ::mlir::Value getNegControl(size_t i) const; - ::mlir::Value getInput(size_t i); - ::mlir::Value getOutput(size_t i); - ::mlir::Value getInputForOutput(::mlir::Value output); - ::mlir::Value getOutputForInput(::mlir::Value input); + Value getQubit(size_t i) const; + Value getTarget(size_t i); + Value getPosControl(size_t i) const; + Value getNegControl(size_t i) const; + Value getInput(size_t i); + Value getOutput(size_t i); + Value getInputForOutput(::mlir::Value output); + Value getOutputForInput(::mlir::Value input); size_t getNumParams() const { return 0; } - ::mlir::flux::ParameterDescriptor getParameter(size_t i) const; + ParameterDescriptor getParameter(size_t i) const; bool hasStaticUnitary() const { return true; } - ::mlir::DenseElementsAttr tryGetStaticMatrix(); - ::llvm::StringRef getBaseSymbol() const { return "x"; } + DenseElementsAttr tryGetStaticMatrix(); + StringRef getBaseSymbol() const { return "x"; } }]; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index aa687cca20..6a910b0726 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -238,15 +238,15 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } size_t getNumPosControls() const; size_t getNumNegControls() const; - ::mlir::Value getQubit(size_t i) const; - ::mlir::Value getTarget(size_t i); - ::mlir::Value getPosControl(size_t i) const; - ::mlir::Value getNegControl(size_t i) const; + Value getQubit(size_t i) const; + Value getTarget(size_t i); + Value getPosControl(size_t i) const; + Value getNegControl(size_t i) const; size_t getNumParams() const { return 0; } - ::mlir::quartz::ParameterDescriptor getParameter(size_t i) const; + ParameterDescriptor getParameter(size_t i) const; bool hasStaticUnitary() const { return true; } - ::mlir::DenseElementsAttr tryGetStaticMatrix(); - ::llvm::StringRef getBaseSymbol() const { return "x"; } + DenseElementsAttr tryGetStaticMatrix(); + StringRef getBaseSymbol() const { return "x"; } }]; } From 7fcc6a04e23314f0ea345f838f25eedab4051b3c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:44:39 +0100 Subject: [PATCH 087/419] XOp cannot have controls --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 6 +++--- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 6 +++--- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 21 +++++++------------ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 21 +++++++------------ 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 414c85361e..b7303b5477 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -250,9 +250,9 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { size_t getNumQubits() { return getNumTargets() + getNumControls(); } size_t getNumTargets() const { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls() const; - size_t getNumNegControls() const; - Value getQubit(size_t i) const; + size_t getNumPosControls() const { return 0; } + size_t getNumNegControls() const { return 0; } + Value getQubit(size_t i); Value getTarget(size_t i); Value getPosControl(size_t i) const; Value getNegControl(size_t i) const; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 6a910b0726..1f7949fc30 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -236,9 +236,9 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { size_t getNumQubits() { return getNumTargets() + getNumControls(); } size_t getNumTargets() const { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls() const; - size_t getNumNegControls() const; - Value getQubit(size_t i) const; + size_t getNumPosControls() const { return 0; } + size_t getNumNegControls() const { return 0; } + Value getQubit(size_t i); Value getTarget(size_t i); Value getPosControl(size_t i) const; Value getNegControl(size_t i) const; diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 1ba58955a9..7464394572 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -118,16 +118,11 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// -size_t XOp::getNumPosControls() const { - llvm_unreachable("Not implemented yet"); -} - -size_t XOp::getNumNegControls() const { - llvm_unreachable("Not implemented yet"); -} - -Value XOp::getQubit(size_t /*i*/) const { - llvm_unreachable("Not implemented yet"); +Value XOp::getQubit(size_t i) { + if (i != 0) { + llvm_unreachable("XOp has only one qubit"); + } + return getQubitIn(); } Value XOp::getTarget(size_t i) { @@ -138,11 +133,11 @@ Value XOp::getTarget(size_t i) { } Value XOp::getPosControl(size_t /*i*/) const { - llvm_unreachable("Not implemented yet"); + llvm_unreachable("XOp does not have controls"); } Value XOp::getNegControl(size_t /*i*/) const { - llvm_unreachable("Not implemented yet"); + llvm_unreachable("XOp does not have controls"); } Value XOp::getInput(size_t i) { @@ -174,7 +169,7 @@ Value XOp::getOutputForInput(Value input) { } ParameterDescriptor XOp::getParameter(size_t /*i*/) const { - llvm_unreachable("XOp has no parameters"); + llvm_unreachable("XOp does not have parameters"); } DenseElementsAttr XOp::tryGetStaticMatrix() { diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index e212998d12..fa0fbb6995 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -116,16 +116,11 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// -size_t XOp::getNumPosControls() const { - llvm_unreachable("Not implemented yet"); -} - -size_t XOp::getNumNegControls() const { - llvm_unreachable("Not implemented yet"); -} - -Value XOp::getQubit(size_t /*i*/) const { - llvm_unreachable("Not implemented yet"); +Value XOp::getQubit(size_t i) { + if (i != 0) { + llvm_unreachable("XOp has only one qubit"); + } + return getQubitIn(); } Value XOp::getTarget(size_t i) { @@ -136,15 +131,15 @@ Value XOp::getTarget(size_t i) { } Value XOp::getPosControl(size_t /*i*/) const { - llvm_unreachable("Not implemented yet"); + llvm_unreachable("XOp does not have controls"); } Value XOp::getNegControl(size_t /*i*/) const { - llvm_unreachable("Not implemented yet"); + llvm_unreachable("XOp does not have controls"); } ParameterDescriptor XOp::getParameter(size_t /*i*/) const { - llvm_unreachable("XOp has no parameters"); + llvm_unreachable("XOp does not have parameters"); } DenseElementsAttr XOp::tryGetStaticMatrix() { From f56baa7169f5cf43874bf15d5afcd2e95f4e150e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:01:23 +0100 Subject: [PATCH 088/419] WIP: Initial support for RXOp --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 22 +++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 35 +++++++- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 18 ++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 1 + .../Quartz/Builder/QuartzProgramBuilder.h | 18 ++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 26 ++++++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 30 +++++++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 38 +++++++- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 37 ++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 22 +++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 89 +++++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 27 ++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 11 +++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 61 +++++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 19 +++- .../pipeline/test_compiler_pipeline.cpp | 32 +++++++ 16 files changed, 479 insertions(+), 7 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index c5254ae8b7..ca9c605fd6 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -258,6 +258,28 @@ class FluxProgramBuilder { */ Value x(Value qubit); + /** + * @brief Apply the RX gate to a qubit with a static angle + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and tracking is updated. + * + * @param angle Rotation angle + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit value + * + * @par Example: + * ```c++ + * builder.rx(1.0, q); + * ``` + * ```mlir + * flux.rx(1.0) %q : !flux.qubit -> !flux.qubit + * ``` + */ + Value rx(double angle, Value qubit); + Value rx(Value angle, Value qubit); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index b7303b5477..fecaef193c 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -258,8 +258,8 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { Value getNegControl(size_t i) const; Value getInput(size_t i); Value getOutput(size_t i); - Value getInputForOutput(::mlir::Value output); - Value getOutputForInput(::mlir::Value input); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); size_t getNumParams() const { return 0; } ParameterDescriptor getParameter(size_t i) const; bool hasStaticUnitary() const { return true; } @@ -268,4 +268,35 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { }]; } +def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { + let arguments = (ins QubitType:$qubit_in, + OptionalAttr:$angle_static, + Optional:$angle_dynamic); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` ($angle_static^)? ($angle_dynamic^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + size_t getNumTargets() const { return 1; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + size_t getNumPosControls() const { return 0; } + size_t getNumNegControls() const { return 0; } + Value getQubit(size_t i); + Value getTarget(size_t i); + Value getPosControl(size_t i) const; + Value getNegControl(size_t i) const; + Value getInput(size_t i); + Value getOutput(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + size_t getNumParams() const { return 1; } + ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + StringRef getBaseSymbol() const { return "rx"; } + }]; + + let hasVerifier = 1; +} + #endif // FluxOPS diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index b262b62a73..7ea21f78d6 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -242,6 +242,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& x(const Value qubit); + /** + * @brief Apply the RX gate to a qubit + * + * @param angle Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.rx(1.0, q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__rx__body(%q, %c) : (!llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& rx(double angle, const Value qubit); + QIRProgramBuilder& rx(Value angle, const Value qubit); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index b9419ab827..83bec2c017 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -27,6 +27,7 @@ static constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; static constexpr auto QIR_X = "__quantum__qis__x__body"; +static constexpr auto QIR_RX = "__quantum__qis__rx__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index dbadd96538..c18368f75b 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -243,6 +243,24 @@ class QuartzProgramBuilder { */ QuartzProgramBuilder& x(Value qubit); + /** + * @brief Apply the RX gate to a qubit + * + * @param angle Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.rx(1.0, q); + * ``` + * ```mlir + * quartz.rx(1.0) %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& rx(double angle, Value qubit); + QuartzProgramBuilder& rx(Value angle, Value qubit); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 1f7949fc30..eff65ba70c 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -250,4 +250,30 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { }]; } +def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { + let arguments = (ins QubitType:$qubit_in, + OptionalAttr:$angle_static, + Optional:$angle_dynamic); + let assemblyFormat = "`(` ($angle_static^)? ($angle_dynamic^)? `)` $qubit_in attr-dict"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + size_t getNumTargets() const { return 1; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + size_t getNumPosControls() const { return 0; } + size_t getNumNegControls() const { return 0; } + Value getQubit(size_t i); + Value getTarget(size_t i); + Value getPosControl(size_t i) const; + Value getNegControl(size_t i) const; + size_t getNumParams() const { return 1; } + ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + StringRef getBaseSymbol() const { return "rx"; } + }]; + + let hasVerifier = 1; +} + #endif // QUARTZ_OPS diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 64f73724be..9ada21e45e 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -253,6 +253,35 @@ struct ConvertFluxXOp final : OpConversionPattern { } }; +// Temporary implementation of RXOp conversion +struct ConvertFluxRXOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::RXOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + auto angle = op.getParameter(0); + FloatAttr angleStatic = nullptr; + if (angle.isStatic) { + angleStatic = rewriter.getFloatAttr(rewriter.getF64Type(), + angle.constantValue.value()); + } + Value angleDynamic = angle.valueOperand; + + // Create quartz.rx (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit, angleStatic, + angleDynamic); + + // Replace the output qubit with the same quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); + } +}; + /** * @brief Pass implementation for Flux-to-Quartz conversion * @@ -303,6 +332,7 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 3fa5309d4a..2b8de6e9b2 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -360,7 +361,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubit(0); // Get the latest Flux qubit value from the state map const Value fluxQubit = getState().qubitMap[quartzQubit]; @@ -378,6 +379,40 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { } }; +// Temporary implementation of RXOp conversion +struct ConvertQuartzRXOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::RXOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto& quartzQubit = op.getQubitIn(); + + // Get the latest Flux qubit value from the state map + const Value fluxQubit = getState().qubitMap[quartzQubit]; + + auto angle = op.getParameter(0); + FloatAttr angleStatic = nullptr; + if (angle.isStatic) { + angleStatic = rewriter.getFloatAttr(rewriter.getF64Type(), + angle.constantValue.value()); + } + Value angleDynamic = angle.valueOperand; + + // Create flux.rx operation (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, + angleStatic, angleDynamic); + + // Update mapping: the Quartz qubit now corresponds to the output qubit + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getResult()); + + return success(); + } +}; + /** * @brief Pass implementation for Quartz-to-Flux conversion * @@ -422,6 +457,7 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 5845aa5abd..65af87cf3f 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -414,6 +414,42 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { } }; +// Temporary implementation of RXOp conversion +struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(RXOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (!llvm.ptr, f64) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), + {LLVM::LLVMPointerType::get(ctx), Float64Type::get(ctx)}); + + // Get or create function declaration + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_RX, qirSignature); + + auto angle = op.getParameter(0); + Value angleValue; + if (angle.isStatic) { + auto angleStatic = rewriter.getFloatAttr(rewriter.getF64Type(), + angle.constantValue.value()); + angleValue = rewriter.create(op.getLoc(), angleStatic) + .getResult(); + } else { + angleValue = angle.valueOperand; + } + + // Replace with call to RX + rewriter.replaceOpWithNewOp( + op, fnDecl, ValueRange{adaptor.getQubitIn(), angleValue}); + return success(); + } +}; + } // namespace /** @@ -740,6 +776,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 901673c964..7ca041e5fb 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -180,6 +180,28 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } +Value FluxProgramBuilder::rx(Value angle, Value qubit) { + auto rxOp = builder.create(loc, qubit, /*angle_static=*/nullptr, angle); + const auto qubitOut = rxOp.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + +Value FluxProgramBuilder::rx(double angle, Value qubit) { + auto angleAttr = builder.getF64FloatAttr(angle); + auto rxOp = + builder.create(loc, qubit, angleAttr, /*angle_dynamic=*/nullptr); + const auto qubitOut = rxOp.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 7464394572..2191be9c53 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -118,6 +118,8 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// +// XOp + Value XOp::getQubit(size_t i) { if (i != 0) { llvm_unreachable("XOp has only one qubit"); @@ -178,6 +180,93 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); } +// RXOp + +Value RXOp::getQubit(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one qubit"); + } + return getQubitIn(); +} + +Value RXOp::getTarget(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one target qubit"); + } + return getQubitIn(); +} + +Value RXOp::getPosControl(size_t /*i*/) const { + llvm_unreachable("RXOp does not have controls"); +} + +Value RXOp::getNegControl(size_t /*i*/) const { + llvm_unreachable("RXOp does not have controls"); +} + +Value RXOp::getInput(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one input qubit"); + } + return getQubitIn(); +} + +Value RXOp::getOutput(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one output qubit"); + } + return getQubitOut(); +} + +Value RXOp::getInputForOutput(Value output) { + if (output != getQubitOut()) { + llvm_unreachable("Given output is not the RXOp's output"); + } + return getQubitIn(); +} + +Value RXOp::getOutputForInput(Value input) { + if (input != getQubitIn()) { + llvm_unreachable("Given input is not the RXOp's input"); + } + return getQubitOut(); +} + +ParameterDescriptor RXOp::getParameter(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one parameter"); + } + return ParameterDescriptor{ + .isStatic = getAngleStatic().has_value(), + .constantValue = getAngleStatic().has_value() + ? std::optional( + getAngleStatic().value().convertToDouble()) + : std::nullopt, + .valueOperand = getAngleDynamic()}; +} + +bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } + +DenseElementsAttr RXOp::tryGetStaticMatrix() { + if (!hasStaticUnitary()) { + return nullptr; + } + auto* ctx = getContext(); + auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto angle = getAngleStatic().value().convertToDouble(); + std::complex c(cos(angle / 2), 0); + std::complex s(0, -sin(angle / 2)); + return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); +} + +LogicalResult RXOp::verify() { + if (getAngleStatic().has_value() && getAngleDynamic()) + return emitOpError("cannot specify both static and dynamic angle"); + if (!getAngleStatic().has_value() && !getAngleDynamic()) + return emitOpError("must specify either static or dynamic angle"); + return success(); +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 1b267c620b..f163b56303 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -249,6 +249,33 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { return *this; } +QIRProgramBuilder& QIRProgramBuilder::rx(Value angle, const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create rx call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + Float64Type::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_RX, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit, angle}); + + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::rx(double angle, const Value qubit) { + // Create constant for angle + auto angleConst = + builder.create(loc, builder.getF64FloatAttr(angle)); + + return rx(angleConst.getResult(), qubit); +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index cce0b289ef..b648be68a5 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -129,6 +129,17 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { return *this; } +QuartzProgramBuilder& QuartzProgramBuilder::rx(double angle, Value qubit) { + auto angleAttr = builder.getF64FloatAttr(angle); + builder.create(loc, qubit, angleAttr, /*angle_dynamic=*/nullptr); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::rx(Value angle, Value qubit) { + builder.create(loc, qubit, /*angle_static=*/nullptr, angle); + return *this; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index fa0fbb6995..5c73c49775 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -116,6 +116,8 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// +// XOp + Value XOp::getQubit(size_t i) { if (i != 0) { llvm_unreachable("XOp has only one qubit"); @@ -147,3 +149,62 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); } + +// RXOp + +Value RXOp::getQubit(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one qubit"); + } + return getQubitIn(); +} + +Value RXOp::getTarget(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one target qubit"); + } + return getQubitIn(); +} + +Value RXOp::getPosControl(size_t /*i*/) const { + llvm_unreachable("RXOp does not have controls"); +} + +Value RXOp::getNegControl(size_t /*i*/) const { + llvm_unreachable("RXOp does not have controls"); +} + +ParameterDescriptor RXOp::getParameter(size_t i) { + if (i != 0) { + llvm_unreachable("RXOp has only one parameter"); + } + return ParameterDescriptor{ + .isStatic = getAngleStatic().has_value(), + .constantValue = getAngleStatic().has_value() + ? std::optional( + getAngleStatic().value().convertToDouble()) + : std::nullopt, + .valueOperand = getAngleDynamic()}; +} + +bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } + +DenseElementsAttr RXOp::tryGetStaticMatrix() { + if (!hasStaticUnitary()) { + return nullptr; + } + auto* ctx = getContext(); + auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto angle = getAngleStatic().value().convertToDouble(); + std::complex c(cos(angle / 2), 0); + std::complex s(0, -sin(angle / 2)); + return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); +} + +LogicalResult RXOp::verify() { + if (getAngleStatic().has_value() && getAngleDynamic()) + return emitOpError("cannot specify both static and dynamic angle"); + if (!getAngleStatic().has_value() && !getAngleDynamic()) + return emitOpError("must specify either static or dynamic angle"); + return success(); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 958b5abcb9..b7e2aafd55 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -222,10 +222,18 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, // Temporary implementation of XOp translation void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { - for (const auto& target : operation.getTargets()) { - const Value qubit = qubits[target]; - builder.x(qubit); - } + const auto& target = operation.getTargets()[0]; + const Value qubit = qubits[target]; + builder.x(qubit); +} + +// Temporary implementation of RXOp translation +void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto angle = operation.getParameter()[0]; + const auto& target = operation.getTargets()[0]; + const Value qubit = qubits[target]; + builder.rx(angle, qubit); } /** @@ -262,6 +270,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::X: addXOp(builder, *operation, qubits); break; + case qc::OpType::RX: + addRXOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index d2c9030961..fa04d80755 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -895,4 +895,36 @@ TEST_F(CompilerPipelineTest, X) { }); } +TEST_F(CompilerPipelineTest, RX) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.rx(1.0, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + b.rx(1.0, q[0]); + }); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + b.rx(1.0, q[0]); + }); + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.rx(1.0, q[0]); + }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .optimization = fluxExpected.get(), + .quartzConversion = quartzExpected.get(), + .qirConversion = qirExpected.get(), + }); +} + } // namespace From d88a63f90821e724ab1862103889c4c949616e6c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:28:11 +0100 Subject: [PATCH 089/419] Define custom builders for RXOp --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 13 +++++++++++++ mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 13 +++++++++++++ .../lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 6 ++---- .../Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 5 ++--- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index fecaef193c..5dfab10fde 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -296,6 +296,19 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { StringRef getBaseSymbol() const { return "rx"; } }]; + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "double":$angle_double), [{ + auto angle_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), angle_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, angle_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$angle_attr), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, angle_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$angle_operand), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, angle_operand); + }]>, + ]; + let hasVerifier = 1; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index eff65ba70c..08e9dabafe 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -273,6 +273,19 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { StringRef getBaseSymbol() const { return "rx"; } }]; + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "double":$angle_double), [{ + auto angle_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), angle_double); + build($_builder, $_state, qubit_in, angle_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$angle_attr), [{ + build($_builder, $_state, qubit_in, angle_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$angle_operand), [{ + build($_builder, $_state, qubit_in, nullptr, angle_operand); + }]>, + ]; + let hasVerifier = 1; } diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 7ca041e5fb..4033b88474 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -181,7 +181,7 @@ Value FluxProgramBuilder::x(Value qubit) { } Value FluxProgramBuilder::rx(Value angle, Value qubit) { - auto rxOp = builder.create(loc, qubit, /*angle_static=*/nullptr, angle); + auto rxOp = builder.create(loc, qubit, angle); const auto qubitOut = rxOp.getQubitOut(); // Update tracking @@ -191,9 +191,7 @@ Value FluxProgramBuilder::rx(Value angle, Value qubit) { } Value FluxProgramBuilder::rx(double angle, Value qubit) { - auto angleAttr = builder.getF64FloatAttr(angle); - auto rxOp = - builder.create(loc, qubit, angleAttr, /*angle_dynamic=*/nullptr); + auto rxOp = builder.create(loc, qubit, angle); const auto qubitOut = rxOp.getQubitOut(); // Update tracking diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index b648be68a5..85a400f6b2 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -130,13 +130,12 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { } QuartzProgramBuilder& QuartzProgramBuilder::rx(double angle, Value qubit) { - auto angleAttr = builder.getF64FloatAttr(angle); - builder.create(loc, qubit, angleAttr, /*angle_dynamic=*/nullptr); + builder.create(loc, qubit, angle); return *this; } QuartzProgramBuilder& QuartzProgramBuilder::rx(Value angle, Value qubit) { - builder.create(loc, qubit, /*angle_static=*/nullptr, angle); + builder.create(loc, qubit, angle); return *this; } From 443120d0c78f82d13c2080bfd9e1d8ca769aec8f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:56:32 +0100 Subject: [PATCH 090/419] Improve usefullness of ParameterDescriptor --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 22 +++++++++++++++++-- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 22 +++++++++++++++++-- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 12 ++++------ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 12 ++++------ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 16 +++++++------- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 8 +------ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 8 +------ 7 files changed, 58 insertions(+), 42 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 12e2ecf8c6..f7cf067486 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -50,9 +50,27 @@ namespace mlir::flux { struct ParameterDescriptor { - bool isStatic; - std::optional constantValue; + mlir::FloatAttr valueAttr; mlir::Value valueOperand; + + ParameterDescriptor(mlir::FloatAttr attr = nullptr, + mlir::Value operand = nullptr) { + assert(!(attr && operand) && "Cannot have both static and dynamic values"); + valueAttr = attr; + valueOperand = operand; + } + + bool isStatic() const { return valueAttr != nullptr; } + bool isDynamic() const { return valueOperand != nullptr; } + + double getValueDouble() const { + if (isDynamic()) { + llvm_unreachable("Cannot get double value from dynamic parameter"); + } + return valueAttr.getValueAsDouble(); + } + mlir::FloatAttr getValueAttr() const { return valueAttr; } + mlir::Value getValueOperand() const { return valueOperand; } }; } // namespace mlir::flux diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 537fddfa97..739d843a36 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -50,9 +50,27 @@ namespace mlir::quartz { struct ParameterDescriptor { - bool isStatic; - std::optional constantValue; + mlir::FloatAttr valueAttr; mlir::Value valueOperand; + + ParameterDescriptor(mlir::FloatAttr attr = nullptr, + mlir::Value operand = nullptr) { + assert(!(attr && operand) && "Cannot have both static and dynamic values"); + valueAttr = attr; + valueOperand = operand; + } + + bool isStatic() const { return valueAttr != nullptr; } + bool isDynamic() const { return valueOperand != nullptr; } + + double getValueDouble() const { + if (isDynamic()) { + llvm_unreachable("Cannot get double value from dynamic parameter"); + } + return valueAttr.getValueAsDouble(); + } + mlir::FloatAttr getValueAttr() const { return valueAttr; } + mlir::Value getValueOperand() const { return valueOperand; } }; } // namespace mlir::quartz diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 9ada21e45e..43dc0894ab 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -264,16 +264,12 @@ struct ConvertFluxRXOp final : OpConversionPattern { const auto& quartzQubit = adaptor.getQubitIn(); auto angle = op.getParameter(0); - FloatAttr angleStatic = nullptr; - if (angle.isStatic) { - angleStatic = rewriter.getFloatAttr(rewriter.getF64Type(), - angle.constantValue.value()); - } - Value angleDynamic = angle.valueOperand; + auto angleAttr = angle.getValueAttr(); + auto angleOperand = angle.getValueOperand(); // Create quartz.rx (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, angleStatic, - angleDynamic); + rewriter.create(op.getLoc(), quartzQubit, angleAttr, + angleOperand); // Replace the output qubit with the same quartz reference rewriter.replaceOp(op, quartzQubit); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 2b8de6e9b2..f89d285b43 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -392,16 +392,12 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { const Value fluxQubit = getState().qubitMap[quartzQubit]; auto angle = op.getParameter(0); - FloatAttr angleStatic = nullptr; - if (angle.isStatic) { - angleStatic = rewriter.getFloatAttr(rewriter.getF64Type(), - angle.constantValue.value()); - } - Value angleDynamic = angle.valueOperand; + auto angleAttr = angle.getValueAttr(); + auto angleOperand = angle.getValueOperand(); // Create flux.rx operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, - angleStatic, angleDynamic); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, angleAttr, + angleOperand); // Update mapping: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 65af87cf3f..0af49de9c4 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -433,19 +433,19 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { getOrCreateFunctionDeclaration(rewriter, op, QIR_RX, qirSignature); auto angle = op.getParameter(0); - Value angleValue; - if (angle.isStatic) { - auto angleStatic = rewriter.getFloatAttr(rewriter.getF64Type(), - angle.constantValue.value()); - angleValue = rewriter.create(op.getLoc(), angleStatic) - .getResult(); + Value angleOperand; + if (angle.isStatic()) { + auto angleAttr = angle.getValueAttr(); + auto constantOp = + rewriter.create(op.getLoc(), angleAttr); + angleOperand = constantOp.getResult(); } else { - angleValue = angle.valueOperand; + angleOperand = angle.getValueOperand(); } // Replace with call to RX rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubitIn(), angleValue}); + op, fnDecl, ValueRange{adaptor.getQubitIn(), angleOperand}); return success(); } }; diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 2191be9c53..8950833fd7 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -236,13 +236,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i != 0) { llvm_unreachable("RXOp has only one parameter"); } - return ParameterDescriptor{ - .isStatic = getAngleStatic().has_value(), - .constantValue = getAngleStatic().has_value() - ? std::optional( - getAngleStatic().value().convertToDouble()) - : std::nullopt, - .valueOperand = getAngleDynamic()}; + return ParameterDescriptor(getAngleStaticAttr(), getAngleDynamic()); } bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 5c73c49775..cf89ffaf2f 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -178,13 +178,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i != 0) { llvm_unreachable("RXOp has only one parameter"); } - return ParameterDescriptor{ - .isStatic = getAngleStatic().has_value(), - .constantValue = getAngleStatic().has_value() - ? std::optional( - getAngleStatic().value().convertToDouble()) - : std::nullopt, - .valueOperand = getAngleDynamic()}; + return ParameterDescriptor(getAngleStaticAttr(), getAngleDynamic()); } bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } From 2bd514748bfb3a96d3322706b67c8d8f942e628e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:18:26 +0100 Subject: [PATCH 091/419] Fix linter errors --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 1 - mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 9 ++++++--- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 9 ++++++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index f89d285b43..01007fb89c 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 0af49de9c4..d7f1031925 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -32,10 +32,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 8950833fd7..b2b640c863 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -18,9 +18,12 @@ #include // IWYU pragma: end_keep +#include +#include #include #include #include +#include #include #include #include @@ -236,7 +239,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i != 0) { llvm_unreachable("RXOp has only one parameter"); } - return ParameterDescriptor(getAngleStaticAttr(), getAngleDynamic()); + return {getAngleStaticAttr(), getAngleDynamic()}; } bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } @@ -248,8 +251,8 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); const auto angle = getAngleStatic().value().convertToDouble(); - std::complex c(cos(angle / 2), 0); - std::complex s(0, -sin(angle / 2)); + const std::complex c(cos(angle / 2), 0); + const std::complex s(0, -sin(angle / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index f163b56303..e279c86b3b 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -21,8 +21,10 @@ #include #include #include +#include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index cf89ffaf2f..421c16dcab 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -17,9 +17,12 @@ #include // IWYU pragma: end_keep +#include +#include #include #include #include +#include #include #include #include @@ -178,7 +181,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i != 0) { llvm_unreachable("RXOp has only one parameter"); } - return ParameterDescriptor(getAngleStaticAttr(), getAngleDynamic()); + return {getAngleStaticAttr(), getAngleDynamic()}; } bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } @@ -190,8 +193,8 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); const auto angle = getAngleStatic().value().convertToDouble(); - std::complex c(cos(angle / 2), 0); - std::complex s(0, -sin(angle / 2)); + const std::complex c(cos(angle / 2), 0); + const std::complex s(0, -sin(angle / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); } From 99582a5c079cd3203adf8f7b78b9ec2f517fc908 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:28:53 +0100 Subject: [PATCH 092/419] Mark interface methods as static --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 32 +++++++++---------- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 32 +++++++++---------- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 10 +++--- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 10 +++--- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 5dfab10fde..e7534234a3 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -248,23 +248,23 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() const { return 1; } + static size_t getNumTargets() { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls() const { return 0; } - size_t getNumNegControls() const { return 0; } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); - Value getPosControl(size_t i) const; - Value getNegControl(size_t i) const; + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); Value getInput(size_t i); Value getOutput(size_t i); Value getInputForOutput(Value output); Value getOutputForInput(Value input); - size_t getNumParams() const { return 0; } - ParameterDescriptor getParameter(size_t i) const; - bool hasStaticUnitary() const { return true; } + static size_t getNumParams() { return 0; } + static ParameterDescriptor getParameter(size_t i); + static bool hasStaticUnitary() { return true; } DenseElementsAttr tryGetStaticMatrix(); - StringRef getBaseSymbol() const { return "x"; } + static StringRef getBaseSymbol() { return "x"; } }]; } @@ -277,23 +277,23 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() const { return 1; } + static size_t getNumTargets() { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls() const { return 0; } - size_t getNumNegControls() const { return 0; } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); - Value getPosControl(size_t i) const; - Value getNegControl(size_t i) const; + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); Value getInput(size_t i); Value getOutput(size_t i); Value getInputForOutput(Value output); Value getOutputForInput(Value input); - size_t getNumParams() const { return 1; } + static size_t getNumParams() { return 1; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); - StringRef getBaseSymbol() const { return "rx"; } + static StringRef getBaseSymbol() { return "rx"; } }]; let builders = [ diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 08e9dabafe..53ccc946af 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -234,19 +234,19 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() const { return 1; } + static size_t getNumTargets() { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls() const { return 0; } - size_t getNumNegControls() const { return 0; } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); - Value getPosControl(size_t i) const; - Value getNegControl(size_t i) const; - size_t getNumParams() const { return 0; } - ParameterDescriptor getParameter(size_t i) const; - bool hasStaticUnitary() const { return true; } + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + static size_t getNumParams() { return 0; } + static ParameterDescriptor getParameter(size_t i); + static bool hasStaticUnitary() { return true; } DenseElementsAttr tryGetStaticMatrix(); - StringRef getBaseSymbol() const { return "x"; } + static StringRef getBaseSymbol() { return "x"; } }]; } @@ -258,19 +258,19 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() const { return 1; } + static size_t getNumTargets() { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - size_t getNumPosControls() const { return 0; } - size_t getNumNegControls() const { return 0; } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); - Value getPosControl(size_t i) const; - Value getNegControl(size_t i) const; - size_t getNumParams() const { return 1; } + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + static size_t getNumParams() { return 1; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); - StringRef getBaseSymbol() const { return "rx"; } + static StringRef getBaseSymbol() { return "rx"; } }]; let builders = [ diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index b2b640c863..b0fd1f9e9e 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -137,11 +137,11 @@ Value XOp::getTarget(size_t i) { return getQubitIn(); } -Value XOp::getPosControl(size_t /*i*/) const { +Value XOp::getPosControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); } -Value XOp::getNegControl(size_t /*i*/) const { +Value XOp::getNegControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); } @@ -173,7 +173,7 @@ Value XOp::getOutputForInput(Value input) { return getQubitOut(); } -ParameterDescriptor XOp::getParameter(size_t /*i*/) const { +ParameterDescriptor XOp::getParameter(size_t /*i*/) { llvm_unreachable("XOp does not have parameters"); } @@ -199,11 +199,11 @@ Value RXOp::getTarget(size_t i) { return getQubitIn(); } -Value RXOp::getPosControl(size_t /*i*/) const { +Value RXOp::getPosControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); } -Value RXOp::getNegControl(size_t /*i*/) const { +Value RXOp::getNegControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 421c16dcab..1f2edc16e1 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -135,15 +135,15 @@ Value XOp::getTarget(size_t i) { return getQubitIn(); } -Value XOp::getPosControl(size_t /*i*/) const { +Value XOp::getPosControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); } -Value XOp::getNegControl(size_t /*i*/) const { +Value XOp::getNegControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); } -ParameterDescriptor XOp::getParameter(size_t /*i*/) const { +ParameterDescriptor XOp::getParameter(size_t /*i*/) { llvm_unreachable("XOp does not have parameters"); } @@ -169,11 +169,11 @@ Value RXOp::getTarget(size_t i) { return getQubitIn(); } -Value RXOp::getPosControl(size_t /*i*/) const { +Value RXOp::getPosControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); } -Value RXOp::getNegControl(size_t /*i*/) const { +Value RXOp::getNegControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); } From df30aab2013303789749ccdec8314f5128f42246 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:22:03 +0100 Subject: [PATCH 093/419] Rename angle to theta --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 8 ++++---- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 20 +++++++++---------- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 6 +++--- .../Quartz/Builder/QuartzProgramBuilder.h | 6 +++--- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 20 +++++++++---------- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 10 +++++----- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 18 ++++++++--------- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 16 +++++++-------- .../Flux/Builder/FluxProgramBuilder.cpp | 8 ++++---- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 18 ++++++++--------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 14 ++++++------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 8 ++++---- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 18 ++++++++--------- .../TranslateQuantumComputationToQuartz.cpp | 4 ++-- 14 files changed, 87 insertions(+), 87 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index ca9c605fd6..48096f23c9 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -259,13 +259,13 @@ class FluxProgramBuilder { Value x(Value qubit); /** - * @brief Apply the RX gate to a qubit with a static angle + * @brief Apply the RX gate to a qubit * * @details * Consumes the input qubit and produces a new output qubit SSA value. * The input is validated and tracking is updated. * - * @param angle Rotation angle + * @param theta Rotation angle * @param qubit Input qubit (must be valid/unconsumed) * @return Output qubit value * @@ -277,8 +277,8 @@ class FluxProgramBuilder { * flux.rx(1.0) %q : !flux.qubit -> !flux.qubit * ``` */ - Value rx(double angle, Value qubit); - Value rx(Value angle, Value qubit); + Value rx(double theta, Value qubit); + Value rx(Value theta, Value qubit); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index e7534234a3..a3201a75d6 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -270,10 +270,10 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { let arguments = (ins QubitType:$qubit_in, - OptionalAttr:$angle_static, - Optional:$angle_dynamic); + OptionalAttr:$theta_attr, + Optional:$theta_operand); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($angle_static^)? ($angle_dynamic^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } @@ -297,15 +297,15 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "double":$angle_double), [{ - auto angle_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), angle_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, angle_attr, nullptr); + OpBuilder<(ins "Value":$qubit_in, "double":$theta_double), [{ + auto theta_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), theta_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, theta_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$angle_attr), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, angle_attr, nullptr); + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$theta_attr), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, theta_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$angle_operand), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, angle_operand); + OpBuilder<(ins "Value":$qubit_in, "Value":$theta_operand), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, theta_operand); }]>, ]; diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 7ea21f78d6..0d92fbaf86 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -245,7 +245,7 @@ class QIRProgramBuilder { /** * @brief Apply the RX gate to a qubit * - * @param angle Rotation angle + * @param theta Rotation angle * @param qubit Input qubit * @return Reference to this builder for method chaining * @@ -257,8 +257,8 @@ class QIRProgramBuilder { * llvm.call @__quantum__qis__rx__body(%q, %c) : (!llvm.ptr, f64) -> () * ``` */ - QIRProgramBuilder& rx(double angle, const Value qubit); - QIRProgramBuilder& rx(Value angle, const Value qubit); + QIRProgramBuilder& rx(double theta, const Value qubit); + QIRProgramBuilder& rx(Value theta, const Value qubit); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index c18368f75b..0657e8b329 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -246,7 +246,7 @@ class QuartzProgramBuilder { /** * @brief Apply the RX gate to a qubit * - * @param angle Rotation angle + * @param theta Rotation angle * @param qubit Input qubit * @return Reference to this builder for method chaining * @@ -258,8 +258,8 @@ class QuartzProgramBuilder { * quartz.rx(1.0) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& rx(double angle, Value qubit); - QuartzProgramBuilder& rx(Value angle, Value qubit); + QuartzProgramBuilder& rx(double theta, Value qubit); + QuartzProgramBuilder& rx(Value theta, Value qubit); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 53ccc946af..4a644845b5 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -252,9 +252,9 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { let arguments = (ins QubitType:$qubit_in, - OptionalAttr:$angle_static, - Optional:$angle_dynamic); - let assemblyFormat = "`(` ($angle_static^)? ($angle_dynamic^)? `)` $qubit_in attr-dict"; + OptionalAttr:$theta_attr, + Optional:$theta_operand); + let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } @@ -274,15 +274,15 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "double":$angle_double), [{ - auto angle_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), angle_double); - build($_builder, $_state, qubit_in, angle_attr, nullptr); + OpBuilder<(ins "Value":$qubit_in, "double":$theta_double), [{ + auto theta_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), theta_double); + build($_builder, $_state, qubit_in, theta_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$angle_attr), [{ - build($_builder, $_state, qubit_in, angle_attr, nullptr); + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$theta_attr), [{ + build($_builder, $_state, qubit_in, theta_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$angle_operand), [{ - build($_builder, $_state, qubit_in, nullptr, angle_operand); + OpBuilder<(ins "Value":$qubit_in, "Value":$theta_operand), [{ + build($_builder, $_state, qubit_in, nullptr, theta_operand); }]>, ]; diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 43dc0894ab..bf8ad0bf51 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -263,13 +263,13 @@ struct ConvertFluxRXOp final : OpConversionPattern { // OpAdaptor provides the already type-converted input qubit const auto& quartzQubit = adaptor.getQubitIn(); - auto angle = op.getParameter(0); - auto angleAttr = angle.getValueAttr(); - auto angleOperand = angle.getValueOperand(); + const auto& theta = op.getParameter(0); + const auto& thetaAttr = theta.getValueAttr(); + const auto& thetaOperand = theta.getValueOperand(); // Create quartz.rx (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, angleAttr, - angleOperand); + rewriter.create(op.getLoc(), quartzQubit, thetaAttr, + thetaOperand); // Replace the output qubit with the same quartz reference rewriter.replaceOp(op, quartzQubit); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 01007fb89c..de52707728 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -281,7 +281,7 @@ struct ConvertQuartzMeasureOp final const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map - const Value fluxQubit = getState().qubitMap[quartzQubit]; + const auto& fluxQubit = getState().qubitMap[quartzQubit]; // Create flux.measure operation (returns both output qubit and bit result) auto fluxOp = rewriter.create( @@ -335,7 +335,7 @@ struct ConvertQuartzResetOp final const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map - const Value fluxQubit = getState().qubitMap[quartzQubit]; + const auto& fluxQubit = getState().qubitMap[quartzQubit]; // Create flux.reset operation (consumes input, produces output) auto fluxOp = @@ -363,7 +363,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { const auto& quartzQubit = op.getQubit(0); // Get the latest Flux qubit value from the state map - const Value fluxQubit = getState().qubitMap[quartzQubit]; + const auto& fluxQubit = getState().qubitMap[quartzQubit]; // Create flux.x operation (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); @@ -388,15 +388,15 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { const auto& quartzQubit = op.getQubitIn(); // Get the latest Flux qubit value from the state map - const Value fluxQubit = getState().qubitMap[quartzQubit]; + const auto fluxQubit = getState().qubitMap[quartzQubit]; - auto angle = op.getParameter(0); - auto angleAttr = angle.getValueAttr(); - auto angleOperand = angle.getValueOperand(); + const auto& theta = op.getParameter(0); + const auto& thetaAttr = theta.getValueAttr(); + const auto& thetaOperand = theta.getValueOperand(); // Create flux.rx operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, angleAttr, - angleOperand); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, thetaAttr, + thetaOperand); // Update mapping: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index d7f1031925..ad95861350 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -434,20 +434,20 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { const auto fnDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_RX, qirSignature); - auto angle = op.getParameter(0); - Value angleOperand; - if (angle.isStatic()) { - auto angleAttr = angle.getValueAttr(); + const auto& theta = op.getParameter(0); + Value thetaOperand; + if (theta.isStatic()) { + const auto& thetaAttr = theta.getValueAttr(); auto constantOp = - rewriter.create(op.getLoc(), angleAttr); - angleOperand = constantOp.getResult(); + rewriter.create(op.getLoc(), thetaAttr); + thetaOperand = constantOp.getResult(); } else { - angleOperand = angle.getValueOperand(); + thetaOperand = theta.getValueOperand(); } // Replace with call to RX rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubitIn(), angleOperand}); + op, fnDecl, ValueRange{adaptor.getQubitIn(), thetaOperand}); return success(); } }; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 4033b88474..0d23b221e4 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -180,8 +180,8 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } -Value FluxProgramBuilder::rx(Value angle, Value qubit) { - auto rxOp = builder.create(loc, qubit, angle); +Value FluxProgramBuilder::rx(Value theta, Value qubit) { + auto rxOp = builder.create(loc, qubit, theta); const auto qubitOut = rxOp.getQubitOut(); // Update tracking @@ -190,8 +190,8 @@ Value FluxProgramBuilder::rx(Value angle, Value qubit) { return qubitOut; } -Value FluxProgramBuilder::rx(double angle, Value qubit) { - auto rxOp = builder.create(loc, qubit, angle); +Value FluxProgramBuilder::rx(double theta, Value qubit) { + auto rxOp = builder.create(loc, qubit, theta); const auto qubitOut = rxOp.getQubitOut(); // Update tracking diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index b0fd1f9e9e..4262bca9f7 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -239,10 +239,10 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i != 0) { llvm_unreachable("RXOp has only one parameter"); } - return {getAngleStaticAttr(), getAngleDynamic()}; + return {getThetaAttrAttr(), getThetaOperand()}; } -bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } +bool RXOp::hasStaticUnitary() { return getThetaAttr().has_value(); } DenseElementsAttr RXOp::tryGetStaticMatrix() { if (!hasStaticUnitary()) { @@ -250,17 +250,17 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto angle = getAngleStatic().value().convertToDouble(); - const std::complex c(cos(angle / 2), 0); - const std::complex s(0, -sin(angle / 2)); + const auto theta = getThetaAttr().value().convertToDouble(); + const std::complex c(cos(theta / 2), 0); + const std::complex s(0, -sin(theta / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); } LogicalResult RXOp::verify() { - if (getAngleStatic().has_value() && getAngleDynamic()) - return emitOpError("cannot specify both static and dynamic angle"); - if (!getAngleStatic().has_value() && !getAngleDynamic()) - return emitOpError("must specify either static or dynamic angle"); + if (getThetaAttr().has_value() && getThetaOperand()) + return emitOpError("cannot specify both static and dynamic theta"); + if (!getThetaAttr().has_value() && !getThetaOperand()) + return emitOpError("must specify either static or dynamic theta"); return success(); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index e279c86b3b..af0470d120 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -251,7 +251,7 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { return *this; } -QIRProgramBuilder& QIRProgramBuilder::rx(Value angle, const Value qubit) { +QIRProgramBuilder& QIRProgramBuilder::rx(Value theta, const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -265,17 +265,17 @@ QIRProgramBuilder& QIRProgramBuilder::rx(Value angle, const Value qubit) { Float64Type::get(builder.getContext())}); auto fnDecl = getOrCreateFunctionDeclaration(builder, module, QIR_RX, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit, angle}); + builder.create(loc, fnDecl, ValueRange{qubit, theta}); return *this; } -QIRProgramBuilder& QIRProgramBuilder::rx(double angle, const Value qubit) { - // Create constant for angle - auto angleConst = - builder.create(loc, builder.getF64FloatAttr(angle)); +QIRProgramBuilder& QIRProgramBuilder::rx(double theta, const Value qubit) { + // Create constant for theta + auto thetaConst = + builder.create(loc, builder.getF64FloatAttr(theta)); - return rx(angleConst.getResult(), qubit); + return rx(thetaConst.getResult(), qubit); } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 85a400f6b2..36d6cb6d5d 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -129,13 +129,13 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { return *this; } -QuartzProgramBuilder& QuartzProgramBuilder::rx(double angle, Value qubit) { - builder.create(loc, qubit, angle); +QuartzProgramBuilder& QuartzProgramBuilder::rx(double theta, Value qubit) { + builder.create(loc, qubit, theta); return *this; } -QuartzProgramBuilder& QuartzProgramBuilder::rx(Value angle, Value qubit) { - builder.create(loc, qubit, angle); +QuartzProgramBuilder& QuartzProgramBuilder::rx(Value theta, Value qubit) { + builder.create(loc, qubit, theta); return *this; } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 1f2edc16e1..06a138c6b8 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -181,10 +181,10 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i != 0) { llvm_unreachable("RXOp has only one parameter"); } - return {getAngleStaticAttr(), getAngleDynamic()}; + return {getThetaAttrAttr(), getThetaOperand()}; } -bool RXOp::hasStaticUnitary() { return getAngleStatic().has_value(); } +bool RXOp::hasStaticUnitary() { return getThetaAttr().has_value(); } DenseElementsAttr RXOp::tryGetStaticMatrix() { if (!hasStaticUnitary()) { @@ -192,16 +192,16 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto angle = getAngleStatic().value().convertToDouble(); - const std::complex c(cos(angle / 2), 0); - const std::complex s(0, -sin(angle / 2)); + const auto theta = getThetaAttr().value().convertToDouble(); + const std::complex c(cos(theta / 2), 0); + const std::complex s(0, -sin(theta / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); } LogicalResult RXOp::verify() { - if (getAngleStatic().has_value() && getAngleDynamic()) - return emitOpError("cannot specify both static and dynamic angle"); - if (!getAngleStatic().has_value() && !getAngleDynamic()) - return emitOpError("must specify either static or dynamic angle"); + if (getThetaAttr().has_value() && getThetaOperand()) + return emitOpError("cannot specify both static and dynamic theta"); + if (!getThetaAttr().has_value() && !getThetaOperand()) + return emitOpError("must specify either static or dynamic theta"); return success(); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index b7e2aafd55..1661d46c7a 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -230,10 +230,10 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, // Temporary implementation of RXOp translation void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { - const auto angle = operation.getParameter()[0]; + const auto& theta = operation.getParameter()[0]; const auto& target = operation.getTargets()[0]; const Value qubit = qubits[target]; - builder.rx(angle, qubit); + builder.rx(theta, qubit); } /** From 7282a8a32a0d6f23c22d45bd60e6925f898c2746 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:28:36 +0100 Subject: [PATCH 094/419] Remove modifier TODOs and namespace specifiers --- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 23 ++++++++----------- .../Dialect/Quartz/IR/QuartzInterfaces.td | 15 +++++------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 8c433b77c0..b17ea7ba57 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -52,37 +52,37 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th qubit (targets + controls combined).", - "::mlir::Value", "getQubit", (ins "size_t":$i) + "Value", "getQubit", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th target input qubit.", - "::mlir::Value", "getTarget", (ins "size_t":$i) + "Value", "getTarget", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th positive control input qubit.", - "::mlir::Value", "getPosControl", (ins "size_t":$i) + "Value", "getPosControl", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th negative control input qubit.", - "::mlir::Value", "getNegControl", (ins "size_t":$i) + "Value", "getNegControl", (ins "size_t":$i) >, // Value semantics threading InterfaceMethod< "Returns the i-th input qubit (targets + controls combined).", - "::mlir::Value", "getInput", (ins "size_t":$i) + "Value", "getInput", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th output qubit (targets + controls combined).", - "::mlir::Value", "getOutput", (ins "size_t":$i) + "Value", "getOutput", (ins "size_t":$i) >, InterfaceMethod< "Returns the input qubit corresponding to the given output qubit.", - "::mlir::Value", "getInputForOutput", (ins "::mlir::Value":$output) + "Value", "getInputForOutput", (ins "Value":$output) >, InterfaceMethod< "Returns the output qubit corresponding to the given input qubit.", - "::mlir::Value", "getOutputForInput", (ins "::mlir::Value":$input) + "Value", "getOutputForInput", (ins "Value":$input) >, // Parameter handling @@ -92,7 +92,7 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "::mlir::flux::ParameterDescriptor", "getParameter", (ins "size_t":$i) + "ParameterDescriptor", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary @@ -117,12 +117,9 @@ def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { InterfaceMethod< "Attempts to extract the static unitary matrix. " "Returns std::nullopt if the operation is symbolic or dynamic.", - "std::optional<::mlir::DenseElementsAttr>", "tryGetStaticMatrix", (ins) + "std::optional", "tryGetStaticMatrix", (ins) >, - // Modifier state - /// TODO: Implement interface methods to query modifier state - // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 805553260e..6b45481b31 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -51,19 +51,19 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th qubit (targets + controls combined).", - "::mlir::Value", "getQubit", (ins "size_t":$i) + "Value", "getQubit", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th target qubit.", - "::mlir::Value", "getTarget", (ins "size_t":$i) + "Value", "getTarget", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th positive control qubit.", - "::mlir::Value", "getPosControl", (ins "size_t":$i) + "Value", "getPosControl", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th negative control qubit.", - "::mlir::Value", "getNegControl", (ins "size_t":$i) + "Value", "getNegControl", (ins "size_t":$i) >, // Parameter handling @@ -73,7 +73,7 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "::mlir::quartz::ParameterDescriptor", "getParameter", (ins "size_t":$i) + "ParameterDescriptor", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary @@ -98,12 +98,9 @@ def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { InterfaceMethod< "Attempts to extract the static unitary matrix. " "Returns std::nullopt if the operation is symbolic or dynamic.", - "std::optional<::mlir::DenseElementsAttr>", "tryGetStaticMatrix", (ins) + "std::optional", "tryGetStaticMatrix", (ins) >, - // Modifier state - /// TODO: Implement interface methods to query modifier state - // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", From 254f0a272f68473fa71b38aba7494c2cc009d8ef Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:50:15 +0100 Subject: [PATCH 095/419] Initial support for U2Op --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 25 +++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 69 ++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 22 ++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 1 + .../Quartz/Builder/QuartzProgramBuilder.h | 21 ++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 64 +++++++++++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 30 ++++++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 35 ++++++ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 49 +++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 38 ++++++- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 102 +++++++++++++++++- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 48 ++++++++- .../Quartz/Builder/QuartzProgramBuilder.cpp | 22 +++- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 74 ++++++++++++- .../TranslateQuantumComputationToQuartz.cpp | 13 +++ .../pipeline/test_compiler_pipeline.cpp | 32 ++++++ 16 files changed, 634 insertions(+), 11 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 48096f23c9..a765b3b324 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -280,6 +280,31 @@ class FluxProgramBuilder { Value rx(double theta, Value qubit); Value rx(Value theta, Value qubit); + /** + * @brief Apply the U2 gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and tracking is updated. + * + * @param phi Rotation angle + * @param lambda Rotation angle + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit value + * + * @par Example: + * ```c++ + * builder.u2(1.0, 0.5, q); + * ``` + * ```mlir + * flux.u2(1.0, 0.5) %q : !flux.qubit -> !flux.qubit + * ``` + */ + Value u2(double phi, double lambda, Value qubit); + Value u2(double phi, Value lambda, Value qubit); + Value u2(Value phi, double lambda, Value qubit); + Value u2(Value phi, Value lambda, Value qubit); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index a3201a75d6..26efdd2ed1 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -312,4 +312,73 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { let hasVerifier = 1; } +def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, AttrSizedOperandSegments]> { + let arguments = (ins QubitType:$qubit_in, + OptionalAttr:$phi_attr, + Optional:$phi_operand, + OptionalAttr:$lambda_attr, + Optional:$lambda_operand); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + static size_t getNumTargets() { return 1; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } + Value getQubit(size_t i); + Value getTarget(size_t i); + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + Value getInput(size_t i); + Value getOutput(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + static size_t getNumParams() { return 2; } + ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "u2"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "double":$lambda_double), [{ + auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); + auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "FloatAttr":$lambda_attr), [{ + auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "Value":$lambda_operand), [{ + auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, nullptr, lambda_operand); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "double":$lambda_double), [{ + auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "FloatAttr":$lambda_attr), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "Value":$lambda_operand), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, nullptr, lambda_operand); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "double":$lambda_double), [{ + auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, phi_operand, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "FloatAttr":$lambda_attr), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, phi_operand, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "Value":$lambda_operand), [{ + build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, phi_operand, nullptr, lambda_operand); + }]>, + ]; + + let hasVerifier = 1; +} + #endif // FluxOPS diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 0d92fbaf86..f49b3981e9 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -260,6 +260,28 @@ class QIRProgramBuilder { QIRProgramBuilder& rx(double theta, const Value qubit); QIRProgramBuilder& rx(Value theta, const Value qubit); + /** + * @brief Apply the U2 gate to a qubit + * + * @param phi Rotation angle + * @param lambda Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.u2(1.0, 0.5, q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__u2__body(%q, %c1, %c2) : (!llvm.ptr, f64, f64) + * -> () + * ``` + */ + QIRProgramBuilder& u2(double phi, double lambda, const Value qubit); + QIRProgramBuilder& u2(double phi, Value lambda, const Value qubit); + QIRProgramBuilder& u2(Value phi, double lambda, const Value qubit); + QIRProgramBuilder& u2(Value phi, Value lambda, const Value qubit); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 83bec2c017..e6b97074c5 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -28,6 +28,7 @@ static constexpr auto QIR_ARRAY_RECORD_OUTPUT = static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; static constexpr auto QIR_X = "__quantum__qis__x__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; +static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 0657e8b329..6bbaa1167a 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -261,6 +261,27 @@ class QuartzProgramBuilder { QuartzProgramBuilder& rx(double theta, Value qubit); QuartzProgramBuilder& rx(Value theta, Value qubit); + /** + * @brief Apply the U2 gate to a qubit + * + * @param phi Rotation angle + * @param lambda Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.u2(1.0, 0.5, q); + * ``` + * ```mlir + * quartz.u2(1.0, 0.5) %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& u2(double phi, double lambda, Value qubit); + QuartzProgramBuilder& u2(Value phi, double lambda, Value qubit); + QuartzProgramBuilder& u2(double phi, Value lambda, Value qubit); + QuartzProgramBuilder& u2(Value phi, Value lambda, Value qubit); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 4a644845b5..6ee0c57174 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -289,4 +289,68 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { let hasVerifier = 1; } +def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, AttrSizedOperandSegments]> { + let arguments = (ins QubitType:$qubit_in, + OptionalAttr:$phi_attr, + Optional:$phi_operand, + OptionalAttr:$lambda_attr, + Optional:$lambda_operand); + let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + static size_t getNumTargets() { return 1; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } + Value getQubit(size_t i); + Value getTarget(size_t i); + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + static size_t getNumParams() { return 2; } + ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "u2"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "double":$lambda_double), [{ + auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); + auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); + build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "FloatAttr":$lambda_attr), [{ + auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); + build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "Value":$lambda_operand), [{ + auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); + build($_builder, $_state, qubit_in, phi_attr, nullptr, nullptr, lambda_operand); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "double":$lambda_double), [{ + auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); + build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "FloatAttr":$lambda_attr), [{ + build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "Value":$lambda_operand), [{ + build($_builder, $_state, qubit_in, phi_attr, nullptr, nullptr, lambda_operand); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "double":$lambda_double), [{ + auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); + build($_builder, $_state, qubit_in, nullptr, phi_operand, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "FloatAttr":$lambda_attr), [{ + build($_builder, $_state, qubit_in, nullptr, phi_operand, lambda_attr, nullptr); + }]>, + OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "Value":$lambda_operand), [{ + build($_builder, $_state, qubit_in, nullptr, phi_operand, nullptr, lambda_operand); + }]>, + ]; + + let hasVerifier = 1; +} + #endif // QUARTZ_OPS diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index bf8ad0bf51..636ea227ed 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -278,6 +278,35 @@ struct ConvertFluxRXOp final : OpConversionPattern { } }; +// Temporary implementation of U2Op conversion +struct ConvertFluxU2Op final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::U2Op op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + const auto& phi = op.getParameter(0); + const auto& phiAttr = phi.getValueAttr(); + const auto& phiOperand = phi.getValueOperand(); + + const auto& lambda = op.getParameter(1); + const auto& lambdaAttr = lambda.getValueAttr(); + const auto& lambdaOperand = lambda.getValueOperand(); + + // Create quartz.u2 (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit, phiAttr, phiOperand, + lambdaAttr, lambdaOperand); + + // Replace the output qubit with the same quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); + } +}; + /** * @brief Pass implementation for Flux-to-Quartz conversion * @@ -329,6 +358,7 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index de52707728..f82c38f794 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -408,6 +408,40 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { } }; +// Temporary implementation of U2Op conversion +struct ConvertQuartzU2Op final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::U2Op op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto& quartzQubit = op.getQubitIn(); + + // Get the latest Flux qubit value from the state map + const auto fluxQubit = getState().qubitMap[quartzQubit]; + + const auto& phi = op.getParameter(0); + const auto& phiAttr = phi.getValueAttr(); + const auto& phiOperand = phi.getValueOperand(); + + const auto& lambda = op.getParameter(1); + const auto& lambdaAttr = lambda.getValueAttr(); + const auto& lambdaOperand = lambda.getValueOperand(); + + // Create flux.u2 operation (consumes input, produces output) + auto fluxOp = rewriter.create( + op.getLoc(), fluxQubit, phiAttr, phiOperand, lambdaAttr, lambdaOperand); + + // Update mapping: the Quartz qubit now corresponds to the output qubit + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getResult()); + + return success(); + } +}; + /** * @brief Pass implementation for Quartz-to-Flux conversion * @@ -453,6 +487,7 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index ad95861350..1907519f8c 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -452,6 +452,54 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { } }; +// Temporary implementation of U2Op conversion +struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(U2Op op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (!llvm.ptr, f64, f64) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), + {LLVM::LLVMPointerType::get(ctx), Float64Type::get(ctx), + Float64Type::get(ctx)}); + + // Get or create function declaration + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_U2, qirSignature); + + const auto& phi = op.getParameter(0); + Value phiOperand; + if (phi.isStatic()) { + const auto& phiAttr = phi.getValueAttr(); + auto constantOp = rewriter.create(op.getLoc(), phiAttr); + phiOperand = constantOp.getResult(); + } else { + phiOperand = phi.getValueOperand(); + } + + const auto& lambda = op.getParameter(1); + Value lambdaOperand; + if (lambda.isStatic()) { + const auto& lambdaAttr = lambda.getValueAttr(); + auto constantOp = + rewriter.create(op.getLoc(), lambdaAttr); + lambdaOperand = constantOp.getResult(); + } else { + lambdaOperand = lambda.getValueOperand(); + } + + // Replace with call to U2 + rewriter.replaceOpWithNewOp( + op, fnDecl, + ValueRange{adaptor.getQubitIn(), phiOperand, lambdaOperand}); + return success(); + } +}; + } // namespace /** @@ -779,6 +827,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0d23b221e4..d03c3b7f8d 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -189,7 +189,6 @@ Value FluxProgramBuilder::rx(Value theta, Value qubit) { return qubitOut; } - Value FluxProgramBuilder::rx(double theta, Value qubit) { auto rxOp = builder.create(loc, qubit, theta); const auto qubitOut = rxOp.getQubitOut(); @@ -200,6 +199,43 @@ Value FluxProgramBuilder::rx(double theta, Value qubit) { return qubitOut; } +Value FluxProgramBuilder::u2(double phi, double lambda, Value qubit) { + auto u2Op = builder.create(loc, qubit, phi, lambda); + const auto qubitOut = u2Op.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} +Value FluxProgramBuilder::u2(double phi, Value lambda, Value qubit) { + auto u2Op = builder.create(loc, qubit, phi, lambda); + const auto qubitOut = u2Op.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} +Value FluxProgramBuilder::u2(Value phi, double lambda, Value qubit) { + auto u2Op = builder.create(loc, qubit, phi, lambda); + const auto qubitOut = u2Op.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} +Value FluxProgramBuilder::u2(Value phi, Value lambda, Value qubit) { + auto u2Op = builder.create(loc, qubit, phi, lambda); + const auto qubitOut = u2Op.getQubitOut(); + + // Update tracking + updateQubitTracking(qubit, qubitOut); + + return qubitOut; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 4262bca9f7..6604605fef 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -242,7 +242,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { return {getThetaAttrAttr(), getThetaOperand()}; } -bool RXOp::hasStaticUnitary() { return getThetaAttr().has_value(); } +bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } DenseElementsAttr RXOp::tryGetStaticMatrix() { if (!hasStaticUnitary()) { @@ -251,9 +251,9 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); const auto theta = getThetaAttr().value().convertToDouble(); - const std::complex c(cos(theta / 2), 0); - const std::complex s(0, -sin(theta / 2)); - return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); + const std::complex m0(std::cos(theta / 2), 0); + const std::complex m1(0, -std::sin(theta / 2)); + return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); } LogicalResult RXOp::verify() { @@ -264,6 +264,100 @@ LogicalResult RXOp::verify() { return success(); } +// U2Op + +Value U2Op::getQubit(size_t i) { + if (i != 0) { + llvm_unreachable("U2Op has only one qubit"); + } + return getQubitIn(); +} + +Value U2Op::getTarget(size_t i) { + if (i != 0) { + llvm_unreachable("U2Op has only one target qubit"); + } + return getQubitIn(); +} + +Value U2Op::getPosControl(size_t /*i*/) { + llvm_unreachable("U2Op does not have controls"); +} + +Value U2Op::getNegControl(size_t /*i*/) { + llvm_unreachable("U2Op does not have controls"); +} + +Value U2Op::getInput(size_t i) { + if (i != 0) { + llvm_unreachable("U2Op has only one input qubit"); + } + return getQubitIn(); +} + +Value U2Op::getOutput(size_t i) { + if (i != 0) { + llvm_unreachable("U2Op has only one output qubit"); + } + return getQubitOut(); +} + +Value U2Op::getInputForOutput(Value output) { + if (output != getQubitOut()) { + llvm_unreachable("Given output is not the U2Op's output"); + } + return getQubitIn(); +} + +Value U2Op::getOutputForInput(Value input) { + if (input != getQubitIn()) { + llvm_unreachable("Given input is not the U2Op's input"); + } + return getQubitOut(); +} + +ParameterDescriptor U2Op::getParameter(size_t i) { + if (i == 0) { + return {getPhiAttrAttr(), getPhiOperand()}; + } + if (i == 1) { + return {getLambdaAttrAttr(), getLambdaOperand()}; + } + llvm_unreachable("U2Op has only two parameters"); +} + +bool U2Op::hasStaticUnitary() { + return (getParameter(0).isStatic() && getParameter(1).isStatic()); +} + +DenseElementsAttr U2Op::tryGetStaticMatrix() { + if (!hasStaticUnitary()) { + return nullptr; + } + auto* ctx = getContext(); + auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto phi = getPhiAttr().value().convertToDouble(); + const auto lambda = getLambdaAttr().value().convertToDouble(); + const std::complex I(0.0, 1.0); + const std::complex m00(1.0, 0.0); + const std::complex m01 = -std::exp(I * lambda); + const std::complex m10 = -std::exp(I * phi); + const std::complex m11 = std::exp(I * (phi + lambda)); + return DenseElementsAttr::get(type, llvm::ArrayRef({m00, m01, m10, m11})); +} + +LogicalResult U2Op::verify() { + if (getPhiAttr().has_value() && getPhiOperand()) + return emitOpError("cannot specify both static and dynamic phi"); + if (!getPhiAttr().has_value() && !getPhiOperand()) + return emitOpError("must specify either static or dynamic phi"); + if (getLambdaAttr().has_value() && getLambdaOperand()) + return emitOpError("cannot specify both static and dynamic lambda"); + if (!getLambdaAttr().has_value() && !getLambdaOperand()) + return emitOpError("must specify either static or dynamic lambda"); + return success(); +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index af0470d120..691523dc26 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -269,7 +269,6 @@ QIRProgramBuilder& QIRProgramBuilder::rx(Value theta, const Value qubit) { return *this; } - QIRProgramBuilder& QIRProgramBuilder::rx(double theta, const Value qubit) { // Create constant for theta auto thetaConst = @@ -278,6 +277,53 @@ QIRProgramBuilder& QIRProgramBuilder::rx(double theta, const Value qubit) { return rx(thetaConst.getResult(), qubit); } +QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, Value lambda, + const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create r call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + Float64Type::get(builder.getContext()), + Float64Type::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_U2, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit, phi, lambda}); + + return *this; +} +QIRProgramBuilder& QIRProgramBuilder::u2(double phi, double lambda, + const Value qubit) { + // Create constants for phi and lambda + auto phiConst = + builder.create(loc, builder.getF64FloatAttr(phi)); + auto lambdaConst = + builder.create(loc, builder.getF64FloatAttr(lambda)); + + return u2(phiConst.getResult(), lambdaConst.getResult(), qubit); +} +QIRProgramBuilder& QIRProgramBuilder::u2(double phi, Value lambda, + const Value qubit) { + // Create constant for phi + auto phiConst = + builder.create(loc, builder.getF64FloatAttr(phi)); + + return u2(phiConst.getResult(), lambda, qubit); +} +QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, double lambda, + const Value qubit) { + // Create constant for lambda + auto lambdaConst = + builder.create(loc, builder.getF64FloatAttr(lambda)); + + return u2(phi, lambdaConst.getResult(), qubit); +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 36d6cb6d5d..4648af8e05 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -133,12 +133,32 @@ QuartzProgramBuilder& QuartzProgramBuilder::rx(double theta, Value qubit) { builder.create(loc, qubit, theta); return *this; } - QuartzProgramBuilder& QuartzProgramBuilder::rx(Value theta, Value qubit) { builder.create(loc, qubit, theta); return *this; } +QuartzProgramBuilder& QuartzProgramBuilder::u2(double phi, double lambda, + Value qubit) { + builder.create(loc, qubit, phi, lambda); + return *this; +} +QuartzProgramBuilder& QuartzProgramBuilder::u2(double phi, Value lambda, + Value qubit) { + builder.create(loc, qubit, phi, lambda); + return *this; +} +QuartzProgramBuilder& QuartzProgramBuilder::u2(Value phi, double lambda, + Value qubit) { + builder.create(loc, qubit, phi, lambda); + return *this; +} +QuartzProgramBuilder& QuartzProgramBuilder::u2(Value phi, Value lambda, + Value qubit) { + builder.create(loc, qubit, phi, lambda); + return *this; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 06a138c6b8..dbe5a176a8 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -184,7 +184,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { return {getThetaAttrAttr(), getThetaOperand()}; } -bool RXOp::hasStaticUnitary() { return getThetaAttr().has_value(); } +bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } DenseElementsAttr RXOp::tryGetStaticMatrix() { if (!hasStaticUnitary()) { @@ -193,9 +193,9 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); const auto theta = getThetaAttr().value().convertToDouble(); - const std::complex c(cos(theta / 2), 0); - const std::complex s(0, -sin(theta / 2)); - return DenseElementsAttr::get(type, llvm::ArrayRef({c, s, s, c})); + const std::complex m0(std::cos(theta / 2), 0); + const std::complex m1(0, -std::sin(theta / 2)); + return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); } LogicalResult RXOp::verify() { @@ -205,3 +205,69 @@ LogicalResult RXOp::verify() { return emitOpError("must specify either static or dynamic theta"); return success(); } + +// U2Op + +Value U2Op::getQubit(size_t i) { + if (i != 0) { + llvm_unreachable("U2Op has only one qubit"); + } + return getQubitIn(); +} + +Value U2Op::getTarget(size_t i) { + if (i != 0) { + llvm_unreachable("U2Op has only one target qubit"); + } + return getQubitIn(); +} + +Value U2Op::getPosControl(size_t /*i*/) { + llvm_unreachable("U2Op does not have controls"); +} + +Value U2Op::getNegControl(size_t /*i*/) { + llvm_unreachable("U2Op does not have controls"); +} + +ParameterDescriptor U2Op::getParameter(size_t i) { + if (i == 0) { + return {getPhiAttrAttr(), getPhiOperand()}; + } + if (i == 1) { + return {getLambdaAttrAttr(), getLambdaOperand()}; + } + llvm_unreachable("U2Op has only two parameters"); +} + +bool U2Op::hasStaticUnitary() { + return (getParameter(0).isStatic() && getParameter(1).isStatic()); +} + +DenseElementsAttr U2Op::tryGetStaticMatrix() { + if (!hasStaticUnitary()) { + return nullptr; + } + auto* ctx = getContext(); + auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto phi = getPhiAttr().value().convertToDouble(); + const auto lambda = getLambdaAttr().value().convertToDouble(); + const std::complex I(0.0, 1.0); + const std::complex m00(1.0, 0.0); + const std::complex m01 = -std::exp(I * lambda); + const std::complex m10 = -std::exp(I * phi); + const std::complex m11 = std::exp(I * (phi + lambda)); + return DenseElementsAttr::get(type, llvm::ArrayRef({m00, m01, m10, m11})); +} + +LogicalResult U2Op::verify() { + if (getPhiAttr().has_value() && getPhiOperand()) + return emitOpError("cannot specify both static and dynamic phi"); + if (!getPhiAttr().has_value() && !getPhiOperand()) + return emitOpError("must specify either static or dynamic phi"); + if (getLambdaAttr().has_value() && getLambdaOperand()) + return emitOpError("cannot specify both static and dynamic lambda"); + if (!getLambdaAttr().has_value() && !getLambdaOperand()) + return emitOpError("must specify either static or dynamic lambda"); + return success(); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 1661d46c7a..542f654c6d 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -236,6 +236,16 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, builder.rx(theta, qubit); } +// Temporary implementation of U2Op translation +void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& phi = operation.getParameter()[0]; + const auto& lambda = operation.getTargets()[1]; + const auto& target = operation.getTargets()[0]; + const Value qubit = qubits[target]; + builder.u2(phi, lambda, qubit); +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -273,6 +283,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::RX: addRXOp(builder, *operation, qubits); break; + case qc::OpType::U2: + addU2Op(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index fa04d80755..75f07e9cb5 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -927,4 +927,36 @@ TEST_F(CompilerPipelineTest, RX) { }); } +TEST_F(CompilerPipelineTest, U2) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.u2(1.0, 0.5, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, q[0]); + }); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, q[0]); + }); + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.u2(1.0, 0.5, q[0]); + }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .optimization = fluxExpected.get(), + .quartzConversion = quartzExpected.get(), + .qirConversion = qirExpected.get(), + }); +} + } // namespace From 6c946d60398ddd54515fccc695169c8e79feeff6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:08:30 +0100 Subject: [PATCH 096/419] Fix linter errors --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 14 +++++++------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 6604605fef..c86ee90248 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -250,7 +250,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto theta = getThetaAttr().value().convertToDouble(); + const auto& theta = getThetaAttr().value().convertToDouble(); const std::complex m0(std::cos(theta / 2), 0); const std::complex m1(0, -std::sin(theta / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); @@ -336,13 +336,13 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto phi = getPhiAttr().value().convertToDouble(); - const auto lambda = getLambdaAttr().value().convertToDouble(); - const std::complex I(0.0, 1.0); + const auto& phi = getPhiAttr().value().convertToDouble(); + const auto& lambda = getLambdaAttr().value().convertToDouble(); + const std::complex i(0.0, 1.0); const std::complex m00(1.0, 0.0); - const std::complex m01 = -std::exp(I * lambda); - const std::complex m10 = -std::exp(I * phi); - const std::complex m11 = std::exp(I * (phi + lambda)); + const std::complex m01 = -std::exp(i * lambda); + const std::complex m10 = -std::exp(i * phi); + const std::complex m11 = std::exp(i * (phi + lambda)); return DenseElementsAttr::get(type, llvm::ArrayRef({m00, m01, m10, m11})); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index dbe5a176a8..6a7a766c55 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -192,7 +192,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto theta = getThetaAttr().value().convertToDouble(); + const auto& theta = getThetaAttr().value().convertToDouble(); const std::complex m0(std::cos(theta / 2), 0); const std::complex m1(0, -std::sin(theta / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); @@ -250,13 +250,13 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto phi = getPhiAttr().value().convertToDouble(); - const auto lambda = getLambdaAttr().value().convertToDouble(); - const std::complex I(0.0, 1.0); + const auto& phi = getPhiAttr().value().convertToDouble(); + const auto& lambda = getLambdaAttr().value().convertToDouble(); + const std::complex i(0.0, 1.0); const std::complex m00(1.0, 0.0); - const std::complex m01 = -std::exp(I * lambda); - const std::complex m10 = -std::exp(I * phi); - const std::complex m11 = std::exp(I * (phi + lambda)); + const std::complex m01 = -std::exp(i * lambda); + const std::complex m10 = -std::exp(i * phi); + const std::complex m11 = std::exp(i * (phi + lambda)); return DenseElementsAttr::get(type, llvm::ArrayRef({m00, m01, m10, m11})); } From 4fddef9a16f19e664a8f681d0987b1eedb1878df Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 22:22:54 +0100 Subject: [PATCH 097/419] Make builder and loc public (at least temporarily) --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 5 +++-- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 5 +++-- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index a765b3b324..20ab10885e 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -346,6 +346,9 @@ class FluxProgramBuilder { */ OwningOpRef finalize(); + OpBuilder builder; + Location loc; + private: //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers @@ -365,9 +368,7 @@ class FluxProgramBuilder { */ void updateQubitTracking(Value inputQubit, Value outputQubit); - OpBuilder builder; ModuleOp module; - Location loc; /// Track valid (unconsumed) qubit SSA values for linear type enforcement. /// Only values present in this set are valid for use in operations. diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index f49b3981e9..9da5a2d7c4 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -324,11 +324,12 @@ class QIRProgramBuilder { */ OwningOpRef finalize(); -private: OpBuilder builder; + Location loc; + +private: ModuleOp module; LLVM::LLVMFuncOp mainFunc; - Location loc; /// Entry block: constants and initialization Block* entryBlock{}; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 6bbaa1167a..7473a6cbfb 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -323,11 +323,12 @@ class QuartzProgramBuilder { */ OwningOpRef finalize(); -private: OpBuilder builder; - ModuleOp module; Location loc; +private: + ModuleOp module; + /// Track allocated qubits for automatic deallocation llvm::DenseSet allocatedQubits; From fc41941224101b3e9ba40fe6383e9cfe30a3548f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:00:32 +0100 Subject: [PATCH 098/419] Throw together simple conversion test (at least temporarily) --- .../pipeline/test_compiler_pipeline.cpp | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 75f07e9cb5..22ade75f47 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -10,6 +10,7 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" #include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" @@ -860,7 +861,7 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { } // ################################################## -// # Temporary Unit Tests +// # Temporary Unitary Operation Tests // ################################################## TEST_F(CompilerPipelineTest, X) { @@ -959,4 +960,111 @@ TEST_F(CompilerPipelineTest, U2) { }); } +// ################################################## +// # Temporary Simple Conversion Tests +// ################################################## + +class SimpleConversionTest : public testing::Test { +protected: + std::unique_ptr context; + + void SetUp() override { + // Register all dialects needed for the full compilation pipeline + DialectRegistry registry; + registry + .insert(); + + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + /** + * @brief Run canonicalization + */ + static void runCanonicalizationPasses(ModuleOp module) { + PassManager pm(module.getContext()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createCSEPass()); + if (pm.run(module).failed()) { + llvm::errs() << "Failed to run canonicalization passes\n"; + } + } + + /** + * @brief Build expected Quartz IR programmatically and run canonicalization + */ + [[nodiscard]] OwningOpRef buildQuartzIR( + const std::function& buildFunc) + const { + quartz::QuartzProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; + } + + /** + * @brief Build expected Flux IR programmatically and run canonicalization + */ + [[nodiscard]] OwningOpRef buildFluxIR( + const std::function& buildFunc) const { + flux::FluxProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; + } + + /** + * @brief Build expected QIR programmatically and run canonicalization + */ + [[nodiscard]] OwningOpRef buildQIR( + const std::function& buildFunc) const { + qir::QIRProgramBuilder builder(context.get()); + builder.initialize(); + buildFunc(builder); + auto module = builder.finalize(); + runCanonicalizationPasses(module.get()); + return module; + } + + std::string captureIR(ModuleOp module) { + std::string result; + llvm::raw_string_ostream os(result); + module.print(os); + os.flush(); + return result; + } +}; + +TEST_F(SimpleConversionTest, RXQuartzToFlux) { + auto module = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.0); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto q = b.allocQubitRegister(1, "q"); + b.rx(thetaOperand, q[0]); + }); + + PassManager pm(module.get().getContext()); + pm.addPass(createQuartzToFlux()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); + ASSERT_TRUE(pm.run(module.get()).succeeded()); + + const auto fluxResult = captureIR(module.get()); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.0); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto q = b.allocQubitRegister(1, "q"); + b.rx(thetaOperand, q[0]); + }); + + EXPECT_TRUE(verify("Quartz to Flux", fluxResult, fluxExpected.get())); +} + } // namespace From de092b5662ac4d8a2ded2b74431597c3572cd7bf Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:39:40 +0100 Subject: [PATCH 099/419] Add Quartz-to-QIR conversion test --- .../pipeline/test_compiler_pipeline.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 22ade75f47..76fe7fe5fb 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -11,6 +11,7 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" #include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" +#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" #include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" @@ -1067,4 +1068,29 @@ TEST_F(SimpleConversionTest, RXQuartzToFlux) { EXPECT_TRUE(verify("Quartz to Flux", fluxResult, fluxExpected.get())); } +TEST_F(SimpleConversionTest, RXQuartzToQIR) { + auto module = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.0); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto q = b.allocQubitRegister(1, "q"); + b.rx(thetaOperand, q[0]); + }); + + PassManager pm(module.get().getContext()); + pm.addPass(createQuartzToQIR()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); + ASSERT_TRUE(pm.run(module.get()).succeeded()); + + // const auto qirResult = captureIR(module.get()); + // const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + // auto thetaAttr = b.builder.getF64FloatAttr(1.0); + // auto thetaOperand = b.builder.create(b.loc, thetaAttr); + // auto q = b.allocQubitRegister(1); + // b.rx(thetaOperand, q[0]); + // }); + + // EXPECT_TRUE(verify("Quartz to QIR", qirResult, qirExpected.get())); +} + } // namespace From 5b93a63db2f86f862a211816c93fe78178c2f7bc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:55:03 +0100 Subject: [PATCH 100/419] Fix linter errors --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 3 +-- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- .../include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 3 +-- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 20ab10885e..fed11e228e 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -347,6 +347,7 @@ class FluxProgramBuilder { OwningOpRef finalize(); OpBuilder builder; + ModuleOp module; Location loc; private: @@ -368,8 +369,6 @@ class FluxProgramBuilder { */ void updateQubitTracking(Value inputQubit, Value outputQubit); - ModuleOp module; - /// Track valid (unconsumed) qubit SSA values for linear type enforcement. /// Only values present in this set are valid for use in operations. /// When an operation consumes a qubit and produces a new one, the old value diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 9da5a2d7c4..08444140a1 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -325,10 +325,10 @@ class QIRProgramBuilder { OwningOpRef finalize(); OpBuilder builder; + ModuleOp module; Location loc; private: - ModuleOp module; LLVM::LLVMFuncOp mainFunc; /// Entry block: constants and initialization diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 7473a6cbfb..b3363a1966 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -324,11 +324,10 @@ class QuartzProgramBuilder { OwningOpRef finalize(); OpBuilder builder; + ModuleOp module; Location loc; private: - ModuleOp module; - /// Track allocated qubits for automatic deallocation llvm::DenseSet allocatedQubits; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 76fe7fe5fb..a028272d60 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1034,7 +1034,7 @@ class SimpleConversionTest : public testing::Test { return module; } - std::string captureIR(ModuleOp module) { + static std::string captureIR(ModuleOp module) { std::string result; llvm::raw_string_ostream os(result); module.print(os); From 6feb9c3b0b9dcbabb135c837b655085e7d285b06 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 07:39:10 +0100 Subject: [PATCH 101/419] Initial support for SWAPOp --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 22 +++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 27 ++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 18 +++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 1 + .../Quartz/Builder/QuartzProgramBuilder.h | 17 +++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 22 +++++++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 22 +++++++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 30 +++++++++ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 26 ++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 12 ++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 66 +++++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 20 ++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 5 ++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 34 ++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 11 ++++ .../pipeline/test_compiler_pipeline.cpp | 32 +++++++++ 16 files changed, 365 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index fed11e228e..d7b30c7680 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -305,6 +305,28 @@ class FluxProgramBuilder { Value u2(Value phi, double lambda, Value qubit); Value u2(Value phi, Value lambda, Value qubit); + /** + * @brief Apply the SWAP gate to two qubits + * + * @details + * Consumes the input qubits and produces new output qubit SSA values. + * The inputs are validated and the tracking is updated. + * + * @param qubit0 Input qubit (must be valid/unconsumed) + * @param qubit1 Input qubit (must be valid/unconsumed) + * @return Output qubit value + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.swap(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit + * ``` + */ + ValueRange swap(Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 26efdd2ed1..14664ac909 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -381,4 +381,31 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, AttrSizedOperandSegme let hasVerifier = 1; } +def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface]> { + let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + static size_t getNumTargets() { return 2; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } + Value getQubit(size_t i); + Value getTarget(size_t i); + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + Value getInput(size_t i); + Value getOutput(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + static size_t getNumParams() { return 0; } + static ParameterDescriptor getParameter(size_t i); + static bool hasStaticUnitary() { return true; } + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "x"; } + }]; +} + #endif // FluxOPS diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 08444140a1..6a68b687fa 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -282,6 +282,24 @@ class QIRProgramBuilder { QIRProgramBuilder& u2(Value phi, double lambda, const Value qubit); QIRProgramBuilder& u2(Value phi, Value lambda, const Value qubit); + /** + * @brief Apply the SWAP gate to two qubits + * + * @param qubit0 Input qubit + * @param qubit1 Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.swap(q0, q1); + * ``` + * ```mlir + * llvm.call @__quantum__qis__swap__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) -> + * () + * ``` + */ + QIRProgramBuilder& swap(const Value qubit0, const Value qubit1); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index e6b97074c5..83ce88baa5 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -29,6 +29,7 @@ static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; static constexpr auto QIR_X = "__quantum__qis__x__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; +static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index b3363a1966..7639adb350 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -282,6 +282,23 @@ class QuartzProgramBuilder { QuartzProgramBuilder& u2(double phi, Value lambda, Value qubit); QuartzProgramBuilder& u2(Value phi, Value lambda, Value qubit); + /** + * @brief Apply the SWAP gate to two qubits + * + * @param qubit0 Input qubit + * @param qubit1 Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.swap(q0, q1); + * ``` + * ```mlir + * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + */ + QuartzProgramBuilder& swap(Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 6ee0c57174..9ba8e443e6 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -353,4 +353,26 @@ def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, AttrSizedOperandS let hasVerifier = 1; } +def SWAPOp : QuartzOp<"swap", traits = [Quartz_UnitaryOpInterface]> { + let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; + + let extraClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + static size_t getNumTargets() { return 2; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } + Value getQubit(size_t i); + Value getTarget(size_t i); + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + static size_t getNumParams() { return 0; } + static ParameterDescriptor getParameter(size_t i); + static bool hasStaticUnitary() { return true; } + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "swap"; } + }]; +} + #endif // QUARTZ_OPS diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 636ea227ed..928430b49a 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -307,6 +307,27 @@ struct ConvertFluxU2Op final : OpConversionPattern { } }; +// Temporary implementation of SWAPOp conversion +struct ConvertFluxSWAPOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::SWAPOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubits + const auto& quartzQubit0 = adaptor.getQubit0In(); + const auto& quartzQubit1 = adaptor.getQubit1In(); + + // Create quartz.swap (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1); + + // Replace the output qubit with the same quartz references + rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + + return success(); + } +}; + /** * @brief Pass implementation for Flux-to-Quartz conversion * @@ -359,6 +380,7 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index f82c38f794..1839ffc261 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -442,6 +442,35 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { } }; +// Temporary implementation of SWAPOp conversion +struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + const auto& quartzQubit0 = op.getQubit(0); + const auto& quartzQubit1 = op.getQubit(1); + + // Get the latest Flux qubit values from the state map + const auto& fluxQubit0 = getState().qubitMap[quartzQubit0]; + const auto& fluxQubit1 = getState().qubitMap[quartzQubit1]; + + // Create flux.swap operation (consumes input, produces output) + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); + + // Update mapping: the Quartz qubit now corresponds to the output qubit + getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getOperands()); + + return success(); + } +}; + /** * @brief Pass implementation for Quartz-to-Flux conversion * @@ -488,6 +517,7 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 1907519f8c..11bf520456 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -500,6 +500,31 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { } }; +// Temporary implementation of SWAPOp conversion +struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(SWAPOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + + // Create QIR function signature: (ptr, ptr) -> void + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), + {LLVM::LLVMPointerType::get(ctx), LLVM::LLVMPointerType::get(ctx)}); + + // Get or create function declaration + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_SWAP, qirSignature); + + // Replace with call to SWAP + rewriter.replaceOpWithNewOp(op, fnDecl, + adaptor.getOperands()); + return success(); + } +}; + } // namespace /** @@ -828,6 +853,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index d03c3b7f8d..7c002f3a3c 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -236,6 +236,18 @@ Value FluxProgramBuilder::u2(Value phi, Value lambda, Value qubit) { return qubitOut; } +ValueRange FluxProgramBuilder::swap(Value qubit0, Value qubit1) { + auto swapOp = builder.create(loc, qubit0, qubit1); + const auto& qubit0Out = swapOp.getQubit0Out(); + const auto& qubit1Out = swapOp.getQubit1Out(); + + // Update tracking + updateQubitTracking(qubit0, qubit0Out); + updateQubitTracking(qubit1, qubit1Out); + + return {qubit0Out, qubit1Out}; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index c86ee90248..863a60185f 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -358,6 +358,72 @@ LogicalResult U2Op::verify() { return success(); } +// SWAPOp + +Value SWAPOp::getQubit(size_t i) { + if (i == 0) { + return getQubit0In(); + } + if (i == 1) { + return getQubit1In(); + } + llvm_unreachable("SWAPOp only has two input qubits"); +} + +Value SWAPOp::getTarget(size_t i) { return getQubit(i); } + +Value SWAPOp::getPosControl(size_t /*i*/) { + llvm_unreachable("SWAPOp does not have controls"); +} + +Value SWAPOp::getNegControl(size_t /*i*/) { + llvm_unreachable("SWAPOp does not have controls"); +} + +Value SWAPOp::getInput(size_t i) { return getQubit(i); } + +Value SWAPOp::getOutput(size_t i) { + if (i == 0) { + return getQubit0Out(); + } + if (i == 1) { + return getQubit1Out(); + } + llvm_unreachable("SWAPOp only has two output qubits"); +} + +Value SWAPOp::getInputForOutput(Value output) { + if (output == getQubit0Out()) { + return getQubit0In(); + } + if (output == getQubit1Out()) { + return getQubit1In(); + } + llvm_unreachable("Given qubit is not an output of the SWAPOp"); +} + +Value SWAPOp::getOutputForInput(Value input) { + if (input == getQubit0In()) { + return getQubit0Out(); + } + if (input == getQubit1In()) { + return getQubit1Out(); + } + llvm_unreachable("Given qubit is not an input of the SWAPOp"); +} + +ParameterDescriptor SWAPOp::getParameter(size_t /*i*/) { + llvm_unreachable("SWAPOp does not have parameters"); +} + +DenseElementsAttr SWAPOp::tryGetStaticMatrix() { + auto* ctx = getContext(); + const auto& type = RankedTensorType::get({4, 4}, Float64Type::get(ctx)); + return DenseElementsAttr::get( + type, llvm::ArrayRef({1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0})); +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 691523dc26..c3a8d7101b 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -324,6 +324,26 @@ QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, double lambda, return u2(phi, lambdaConst.getResult(), qubit); } +QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, + const Value qubit1) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create x call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_SWAP, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit0, qubit1}); + + return *this; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 4648af8e05..9874036929 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -159,6 +159,11 @@ QuartzProgramBuilder& QuartzProgramBuilder::u2(Value phi, Value lambda, return *this; } +QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { + builder.create(loc, qubit0, qubit1); + return *this; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 6a7a766c55..be1b5f61c4 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -271,3 +271,37 @@ LogicalResult U2Op::verify() { return emitOpError("must specify either static or dynamic lambda"); return success(); } + +// SWAPOp + +Value SWAPOp::getQubit(size_t i) { + if (i == 0) { + return getQubit0In(); + } + if (i == 1) { + return getQubit1In(); + } + llvm_unreachable("SWAPOp only has two input qubits"); +} + +Value SWAPOp::getTarget(size_t i) { return getQubit(i); } + +Value SWAPOp::getPosControl(size_t /*i*/) { + llvm_unreachable("SWAPOp does not have controls"); +} + +Value SWAPOp::getNegControl(size_t /*i*/) { + llvm_unreachable("SWAPOp does not have controls"); +} + +ParameterDescriptor SWAPOp::getParameter(size_t /*i*/) { + llvm_unreachable("SWAPOp does not have parameters"); +} + +DenseElementsAttr SWAPOp::tryGetStaticMatrix() { + auto* ctx = getContext(); + const auto& type = RankedTensorType::get({4, 4}, Float64Type::get(ctx)); + return DenseElementsAttr::get( + type, llvm::ArrayRef({1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0})); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 542f654c6d..84aa09a837 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -246,6 +246,14 @@ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, builder.u2(phi, lambda, qubit); } +// Temporary implementation of SWAPOp translation +void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& qubit0 = qubits[operation.getTargets()[0]]; + const auto& qubit1 = qubits[operation.getTargets()[1]]; + builder.swap(qubit0, qubit1); +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -286,6 +294,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::U2: addU2Op(builder, *operation, qubits); break; + case qc::OpType::SWAP: + addSWAPOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index a028272d60..51b2462062 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -961,6 +961,38 @@ TEST_F(CompilerPipelineTest, U2) { }); } +TEST_F(CompilerPipelineTest, SWAP) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.swap(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(2, "q"); + b.swap(q[0], q[1]); + }); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(2, "q"); + b.swap(q[0], q[1]); + }); + const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.swap(q[0], q[1]); + }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .optimization = fluxExpected.get(), + .quartzConversion = quartzExpected.get(), + .qirConversion = qirExpected.get(), + }); +} + // ################################################## // # Temporary Simple Conversion Tests // ################################################## From 2dafe7c43360a3939ea6c692dd95008d5e34d8e1 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 07:58:55 +0100 Subject: [PATCH 102/419] Streamline implementation --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 26 ++-- .../Flux/Builder/FluxProgramBuilder.cpp | 14 +- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 126 +++++++----------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 4 +- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 49 +++---- .../TranslateQuantumComputationToQuartz.cpp | 11 +- 6 files changed, 91 insertions(+), 139 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index d7b30c7680..853ee1104c 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -243,17 +243,17 @@ class FluxProgramBuilder { * * @details * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and tracking is updated. + * The input is validated and the tracking is updated. * * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit value + * @return Output qubit * * @par Example: * ```c++ - * q = builder.x(q); + * q_out = builder.x(q_in); * ``` * ```mlir - * %q_out = flux.x %q : !flux.qubit -> !flux.qubit + * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit * ``` */ Value x(Value qubit); @@ -263,18 +263,18 @@ class FluxProgramBuilder { * * @details * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and tracking is updated. + * The input is validated and the tracking is updated. * * @param theta Rotation angle * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit value + * @return Output qubit * * @par Example: * ```c++ - * builder.rx(1.0, q); + * q_out = builder.rx(1.0, q_in); * ``` * ```mlir - * flux.rx(1.0) %q : !flux.qubit -> !flux.qubit + * %q_out = flux.rx(1.0) %q_in : !flux.qubit -> !flux.qubit * ``` */ Value rx(double theta, Value qubit); @@ -285,19 +285,19 @@ class FluxProgramBuilder { * * @details * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and tracking is updated. + * The input is validated and the tracking is updated. * * @param phi Rotation angle * @param lambda Rotation angle * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit value + * @return Output qubit * * @par Example: * ```c++ - * builder.u2(1.0, 0.5, q); + * q_out = builder.u2(1.0, 0.5, q_in); * ``` * ```mlir - * flux.u2(1.0, 0.5) %q : !flux.qubit -> !flux.qubit + * %q_out = flux.u2(1.0, 0.5) %q_in : !flux.qubit -> !flux.qubit * ``` */ Value u2(double phi, double lambda, Value qubit); @@ -314,7 +314,7 @@ class FluxProgramBuilder { * * @param qubit0 Input qubit (must be valid/unconsumed) * @param qubit1 Input qubit (must be valid/unconsumed) - * @return Output qubit value + * @return Output qubits * * @par Example: * ```c++ diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 7c002f3a3c..199b5e3aca 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -172,7 +172,7 @@ Value FluxProgramBuilder::reset(Value qubit) { Value FluxProgramBuilder::x(Value qubit) { auto xOp = builder.create(loc, qubit); - const auto qubitOut = xOp.getQubitOut(); + const auto& qubitOut = xOp.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); @@ -182,7 +182,7 @@ Value FluxProgramBuilder::x(Value qubit) { Value FluxProgramBuilder::rx(Value theta, Value qubit) { auto rxOp = builder.create(loc, qubit, theta); - const auto qubitOut = rxOp.getQubitOut(); + const auto& qubitOut = rxOp.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); @@ -191,7 +191,7 @@ Value FluxProgramBuilder::rx(Value theta, Value qubit) { } Value FluxProgramBuilder::rx(double theta, Value qubit) { auto rxOp = builder.create(loc, qubit, theta); - const auto qubitOut = rxOp.getQubitOut(); + const auto& qubitOut = rxOp.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); @@ -201,7 +201,7 @@ Value FluxProgramBuilder::rx(double theta, Value qubit) { Value FluxProgramBuilder::u2(double phi, double lambda, Value qubit) { auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto qubitOut = u2Op.getQubitOut(); + const auto& qubitOut = u2Op.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); @@ -210,7 +210,7 @@ Value FluxProgramBuilder::u2(double phi, double lambda, Value qubit) { } Value FluxProgramBuilder::u2(double phi, Value lambda, Value qubit) { auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto qubitOut = u2Op.getQubitOut(); + const auto& qubitOut = u2Op.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); @@ -219,7 +219,7 @@ Value FluxProgramBuilder::u2(double phi, Value lambda, Value qubit) { } Value FluxProgramBuilder::u2(Value phi, double lambda, Value qubit) { auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto qubitOut = u2Op.getQubitOut(); + const auto& qubitOut = u2Op.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); @@ -228,7 +228,7 @@ Value FluxProgramBuilder::u2(Value phi, double lambda, Value qubit) { } Value FluxProgramBuilder::u2(Value phi, Value lambda, Value qubit) { auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto qubitOut = u2Op.getQubitOut(); + const auto& qubitOut = u2Op.getQubitOut(); // Update tracking updateQubitTracking(qubit, qubitOut); diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 863a60185f..c0c757694c 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -124,18 +124,13 @@ LogicalResult MeasureOp::verify() { // XOp Value XOp::getQubit(size_t i) { - if (i != 0) { - llvm_unreachable("XOp has only one qubit"); + if (i == 0) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("XOp has one input qubit"); } -Value XOp::getTarget(size_t i) { - if (i != 0) { - llvm_unreachable("XOp has only one target qubit"); - } - return getQubitIn(); -} +Value XOp::getTarget(size_t i) { return getQubit(i); } Value XOp::getPosControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); @@ -145,32 +140,27 @@ Value XOp::getNegControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); } -Value XOp::getInput(size_t i) { - if (i != 0) { - llvm_unreachable("XOp has only one input qubit"); - } - return getQubitIn(); -} +Value XOp::getInput(size_t i) { return getQubit(i); } Value XOp::getOutput(size_t i) { - if (i != 0) { - llvm_unreachable("XOp has only one output qubit"); + if (i == 0) { + return getQubitOut(); } - return getQubitOut(); + llvm_unreachable("XOp has one output qubit"); } Value XOp::getInputForOutput(Value output) { - if (output != getQubitOut()) { - llvm_unreachable("Given output is not the XOp's output"); + if (output == getQubitOut()) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("Given qubit is not an output of the XOp"); } Value XOp::getOutputForInput(Value input) { - if (input != getQubitIn()) { - llvm_unreachable("Given input is not the XOp's input"); + if (input == getQubitIn()) { + return getQubitOut(); } - return getQubitOut(); + llvm_unreachable("Given qubit is not an input of the XOp"); } ParameterDescriptor XOp::getParameter(size_t /*i*/) { @@ -186,18 +176,13 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { // RXOp Value RXOp::getQubit(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one qubit"); + if (i == 0) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("RXOp has one input qubit"); } -Value RXOp::getTarget(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one target qubit"); - } - return getQubitIn(); -} +Value RXOp::getTarget(size_t i) { return getQubit(i); } Value RXOp::getPosControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); @@ -207,39 +192,34 @@ Value RXOp::getNegControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); } -Value RXOp::getInput(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one input qubit"); - } - return getQubitIn(); -} +Value RXOp::getInput(size_t i) { return getQubit(i); } Value RXOp::getOutput(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one output qubit"); + if (i == 0) { + return getQubitOut(); } - return getQubitOut(); + llvm_unreachable("RXOp has one output qubit"); } Value RXOp::getInputForOutput(Value output) { - if (output != getQubitOut()) { - llvm_unreachable("Given output is not the RXOp's output"); + if (output == getQubitOut()) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("Given qubit is not an output of the RXOp"); } Value RXOp::getOutputForInput(Value input) { - if (input != getQubitIn()) { - llvm_unreachable("Given input is not the RXOp's input"); + if (input == getQubitIn()) { + return getQubitOut(); } - return getQubitOut(); + llvm_unreachable("Given qubit is not an input of the RXOp"); } ParameterDescriptor RXOp::getParameter(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one parameter"); + if (i == 0) { + return {getThetaAttrAttr(), getThetaOperand()}; } - return {getThetaAttrAttr(), getThetaOperand()}; + llvm_unreachable("RXOp has one parameter"); } bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } @@ -267,18 +247,13 @@ LogicalResult RXOp::verify() { // U2Op Value U2Op::getQubit(size_t i) { - if (i != 0) { - llvm_unreachable("U2Op has only one qubit"); + if (i == 0) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("U2Op has one input qubit"); } -Value U2Op::getTarget(size_t i) { - if (i != 0) { - llvm_unreachable("U2Op has only one target qubit"); - } - return getQubitIn(); -} +Value U2Op::getTarget(size_t i) { return getQubit(i); } Value U2Op::getPosControl(size_t /*i*/) { llvm_unreachable("U2Op does not have controls"); @@ -288,32 +263,27 @@ Value U2Op::getNegControl(size_t /*i*/) { llvm_unreachable("U2Op does not have controls"); } -Value U2Op::getInput(size_t i) { - if (i != 0) { - llvm_unreachable("U2Op has only one input qubit"); - } - return getQubitIn(); -} +Value U2Op::getInput(size_t i) { return getQubit(i); } Value U2Op::getOutput(size_t i) { - if (i != 0) { - llvm_unreachable("U2Op has only one output qubit"); + if (i == 0) { + return getQubitOut(); } - return getQubitOut(); + llvm_unreachable("U2Op has one output qubit"); } Value U2Op::getInputForOutput(Value output) { - if (output != getQubitOut()) { - llvm_unreachable("Given output is not the U2Op's output"); + if (output == getQubitOut()) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("Given qubit is not an output of the U2Op"); } Value U2Op::getOutputForInput(Value input) { - if (input != getQubitIn()) { - llvm_unreachable("Given input is not the U2Op's input"); + if (input == getQubitIn()) { + return getQubitOut(); } - return getQubitOut(); + llvm_unreachable("Given qubit is not an input of the U2Op"); } ParameterDescriptor U2Op::getParameter(size_t i) { @@ -323,7 +293,7 @@ ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 1) { return {getLambdaAttrAttr(), getLambdaOperand()}; } - llvm_unreachable("U2Op has only two parameters"); + llvm_unreachable("U2Op has two parameters"); } bool U2Op::hasStaticUnitary() { @@ -367,7 +337,7 @@ Value SWAPOp::getQubit(size_t i) { if (i == 1) { return getQubit1In(); } - llvm_unreachable("SWAPOp only has two input qubits"); + llvm_unreachable("SWAPOp has two input qubits"); } Value SWAPOp::getTarget(size_t i) { return getQubit(i); } @@ -389,7 +359,7 @@ Value SWAPOp::getOutput(size_t i) { if (i == 1) { return getQubit1Out(); } - llvm_unreachable("SWAPOp only has two output qubits"); + llvm_unreachable("SWAPOp has two output qubits"); } Value SWAPOp::getInputForOutput(Value output) { diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index c3a8d7101b..0731755891 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -285,7 +285,7 @@ QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, Value lambda, // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create r call + // Create u2 call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {LLVM::LLVMPointerType::get(builder.getContext()), @@ -332,7 +332,7 @@ QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create x call + // Create swap call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {LLVM::LLVMPointerType::get(builder.getContext()), diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index be1b5f61c4..128171b6f5 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -122,18 +122,13 @@ LogicalResult MeasureOp::verify() { // XOp Value XOp::getQubit(size_t i) { - if (i != 0) { - llvm_unreachable("XOp has only one qubit"); + if (i == 0) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("XOp has one input qubit"); } -Value XOp::getTarget(size_t i) { - if (i != 0) { - llvm_unreachable("XOp has only one target qubit"); - } - return getQubitIn(); -} +Value XOp::getTarget(size_t i) { return getQubit(i); } Value XOp::getPosControl(size_t /*i*/) { llvm_unreachable("XOp does not have controls"); @@ -156,18 +151,13 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { // RXOp Value RXOp::getQubit(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one qubit"); + if (i == 0) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("RXOp has one input qubit"); } -Value RXOp::getTarget(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one target qubit"); - } - return getQubitIn(); -} +Value RXOp::getTarget(size_t i) { return getQubit(i); } Value RXOp::getPosControl(size_t /*i*/) { llvm_unreachable("RXOp does not have controls"); @@ -178,10 +168,10 @@ Value RXOp::getNegControl(size_t /*i*/) { } ParameterDescriptor RXOp::getParameter(size_t i) { - if (i != 0) { - llvm_unreachable("RXOp has only one parameter"); + if (i == 0) { + return {getThetaAttrAttr(), getThetaOperand()}; } - return {getThetaAttrAttr(), getThetaOperand()}; + llvm_unreachable("RXOp has one parameter"); } bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } @@ -209,18 +199,13 @@ LogicalResult RXOp::verify() { // U2Op Value U2Op::getQubit(size_t i) { - if (i != 0) { - llvm_unreachable("U2Op has only one qubit"); + if (i == 0) { + return getQubitIn(); } - return getQubitIn(); + llvm_unreachable("SWAPOp has one input qubit"); } -Value U2Op::getTarget(size_t i) { - if (i != 0) { - llvm_unreachable("U2Op has only one target qubit"); - } - return getQubitIn(); -} +Value U2Op::getTarget(size_t i) { return getQubit(i); } Value U2Op::getPosControl(size_t /*i*/) { llvm_unreachable("U2Op does not have controls"); @@ -237,7 +222,7 @@ ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 1) { return {getLambdaAttrAttr(), getLambdaOperand()}; } - llvm_unreachable("U2Op has only two parameters"); + llvm_unreachable("U2Op has two parameters"); } bool U2Op::hasStaticUnitary() { @@ -281,7 +266,7 @@ Value SWAPOp::getQubit(size_t i) { if (i == 1) { return getQubit1In(); } - llvm_unreachable("SWAPOp only has two input qubits"); + llvm_unreachable("SWAPOp has two input qubits"); } Value SWAPOp::getTarget(size_t i) { return getQubit(i); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 84aa09a837..c49831d7f6 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -222,8 +222,7 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, // Temporary implementation of XOp translation void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { - const auto& target = operation.getTargets()[0]; - const Value qubit = qubits[target]; + const auto& qubit = qubits[operation.getTargets()[0]]; builder.x(qubit); } @@ -231,8 +230,7 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { const auto& theta = operation.getParameter()[0]; - const auto& target = operation.getTargets()[0]; - const Value qubit = qubits[target]; + const auto& qubit = qubits[operation.getTargets()[0]]; builder.rx(theta, qubit); } @@ -240,9 +238,8 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { const auto& phi = operation.getParameter()[0]; - const auto& lambda = operation.getTargets()[1]; - const auto& target = operation.getTargets()[0]; - const Value qubit = qubits[target]; + const auto& lambda = operation.getParameter()[1]; + const auto& qubit = qubits[operation.getTargets()[0]]; builder.u2(phi, lambda, qubit); } From 925f4a8dfe4bbc15a4a84844961361a48afa212b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 08:12:21 +0100 Subject: [PATCH 103/419] Fix linter errors --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 3 ++- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 853ee1104c..893afcec10 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace mlir::flux { @@ -325,7 +326,7 @@ class FluxProgramBuilder { * !flux.qubit, !flux.qubit * ``` */ - ValueRange swap(Value qubit0, Value qubit1); + std::vector swap(Value qubit0, Value qubit1); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 199b5e3aca..daec36ce84 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace mlir::flux { @@ -236,7 +237,7 @@ Value FluxProgramBuilder::u2(Value phi, Value lambda, Value qubit) { return qubitOut; } -ValueRange FluxProgramBuilder::swap(Value qubit0, Value qubit1) { +std::vector FluxProgramBuilder::swap(Value qubit0, Value qubit1) { auto swapOp = builder.create(loc, qubit0, qubit1); const auto& qubit0Out = swapOp.getQubit0Out(); const auto& qubit1Out = swapOp.getQubit1Out(); From d7522cd2a46817d2eec3fc96006f94266c6dea83 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 08:58:09 +0100 Subject: [PATCH 104/419] Initial definition of TargetArityTrait for Flux dialect --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 8 +++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 60 +++++++++++-------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index f7cf067486..f9c24cab1b 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -49,6 +49,14 @@ namespace mlir::flux { +template +class OneTargetTrait + : public mlir::OpTrait::TraitBase {}; + +template +class TwoTargetTrait + : public mlir::OpTrait::TraitBase {}; + struct ParameterDescriptor { mlir::FloatAttr valueAttr; mlir::Value valueOperand; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 14664ac909..f1b0bb9ec7 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -238,20 +238,47 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { } //===----------------------------------------------------------------------===// -// Unitary Operations +// Traits //===----------------------------------------------------------------------===// -def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { - let arguments = (ins QubitType:$qubit_in); - let results = (outs QubitType:$qubit_out); - let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; +class OneTargetTrait : NativeOpTrait<"OneTargetTrait"> { + let cppNamespace = "::mlir::flux"; +} - let extraClassDeclaration = [{ +def OneTarget : OneTargetTrait { + let extraConcreteClassDeclaration = [{ size_t getNumQubits() { return getNumTargets() + getNumControls(); } static size_t getNumTargets() { return 1; } size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } static size_t getNumPosControls() { return 0; } static size_t getNumNegControls() { return 0; } + }]; +} + +class TwoTargetTrait : NativeOpTrait<"TwoTargetTrait"> { + let cppNamespace = "::mlir::flux"; +} + +def TwoTarget : TwoTargetTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumQubits() { return getNumTargets() + getNumControls(); } + static size_t getNumTargets() { return 2; } + size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } + }]; +} + +//===----------------------------------------------------------------------===// +// Unitary Operations +//===----------------------------------------------------------------------===// + +def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface, OneTarget]> { + let arguments = (ins QubitType:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ Value getQubit(size_t i); Value getTarget(size_t i); static Value getPosControl(size_t i); @@ -268,7 +295,7 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface]> { }]; } -def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { +def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta_attr, Optional:$theta_operand); @@ -276,11 +303,6 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 1; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); static Value getPosControl(size_t i); @@ -312,7 +334,7 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface]> { let hasVerifier = 1; } -def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, AttrSizedOperandSegments]> { +def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi_attr, Optional:$phi_operand, @@ -322,11 +344,6 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, AttrSizedOperandSegme let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 1; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); static Value getPosControl(size_t i); @@ -381,17 +398,12 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, AttrSizedOperandSegme let hasVerifier = 1; } -def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface]> { +def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface, TwoTarget]> { let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 2; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } Value getQubit(size_t i); Value getTarget(size_t i); static Value getPosControl(size_t i); From 8a8020f66373ad5370175a1641fb4a25899f792c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:58:50 +0100 Subject: [PATCH 105/419] Move more logic to trait definitions --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 152 +++++++++++----- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 172 ------------------- 2 files changed, 110 insertions(+), 214 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index f1b0bb9ec7..3a7d6d2c83 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -247,11 +247,55 @@ class OneTargetTrait : NativeOpTrait<"OneTargetTrait"> { def OneTarget : OneTargetTrait { let extraConcreteClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 1; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } + size_t getNumQubits() { return 1; } + size_t getNumTargets() { return 1; } + size_t getNumControls() { return 0; } + size_t getNumPosControls() { return 0; } + size_t getNumNegControls() { return 0; } + + Value getQubit(size_t i) { + if (i == 0) { + return getQubitIn(); + } + llvm_unreachable("Operation has one input qubit"); + } + + Value getTarget(size_t i) { + return getQubit(i); + } + + Value getPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getInput(size_t i) { + return getQubit(i); + } + + Value getOutput(size_t i) { + if (i == 0) { + return getQubitOut(); + } + llvm_unreachable("Operation has one output qubit"); + } + + Value getInputForOutput(Value output) { + if (output == getQubitOut()) { + return getQubitIn(); + } + llvm_unreachable("Given qubit is not an output of the operation"); + } + + Value getOutputForInput(Value input) { + if (input == getQubitIn()) { + return getQubitOut(); + } + llvm_unreachable("Given qubit is not an input of the operation"); + } }]; } @@ -261,11 +305,67 @@ class TwoTargetTrait : NativeOpTrait<"TwoTargetTrait"> { def TwoTarget : TwoTargetTrait { let extraConcreteClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 2; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } + size_t getNumQubits() { return 2; } + size_t getNumTargets() { return 2; } + size_t getNumControls() { return 0; } + size_t getNumPosControls() { return 0; } + size_t getNumNegControls() { return 0; } + + Value getQubit(size_t i) { + if (i == 0) { + return getQubit0In(); + } + if (i == 1) { + return getQubit1In(); + } + llvm_unreachable("Operation has two input qubits"); + } + + Value getTarget(size_t i) { + return getQubit(i); + } + + Value getPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getInput(size_t i) { + return getQubit(i); + } + + Value getOutput(size_t i) { + if (i == 0) { + return getQubit0Out(); + } + if (i == 1) { + return getQubit1Out(); + } + llvm_unreachable("Operation has two output qubits"); + } + + Value getInputForOutput(Value output) { + if (output == getQubit0Out()) { + return getQubit0In(); + } + if (output == getQubit1Out()) { + return getQubit1In(); + } + llvm_unreachable("Given qubit is not an output of the operation"); + } + + Value getOutputForInput(Value input) { + if (input == getQubit0In()) { + return getQubit0Out(); + } + if (input == getQubit1In()) { + return getQubit1Out(); + } + llvm_unreachable("Given qubit is not an input of the operation"); + } }]; } @@ -279,14 +379,6 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface, OneTarget]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); - Value getInput(size_t i); - Value getOutput(size_t i); - Value getInputForOutput(Value output); - Value getOutputForInput(Value input); static size_t getNumParams() { return 0; } static ParameterDescriptor getParameter(size_t i); static bool hasStaticUnitary() { return true; } @@ -303,14 +395,6 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget]> { let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); - Value getInput(size_t i); - Value getOutput(size_t i); - Value getInputForOutput(Value output); - Value getOutputForInput(Value input); static size_t getNumParams() { return 1; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); @@ -344,14 +428,6 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, AttrSizedO let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); - Value getInput(size_t i); - Value getOutput(size_t i); - Value getInputForOutput(Value output); - Value getOutputForInput(Value input); static size_t getNumParams() { return 2; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); @@ -404,14 +480,6 @@ def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface, TwoTarget]> { let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); - Value getInput(size_t i); - Value getOutput(size_t i); - Value getInputForOutput(Value output); - Value getOutputForInput(Value input); static size_t getNumParams() { return 0; } static ParameterDescriptor getParameter(size_t i); static bool hasStaticUnitary() { return true; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index c0c757694c..d23745744d 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -123,46 +123,6 @@ LogicalResult MeasureOp::verify() { // XOp -Value XOp::getQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm_unreachable("XOp has one input qubit"); -} - -Value XOp::getTarget(size_t i) { return getQubit(i); } - -Value XOp::getPosControl(size_t /*i*/) { - llvm_unreachable("XOp does not have controls"); -} - -Value XOp::getNegControl(size_t /*i*/) { - llvm_unreachable("XOp does not have controls"); -} - -Value XOp::getInput(size_t i) { return getQubit(i); } - -Value XOp::getOutput(size_t i) { - if (i == 0) { - return getQubitOut(); - } - llvm_unreachable("XOp has one output qubit"); -} - -Value XOp::getInputForOutput(Value output) { - if (output == getQubitOut()) { - return getQubitIn(); - } - llvm_unreachable("Given qubit is not an output of the XOp"); -} - -Value XOp::getOutputForInput(Value input) { - if (input == getQubitIn()) { - return getQubitOut(); - } - llvm_unreachable("Given qubit is not an input of the XOp"); -} - ParameterDescriptor XOp::getParameter(size_t /*i*/) { llvm_unreachable("XOp does not have parameters"); } @@ -175,46 +135,6 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { // RXOp -Value RXOp::getQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm_unreachable("RXOp has one input qubit"); -} - -Value RXOp::getTarget(size_t i) { return getQubit(i); } - -Value RXOp::getPosControl(size_t /*i*/) { - llvm_unreachable("RXOp does not have controls"); -} - -Value RXOp::getNegControl(size_t /*i*/) { - llvm_unreachable("RXOp does not have controls"); -} - -Value RXOp::getInput(size_t i) { return getQubit(i); } - -Value RXOp::getOutput(size_t i) { - if (i == 0) { - return getQubitOut(); - } - llvm_unreachable("RXOp has one output qubit"); -} - -Value RXOp::getInputForOutput(Value output) { - if (output == getQubitOut()) { - return getQubitIn(); - } - llvm_unreachable("Given qubit is not an output of the RXOp"); -} - -Value RXOp::getOutputForInput(Value input) { - if (input == getQubitIn()) { - return getQubitOut(); - } - llvm_unreachable("Given qubit is not an input of the RXOp"); -} - ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { return {getThetaAttrAttr(), getThetaOperand()}; @@ -246,46 +166,6 @@ LogicalResult RXOp::verify() { // U2Op -Value U2Op::getQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm_unreachable("U2Op has one input qubit"); -} - -Value U2Op::getTarget(size_t i) { return getQubit(i); } - -Value U2Op::getPosControl(size_t /*i*/) { - llvm_unreachable("U2Op does not have controls"); -} - -Value U2Op::getNegControl(size_t /*i*/) { - llvm_unreachable("U2Op does not have controls"); -} - -Value U2Op::getInput(size_t i) { return getQubit(i); } - -Value U2Op::getOutput(size_t i) { - if (i == 0) { - return getQubitOut(); - } - llvm_unreachable("U2Op has one output qubit"); -} - -Value U2Op::getInputForOutput(Value output) { - if (output == getQubitOut()) { - return getQubitIn(); - } - llvm_unreachable("Given qubit is not an output of the U2Op"); -} - -Value U2Op::getOutputForInput(Value input) { - if (input == getQubitIn()) { - return getQubitOut(); - } - llvm_unreachable("Given qubit is not an input of the U2Op"); -} - ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 0) { return {getPhiAttrAttr(), getPhiOperand()}; @@ -330,58 +210,6 @@ LogicalResult U2Op::verify() { // SWAPOp -Value SWAPOp::getQubit(size_t i) { - if (i == 0) { - return getQubit0In(); - } - if (i == 1) { - return getQubit1In(); - } - llvm_unreachable("SWAPOp has two input qubits"); -} - -Value SWAPOp::getTarget(size_t i) { return getQubit(i); } - -Value SWAPOp::getPosControl(size_t /*i*/) { - llvm_unreachable("SWAPOp does not have controls"); -} - -Value SWAPOp::getNegControl(size_t /*i*/) { - llvm_unreachable("SWAPOp does not have controls"); -} - -Value SWAPOp::getInput(size_t i) { return getQubit(i); } - -Value SWAPOp::getOutput(size_t i) { - if (i == 0) { - return getQubit0Out(); - } - if (i == 1) { - return getQubit1Out(); - } - llvm_unreachable("SWAPOp has two output qubits"); -} - -Value SWAPOp::getInputForOutput(Value output) { - if (output == getQubit0Out()) { - return getQubit0In(); - } - if (output == getQubit1Out()) { - return getQubit1In(); - } - llvm_unreachable("Given qubit is not an output of the SWAPOp"); -} - -Value SWAPOp::getOutputForInput(Value input) { - if (input == getQubit0In()) { - return getQubit0Out(); - } - if (input == getQubit1In()) { - return getQubit1Out(); - } - llvm_unreachable("Given qubit is not an input of the SWAPOp"); -} - ParameterDescriptor SWAPOp::getParameter(size_t /*i*/) { llvm_unreachable("SWAPOp does not have parameters"); } From efd603657b17491fe2344b64e289acc60a40c742 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:03:00 +0100 Subject: [PATCH 106/419] Use single TargetArityTrait class (as the previous commit message erroneously suggested) --- mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h | 8 ++------ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 10 +++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index f9c24cab1b..09cc4c7b9b 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -50,12 +50,8 @@ namespace mlir::flux { template -class OneTargetTrait - : public mlir::OpTrait::TraitBase {}; - -template -class TwoTargetTrait - : public mlir::OpTrait::TraitBase {}; +class TargetArityTrait + : public mlir::OpTrait::TraitBase {}; struct ParameterDescriptor { mlir::FloatAttr valueAttr; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 3a7d6d2c83..884a973b84 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -241,11 +241,11 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { // Traits //===----------------------------------------------------------------------===// -class OneTargetTrait : NativeOpTrait<"OneTargetTrait"> { +class TargetArityTrait : NativeOpTrait<"TargetArityTrait"> { let cppNamespace = "::mlir::flux"; } -def OneTarget : OneTargetTrait { +def OneTarget : TargetArityTrait { let extraConcreteClassDeclaration = [{ size_t getNumQubits() { return 1; } size_t getNumTargets() { return 1; } @@ -299,11 +299,7 @@ def OneTarget : OneTargetTrait { }]; } -class TwoTargetTrait : NativeOpTrait<"TwoTargetTrait"> { - let cppNamespace = "::mlir::flux"; -} - -def TwoTarget : TwoTargetTrait { +def TwoTarget : TargetArityTrait { let extraConcreteClassDeclaration = [{ size_t getNumQubits() { return 2; } size_t getNumTargets() { return 2; } From ba542a9ed5af90a3aada9281a96e4813266836f2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:10:55 +0100 Subject: [PATCH 107/419] Define TargetArityTrait also for Quartz dialect --- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 4 + .../mlir/Dialect/Quartz/IR/QuartzOps.td | 113 +++++++++++------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 71 ----------- 3 files changed, 77 insertions(+), 111 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 739d843a36..079cf2bfce 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -49,6 +49,10 @@ namespace mlir::quartz { +template +class TargetArityTrait + : public mlir::OpTrait::TraitBase {}; + struct ParameterDescriptor { mlir::FloatAttr valueAttr; mlir::Value valueOperand; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 9ba8e443e6..c05181614e 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -224,24 +224,84 @@ def ResetOp : QuartzOp<"reset"> { let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; } +//===----------------------------------------------------------------------===// +// Traits +//===----------------------------------------------------------------------===// + +class TargetArityTrait : NativeOpTrait<"TargetArityTrait"> { + let cppNamespace = "::mlir::quartz"; +} + +def OneTarget : TargetArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumQubits() { return 1; } + size_t getNumTargets() { return 1; } + size_t getNumControls() { return 0; } + size_t getNumPosControls() { return 0; } + size_t getNumNegControls() { return 0; } + + Value getQubit(size_t i) { + if (i == 0) { + return getQubitIn(); + } + llvm_unreachable("Operation has one input qubit"); + } + + Value getTarget(size_t i) { + return getQubit(i); + } + + Value getPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + }]; +} + +def TwoTarget : TargetArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumQubits() { return 2; } + size_t getNumTargets() { return 2; } + size_t getNumControls() { return 0; } + size_t getNumPosControls() { return 0; } + size_t getNumNegControls() { return 0; } + + Value getQubit(size_t i) { + if (i == 0) { + return getQubit0In(); + } + if (i == 1) { + return getQubit1In(); + } + llvm_unreachable("Operation has two input qubits"); + } + + Value getTarget(size_t i) { + return getQubit(i); + } + + Value getPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + }]; +} + //===----------------------------------------------------------------------===// // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { +def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface, OneTarget]> { let arguments = (ins QubitType:$qubit_in); let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 1; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); static size_t getNumParams() { return 0; } static ParameterDescriptor getParameter(size_t i); static bool hasStaticUnitary() { return true; } @@ -250,22 +310,13 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface]> { }]; } -def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { +def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface, OneTarget]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta_attr, Optional:$theta_operand); let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 1; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); static size_t getNumParams() { return 1; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); @@ -289,7 +340,7 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface]> { let hasVerifier = 1; } -def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, AttrSizedOperandSegments]> { +def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi_attr, Optional:$phi_operand, @@ -298,15 +349,6 @@ def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, AttrSizedOperandS let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 1; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); static size_t getNumParams() { return 2; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); @@ -353,20 +395,11 @@ def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, AttrSizedOperandS let hasVerifier = 1; } -def SWAPOp : QuartzOp<"swap", traits = [Quartz_UnitaryOpInterface]> { +def SWAPOp : QuartzOp<"swap", traits = [Quartz_UnitaryOpInterface, TwoTarget]> { let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ - size_t getNumQubits() { return getNumTargets() + getNumControls(); } - static size_t getNumTargets() { return 2; } - size_t getNumControls() { return getNumPosControls() + getNumNegControls(); } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } - Value getQubit(size_t i); - Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); static size_t getNumParams() { return 0; } static ParameterDescriptor getParameter(size_t i); static bool hasStaticUnitary() { return true; } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 128171b6f5..6527edd15f 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -121,23 +121,6 @@ LogicalResult MeasureOp::verify() { // XOp -Value XOp::getQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm_unreachable("XOp has one input qubit"); -} - -Value XOp::getTarget(size_t i) { return getQubit(i); } - -Value XOp::getPosControl(size_t /*i*/) { - llvm_unreachable("XOp does not have controls"); -} - -Value XOp::getNegControl(size_t /*i*/) { - llvm_unreachable("XOp does not have controls"); -} - ParameterDescriptor XOp::getParameter(size_t /*i*/) { llvm_unreachable("XOp does not have parameters"); } @@ -150,23 +133,6 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { // RXOp -Value RXOp::getQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm_unreachable("RXOp has one input qubit"); -} - -Value RXOp::getTarget(size_t i) { return getQubit(i); } - -Value RXOp::getPosControl(size_t /*i*/) { - llvm_unreachable("RXOp does not have controls"); -} - -Value RXOp::getNegControl(size_t /*i*/) { - llvm_unreachable("RXOp does not have controls"); -} - ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { return {getThetaAttrAttr(), getThetaOperand()}; @@ -198,23 +164,6 @@ LogicalResult RXOp::verify() { // U2Op -Value U2Op::getQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm_unreachable("SWAPOp has one input qubit"); -} - -Value U2Op::getTarget(size_t i) { return getQubit(i); } - -Value U2Op::getPosControl(size_t /*i*/) { - llvm_unreachable("U2Op does not have controls"); -} - -Value U2Op::getNegControl(size_t /*i*/) { - llvm_unreachable("U2Op does not have controls"); -} - ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 0) { return {getPhiAttrAttr(), getPhiOperand()}; @@ -259,26 +208,6 @@ LogicalResult U2Op::verify() { // SWAPOp -Value SWAPOp::getQubit(size_t i) { - if (i == 0) { - return getQubit0In(); - } - if (i == 1) { - return getQubit1In(); - } - llvm_unreachable("SWAPOp has two input qubits"); -} - -Value SWAPOp::getTarget(size_t i) { return getQubit(i); } - -Value SWAPOp::getPosControl(size_t /*i*/) { - llvm_unreachable("SWAPOp does not have controls"); -} - -Value SWAPOp::getNegControl(size_t /*i*/) { - llvm_unreachable("SWAPOp does not have controls"); -} - ParameterDescriptor SWAPOp::getParameter(size_t /*i*/) { llvm_unreachable("SWAPOp does not have parameters"); } From 2f8fcf9a8dffb7f30cb676c104a31ebb7e090258 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:19:41 +0100 Subject: [PATCH 108/419] Define simple ParameterArityTrait --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 4 ++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 44 ++++++++++++++----- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 4 ++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 44 ++++++++++++++----- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 8 ---- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 8 ---- 6 files changed, 72 insertions(+), 40 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 09cc4c7b9b..3929310b3d 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -53,6 +53,10 @@ template class TargetArityTrait : public mlir::OpTrait::TraitBase {}; +template +class ParameterArityTrait + : public mlir::OpTrait::TraitBase {}; + struct ParameterDescriptor { mlir::FloatAttr valueAttr; mlir::Value valueOperand; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 884a973b84..f7f18799cc 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -365,25 +365,50 @@ def TwoTarget : TargetArityTrait { }]; } +class ParameterArityTrait : NativeOpTrait<"ParameterArityTrait"> { + let cppNamespace = "::mlir::flux"; +} + +def ZeroParameter : ParameterArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumParams() { return 0; } + + ParameterDescriptor getParameter(size_t i) { + llvm_unreachable("Operation does not have parameters"); + } + + bool hasStaticUnitary() { return true; } + }]; +} + +def OneParameter : ParameterArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumParams() { return 1; } + }]; +} + +def TwoParameter : ParameterArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumParams() { return 2; } + }]; +} + //===----------------------------------------------------------------------===// // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface, OneTarget]> { +def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface, OneTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 0; } - static ParameterDescriptor getParameter(size_t i); - static bool hasStaticUnitary() { return true; } DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; } -def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget]> { +def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget, OneParameter]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta_attr, Optional:$theta_operand); @@ -391,7 +416,6 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget]> { let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 1; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); @@ -414,7 +438,7 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget]> { let hasVerifier = 1; } -def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, AttrSizedOperandSegments]> { +def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi_attr, Optional:$phi_operand, @@ -424,7 +448,6 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, AttrSizedO let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 2; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); @@ -470,15 +493,12 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, AttrSizedO let hasVerifier = 1; } -def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface, TwoTarget]> { +def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface, TwoTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 0; } - static ParameterDescriptor getParameter(size_t i); - static bool hasStaticUnitary() { return true; } DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 079cf2bfce..59a1fd0838 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -53,6 +53,10 @@ template class TargetArityTrait : public mlir::OpTrait::TraitBase {}; +template +class ParameterArityTrait + : public mlir::OpTrait::TraitBase {}; + struct ParameterDescriptor { mlir::FloatAttr valueAttr; mlir::Value valueOperand; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index c05181614e..3358277d91 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -293,31 +293,55 @@ def TwoTarget : TargetArityTrait { }]; } +class ParameterArityTrait : NativeOpTrait<"ParameterArityTrait"> { + let cppNamespace = "::mlir::quartz"; +} + +def ZeroParameter : ParameterArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumParams() { return 0; } + + ParameterDescriptor getParameter(size_t i) { + llvm_unreachable("Operation does not have parameters"); + } + + bool hasStaticUnitary() { return true; } + }]; +} + +def OneParameter : ParameterArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumParams() { return 1; } + }]; +} + +def TwoParameter : ParameterArityTrait { + let extraConcreteClassDeclaration = [{ + size_t getNumParams() { return 2; } + }]; +} + //===----------------------------------------------------------------------===// // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface, OneTarget]> { +def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface, OneTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit_in); let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 0; } - static ParameterDescriptor getParameter(size_t i); - static bool hasStaticUnitary() { return true; } DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; } -def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface, OneTarget]> { +def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface, OneTarget, OneParameter]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta_attr, Optional:$theta_operand); let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 1; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); @@ -340,7 +364,7 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface, OneTarget]> { let hasVerifier = 1; } -def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, AttrSizedOperandSegments]> { +def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi_attr, Optional:$phi_operand, @@ -349,7 +373,6 @@ def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, AttrSi let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 2; } ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); @@ -395,14 +418,11 @@ def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, AttrSi let hasVerifier = 1; } -def SWAPOp : QuartzOp<"swap", traits = [Quartz_UnitaryOpInterface, TwoTarget]> { +def SWAPOp : QuartzOp<"swap", traits = [Quartz_UnitaryOpInterface, TwoTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ - static size_t getNumParams() { return 0; } - static ParameterDescriptor getParameter(size_t i); - static bool hasStaticUnitary() { return true; } DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "swap"; } }]; diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index d23745744d..c5cd633770 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -123,10 +123,6 @@ LogicalResult MeasureOp::verify() { // XOp -ParameterDescriptor XOp::getParameter(size_t /*i*/) { - llvm_unreachable("XOp does not have parameters"); -} - DenseElementsAttr XOp::tryGetStaticMatrix() { auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); @@ -210,10 +206,6 @@ LogicalResult U2Op::verify() { // SWAPOp -ParameterDescriptor SWAPOp::getParameter(size_t /*i*/) { - llvm_unreachable("SWAPOp does not have parameters"); -} - DenseElementsAttr SWAPOp::tryGetStaticMatrix() { auto* ctx = getContext(); const auto& type = RankedTensorType::get({4, 4}, Float64Type::get(ctx)); diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 6527edd15f..c71b746ef3 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -121,10 +121,6 @@ LogicalResult MeasureOp::verify() { // XOp -ParameterDescriptor XOp::getParameter(size_t /*i*/) { - llvm_unreachable("XOp does not have parameters"); -} - DenseElementsAttr XOp::tryGetStaticMatrix() { auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); @@ -208,10 +204,6 @@ LogicalResult U2Op::verify() { // SWAPOp -ParameterDescriptor SWAPOp::getParameter(size_t /*i*/) { - llvm_unreachable("SWAPOp does not have parameters"); -} - DenseElementsAttr SWAPOp::tryGetStaticMatrix() { auto* ctx = getContext(); const auto& type = RankedTensorType::get({4, 4}, Float64Type::get(ctx)); From f60b3ccc84c5143c94e7f01b5d459912dff4eb99 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:20:24 +0100 Subject: [PATCH 109/419] Rename dialect name from interface name --- mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td | 2 +- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 8 ++++---- mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td | 2 +- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index b17ea7ba57..7445e93226 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -15,7 +15,7 @@ include "mlir/IR/OpBase.td" // UnitaryOpInterface //===----------------------------------------------------------------------===// -def Flux_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { +def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let description = [{ This interface provides a unified API for all operations that apply or produce a unitary transformation in the Flux dialect. This includes base diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index f7f18799cc..afb2210832 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -397,7 +397,7 @@ def TwoParameter : ParameterArityTrait { // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface, OneTarget, ZeroParameter]> { +def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -408,7 +408,7 @@ def XOp : FluxOp<"x", traits = [Flux_UnitaryOpInterface, OneTarget, ZeroParamete }]; } -def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget, OneParameter]> { +def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta_attr, Optional:$theta_operand); @@ -438,7 +438,7 @@ def RXOp : FluxOp<"rx", traits = [Flux_UnitaryOpInterface, OneTarget, OneParamet let hasVerifier = 1; } -def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { +def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi_attr, Optional:$phi_operand, @@ -493,7 +493,7 @@ def U2Op : FluxOp<"u2", traits = [Flux_UnitaryOpInterface, OneTarget, TwoParamet let hasVerifier = 1; } -def SWAPOp : FluxOp<"swap", traits = [Flux_UnitaryOpInterface, TwoTarget, ZeroParameter]> { +def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 6b45481b31..1c95ac8fd2 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -15,7 +15,7 @@ include "mlir/IR/OpBase.td" // UnitaryOpInterface //===----------------------------------------------------------------------===// -def Quartz_UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { +def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let description = [{ This interface provides a unified API for all operations that apply or produce a unitary transformation. This includes base gates, user-defined diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 3358277d91..998456f5a5 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -325,7 +325,7 @@ def TwoParameter : ParameterArityTrait { // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface, OneTarget, ZeroParameter]> { +def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit_in); let assemblyFormat = "$qubit_in attr-dict"; @@ -335,7 +335,7 @@ def XOp : QuartzOp<"x", traits = [Quartz_UnitaryOpInterface, OneTarget, ZeroPara }]; } -def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface, OneTarget, OneParameter]> { +def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta_attr, Optional:$theta_operand); @@ -364,7 +364,7 @@ def RXOp : QuartzOp<"rx", traits = [Quartz_UnitaryOpInterface, OneTarget, OnePar let hasVerifier = 1; } -def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { +def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi_attr, Optional:$phi_operand, @@ -418,7 +418,7 @@ def U2Op : QuartzOp<"u2", traits = [Quartz_UnitaryOpInterface, OneTarget, TwoPar let hasVerifier = 1; } -def SWAPOp : QuartzOp<"swap", traits = [Quartz_UnitaryOpInterface, TwoTarget, ZeroParameter]> { +def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; From 4a73fa41c5cfd90d7f92415c1798963030e7e00f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:26:07 +0100 Subject: [PATCH 110/419] Rename qubit_in to qubit for Quartz dialect --- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 70 +++++++++---------- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 5 +- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 998456f5a5..e024bc00ab 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -242,7 +242,7 @@ def OneTarget : TargetArityTrait { Value getQubit(size_t i) { if (i == 0) { - return getQubitIn(); + return getQubit(); } llvm_unreachable("Operation has one input qubit"); } @@ -271,10 +271,10 @@ def TwoTarget : TargetArityTrait { Value getQubit(size_t i) { if (i == 0) { - return getQubit0In(); + return getQubit0(); } if (i == 1) { - return getQubit1In(); + return getQubit1(); } llvm_unreachable("Operation has two input qubits"); } @@ -326,8 +326,8 @@ def TwoParameter : ParameterArityTrait { //===----------------------------------------------------------------------===// def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { - let arguments = (ins QubitType:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let arguments = (ins QubitType:$qubit); + let assemblyFormat = "$qubit attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -336,10 +336,10 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> } def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { - let arguments = (ins QubitType:$qubit_in, + let arguments = (ins QubitType:$qubit, OptionalAttr:$theta_attr, Optional:$theta_operand); - let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); @@ -349,15 +349,15 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "double":$theta_double), [{ + OpBuilder<(ins "Value":$qubit, "double":$theta_double), [{ auto theta_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), theta_double); - build($_builder, $_state, qubit_in, theta_attr, nullptr); + build($_builder, $_state, qubit, theta_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$theta_attr), [{ - build($_builder, $_state, qubit_in, theta_attr, nullptr); + OpBuilder<(ins "Value":$qubit, "FloatAttr":$theta_attr), [{ + build($_builder, $_state, qubit, theta_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$theta_operand), [{ - build($_builder, $_state, qubit_in, nullptr, theta_operand); + OpBuilder<(ins "Value":$qubit, "Value":$theta_operand), [{ + build($_builder, $_state, qubit, nullptr, theta_operand); }]>, ]; @@ -365,12 +365,12 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] } def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { - let arguments = (ins QubitType:$qubit_in, + let arguments = (ins QubitType:$qubit, OptionalAttr:$phi_attr, Optional:$phi_operand, OptionalAttr:$lambda_attr, Optional:$lambda_operand); - let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); @@ -380,38 +380,38 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "double":$lambda_double), [{ + OpBuilder<(ins "Value":$qubit, "double":$phi_double, "double":$lambda_double), [{ auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "FloatAttr":$lambda_attr), [{ + OpBuilder<(ins "Value":$qubit, "double":$phi_double, "FloatAttr":$lambda_attr), [{ auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "Value":$lambda_operand), [{ + OpBuilder<(ins "Value":$qubit, "double":$phi_double, "Value":$lambda_operand), [{ auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - build($_builder, $_state, qubit_in, phi_attr, nullptr, nullptr, lambda_operand); + build($_builder, $_state, qubit, phi_attr, nullptr, nullptr, lambda_operand); }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "double":$lambda_double), [{ + OpBuilder<(ins "Value":$qubit, "FloatAttr":$phi_attr, "double":$lambda_double), [{ auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "FloatAttr":$lambda_attr), [{ - build($_builder, $_state, qubit_in, phi_attr, nullptr, lambda_attr, nullptr); + OpBuilder<(ins "Value":$qubit, "FloatAttr":$phi_attr, "FloatAttr":$lambda_attr), [{ + build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "Value":$lambda_operand), [{ - build($_builder, $_state, qubit_in, phi_attr, nullptr, nullptr, lambda_operand); + OpBuilder<(ins "Value":$qubit, "FloatAttr":$phi_attr, "Value":$lambda_operand), [{ + build($_builder, $_state, qubit, phi_attr, nullptr, nullptr, lambda_operand); }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "double":$lambda_double), [{ + OpBuilder<(ins "Value":$qubit, "Value":$phi_operand, "double":$lambda_double), [{ auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, qubit_in, nullptr, phi_operand, lambda_attr, nullptr); + build($_builder, $_state, qubit, nullptr, phi_operand, lambda_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "FloatAttr":$lambda_attr), [{ - build($_builder, $_state, qubit_in, nullptr, phi_operand, lambda_attr, nullptr); + OpBuilder<(ins "Value":$qubit, "Value":$phi_operand, "FloatAttr":$lambda_attr), [{ + build($_builder, $_state, qubit, nullptr, phi_operand, lambda_attr, nullptr); }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "Value":$lambda_operand), [{ - build($_builder, $_state, qubit_in, nullptr, phi_operand, nullptr, lambda_operand); + OpBuilder<(ins "Value":$qubit, "Value":$phi_operand, "Value":$lambda_operand), [{ + build($_builder, $_state, qubit, nullptr, phi_operand, nullptr, lambda_operand); }]>, ]; @@ -419,8 +419,8 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, } def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { - let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); - let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; + let arguments = (ins QubitType:$qubit0, QubitType:$qubit1); + let assemblyFormat = "$qubit0 `,` $qubit1 attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 1839ffc261..9de962e742 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -385,7 +385,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::RXOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map const auto fluxQubit = getState().qubitMap[quartzQubit]; @@ -415,7 +415,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::U2Op op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map const auto fluxQubit = getState().qubitMap[quartzQubit]; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 11bf520456..86dec27312 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -447,7 +447,7 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { // Replace with call to RX rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubitIn(), thetaOperand}); + op, fnDecl, ValueRange{adaptor.getQubit(), thetaOperand}); return success(); } }; @@ -494,8 +494,7 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { // Replace with call to U2 rewriter.replaceOpWithNewOp( - op, fnDecl, - ValueRange{adaptor.getQubitIn(), phiOperand, lambdaOperand}); + op, fnDecl, ValueRange{adaptor.getQubit(), phiOperand, lambdaOperand}); return success(); } }; From 245944dfd82c7e0f91275bc61d1969a259877359 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:35:44 +0100 Subject: [PATCH 111/419] Define isTwoQubit() method --- mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td | 5 +++++ mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 7445e93226..8fbeb7073a 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -107,6 +107,11 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "bool", "isSingleQubit", (ins), [{ return getNumQubits(impl, tablegen_opaque_val) == 1; }] >, + InterfaceMethod< + "Returns true if the operation acts on two qubits.", + "bool", "isTwoQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 2; }] + >, /// TODO: Add more convenience methods as necessary // Matrix extraction diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 1c95ac8fd2..754b873606 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -88,6 +88,11 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "bool", "isSingleQubit", (ins), [{ return getNumQubits(impl, tablegen_opaque_val) == 1; }] >, + InterfaceMethod< + "Returns true if the operation acts on two qubits.", + "bool", "isTwoQubit", (ins), + [{ return getNumQubits(impl, tablegen_opaque_val) == 2; }] + >, /// TODO: I am fairly sure that there are quite some further convenience methods that would be helpful here, e.g., whether it is a two-qubit gate // Matrix extraction From bdaa0dd6382a0880bd11e5d6edb2b035c7c5e4ec Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:52:36 +0100 Subject: [PATCH 112/419] Apply suggestion from @burgholzer Co-authored-by: Lukas Burgholzer Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> --- mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 754b873606..5931ae1ac7 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -109,7 +109,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", - "::llvm::StringRef", "getBaseSymbol", (ins) + "StringRef", "getBaseSymbol", (ins) >, ]; } From abc0eff66bedce1b1bc6354e737041bdf17ad789 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:53:35 +0100 Subject: [PATCH 113/419] Remove unnecessary namespace specifier --- mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 8fbeb7073a..57d7fc9049 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -128,7 +128,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { // Identification InterfaceMethod< "Returns the base symbol/mnemonic of the operation.", - "::llvm::StringRef", "getBaseSymbol", (ins) + "StringRef", "getBaseSymbol", (ins) >, ]; } From 3b46338d20c81448b80cbde50b0866a1676dea86 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:09:28 +0100 Subject: [PATCH 114/419] Apply suggestion from @burgholzer Co-authored-by: Lukas Burgholzer Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 9de962e742..b6268c8622 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -360,7 +360,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubit(0); + const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map const auto& fluxQubit = getState().qubitMap[quartzQubit]; From e7290a8961d8b159e267e423bfec44329a6e2003 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:16:59 +0100 Subject: [PATCH 115/419] Rename angle_attr to angle and angle_operand to angle_dyn --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 16 ++++++------- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 16 ++++++------- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 24 +++++++++---------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 24 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index afb2210832..a2ebfa326b 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -410,10 +410,10 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { let arguments = (ins QubitType:$qubit_in, - OptionalAttr:$theta_attr, - Optional:$theta_operand); + OptionalAttr:$theta, + Optional:$theta_dyn); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` ($theta^)? ($theta_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); @@ -440,12 +440,12 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit_in, - OptionalAttr:$phi_attr, - Optional:$phi_operand, - OptionalAttr:$lambda_attr, - Optional:$lambda_operand); + OptionalAttr:$phi, + Optional:$phi_dyn, + OptionalAttr:$lambda, + Optional:$lambda_dyn); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` ($phi^)? ($phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index e024bc00ab..506d2abb46 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -337,9 +337,9 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { let arguments = (ins QubitType:$qubit, - OptionalAttr:$theta_attr, - Optional:$theta_operand); - let assemblyFormat = "`(` ($theta_attr^)? ($theta_operand^)? `)` $qubit attr-dict"; + OptionalAttr:$theta, + Optional:$theta_dyn); + let assemblyFormat = "`(` ($theta^)? ($theta_dyn^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); @@ -366,11 +366,11 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { let arguments = (ins QubitType:$qubit, - OptionalAttr:$phi_attr, - Optional:$phi_operand, - OptionalAttr:$lambda_attr, - Optional:$lambda_operand); - let assemblyFormat = "`(` ($phi_attr^)? ($phi_operand^)? `,` ($lambda_attr^)? ($lambda_operand^)? `)` $qubit attr-dict"; + OptionalAttr:$phi, + Optional:$phi_dyn, + OptionalAttr:$lambda, + Optional:$lambda_dyn); + let assemblyFormat = "`(` ($phi^)? ($phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index c5cd633770..3aef48287e 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -133,7 +133,7 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { - return {getThetaAttrAttr(), getThetaOperand()}; + return {getThetaAttr(), getThetaDyn()}; } llvm_unreachable("RXOp has one parameter"); } @@ -146,16 +146,16 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto& theta = getThetaAttr().value().convertToDouble(); + const auto& theta = getTheta().value().convertToDouble(); const std::complex m0(std::cos(theta / 2), 0); const std::complex m1(0, -std::sin(theta / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); } LogicalResult RXOp::verify() { - if (getThetaAttr().has_value() && getThetaOperand()) + if (getTheta().has_value() && getThetaDyn()) return emitOpError("cannot specify both static and dynamic theta"); - if (!getThetaAttr().has_value() && !getThetaOperand()) + if (!getTheta().has_value() && !getThetaDyn()) return emitOpError("must specify either static or dynamic theta"); return success(); } @@ -164,10 +164,10 @@ LogicalResult RXOp::verify() { ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 0) { - return {getPhiAttrAttr(), getPhiOperand()}; + return {getPhiAttr(), getPhiDyn()}; } if (i == 1) { - return {getLambdaAttrAttr(), getLambdaOperand()}; + return {getLambdaAttr(), getLambdaDyn()}; } llvm_unreachable("U2Op has two parameters"); } @@ -182,8 +182,8 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto& phi = getPhiAttr().value().convertToDouble(); - const auto& lambda = getLambdaAttr().value().convertToDouble(); + const auto& phi = getPhi().value().convertToDouble(); + const auto& lambda = getLambda().value().convertToDouble(); const std::complex i(0.0, 1.0); const std::complex m00(1.0, 0.0); const std::complex m01 = -std::exp(i * lambda); @@ -193,13 +193,13 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (getPhiAttr().has_value() && getPhiOperand()) + if (getPhi().has_value() && getPhiDyn()) return emitOpError("cannot specify both static and dynamic phi"); - if (!getPhiAttr().has_value() && !getPhiOperand()) + if (!getPhi().has_value() && !getPhiDyn()) return emitOpError("must specify either static or dynamic phi"); - if (getLambdaAttr().has_value() && getLambdaOperand()) + if (getLambda().has_value() && getLambdaDyn()) return emitOpError("cannot specify both static and dynamic lambda"); - if (!getLambdaAttr().has_value() && !getLambdaOperand()) + if (!getLambda().has_value() && !getLambdaDyn()) return emitOpError("must specify either static or dynamic lambda"); return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index c71b746ef3..9d0bfc8dec 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -131,7 +131,7 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { - return {getThetaAttrAttr(), getThetaOperand()}; + return {getThetaAttr(), getThetaDyn()}; } llvm_unreachable("RXOp has one parameter"); } @@ -144,16 +144,16 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto& theta = getThetaAttr().value().convertToDouble(); + const auto& theta = getTheta().value().convertToDouble(); const std::complex m0(std::cos(theta / 2), 0); const std::complex m1(0, -std::sin(theta / 2)); return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); } LogicalResult RXOp::verify() { - if (getThetaAttr().has_value() && getThetaOperand()) + if (getTheta().has_value() && getThetaDyn()) return emitOpError("cannot specify both static and dynamic theta"); - if (!getThetaAttr().has_value() && !getThetaOperand()) + if (!getTheta().has_value() && !getThetaDyn()) return emitOpError("must specify either static or dynamic theta"); return success(); } @@ -162,10 +162,10 @@ LogicalResult RXOp::verify() { ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 0) { - return {getPhiAttrAttr(), getPhiOperand()}; + return {getPhiAttr(), getPhiDyn()}; } if (i == 1) { - return {getLambdaAttrAttr(), getLambdaOperand()}; + return {getLambdaAttr(), getLambdaDyn()}; } llvm_unreachable("U2Op has two parameters"); } @@ -180,8 +180,8 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } auto* ctx = getContext(); auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - const auto& phi = getPhiAttr().value().convertToDouble(); - const auto& lambda = getLambdaAttr().value().convertToDouble(); + const auto& phi = getPhi().value().convertToDouble(); + const auto& lambda = getLambda().value().convertToDouble(); const std::complex i(0.0, 1.0); const std::complex m00(1.0, 0.0); const std::complex m01 = -std::exp(i * lambda); @@ -191,13 +191,13 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (getPhiAttr().has_value() && getPhiOperand()) + if (getPhi().has_value() && getPhiDyn()) return emitOpError("cannot specify both static and dynamic phi"); - if (!getPhiAttr().has_value() && !getPhiOperand()) + if (!getPhi().has_value() && !getPhiDyn()) return emitOpError("must specify either static or dynamic phi"); - if (getLambdaAttr().has_value() && getLambdaOperand()) + if (getLambda().has_value() && getLambdaDyn()) return emitOpError("cannot specify both static and dynamic lambda"); - if (!getLambdaAttr().has_value() && !getLambdaOperand()) + if (!getLambda().has_value() && !getLambdaDyn()) return emitOpError("must specify either static or dynamic lambda"); return success(); } From f600ec1b5544b6fc356e6b9bba67e9df54a75ffa Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:25:17 +0100 Subject: [PATCH 116/419] Do not use interface methods in conversions --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 22 ++++----- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 27 +++++------ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 45 +++++++++---------- 3 files changed, 41 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 928430b49a..73fbca1478 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -263,13 +263,11 @@ struct ConvertFluxRXOp final : OpConversionPattern { // OpAdaptor provides the already type-converted input qubit const auto& quartzQubit = adaptor.getQubitIn(); - const auto& theta = op.getParameter(0); - const auto& thetaAttr = theta.getValueAttr(); - const auto& thetaOperand = theta.getValueOperand(); + const auto& theta = op.getThetaAttr(); + const auto& thetaDyn = op.getThetaDyn(); // Create quartz.rx (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, thetaAttr, - thetaOperand); + rewriter.create(op.getLoc(), quartzQubit, theta, thetaDyn); // Replace the output qubit with the same quartz reference rewriter.replaceOp(op, quartzQubit); @@ -288,17 +286,15 @@ struct ConvertFluxU2Op final : OpConversionPattern { // OpAdaptor provides the already type-converted input qubit const auto& quartzQubit = adaptor.getQubitIn(); - const auto& phi = op.getParameter(0); - const auto& phiAttr = phi.getValueAttr(); - const auto& phiOperand = phi.getValueOperand(); + const auto& phi = op.getPhiAttr(); + const auto& phiDyn = op.getPhiDyn(); - const auto& lambda = op.getParameter(1); - const auto& lambdaAttr = lambda.getValueAttr(); - const auto& lambdaOperand = lambda.getValueOperand(); + const auto& lambda = op.getLambdaAttr(); + const auto& lambdaDyn = op.getLambdaDyn(); // Create quartz.u2 (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, phiAttr, phiOperand, - lambdaAttr, lambdaOperand); + rewriter.create(op.getLoc(), quartzQubit, phi, phiDyn, lambda, + lambdaDyn); // Replace the output qubit with the same quartz reference rewriter.replaceOp(op, quartzQubit); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index b6268c8622..48caefb478 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -390,13 +390,12 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { // Get the latest Flux qubit value from the state map const auto fluxQubit = getState().qubitMap[quartzQubit]; - const auto& theta = op.getParameter(0); - const auto& thetaAttr = theta.getValueAttr(); - const auto& thetaOperand = theta.getValueOperand(); + const auto& theta = op.getThetaAttr(); + const auto& thetaDyn = op.getThetaDyn(); // Create flux.rx operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, thetaAttr, - thetaOperand); + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit, theta, thetaDyn); // Update mapping: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); @@ -420,17 +419,15 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { // Get the latest Flux qubit value from the state map const auto fluxQubit = getState().qubitMap[quartzQubit]; - const auto& phi = op.getParameter(0); - const auto& phiAttr = phi.getValueAttr(); - const auto& phiOperand = phi.getValueOperand(); + const auto& phi = op.getPhiAttr(); + const auto& phiDyn = op.getPhiDyn(); - const auto& lambda = op.getParameter(1); - const auto& lambdaAttr = lambda.getValueAttr(); - const auto& lambdaOperand = lambda.getValueOperand(); + const auto& lambda = op.getLambdaAttr(); + const auto& lambdaDyn = op.getLambdaDyn(); // Create flux.u2 operation (consumes input, produces output) - auto fluxOp = rewriter.create( - op.getLoc(), fluxQubit, phiAttr, phiOperand, lambdaAttr, lambdaOperand); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, phi, + phiDyn, lambda, lambdaDyn); // Update mapping: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); @@ -449,8 +446,8 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit0 = op.getQubit(0); - const auto& quartzQubit1 = op.getQubit(1); + const auto& quartzQubit0 = op.getQubit0(); + const auto& quartzQubit1 = op.getQubit1(); // Get the latest Flux qubit values from the state map const auto& fluxQubit0 = getState().qubitMap[quartzQubit0]; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 86dec27312..2d169bf4d9 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -434,20 +434,18 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { const auto fnDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_RX, qirSignature); - const auto& theta = op.getParameter(0); - Value thetaOperand; - if (theta.isStatic()) { - const auto& thetaAttr = theta.getValueAttr(); - auto constantOp = - rewriter.create(op.getLoc(), thetaAttr); - thetaOperand = constantOp.getResult(); + Value thetaDyn; + if (op.getTheta().has_value()) { + const auto& theta = op.getThetaAttr(); + auto constantOp = rewriter.create(op.getLoc(), theta); + thetaDyn = constantOp.getResult(); } else { - thetaOperand = theta.getValueOperand(); + thetaDyn = op.getThetaDyn(); } // Replace with call to RX rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubit(), thetaOperand}); + op, fnDecl, ValueRange{adaptor.getQubit(), thetaDyn}); return success(); } }; @@ -471,30 +469,27 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { const auto fnDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_U2, qirSignature); - const auto& phi = op.getParameter(0); - Value phiOperand; - if (phi.isStatic()) { - const auto& phiAttr = phi.getValueAttr(); - auto constantOp = rewriter.create(op.getLoc(), phiAttr); - phiOperand = constantOp.getResult(); + Value phiDyn; + if (op.getPhi().has_value()) { + const auto& phi = op.getPhiAttr(); + auto constantOp = rewriter.create(op.getLoc(), phi); + phiDyn = constantOp.getResult(); } else { - phiOperand = phi.getValueOperand(); + phiDyn = op.getPhiDyn(); } - const auto& lambda = op.getParameter(1); - Value lambdaOperand; - if (lambda.isStatic()) { - const auto& lambdaAttr = lambda.getValueAttr(); - auto constantOp = - rewriter.create(op.getLoc(), lambdaAttr); - lambdaOperand = constantOp.getResult(); + Value lambdaDyn; + if (op.getLambda().has_value()) { + const auto& lambda = op.getLambdaAttr(); + auto constantOp = rewriter.create(op.getLoc(), lambda); + lambdaDyn = constantOp.getResult(); } else { - lambdaOperand = lambda.getValueOperand(); + lambdaDyn = op.getLambdaDyn(); } // Replace with call to U2 rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubit(), phiOperand, lambdaOperand}); + op, fnDecl, ValueRange{adaptor.getQubit(), phiDyn, lambdaDyn}); return success(); } }; From 9cbb17b20b8441de52fc0bac4c8707356dc6476a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:35:21 +0100 Subject: [PATCH 117/419] Fix assembly format for operations with parameters --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 4 ++-- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index a2ebfa326b..02a01773cb 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -413,7 +413,7 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> OptionalAttr:$theta, Optional:$theta_dyn); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($theta^)? ($theta_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` ($theta^)? (`` $theta_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); @@ -445,7 +445,7 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, A OptionalAttr:$lambda, Optional:$lambda_dyn); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($phi^)? ($phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` ($phi^)? (`` $phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 506d2abb46..034b4c55bd 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -339,7 +339,7 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] let arguments = (ins QubitType:$qubit, OptionalAttr:$theta, Optional:$theta_dyn); - let assemblyFormat = "`(` ($theta^)? ($theta_dyn^)? `)` $qubit attr-dict"; + let assemblyFormat = "`(` ($theta^)? (`` $theta_dyn^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); @@ -370,7 +370,7 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, Optional:$phi_dyn, OptionalAttr:$lambda, Optional:$lambda_dyn); - let assemblyFormat = "`(` ($phi^)? ($phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit attr-dict"; + let assemblyFormat = "`(` ($phi^)? (`` $phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ ParameterDescriptor getParameter(size_t i); From 4aaf191db082dba072429df26d720b9ca937b24e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:54:29 +0100 Subject: [PATCH 118/419] Simplify verifiers --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 18 ++++++------------ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 18 ++++++------------ 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 3aef48287e..7785913954 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -153,10 +153,8 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } LogicalResult RXOp::verify() { - if (getTheta().has_value() && getThetaDyn()) - return emitOpError("cannot specify both static and dynamic theta"); - if (!getTheta().has_value() && !getThetaDyn()) - return emitOpError("must specify either static or dynamic theta"); + if (!(getTheta().has_value() ^ getThetaDyn() != nullptr)) + return emitOpError("must specify exactly one of static or dynamic theta"); return success(); } @@ -193,14 +191,10 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (getPhi().has_value() && getPhiDyn()) - return emitOpError("cannot specify both static and dynamic phi"); - if (!getPhi().has_value() && !getPhiDyn()) - return emitOpError("must specify either static or dynamic phi"); - if (getLambda().has_value() && getLambdaDyn()) - return emitOpError("cannot specify both static and dynamic lambda"); - if (!getLambda().has_value() && !getLambdaDyn()) - return emitOpError("must specify either static or dynamic lambda"); + if (!(getPhi().has_value() ^ getPhiDyn() != nullptr)) + return emitOpError("must specify exactly one of static or dynamic phi"); + if (!(getLambda().has_value() ^ getLambdaDyn() != nullptr)) + return emitOpError("must specify exactly one of static or dynamic lambda"); return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 9d0bfc8dec..04ec2b1a06 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -151,10 +151,8 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } LogicalResult RXOp::verify() { - if (getTheta().has_value() && getThetaDyn()) - return emitOpError("cannot specify both static and dynamic theta"); - if (!getTheta().has_value() && !getThetaDyn()) - return emitOpError("must specify either static or dynamic theta"); + if (!(getTheta().has_value() ^ getThetaDyn() != nullptr)) + return emitOpError("must specify exactly one of static or dynamic theta"); return success(); } @@ -191,14 +189,10 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (getPhi().has_value() && getPhiDyn()) - return emitOpError("cannot specify both static and dynamic phi"); - if (!getPhi().has_value() && !getPhiDyn()) - return emitOpError("must specify either static or dynamic phi"); - if (getLambda().has_value() && getLambdaDyn()) - return emitOpError("cannot specify both static and dynamic lambda"); - if (!getLambda().has_value() && !getLambdaDyn()) - return emitOpError("must specify either static or dynamic lambda"); + if (!(getPhi().has_value() ^ getPhiDyn() != nullptr)) + return emitOpError("must specify exactly one of static or dynamic phi"); + if (!(getLambda().has_value() ^ getLambdaDyn() != nullptr)) + return emitOpError("must specify exactly one of static or dynamic lambda"); return success(); } From 383d1bd0ae2afbe7e5aca7bc8ce91d778572be4b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:37:15 +0100 Subject: [PATCH 119/419] Fix linter errors --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 9 ++++++--- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 7785913954..bfdb748ffb 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -153,8 +153,9 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } LogicalResult RXOp::verify() { - if (!(getTheta().has_value() ^ getThetaDyn() != nullptr)) + if (!(getTheta().has_value() ^ bool(getThetaDyn()))) { return emitOpError("must specify exactly one of static or dynamic theta"); + } return success(); } @@ -191,10 +192,12 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (!(getPhi().has_value() ^ getPhiDyn() != nullptr)) + if (!(getPhi().has_value() ^ bool(getPhiDyn()))) { return emitOpError("must specify exactly one of static or dynamic phi"); - if (!(getLambda().has_value() ^ getLambdaDyn() != nullptr)) + } + if (!(getLambda().has_value() ^ bool(getLambdaDyn()))) { return emitOpError("must specify exactly one of static or dynamic lambda"); + } return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 04ec2b1a06..4ff8c84ce2 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -151,8 +151,9 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } LogicalResult RXOp::verify() { - if (!(getTheta().has_value() ^ getThetaDyn() != nullptr)) + if (!(getTheta().has_value() ^ bool(getThetaDyn()))) { return emitOpError("must specify exactly one of static or dynamic theta"); + } return success(); } @@ -189,10 +190,12 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (!(getPhi().has_value() ^ getPhiDyn() != nullptr)) + if (!(getPhi().has_value() ^ bool(getPhiDyn()))) { return emitOpError("must specify exactly one of static or dynamic phi"); - if (!(getLambda().has_value() ^ getLambdaDyn() != nullptr)) + } + if (!(getLambda().has_value() ^ bool(getLambdaDyn()))) { return emitOpError("must specify exactly one of static or dynamic lambda"); + } return success(); } From c54021c89d1a365c00bdb3b8d2122ad3b753eb50 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:12:04 +0100 Subject: [PATCH 120/419] Centralize matrix definitions --- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 48 +++++++++++++++++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 31 +++++------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 31 +++++------- 3 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 mlir/include/mlir/Dialect/Utils/MatrixUtils.h diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h new file mode 100644 index 0000000000..6677273ffd --- /dev/null +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +using namespace std::complex_literals; + +namespace mlir::utils { + +inline llvm::ArrayRef> getMatrixX() { + return {0.0, 1.0, // row 0 + 1.0, 0.0}; // row 1 +} + +inline llvm::ArrayRef> getMatrixRX(double theta) { + const std::complex m0(std::cos(theta / 2), 0); + const std::complex m1(0, -std::sin(theta / 2)); + return {m0, m1, m1, m0}; +} + +inline llvm::ArrayRef> getMatrixU2(double phi, + double lambda) { + const std::complex m00(1.0, 0.0); + const std::complex m01 = -std::exp(1i * lambda); + const std::complex m10 = -std::exp(1i * phi); + const std::complex m11 = std::exp(1i * (phi + lambda)); + return {m00, m01, m10, m11}; +} + +inline llvm::ArrayRef> getMatrixSWAP() { + return {1.0, 0.0, 0.0, 0.0, // row 0 + 0.0, 0.0, 1.0, 0.0, // row 1 + 0.0, 1.0, 0.0, 0.0, // row 2 + 0.0, 0.0, 0.0, 1.0}; // row 3 +} + +} // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index bfdb748ffb..40da63615e 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" // IWYU pragma: associated +#include "mlir/Dialect/Utils/MatrixUtils.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -18,7 +19,6 @@ #include // IWYU pragma: end_keep -#include #include #include #include @@ -125,8 +125,9 @@ LogicalResult MeasureOp::verify() { DenseElementsAttr XOp::tryGetStaticMatrix() { auto* ctx = getContext(); - auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + return DenseElementsAttr::get(type, mlir::utils::getMatrixX()); } // RXOp @@ -145,11 +146,10 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { return nullptr; } auto* ctx = getContext(); - auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& theta = getTheta().value().convertToDouble(); - const std::complex m0(std::cos(theta / 2), 0); - const std::complex m1(0, -std::sin(theta / 2)); - return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); + return DenseElementsAttr::get(type, mlir::utils::getMatrixRX(theta)); } LogicalResult RXOp::verify() { @@ -180,15 +180,11 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { return nullptr; } auto* ctx = getContext(); - auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& phi = getPhi().value().convertToDouble(); const auto& lambda = getLambda().value().convertToDouble(); - const std::complex i(0.0, 1.0); - const std::complex m00(1.0, 0.0); - const std::complex m01 = -std::exp(i * lambda); - const std::complex m10 = -std::exp(i * phi); - const std::complex m11 = std::exp(i * (phi + lambda)); - return DenseElementsAttr::get(type, llvm::ArrayRef({m00, m01, m10, m11})); + return DenseElementsAttr::get(type, mlir::utils::getMatrixU2(phi, lambda)); } LogicalResult U2Op::verify() { @@ -205,10 +201,9 @@ LogicalResult U2Op::verify() { DenseElementsAttr SWAPOp::tryGetStaticMatrix() { auto* ctx = getContext(); - const auto& type = RankedTensorType::get({4, 4}, Float64Type::get(ctx)); - return DenseElementsAttr::get( - type, llvm::ArrayRef({1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 1.0})); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + return DenseElementsAttr::get(type, mlir::utils::getMatrixSWAP()); } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 4ff8c84ce2..c84177654f 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" // IWYU pragma: associated +#include "mlir/Dialect/Utils/MatrixUtils.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -17,7 +18,6 @@ #include // IWYU pragma: end_keep -#include #include #include #include @@ -123,8 +123,9 @@ LogicalResult MeasureOp::verify() { DenseElementsAttr XOp::tryGetStaticMatrix() { auto* ctx = getContext(); - auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); - return DenseElementsAttr::get(type, llvm::ArrayRef({0.0, 1.0, 1.0, 0.0})); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + return DenseElementsAttr::get(type, mlir::utils::getMatrixX()); } // RXOp @@ -143,11 +144,10 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { return nullptr; } auto* ctx = getContext(); - auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& theta = getTheta().value().convertToDouble(); - const std::complex m0(std::cos(theta / 2), 0); - const std::complex m1(0, -std::sin(theta / 2)); - return DenseElementsAttr::get(type, llvm::ArrayRef({m0, m1, m1, m0})); + return DenseElementsAttr::get(type, mlir::utils::getMatrixRX(theta)); } LogicalResult RXOp::verify() { @@ -178,15 +178,11 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { return nullptr; } auto* ctx = getContext(); - auto type = RankedTensorType::get({2, 2}, Float64Type::get(ctx)); + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& phi = getPhi().value().convertToDouble(); const auto& lambda = getLambda().value().convertToDouble(); - const std::complex i(0.0, 1.0); - const std::complex m00(1.0, 0.0); - const std::complex m01 = -std::exp(i * lambda); - const std::complex m10 = -std::exp(i * phi); - const std::complex m11 = std::exp(i * (phi + lambda)); - return DenseElementsAttr::get(type, llvm::ArrayRef({m00, m01, m10, m11})); + return DenseElementsAttr::get(type, mlir::utils::getMatrixU2(phi, lambda)); } LogicalResult U2Op::verify() { @@ -203,8 +199,7 @@ LogicalResult U2Op::verify() { DenseElementsAttr SWAPOp::tryGetStaticMatrix() { auto* ctx = getContext(); - const auto& type = RankedTensorType::get({4, 4}, Float64Type::get(ctx)); - return DenseElementsAttr::get( - type, llvm::ArrayRef({1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 1.0})); + const auto& type = + RankedTensorType::get({4, 4}, ComplexType::get(Float64Type::get(ctx))); + return DenseElementsAttr::get(type, mlir::utils::getMatrixSWAP()); } From 7f84910d7dc887ace3b5b5f21ad2e7dff9daf63f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:18:56 +0100 Subject: [PATCH 121/419] Use std::pair instead of std::vector --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 2 +- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 893afcec10..11beb88db9 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -326,7 +326,7 @@ class FluxProgramBuilder { * !flux.qubit, !flux.qubit * ``` */ - std::vector swap(Value qubit0, Value qubit1); + std::pair swap(Value qubit0, Value qubit1); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index daec36ce84..eb0abee154 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -237,7 +237,7 @@ Value FluxProgramBuilder::u2(Value phi, Value lambda, Value qubit) { return qubitOut; } -std::vector FluxProgramBuilder::swap(Value qubit0, Value qubit1) { +std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { auto swapOp = builder.create(loc, qubit0, qubit1); const auto& qubit0Out = swapOp.getQubit0Out(); const auto& qubit1Out = swapOp.getQubit1Out(); From 5e7c0f96143fcdbe20da662ae982ad7e0143d43d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:57:53 +0100 Subject: [PATCH 122/419] Use std::variant in program builders --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 10 +-- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 10 +-- .../Quartz/Builder/QuartzProgramBuilder.h | 10 +-- .../Flux/Builder/FluxProgramBuilder.cpp | 61 ++++++--------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 78 ++++++++++--------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 53 +++++++------ 6 files changed, 107 insertions(+), 115 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 11beb88db9..b184cf66b6 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace mlir::flux { @@ -278,8 +279,7 @@ class FluxProgramBuilder { * %q_out = flux.rx(1.0) %q_in : !flux.qubit -> !flux.qubit * ``` */ - Value rx(double theta, Value qubit); - Value rx(Value theta, Value qubit); + Value rx(std::variant theta, Value qubit); /** * @brief Apply the U2 gate to a qubit @@ -301,10 +301,8 @@ class FluxProgramBuilder { * %q_out = flux.u2(1.0, 0.5) %q_in : !flux.qubit -> !flux.qubit * ``` */ - Value u2(double phi, double lambda, Value qubit); - Value u2(double phi, Value lambda, Value qubit); - Value u2(Value phi, double lambda, Value qubit); - Value u2(Value phi, Value lambda, Value qubit); + Value u2(std::variant phi, std::variant lambda, + Value qubit); /** * @brief Apply the SWAP gate to two qubits diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 6a68b687fa..3569f75bc4 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace mlir::qir { @@ -257,8 +258,7 @@ class QIRProgramBuilder { * llvm.call @__quantum__qis__rx__body(%q, %c) : (!llvm.ptr, f64) -> () * ``` */ - QIRProgramBuilder& rx(double theta, const Value qubit); - QIRProgramBuilder& rx(Value theta, const Value qubit); + QIRProgramBuilder& rx(std::variant theta, const Value qubit); /** * @brief Apply the U2 gate to a qubit @@ -277,10 +277,8 @@ class QIRProgramBuilder { * -> () * ``` */ - QIRProgramBuilder& u2(double phi, double lambda, const Value qubit); - QIRProgramBuilder& u2(double phi, Value lambda, const Value qubit); - QIRProgramBuilder& u2(Value phi, double lambda, const Value qubit); - QIRProgramBuilder& u2(Value phi, Value lambda, const Value qubit); + QIRProgramBuilder& u2(std::variant phi, + std::variant lambda, const Value qubit); /** * @brief Apply the SWAP gate to two qubits diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 7639adb350..aff5598619 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace mlir::quartz { @@ -258,8 +259,7 @@ class QuartzProgramBuilder { * quartz.rx(1.0) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& rx(double theta, Value qubit); - QuartzProgramBuilder& rx(Value theta, Value qubit); + QuartzProgramBuilder& rx(std::variant theta, Value qubit); /** * @brief Apply the U2 gate to a qubit @@ -277,10 +277,8 @@ class QuartzProgramBuilder { * quartz.u2(1.0, 0.5) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& u2(double phi, double lambda, Value qubit); - QuartzProgramBuilder& u2(Value phi, double lambda, Value qubit); - QuartzProgramBuilder& u2(double phi, Value lambda, Value qubit); - QuartzProgramBuilder& u2(Value phi, Value lambda, Value qubit); + QuartzProgramBuilder& u2(std::variant phi, + std::variant lambda, Value qubit); /** * @brief Apply the SWAP gate to two qubits diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index eb0abee154..4eef565750 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -181,17 +181,14 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } -Value FluxProgramBuilder::rx(Value theta, Value qubit) { - auto rxOp = builder.create(loc, qubit, theta); - const auto& qubitOut = rxOp.getQubitOut(); - - // Update tracking - updateQubitTracking(qubit, qubitOut); +Value FluxProgramBuilder::rx(std::variant theta, Value qubit) { + RXOp rxOp; + if (std::holds_alternative(theta)) { + rxOp = builder.create(loc, qubit, std::get(theta)); + } else { + rxOp = builder.create(loc, qubit, std::get(theta)); + } - return qubitOut; -} -Value FluxProgramBuilder::rx(double theta, Value qubit) { - auto rxOp = builder.create(loc, qubit, theta); const auto& qubitOut = rxOp.getQubitOut(); // Update tracking @@ -200,35 +197,27 @@ Value FluxProgramBuilder::rx(double theta, Value qubit) { return qubitOut; } -Value FluxProgramBuilder::u2(double phi, double lambda, Value qubit) { - auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto& qubitOut = u2Op.getQubitOut(); - - // Update tracking - updateQubitTracking(qubit, qubitOut); - - return qubitOut; -} -Value FluxProgramBuilder::u2(double phi, Value lambda, Value qubit) { - auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto& qubitOut = u2Op.getQubitOut(); - - // Update tracking - updateQubitTracking(qubit, qubitOut); +Value FluxProgramBuilder::u2(std::variant phi, + std::variant lambda, Value qubit) { + FloatAttr phiAttr = nullptr; + Value phiValue = nullptr; + if (std::holds_alternative(phi)) { + phiAttr = builder.getF64FloatAttr(std::get(phi)); + } else { + phiValue = std::get(phi); + } - return qubitOut; -} -Value FluxProgramBuilder::u2(Value phi, double lambda, Value qubit) { - auto u2Op = builder.create(loc, qubit, phi, lambda); - const auto& qubitOut = u2Op.getQubitOut(); + FloatAttr lambdaAttr = nullptr; + Value lambdaValue = nullptr; + if (std::holds_alternative(lambda)) { + lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); + } else { + lambdaValue = std::get(lambda); + } - // Update tracking - updateQubitTracking(qubit, qubitOut); + auto u2Op = builder.create(loc, qubit, phiAttr, phiValue, lambdaAttr, + lambdaValue); - return qubitOut; -} -Value FluxProgramBuilder::u2(Value phi, Value lambda, Value qubit) { - auto u2Op = builder.create(loc, qubit, phi, lambda); const auto& qubitOut = u2Op.getQubitOut(); // Update tracking diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 0731755891..2c9b4089f4 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -251,13 +251,25 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { return *this; } -QIRProgramBuilder& QIRProgramBuilder::rx(Value theta, const Value qubit) { +QIRProgramBuilder& QIRProgramBuilder::rx(std::variant theta, + const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); + Value thetaOperand; + if (std::holds_alternative(theta)) { + thetaOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(theta))) + .getResult(); + } else { + thetaOperand = std::get(theta); + } + // Create rx call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), @@ -265,19 +277,13 @@ QIRProgramBuilder& QIRProgramBuilder::rx(Value theta, const Value qubit) { Float64Type::get(builder.getContext())}); auto fnDecl = getOrCreateFunctionDeclaration(builder, module, QIR_RX, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit, theta}); + builder.create(loc, fnDecl, ValueRange{qubit, thetaOperand}); return *this; } -QIRProgramBuilder& QIRProgramBuilder::rx(double theta, const Value qubit) { - // Create constant for theta - auto thetaConst = - builder.create(loc, builder.getF64FloatAttr(theta)); - - return rx(thetaConst.getResult(), qubit); -} -QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, Value lambda, +QIRProgramBuilder& QIRProgramBuilder::u2(std::variant phi, + std::variant lambda, const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -285,6 +291,29 @@ QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, Value lambda, // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); + Value phiOperand; + if (std::holds_alternative(phi)) { + phiOperand = builder + .create( + loc, builder.getF64FloatAttr(std::get(phi))) + .getResult(); + } else { + phiOperand = std::get(phi); + ; + } + + Value lambdaOperand; + if (std::holds_alternative(lambda)) { + lambdaOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(lambda))) + .getResult(); + } else { + lambdaOperand = std::get(lambda); + ; + } + // Create u2 call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), @@ -293,36 +322,11 @@ QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, Value lambda, Float64Type::get(builder.getContext())}); auto fnDecl = getOrCreateFunctionDeclaration(builder, module, QIR_U2, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit, phi, lambda}); + builder.create(loc, fnDecl, + ValueRange{qubit, phiOperand, lambdaOperand}); return *this; } -QIRProgramBuilder& QIRProgramBuilder::u2(double phi, double lambda, - const Value qubit) { - // Create constants for phi and lambda - auto phiConst = - builder.create(loc, builder.getF64FloatAttr(phi)); - auto lambdaConst = - builder.create(loc, builder.getF64FloatAttr(lambda)); - - return u2(phiConst.getResult(), lambdaConst.getResult(), qubit); -} -QIRProgramBuilder& QIRProgramBuilder::u2(double phi, Value lambda, - const Value qubit) { - // Create constant for phi - auto phiConst = - builder.create(loc, builder.getF64FloatAttr(phi)); - - return u2(phiConst.getResult(), lambda, qubit); -} -QIRProgramBuilder& QIRProgramBuilder::u2(Value phi, double lambda, - const Value qubit) { - // Create constant for lambda - auto lambdaConst = - builder.create(loc, builder.getF64FloatAttr(lambda)); - - return u2(phi, lambdaConst.getResult(), qubit); -} QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, const Value qubit1) { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 9874036929..2a0f2e57cc 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace mlir::quartz { @@ -129,33 +130,37 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { return *this; } -QuartzProgramBuilder& QuartzProgramBuilder::rx(double theta, Value qubit) { - builder.create(loc, qubit, theta); - return *this; -} -QuartzProgramBuilder& QuartzProgramBuilder::rx(Value theta, Value qubit) { - builder.create(loc, qubit, theta); +QuartzProgramBuilder& +QuartzProgramBuilder::rx(std::variant theta, Value qubit) { + if (std::holds_alternative(theta)) { + builder.create(loc, qubit, std::get(theta)); + } else { + builder.create(loc, qubit, std::get(theta)); + } return *this; } -QuartzProgramBuilder& QuartzProgramBuilder::u2(double phi, double lambda, - Value qubit) { - builder.create(loc, qubit, phi, lambda); - return *this; -} -QuartzProgramBuilder& QuartzProgramBuilder::u2(double phi, Value lambda, - Value qubit) { - builder.create(loc, qubit, phi, lambda); - return *this; -} -QuartzProgramBuilder& QuartzProgramBuilder::u2(Value phi, double lambda, - Value qubit) { - builder.create(loc, qubit, phi, lambda); - return *this; -} -QuartzProgramBuilder& QuartzProgramBuilder::u2(Value phi, Value lambda, - Value qubit) { - builder.create(loc, qubit, phi, lambda); +QuartzProgramBuilder& +QuartzProgramBuilder::u2(std::variant phi, + std::variant lambda, Value qubit) { + FloatAttr phiAttr = nullptr; + Value phiValue = nullptr; + if (std::holds_alternative(phi)) { + phiAttr = builder.getF64FloatAttr(std::get(phi)); + } else { + phiValue = std::get(phi); + } + + FloatAttr lambdaAttr = nullptr; + Value lambdaValue = nullptr; + if (std::holds_alternative(lambda)) { + lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); + } else { + lambdaValue = std::get(lambda); + } + + builder.create(loc, qubit, phiAttr, phiValue, lambdaAttr, lambdaValue); + return *this; } From 3406397f1bb3de1976017a06df80abf2f5fa1fd8 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:15:15 +0100 Subject: [PATCH 123/419] Centralize ParameterDescriptor definition and make it a class --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 26 +---------- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 2 +- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 6 +-- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 26 +---------- .../Dialect/Quartz/IR/QuartzInterfaces.td | 2 +- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 6 +-- .../mlir/Dialect/Utils/ParameterDescriptor.h | 43 +++++++++++++++++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 9 ++-- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 9 ++-- 9 files changed, 65 insertions(+), 64 deletions(-) create mode 100644 mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 3929310b3d..d96d45221e 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -10,6 +10,8 @@ #pragma once +#include "mlir/Dialect/Utils/ParameterDescriptor.h" + #include #include #include @@ -57,30 +59,6 @@ template class ParameterArityTrait : public mlir::OpTrait::TraitBase {}; -struct ParameterDescriptor { - mlir::FloatAttr valueAttr; - mlir::Value valueOperand; - - ParameterDescriptor(mlir::FloatAttr attr = nullptr, - mlir::Value operand = nullptr) { - assert(!(attr && operand) && "Cannot have both static and dynamic values"); - valueAttr = attr; - valueOperand = operand; - } - - bool isStatic() const { return valueAttr != nullptr; } - bool isDynamic() const { return valueOperand != nullptr; } - - double getValueDouble() const { - if (isDynamic()) { - llvm_unreachable("Cannot get double value from dynamic parameter"); - } - return valueAttr.getValueAsDouble(); - } - mlir::FloatAttr getValueAttr() const { return valueAttr; } - mlir::Value getValueOperand() const { return valueOperand; } -}; - } // namespace mlir::flux #include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 57d7fc9049..b06b4ca76a 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -92,7 +92,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "ParameterDescriptor", "getParameter", (ins "size_t":$i) + "::mlir::utils::ParameterDescriptor", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 02a01773cb..ab8ebdfb9f 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -373,7 +373,7 @@ def ZeroParameter : ParameterArityTrait { let extraConcreteClassDeclaration = [{ size_t getNumParams() { return 0; } - ParameterDescriptor getParameter(size_t i) { + ::mlir::utils::ParameterDescriptor getParameter(size_t i) { llvm_unreachable("Operation does not have parameters"); } @@ -416,7 +416,7 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> let assemblyFormat = "`(` ($theta^)? (`` $theta_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - ParameterDescriptor getParameter(size_t i); + ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } @@ -448,7 +448,7 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, A let assemblyFormat = "`(` ($phi^)? (`` $phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - ParameterDescriptor getParameter(size_t i); + ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 59a1fd0838..fb7fbd0bca 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -10,6 +10,8 @@ #pragma once +#include "mlir/Dialect/Utils/ParameterDescriptor.h" + #include #include #include @@ -57,30 +59,6 @@ template class ParameterArityTrait : public mlir::OpTrait::TraitBase {}; -struct ParameterDescriptor { - mlir::FloatAttr valueAttr; - mlir::Value valueOperand; - - ParameterDescriptor(mlir::FloatAttr attr = nullptr, - mlir::Value operand = nullptr) { - assert(!(attr && operand) && "Cannot have both static and dynamic values"); - valueAttr = attr; - valueOperand = operand; - } - - bool isStatic() const { return valueAttr != nullptr; } - bool isDynamic() const { return valueOperand != nullptr; } - - double getValueDouble() const { - if (isDynamic()) { - llvm_unreachable("Cannot get double value from dynamic parameter"); - } - return valueAttr.getValueAsDouble(); - } - mlir::FloatAttr getValueAttr() const { return valueAttr; } - mlir::Value getValueOperand() const { return valueOperand; } -}; - } // namespace mlir::quartz #include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 5931ae1ac7..3dcda1096c 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -73,7 +73,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "ParameterDescriptor", "getParameter", (ins "size_t":$i) + "::mlir::utils::ParameterDescriptor", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 034b4c55bd..259381796c 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -301,7 +301,7 @@ def ZeroParameter : ParameterArityTrait { let extraConcreteClassDeclaration = [{ size_t getNumParams() { return 0; } - ParameterDescriptor getParameter(size_t i) { + ::mlir::utils::ParameterDescriptor getParameter(size_t i) { llvm_unreachable("Operation does not have parameters"); } @@ -342,7 +342,7 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] let assemblyFormat = "`(` ($theta^)? (`` $theta_dyn^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ - ParameterDescriptor getParameter(size_t i); + ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } @@ -373,7 +373,7 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, let assemblyFormat = "`(` ($phi^)? (`` $phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit attr-dict"; let extraClassDeclaration = [{ - ParameterDescriptor getParameter(size_t i); + ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } diff --git a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h new file mode 100644 index 0000000000..9b9eb600e1 --- /dev/null +++ b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include + +namespace mlir::utils { + +class ParameterDescriptor { + mlir::FloatAttr valueAttr; + mlir::Value valueOperand; + +public: + ParameterDescriptor(mlir::FloatAttr attr = nullptr, + mlir::Value operand = nullptr) { + assert(!(attr && operand) && "Cannot have both static and dynamic values"); + valueAttr = attr; + valueOperand = operand; + } + + bool isStatic() const { return valueAttr != nullptr; } + bool isDynamic() const { return valueOperand != nullptr; } + + double getValueDouble() const { + if (isDynamic()) { + llvm_unreachable("Cannot get double value from dynamic parameter"); + } + return valueAttr.getValueAsDouble(); + } + mlir::FloatAttr getValueAttr() const { return valueAttr; } + mlir::Value getValueOperand() const { return valueOperand; } +}; + +} // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 40da63615e..2f234ddaf8 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -31,6 +31,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; //===----------------------------------------------------------------------===// // Dialect @@ -127,7 +128,7 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, mlir::utils::getMatrixX()); + return DenseElementsAttr::get(type, getMatrixX()); } // RXOp @@ -149,7 +150,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& theta = getTheta().value().convertToDouble(); - return DenseElementsAttr::get(type, mlir::utils::getMatrixRX(theta)); + return DenseElementsAttr::get(type, getMatrixRX(theta)); } LogicalResult RXOp::verify() { @@ -184,7 +185,7 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& phi = getPhi().value().convertToDouble(); const auto& lambda = getLambda().value().convertToDouble(); - return DenseElementsAttr::get(type, mlir::utils::getMatrixU2(phi, lambda)); + return DenseElementsAttr::get(type, getMatrixU2(phi, lambda)); } LogicalResult U2Op::verify() { @@ -203,7 +204,7 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({4, 4}, complexType); - return DenseElementsAttr::get(type, mlir::utils::getMatrixSWAP()); + return DenseElementsAttr::get(type, getMatrixSWAP()); } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index c84177654f..3916f4e115 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -29,6 +29,7 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; //===----------------------------------------------------------------------===// // Dialect @@ -125,7 +126,7 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, mlir::utils::getMatrixX()); + return DenseElementsAttr::get(type, getMatrixX()); } // RXOp @@ -147,7 +148,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& theta = getTheta().value().convertToDouble(); - return DenseElementsAttr::get(type, mlir::utils::getMatrixRX(theta)); + return DenseElementsAttr::get(type, getMatrixRX(theta)); } LogicalResult RXOp::verify() { @@ -182,7 +183,7 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { const auto& type = RankedTensorType::get({2, 2}, complexType); const auto& phi = getPhi().value().convertToDouble(); const auto& lambda = getLambda().value().convertToDouble(); - return DenseElementsAttr::get(type, mlir::utils::getMatrixU2(phi, lambda)); + return DenseElementsAttr::get(type, getMatrixU2(phi, lambda)); } LogicalResult U2Op::verify() { @@ -201,5 +202,5 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { auto* ctx = getContext(); const auto& type = RankedTensorType::get({4, 4}, ComplexType::get(Float64Type::get(ctx))); - return DenseElementsAttr::get(type, mlir::utils::getMatrixSWAP()); + return DenseElementsAttr::get(type, getMatrixSWAP()); } From b59374836d0b459dba0ff6e56d2db1fc6974409c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:19:23 +0100 Subject: [PATCH 124/419] Add docstrings to ParameterDescriptor --- .../mlir/Dialect/Utils/ParameterDescriptor.h | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h index 9b9eb600e1..999db444af 100644 --- a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h +++ b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h @@ -15,11 +15,20 @@ namespace mlir::utils { +/** + * @brief Descriptor for a parameter that can be either static or dynamic + */ class ParameterDescriptor { mlir::FloatAttr valueAttr; mlir::Value valueOperand; public: + /** + * @brief Construct a new ParameterDescriptor object + * + * @param attr Static float attribute (optional) + * @param operand Dynamic value operand (optional) + */ ParameterDescriptor(mlir::FloatAttr attr = nullptr, mlir::Value operand = nullptr) { assert(!(attr && operand) && "Cannot have both static and dynamic values"); @@ -27,16 +36,44 @@ class ParameterDescriptor { valueOperand = operand; } + /** + * @brief Check if the parameter is static + * + * @return true if static, false if dynamic + */ bool isStatic() const { return valueAttr != nullptr; } + + /** + * @brief Check if the parameter is dynamic + * + * @return true if dynamic, false if static + */ bool isDynamic() const { return valueOperand != nullptr; } + /** + * @brief Try to get the double value of the parameter + * + * @return double value + */ double getValueDouble() const { if (isDynamic()) { llvm_unreachable("Cannot get double value from dynamic parameter"); } return valueAttr.getValueAsDouble(); } + + /** + * @brief Get the static float attribute + * + * @return mlir::FloatAttr Static float attribute (nullptr if dynamic) + */ mlir::FloatAttr getValueAttr() const { return valueAttr; } + + /** + * @brief Get the dynamic value operand + * + * @return mlir::Value Dynamic value operand (nullptr if static) + */ mlir::Value getValueOperand() const { return valueOperand; } }; From 65a6bdb081a1d906b5a07d8e57abc93593fe53e3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:39:41 +0100 Subject: [PATCH 125/419] Add descriptions to unitary operations --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 8 ++-- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 42 +++++++++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.h | 8 ++-- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 42 +++++++++++++++++++ 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index b184cf66b6..878818ca5e 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -241,7 +241,7 @@ class FluxProgramBuilder { //===--------------------------------------------------------------------===// /** - * @brief Apply the X gate to a qubit + * @brief Apply an X gate to a qubit * * @details * Consumes the input qubit and produces a new output qubit SSA value. @@ -261,7 +261,7 @@ class FluxProgramBuilder { Value x(Value qubit); /** - * @brief Apply the RX gate to a qubit + * @brief Apply an RX gate to a qubit * * @details * Consumes the input qubit and produces a new output qubit SSA value. @@ -282,7 +282,7 @@ class FluxProgramBuilder { Value rx(std::variant theta, Value qubit); /** - * @brief Apply the U2 gate to a qubit + * @brief Apply a U2 gate to a qubit * * @details * Consumes the input qubit and produces a new output qubit SSA value. @@ -305,7 +305,7 @@ class FluxProgramBuilder { Value qubit); /** - * @brief Apply the SWAP gate to two qubits + * @brief Apply a SWAP gate to two qubits * * @details * Consumes the input qubits and produces new output qubit SSA values. diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index ab8ebdfb9f..ec81e56cc9 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -398,6 +398,16 @@ def TwoParameter : ParameterArityTrait { //===----------------------------------------------------------------------===// def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { + let summary = "Apply an X gate to a qubit"; + let description = [{ + Applies an X gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -409,6 +419,17 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { } def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { + let summary = "Apply an RX gate to a qubit"; + let description = [{ + Applies an RX gate to a qubit and returns the transformed qubit. + The angle theta can be provided as a static attribute or as a dynamic operand. + + Example: + ```mlir + %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit_in, OptionalAttr:$theta, Optional:$theta_dyn); @@ -439,6 +460,17 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> } def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { + let summary = "Apply a U2 gate to a qubit"; + let description = [{ + Applies a U2 gate to a qubit and returns the transformed qubit. + The angles phi and lambda can be provided as static attributes or as dynamic operands. + + Example: + ```mlir + %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit_in, OptionalAttr:$phi, Optional:$phi_dyn, @@ -494,6 +526,16 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, A } def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { + let summary = "Apply a SWAP gate to two qubits"; + let description = [{ + Applies a SWAP gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index aff5598619..8ba2d4308d 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -229,7 +229,7 @@ class QuartzProgramBuilder { //===--------------------------------------------------------------------===// /** - * @brief Apply the X gate to a qubit + * @brief Apply an X gate to a qubit * * @param qubit Input qubit * @return Reference to this builder for method chaining @@ -245,7 +245,7 @@ class QuartzProgramBuilder { QuartzProgramBuilder& x(Value qubit); /** - * @brief Apply the RX gate to a qubit + * @brief Apply an RX gate to a qubit * * @param theta Rotation angle * @param qubit Input qubit @@ -262,7 +262,7 @@ class QuartzProgramBuilder { QuartzProgramBuilder& rx(std::variant theta, Value qubit); /** - * @brief Apply the U2 gate to a qubit + * @brief Apply a U2 gate to a qubit * * @param phi Rotation angle * @param lambda Rotation angle @@ -281,7 +281,7 @@ class QuartzProgramBuilder { std::variant lambda, Value qubit); /** - * @brief Apply the SWAP gate to two qubits + * @brief Apply a SWAP gate to two qubits * * @param qubit0 Input qubit * @param qubit1 Input qubit diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 259381796c..0fd9160fb0 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -326,6 +326,16 @@ def TwoParameter : ParameterArityTrait { //===----------------------------------------------------------------------===// def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { + let summary = "Apply an X gate to a qubit"; + let description = [{ + Applies an X gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.x %q : !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict"; @@ -336,6 +346,17 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> } def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { + let summary = "Apply an RX gate to a qubit"; + let description = [{ + Applies an RX gate to a qubit, modifying it in place. + The angle theta can be provided as a static attribute or as a dynamic operand. + + Example: + ```mlir + quartz.rx(%theta) %q : !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit, OptionalAttr:$theta, Optional:$theta_dyn); @@ -365,6 +386,17 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] } def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { + let summary = "Apply a U2 gate to a qubit"; + let description = [{ + Applies a U2 gate to a qubit, modifying it in place. + The angles phi and lambda can be provided as static attributes or as dynamic operands. + + Example: + ```mlir + quartz.u2(%phi, %lambda) %q : !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit, OptionalAttr:$phi, Optional:$phi_dyn, @@ -419,6 +451,16 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, } def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { + let summary = "Apply a SWAP gate to two qubits"; + let description = [{ + Applies a SWAP gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.swap %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + let arguments = (ins QubitType:$qubit0, QubitType:$qubit1); let assemblyFormat = "$qubit0 `,` $qubit1 attr-dict"; From 92a8405f11773082247dd79adde27bdbb75cbc27 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 14:01:05 +0100 Subject: [PATCH 126/419] Add docstrings to conversion patterns --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 52 +++++++++++++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 52 +++++++++++++++++-- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 50 ++++++++++++++++-- 3 files changed, 142 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 73fbca1478..6d7ce47c0f 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -233,7 +233,18 @@ struct ConvertFluxResetOp final : OpConversionPattern { } }; -// Temporary implementation of XOp conversion +/** + * @brief Converts flux.x to quartz.x + * + * @details + * Example transformation: + * ```mlir + * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit + * // becomes: + * quartz.x %q : !quartz.qubit + * // %q_out uses are replaced with %q + * ``` + */ struct ConvertFluxXOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -253,7 +264,18 @@ struct ConvertFluxXOp final : OpConversionPattern { } }; -// Temporary implementation of RXOp conversion +/** + * @brief Converts flux.rx to quartz.rx + * + * @details + * Example transformation: + * ```mlir + * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit + * // becomes: + * quartz.rx(%theta) %q : !quartz.qubit + * // %q_out uses are replaced with %q + * ``` + */ struct ConvertFluxRXOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -276,7 +298,18 @@ struct ConvertFluxRXOp final : OpConversionPattern { } }; -// Temporary implementation of U2Op conversion +/** + * @brief Converts flux.u2 to quartz.u2 + * + * @details + * Example transformation: + * ```mlir + * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit + * // becomes: + * quartz.u2(%phi, %lambda) %q : !quartz.qubit + * // %q_out uses are replaced with %q + * ``` + */ struct ConvertFluxU2Op final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -303,7 +336,18 @@ struct ConvertFluxU2Op final : OpConversionPattern { } }; -// Temporary implementation of SWAPOp conversion +/** + * @brief Converts flux.swap to quartz.swap + * + * @details + * Example transformation: + * ```mlir + * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit + * // becomes: + * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit + * // {%q0_out, %q1_out} uses are replaced with {%q0, %q1} + * ``` + */ struct ConvertFluxSWAPOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 48caefb478..abd7cdb4fc 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -353,7 +353,18 @@ struct ConvertQuartzResetOp final } }; -// Temporary implementation of XOp conversion +/** + * @brief Converts quartz.x to flux.x + * + * @details + * Example transformation: + * ```mlir + * quartz.x %q : !quartz.qubit + * // becomes (where %q maps to %q_in): + * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit + * // state updated: %q now maps to %q_out + * ``` + */ struct ConvertQuartzXOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -378,7 +389,17 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { } }; -// Temporary implementation of RXOp conversion +/** + * @brief Converts quartz.rx to flux.rx + * + * @details + * Example transformation: + * ```mlir + * quartz.rx(%theta) %q : !quartz.qubit + * // becomes (where %q maps to %q_in): + * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit + * // state updated: %q now maps to %q_out + */ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -407,7 +428,18 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { } }; -// Temporary implementation of U2Op conversion +/** + * @brief Converts quartz.u2 to flux.u2 + * + * @details + * Example transformation: + * ```mlir + * quartz.u2(%phi, %lambda) %q : !quartz.qubit + * // becomes (where %q maps to %q_in): + * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit + * // state updated: %q now maps to %q_out + * ``` + */ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -439,7 +471,19 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { } }; -// Temporary implementation of SWAPOp conversion +/** + * @brief Converts quartz.swap to flux.swap + * + * @details + * Example transformation: + * ```mlir + * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit + * // becomes (where {%q0, %q1} map to {%q0_in, %q1_in}): + * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit + * // state updated: {%q0, %q1} now map to {%q0_out, %q1_out} + * ``` + */ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 2d169bf4d9..d03e154bdb 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -392,7 +392,17 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } }; -// Temporary implementation of XOp conversion +/** + * @brief Converts quartz.x operation to QIR x + * + * @details + * Example transformation: + * ```mlir + * quartz.x %q : !quartz.qubit + * // becomes: + * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () + * ``` + */ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -416,7 +426,17 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { } }; -// Temporary implementation of RXOp conversion +/** + * @brief Converts quartz.rx operation to QIR RX + * + * @details + * Example transformation: + * ```mlir + * quartz.rx %q(%theta) : !quartz.qubit + * // becomes: + * llvm.call @__quantum__qis__rx__body(%q, %theta) : (!llvm.ptr, f64) -> () + * ``` + */ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -450,7 +470,18 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { } }; -// Temporary implementation of U2Op conversion +/** + * @brief Converts quartz.u2 operation to QIR U2 + * + * @details + * Example transformation: + * ```mlir + * quartz.u2 %q(%phi, %lambda) : !quartz.qubit + * // becomes: + * llvm.call @__quantum__qis__u2__body(%q, %phi, %lambda) : (!llvm.ptr, f64, + * f64) -> () + * ``` + */ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -494,7 +525,18 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { } }; -// Temporary implementation of SWAPOp conversion +/** + * @brief Converts quartz.swap operation to QIR SWAP + * + * @details + * Example transformation: + * ```mlir + * quartz.swap %q1, %q2 : !quartz.qubit, !quartz.qubit + * // becomes: + * llvm.call @__quantum__qis__swap__body(%q1, %q2) : (!llvm.ptr, !llvm.ptr) -> + * () + * ``` + */ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; From 6f7cf382efed5abf5a792d6af6a57dec98a2e121 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:07:41 +0100 Subject: [PATCH 127/419] Fix linter errors --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 2 +- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 9 ++++----- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 1 + mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 9 ++++----- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 4eef565750..d35484e8b0 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include namespace mlir::flux { diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 2f234ddaf8..f009ed14c2 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" // IWYU pragma: associated #include "mlir/Dialect/Utils/MatrixUtils.h" +#include "mlir/Dialect/Utils/ParameterDescriptor.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -19,9 +20,7 @@ #include // IWYU pragma: end_keep -#include #include -#include #include #include #include @@ -154,7 +153,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } LogicalResult RXOp::verify() { - if (!(getTheta().has_value() ^ bool(getThetaDyn()))) { + if (getTheta().has_value() == (getThetaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic theta"); } return success(); @@ -189,10 +188,10 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (!(getPhi().has_value() ^ bool(getPhiDyn()))) { + if (getPhi().has_value() == (getPhiDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic phi"); } - if (!(getLambda().has_value() ^ bool(getLambdaDyn()))) { + if (getLambda().has_value() == (getLambdaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic lambda"); } return success(); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 2c9b4089f4..e79e4a88af 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace mlir::qir { diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 3916f4e115..c1eda8ea1e 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" // IWYU pragma: associated #include "mlir/Dialect/Utils/MatrixUtils.h" +#include "mlir/Dialect/Utils/ParameterDescriptor.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -18,9 +19,7 @@ #include // IWYU pragma: end_keep -#include #include -#include #include #include #include @@ -152,7 +151,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } LogicalResult RXOp::verify() { - if (!(getTheta().has_value() ^ bool(getThetaDyn()))) { + if (getTheta().has_value() == (getThetaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic theta"); } return success(); @@ -187,10 +186,10 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } LogicalResult U2Op::verify() { - if (!(getPhi().has_value() ^ bool(getPhiDyn()))) { + if (getPhi().has_value() == (getPhiDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic phi"); } - if (!(getLambda().has_value() ^ bool(getLambdaDyn()))) { + if (getLambda().has_value() == (getLambdaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic lambda"); } return success(); From b2f0395701af763fc256440f820209f83acb5cd4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:08:18 +0100 Subject: [PATCH 128/419] Fix U2 matrix --- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 6677273ffd..29cfaccaab 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -31,10 +31,10 @@ inline llvm::ArrayRef> getMatrixRX(double theta) { inline llvm::ArrayRef> getMatrixU2(double phi, double lambda) { - const std::complex m00(1.0, 0.0); - const std::complex m01 = -std::exp(1i * lambda); - const std::complex m10 = -std::exp(1i * phi); - const std::complex m11 = std::exp(1i * (phi + lambda)); + const std::complex m00(1.0 / std::sqrt(2), 0.0); + const std::complex m01 = -std::exp(1i * lambda) / std::sqrt(2); + const std::complex m10 = std::exp(1i * phi) / std::sqrt(2); + const std::complex m11 = std::exp(1i * (phi + lambda)) / std::sqrt(2); return {m00, m01, m10, m11}; } From 1454052d389d75ac827f3e845cf291169cc1ee96 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:15:40 +0100 Subject: [PATCH 129/419] Implement simple canonicalization patterns for X and RX --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 3 + mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 76 ++++++++++++++- .../pipeline/test_compiler_pipeline.cpp | 94 +++++++++++++------ 3 files changed, 139 insertions(+), 34 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index ec81e56cc9..ca67164e10 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -416,6 +416,8 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; + + let hasCanonicalizer = 1; } def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { @@ -456,6 +458,7 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> }]>, ]; + let hasCanonicalizer = 1; let hasVerifier = 1; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index f009ed14c2..d3e065b950 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -211,10 +211,10 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { //===----------------------------------------------------------------------===// namespace { + /** - * @class RemoveAllocDeallocPair - * @brief A class designed to identify and remove matching allocation and - * deallocation pairs without operations between them. + * @brief Remove matching allocation and deallocation pairs without operations + * between them. */ struct RemoveAllocDeallocPair final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; @@ -234,6 +234,9 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { } }; +/** + * @brief Remove reset operations that immediately follow an allocation. + */ struct RemoveResetAfterAlloc final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; @@ -250,6 +253,63 @@ struct RemoveResetAfterAlloc final : OpRewritePattern { return success(); } }; + +/** + * @brief Remove subsequent X operations on the same qubit. + */ +struct RemoveSubsequentX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XOp xOp, + PatternRewriter& rewriter) const override { + auto prevOp = xOp.getQubitIn().getDefiningOp(); + + // Check if the predecessor is an XOp + if (!prevOp) { + return failure(); + } + + // Remove both XOps + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(xOp, xOp.getQubitIn()); + return success(); + } +}; + +/** + * @brief Merge subsequent RX operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXOp rxOp, + PatternRewriter& rewriter) const override { + auto prevOp = rxOp.getQubitIn().getDefiningOp(); + + // Check if the predecessor is an RXOp + if (!prevOp) { + return failure(); + } + + // Check if both RXOps have static angles + const auto& theta = prevOp.getParameter(0); + const auto& prevTheta = rxOp.getParameter(0); + if (!theta.isStatic() || !prevTheta.isStatic()) { + return failure(); + } + + // Merge the two RXOps + const auto newTheta = theta.getValueDouble() + prevTheta.getValueDouble(); + auto newOp = + rewriter.create(rxOp.getLoc(), rxOp.getQubitIn(), newTheta); + + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(rxOp, newOp.getResult()); + return success(); + } +}; + } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -261,3 +321,13 @@ void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); } + +void XOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 51b2462062..b8f0ce02ce 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -869,31 +869,49 @@ TEST_F(CompilerPipelineTest, X) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.x(0); + qc.x(0); + qc.x(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.x(q[0]); - }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.x(q[0]); + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + b.x(q); + b.x(q); + b.x(q); }); - const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.x(q[0]); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + auto q1 = b.x(q0); + auto q2 = b.x(q1); + b.x(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + b.x(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + b.x(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + auto q = reg[0]; + b.x(q); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = quartzExpected.get(), - .qirConversion = qirExpected.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -901,31 +919,45 @@ TEST_F(CompilerPipelineTest, RX) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.rx(1.0, 0); + qc.rx(0.5, 0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.rx(1.0, q[0]); - }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.rx(1.0, q[0]); + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + b.rx(1.0, q); + b.rx(0.5, q); }); - const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + auto q1 = b.rx(1.0, q0); + b.rx(0.5, q1); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + b.rx(1.5, q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + b.rx(1.5, q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.rx(1.0, q[0]); + b.rx(1.5, q[0]); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = quartzExpected.get(), - .qirConversion = qirExpected.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } From fe19940bb87377773a6c622fd8dd26b557f854f3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 17:23:26 +0100 Subject: [PATCH 130/419] Move some trait logic to header files --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 36 ++++++++++--- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 54 ++++--------------- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 35 +++++++++--- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 54 ++++--------------- 4 files changed, 75 insertions(+), 104 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index d96d45221e..52309c0a45 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -51,13 +51,35 @@ namespace mlir::flux { -template -class TargetArityTrait - : public mlir::OpTrait::TraitBase {}; - -template -class ParameterArityTrait - : public mlir::OpTrait::TraitBase {}; +template class TargetArityTrait { +public: + template + class Impl : public mlir::OpTrait::TraitBase { + public: + size_t getNumQubits() { return n; } + size_t getNumTargets() { return n; } + size_t getNumControls() { return 0; } + size_t getNumPosControls() { return 0; } + size_t getNumNegControls() { return 0; } + + Value getPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + }; +}; + +template class ParameterArityTrait { +public: + template + class Impl : public mlir::OpTrait::TraitBase { + public: + size_t getNumParams() { return n; } + }; +}; } // namespace mlir::flux diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index ca67164e10..b0fac394ec 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -241,18 +241,12 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { // Traits //===----------------------------------------------------------------------===// -class TargetArityTrait : NativeOpTrait<"TargetArityTrait"> { +class TargetArityTrait : ParamNativeOpTrait<"TargetArityTrait", !cast(n)> { let cppNamespace = "::mlir::flux"; } -def OneTarget : TargetArityTrait { +def OneTarget : TargetArityTrait<1> { let extraConcreteClassDeclaration = [{ - size_t getNumQubits() { return 1; } - size_t getNumTargets() { return 1; } - size_t getNumControls() { return 0; } - size_t getNumPosControls() { return 0; } - size_t getNumNegControls() { return 0; } - Value getQubit(size_t i) { if (i == 0) { return getQubitIn(); @@ -264,14 +258,6 @@ def OneTarget : TargetArityTrait { return getQubit(i); } - Value getPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } - - Value getNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } - Value getInput(size_t i) { return getQubit(i); } @@ -299,14 +285,8 @@ def OneTarget : TargetArityTrait { }]; } -def TwoTarget : TargetArityTrait { +def TwoTarget : TargetArityTrait<2> { let extraConcreteClassDeclaration = [{ - size_t getNumQubits() { return 2; } - size_t getNumTargets() { return 2; } - size_t getNumControls() { return 0; } - size_t getNumPosControls() { return 0; } - size_t getNumNegControls() { return 0; } - Value getQubit(size_t i) { if (i == 0) { return getQubit0In(); @@ -321,14 +301,6 @@ def TwoTarget : TargetArityTrait { return getQubit(i); } - Value getPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } - - Value getNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } - Value getInput(size_t i) { return getQubit(i); } @@ -365,14 +337,12 @@ def TwoTarget : TargetArityTrait { }]; } -class ParameterArityTrait : NativeOpTrait<"ParameterArityTrait"> { +class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { let cppNamespace = "::mlir::flux"; } -def ZeroParameter : ParameterArityTrait { +def ZeroParameter : ParameterArityTrait<0> { let extraConcreteClassDeclaration = [{ - size_t getNumParams() { return 0; } - ::mlir::utils::ParameterDescriptor getParameter(size_t i) { llvm_unreachable("Operation does not have parameters"); } @@ -381,17 +351,11 @@ def ZeroParameter : ParameterArityTrait { }]; } -def OneParameter : ParameterArityTrait { - let extraConcreteClassDeclaration = [{ - size_t getNumParams() { return 1; } - }]; -} +def OneParameter : ParameterArityTrait<1>; -def TwoParameter : ParameterArityTrait { - let extraConcreteClassDeclaration = [{ - size_t getNumParams() { return 2; } - }]; -} +def TwoParameter : ParameterArityTrait<2>; + +def ThreeParameter : ParameterArityTrait<3>; //===----------------------------------------------------------------------===// // Unitary Operations diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index fb7fbd0bca..0bd028744f 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -51,13 +51,34 @@ namespace mlir::quartz { -template -class TargetArityTrait - : public mlir::OpTrait::TraitBase {}; - -template -class ParameterArityTrait - : public mlir::OpTrait::TraitBase {}; +template class TargetArityTrait { +public: + template + class Impl : public mlir::OpTrait::TraitBase { + public: + size_t getNumQubits() { return n; } + size_t getNumTargets() { return n; } + size_t getNumControls() { return 0; } + size_t getNumPosControls() { return 0; } + size_t getNumNegControls() { return 0; } + Value getPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + }; +}; + +template class ParameterArityTrait { +public: + template + class Impl : public mlir::OpTrait::TraitBase { + public: + size_t getNumParams() { return n; } + }; +}; } // namespace mlir::quartz diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 0fd9160fb0..f142096e52 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -228,18 +228,12 @@ def ResetOp : QuartzOp<"reset"> { // Traits //===----------------------------------------------------------------------===// -class TargetArityTrait : NativeOpTrait<"TargetArityTrait"> { +class TargetArityTrait : ParamNativeOpTrait<"TargetArityTrait", !cast(n)> { let cppNamespace = "::mlir::quartz"; } -def OneTarget : TargetArityTrait { +def OneTarget : TargetArityTrait<1> { let extraConcreteClassDeclaration = [{ - size_t getNumQubits() { return 1; } - size_t getNumTargets() { return 1; } - size_t getNumControls() { return 0; } - size_t getNumPosControls() { return 0; } - size_t getNumNegControls() { return 0; } - Value getQubit(size_t i) { if (i == 0) { return getQubit(); @@ -250,25 +244,11 @@ def OneTarget : TargetArityTrait { Value getTarget(size_t i) { return getQubit(i); } - - Value getPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } - - Value getNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } }]; } -def TwoTarget : TargetArityTrait { +def TwoTarget : TargetArityTrait<2> { let extraConcreteClassDeclaration = [{ - size_t getNumQubits() { return 2; } - size_t getNumTargets() { return 2; } - size_t getNumControls() { return 0; } - size_t getNumPosControls() { return 0; } - size_t getNumNegControls() { return 0; } - Value getQubit(size_t i) { if (i == 0) { return getQubit0(); @@ -282,25 +262,15 @@ def TwoTarget : TargetArityTrait { Value getTarget(size_t i) { return getQubit(i); } - - Value getPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } - - Value getNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); - } }]; } -class ParameterArityTrait : NativeOpTrait<"ParameterArityTrait"> { +class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { let cppNamespace = "::mlir::quartz"; } -def ZeroParameter : ParameterArityTrait { +def ZeroParameter : ParameterArityTrait<0> { let extraConcreteClassDeclaration = [{ - size_t getNumParams() { return 0; } - ::mlir::utils::ParameterDescriptor getParameter(size_t i) { llvm_unreachable("Operation does not have parameters"); } @@ -309,17 +279,11 @@ def ZeroParameter : ParameterArityTrait { }]; } -def OneParameter : ParameterArityTrait { - let extraConcreteClassDeclaration = [{ - size_t getNumParams() { return 1; } - }]; -} +def OneParameter : ParameterArityTrait<1>; -def TwoParameter : ParameterArityTrait { - let extraConcreteClassDeclaration = [{ - size_t getNumParams() { return 2; } - }]; -} +def TwoParameter : ParameterArityTrait<2>; + +def ThreeParameter : ParameterArityTrait<2>; //===----------------------------------------------------------------------===// // Unitary Operations From d117446a1cac2a4b8740aa8bb8f5340b0fb9066a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:07:28 +0100 Subject: [PATCH 131/419] Improve querying of inputs and outputs in Flux interface --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 12 +++++- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 30 ++++++++------ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 41 +++++++++---------- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 1 + 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 52309c0a45..97a33f56e9 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -62,11 +62,19 @@ template class TargetArityTrait { size_t getNumPosControls() { return 0; } size_t getNumNegControls() { return 0; } - Value getPosControl(size_t i) { + Value getInputPosControl(size_t i) { llvm_unreachable("Operation does not have controls"); } - Value getNegControl(size_t i) { + Value getOutputPosControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getInputNegControl(size_t i) { + llvm_unreachable("Operation does not have controls"); + } + + Value getOutputNegControl(size_t i) { llvm_unreachable("Operation does not have controls"); } }; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index b06b4ca76a..efce833cc6 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -51,30 +51,36 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "size_t", "getNumNegControls", (ins) >, InterfaceMethod< - "Returns the i-th qubit (targets + controls combined).", - "Value", "getQubit", (ins "size_t":$i) + "Returns the i-th input qubit (targets + controls combined).", + "Value", "getInputQubit", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th output qubit (targets + controls combined).", + "Value", "getOutputQubit", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th target input qubit.", - "Value", "getTarget", (ins "size_t":$i) + "Value", "getInputTarget", (ins "size_t":$i) + >, + InterfaceMethod< + "Returns the i-th target output qubit.", + "Value", "getOutputTarget", (ins "size_t":$i) >, InterfaceMethod< "Returns the i-th positive control input qubit.", - "Value", "getPosControl", (ins "size_t":$i) + "Value", "getInputPosControl", (ins "size_t":$i) >, InterfaceMethod< - "Returns the i-th negative control input qubit.", - "Value", "getNegControl", (ins "size_t":$i) + "Returns the i-th positive control output qubit.", + "Value", "getOutputPosControl", (ins "size_t":$i) >, - - // Value semantics threading InterfaceMethod< - "Returns the i-th input qubit (targets + controls combined).", - "Value", "getInput", (ins "size_t":$i) + "Returns the i-th negative control input qubit.", + "Value", "getInputNegControl", (ins "size_t":$i) >, InterfaceMethod< - "Returns the i-th output qubit (targets + controls combined).", - "Value", "getOutput", (ins "size_t":$i) + "Returns the i-th negative control output qubit.", + "Value", "getOutputNegControl", (ins "size_t":$i) >, InterfaceMethod< "Returns the input qubit corresponding to the given output qubit.", diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index b0fac394ec..cb3fa4c7e1 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -247,28 +247,28 @@ class TargetArityTrait : ParamNativeOpTrait<"TargetArityTrait", !cast { let extraConcreteClassDeclaration = [{ - Value getQubit(size_t i) { + Value getInputQubit(size_t i) { if (i == 0) { return getQubitIn(); } llvm_unreachable("Operation has one input qubit"); } - Value getTarget(size_t i) { - return getQubit(i); - } - - Value getInput(size_t i) { - return getQubit(i); - } - - Value getOutput(size_t i) { + Value getOutputQubit(size_t i) { if (i == 0) { return getQubitOut(); } llvm_unreachable("Operation has one output qubit"); } + Value getInputTarget(size_t i) { + return getInputQubit(i); + } + + Value getOutputTarget(size_t i) { + return getOutputQubit(i); + } + Value getInputForOutput(Value output) { if (output == getQubitOut()) { return getQubitIn(); @@ -287,25 +287,16 @@ def OneTarget : TargetArityTrait<1> { def TwoTarget : TargetArityTrait<2> { let extraConcreteClassDeclaration = [{ - Value getQubit(size_t i) { + Value getInputQubit(size_t i) { if (i == 0) { return getQubit0In(); } if (i == 1) { return getQubit1In(); } - llvm_unreachable("Operation has two input qubits"); } - Value getTarget(size_t i) { - return getQubit(i); - } - - Value getInput(size_t i) { - return getQubit(i); - } - - Value getOutput(size_t i) { + Value getOutputQubit(size_t i) { if (i == 0) { return getQubit0Out(); } @@ -315,6 +306,14 @@ def TwoTarget : TargetArityTrait<2> { llvm_unreachable("Operation has two output qubits"); } + Value getInputTarget(size_t i) { + return getInputQubit(i); + } + + Value getOutputTarget(size_t i) { + return getOutputQubit(i); + } + Value getInputForOutput(Value output) { if (output == getQubit0Out()) { return getQubit0In(); diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 0bd028744f..8883661f01 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -61,6 +61,7 @@ template class TargetArityTrait { size_t getNumControls() { return 0; } size_t getNumPosControls() { return 0; } size_t getNumNegControls() { return 0; } + Value getPosControl(size_t i) { llvm_unreachable("Operation does not have controls"); } From f09c5817ece8643ce33225df4b2c2b70099ae092 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:40:44 +0100 Subject: [PATCH 132/419] Very initial implementation of CtrlOp in Quartz --- .../Quartz/Builder/QuartzProgramBuilder.h | 7 ++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 33 +++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 18 +++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 68 +++++++++++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 17 ++++- .../pipeline/test_compiler_pipeline.cpp | 22 ++++++ 6 files changed, 163 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 8ba2d4308d..7df1651778 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -297,6 +297,13 @@ class QuartzProgramBuilder { */ QuartzProgramBuilder& swap(Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// + // Modifiers + //===--------------------------------------------------------------------===// + + QuartzProgramBuilder& ctrl(ValueRange controls, + std::function body); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index f142096e52..76b304cf21 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -14,6 +14,7 @@ include "mlir/IR/BuiltinTypeInterfaces.td" include "mlir/IR/DialectBase.td" include "mlir/IR/EnumAttr.td" include "mlir/IR/OpBase.td" +include "mlir/IR/RegionKindInterface.td" include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" @@ -434,4 +435,36 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParam }]; } +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +def YieldOp : QuartzOp<"yield", traits = [Terminator]> { + let assemblyFormat = "attr-dict"; +} + +def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { + let arguments = (ins Variadic:$controls); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = "`(` $controls `)` $body attr-dict"; + + let extraClassDeclaration = [{ + UnitaryOpInterface getBodyUnitary(); + size_t getNumQubits(); + size_t getNumTargets(); + size_t getNumControls(); + size_t getNumPosControls(); + size_t getNumNegControls(); + Value getQubit(size_t i); + Value getTarget(size_t i); + Value getPosControl(size_t i); + Value getNegControl(size_t i); + size_t getNumParams(); + ::mlir::utils::ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ctrl"; } + }]; +} + #endif // QUARTZ_OPS diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 2a0f2e57cc..3d71866e51 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -169,6 +169,24 @@ QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { return *this; } +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +QuartzProgramBuilder& +QuartzProgramBuilder::ctrl(ValueRange controls, + std::function body) { + auto ctrlOp = builder.create(loc, controls); + + const mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&ctrlOp.getBody().emplaceBlock()); + + body(*this); + builder.create(loc); + + return *this; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index c1eda8ea1e..b1d7eb23b0 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -203,3 +203,71 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { RankedTensorType::get({4, 4}, ComplexType::get(Float64Type::get(ctx))); return DenseElementsAttr::get(type, getMatrixSWAP()); } + +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +UnitaryOpInterface CtrlOp::getBodyUnitary() { + return llvm::dyn_cast(&getBody().front().front()); +} + +size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } + +size_t CtrlOp::getNumTargets() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getNumTargets(); +} + +size_t CtrlOp::getNumControls() { return getNumPosControls(); } + +size_t CtrlOp::getNumPosControls() { return getControls().size(); } + +size_t CtrlOp::getNumNegControls() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getNumNegControls(); +} + +Value CtrlOp::getQubit(size_t i) { + if (i < getNumTargets()) { + return getTarget(i); + } + if (getNumTargets() <= i && i < getNumPosControls()) { + return getPosControl(i - getNumTargets()); + } + if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { + return getNegControl(i - getNumTargets() - getNumPosControls()); + } + llvm_unreachable("Invalid qubit index"); +} + +Value CtrlOp::getTarget(size_t i) { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getTarget(i); +} + +Value CtrlOp::getPosControl(size_t i) { return getControls()[i]; } + +Value CtrlOp::getNegControl(size_t i) { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getNegControl(i); +} + +size_t CtrlOp::getNumParams() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getNumParams(); +} + +bool CtrlOp::hasStaticUnitary() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.hasStaticUnitary(); +} + +ParameterDescriptor CtrlOp::getParameter(size_t i) { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getParameter(i); +} + +DenseElementsAttr CtrlOp::tryGetStaticMatrix() { + llvm_unreachable("Not implemented yet"); // TODO +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index c49831d7f6..d8884e51e2 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -222,8 +222,21 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, // Temporary implementation of XOp translation void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { - const auto& qubit = qubits[operation.getTargets()[0]]; - builder.x(qubit); + const auto& target = qubits[operation.getTargets()[0]]; + if (operation.getControls().size() == 0) { + builder.x(target); + } else { + llvm::SmallVector controls; + for (const auto& [control, type] : operation.getControls()) { + if (type == qc::Control::Type::Neg) { + continue; + } + controls.push_back(qubits[control]); + } + builder.ctrl(controls, [target](QuartzProgramBuilder& bodyBuilder) { + bodyBuilder.x(target); + }); + } } // Temporary implementation of RXOp translation diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index b8f0ce02ce..51af7551d1 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1157,4 +1157,26 @@ TEST_F(SimpleConversionTest, RXQuartzToQIR) { // EXPECT_TRUE(verify("Quartz to QIR", qirResult, qirExpected.get())); } +TEST_F(SimpleConversionTest, CX) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.cx(0, 1); + + const auto module = translateQuantumComputationToQuartz(context.get(), qc); + ASSERT_TRUE(module); + runCanonicalizationPasses(module.get()); + const auto moduleIR = captureIR(module.get()); + + const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl(q0, [q1](quartz::QuartzProgramBuilder& bodyBuilder) { + bodyBuilder.x(q1); + }); + }); + + EXPECT_TRUE(verify("Translation", moduleIR, expected.get())); +} + } // namespace From d1c44179dd1a610910e1eaefe95aef5b01a375a6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 30 Oct 2025 21:28:01 +0100 Subject: [PATCH 133/419] Fix linter errors --- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 5 +++-- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 8 +++++--- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 1 + .../Translation/TranslateQuantumComputationToQuartz.cpp | 3 ++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 7df1651778..ec6cd3761e 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -301,8 +301,9 @@ class QuartzProgramBuilder { // Modifiers //===--------------------------------------------------------------------===// - QuartzProgramBuilder& ctrl(ValueRange controls, - std::function body); + QuartzProgramBuilder& + ctrl(ValueRange controls, + const std::function& body); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 76b304cf21..53fe20313b 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -462,7 +462,7 @@ def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { size_t getNumParams(); ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); + static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; } diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 3d71866e51..ef3359a1d8 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include +#include #include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include #include namespace mlir::quartz { @@ -173,9 +175,9 @@ QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { // Modifiers //===----------------------------------------------------------------------===// -QuartzProgramBuilder& -QuartzProgramBuilder::ctrl(ValueRange controls, - std::function body) { +QuartzProgramBuilder& QuartzProgramBuilder::ctrl( + ValueRange controls, + const std::function& body) { auto ctrlOp = builder.create(loc, controls); const mlir::OpBuilder::InsertionGuard guard(builder); diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index b1d7eb23b0..7bc8b3bedf 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -20,6 +20,7 @@ // IWYU pragma: end_keep #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index d8884e51e2..f0e6482e14 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -12,6 +12,7 @@ #include "ir/QuantumComputation.hpp" #include "ir/Register.hpp" +#include "ir/operations/Control.hpp" #include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" @@ -223,7 +224,7 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { const auto& target = qubits[operation.getTargets()[0]]; - if (operation.getControls().size() == 0) { + if (operation.getControls().empty()) { builder.x(target); } else { llvm::SmallVector controls; From 1a121ff2a113bd93d7ed09475cb27ed94f99cdf9 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:36:28 +0100 Subject: [PATCH 134/419] Simplify lambdas --- .../Translation/TranslateQuantumComputationToQuartz.cpp | 4 +--- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index f0e6482e14..5f25ebdc29 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -234,9 +234,7 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } controls.push_back(qubits[control]); } - builder.ctrl(controls, [target](QuartzProgramBuilder& bodyBuilder) { - bodyBuilder.x(target); - }); + builder.ctrl(controls, [&](auto& builder) { builder.x(target); }); } } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 51af7551d1..d1620d4e6f 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1171,9 +1171,7 @@ TEST_F(SimpleConversionTest, CX) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; - b.ctrl(q0, [q1](quartz::QuartzProgramBuilder& bodyBuilder) { - bodyBuilder.x(q1); - }); + b.ctrl(q0, [&](auto& b) { b.x(q1); }); }); EXPECT_TRUE(verify("Translation", moduleIR, expected.get())); From 22379d96d3a193d5bcc7b7c9fa7338d4ba8851ae Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 31 Oct 2025 17:21:19 +0100 Subject: [PATCH 135/419] Very initial implementation of CtrlOp in Flux --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 10 ++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 47 ++++++++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 102 ++++++++++++++-- .../Flux/Builder/FluxProgramBuilder.cpp | 36 +++++- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 114 ++++++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 38 ++++-- 6 files changed, 329 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 878818ca5e..722a2d1b5d 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -326,6 +326,14 @@ class FluxProgramBuilder { */ std::pair swap(Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// + // Modifiers + //===--------------------------------------------------------------------===// + + std::pair, SmallVector> + ctrl(SmallVector controls, SmallVector targets, + const std::function(FluxProgramBuilder&)>& body); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// @@ -372,6 +380,8 @@ class FluxProgramBuilder { Location loc; private: + bool inRegion = false; + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index cb3fa4c7e1..3859faab26 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -512,4 +512,51 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParamet }]; } +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +def YieldOp : FluxOp<"yield", traits = [Terminator]> { + let arguments = (ins Variadic:$targets); + let assemblyFormat = "$targets attr-dict"; +} + +def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegments, AttrSizedResultSegments]> { + let arguments = (ins Variadic:$controls_in, Variadic:$targets_in); + let results = (outs Variadic:$controls_out, Variadic:$targets_out); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = "`(` $controls_in `_` $targets_in `)` $body attr-dict `:` type($controls_in) `_` type($targets_in) `->` type($controls_out) `_` type($targets_out)"; + + let extraClassDeclaration = [{ + UnitaryOpInterface getBodyUnitary(); + size_t getNumQubits(); + size_t getNumTargets(); + size_t getNumControls(); + size_t getNumPosControls(); + size_t getNumNegControls(); + Value getInputQubit(size_t i); + Value getOutputQubit(size_t i); + Value getInputTarget(size_t i); + Value getOutputTarget(size_t i); + Value getInputPosControl(size_t i); + Value getOutputPosControl(size_t i); + Value getInputNegControl(size_t i); + Value getOutputNegControl(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + size_t getNumParams(); + ::mlir::utils::ParameterDescriptor getParameter(size_t i); + bool hasStaticUnitary(); + static DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ctrl"; } + }]; + + let builders = [ + OpBuilder<(ins "ValueRange":$controls_in, "ValueRange":$targets_in), [{ + auto qubit_type = QubitType::get($_builder.getContext()); + build($_builder, $_state, qubit_type, qubit_type, controls_in, targets_in); + }]>, + ]; +} + #endif // FluxOPS diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index abd7cdb4fc..17ba46f777 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -373,14 +374,23 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { const auto& quartzQubit = op.getQubit(); - // Get the latest Flux qubit value from the state map - const auto& fluxQubit = getState().qubitMap[quartzQubit]; + auto inRegion = dyn_cast(op->getParentOp()) != nullptr; + + // Get the latest Flux qubit + Value fluxQubitIn; + if (inRegion) { + fluxQubitIn = rewriter.getRemappedValue(quartzQubit); + } else { + fluxQubitIn = getState().qubitMap[quartzQubit]; + } // Create flux.x operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubitIn); - // Update mapping: the Quartz qubit now corresponds to the output qubit - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + // Update state map + if (!inRegion) { + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } // Replace the Quartz operation with the Flux operation rewriter.replaceOp(op, fluxOp.getResult()); @@ -418,7 +428,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, theta, thetaDyn); - // Update mapping: the Quartz qubit now corresponds to the output qubit + // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); // Replace the Quartz operation with the Flux operation @@ -461,7 +471,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, phi, phiDyn, lambda, lambdaDyn); - // Update mapping: the Quartz qubit now corresponds to the output qubit + // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); // Replace the Quartz operation with the Flux operation @@ -501,7 +511,7 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { auto fluxOp = rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); - // Update mapping: the Quartz qubit now corresponds to the output qubit + // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); @@ -512,6 +522,80 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { } }; +struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::CtrlOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Get Flux controls from state map + const auto& quartzControls = op.getControls(); + SmallVector fluxControls; + fluxControls.reserve(quartzControls.size()); + for (const auto& quartzControl : quartzControls) { + fluxControls.push_back(getState().qubitMap[quartzControl]); + } + + // Get Flux targets from state map + SmallVector fluxTargets; + fluxTargets.reserve(op.getNumTargets()); + for (auto i = 0; i < op.getNumTargets(); ++i) { + const auto& quartzTarget = op.getTarget(i); + fluxTargets.push_back(getState().qubitMap[quartzTarget]); + } + + // Create flux.ctrl operation + auto fluxOp = + rewriter.create(op.getLoc(), fluxControls, fluxTargets); + + // Clone the body region from Quartz to Flux + IRMapping regionMap; + for (auto i = 0; i < op.getNumTargets(); ++i) { + regionMap.map(op.getTarget(i), fluxTargets[i]); + } + rewriter.cloneRegionBefore(op.getBody(), fluxOp.getBody(), + fluxOp.getBody().end(), regionMap); + + // Update state map + for (auto i = 0; i < op.getNumPosControls(); ++i) { + const auto& quartzControl = quartzControls[i]; + getState().qubitMap[quartzControl] = fluxOp.getControlsOut()[i]; + } + for (auto i = 0; i < op.getNumTargets(); ++i) { + const auto& quartzTarget = op.getTarget(i); + getState().qubitMap[quartzTarget] = fluxOp.getTargetsOut()[i]; + } + + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getOperands()); + + return success(); + } +}; + +struct ConvertQuartzYieldOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::YieldOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto ctrlOp = dyn_cast(op->getParentOp()); + auto unitaryOp = ctrlOp.getBodyUnitary(); + + SmallVector targets; + targets.reserve(unitaryOp.getNumTargets()); + for (auto i = 0; i < unitaryOp.getNumTargets(); ++i) { + auto yield = rewriter.getRemappedValue(unitaryOp.getOutputTarget(i)); + targets.push_back(yield); + } + + rewriter.replaceOpWithNewOp(op, targets); + + return success(); + } +}; + /** * @brief Pass implementation for Quartz-to-Flux conversion * @@ -559,6 +643,8 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index d35484e8b0..af3c6031df 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -176,7 +176,9 @@ Value FluxProgramBuilder::x(Value qubit) { const auto& qubitOut = xOp.getQubitOut(); // Update tracking - updateQubitTracking(qubit, qubitOut); + if (!inRegion) { + updateQubitTracking(qubit, qubitOut); + } return qubitOut; } @@ -238,6 +240,38 @@ std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { return {qubit0Out, qubit1Out}; } +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +std::pair, SmallVector> FluxProgramBuilder::ctrl( + SmallVector controls, SmallVector targets, + const std::function(FluxProgramBuilder&)>& body) { + auto ctrlOp = builder.create(loc, controls, targets); + + const mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&ctrlOp.getBody().emplaceBlock()); + + inRegion = true; + auto targetsYield = body(*this); + inRegion = false; + + builder.create(loc, targetsYield); + + // Update tracking + const auto& controlsOut = ctrlOp.getControlsOut(); + for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + updateQubitTracking(control, controlOut); + } + + const auto& targetsOut = ctrlOp.getTargetsOut(); + for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { + updateQubitTracking(target, targetOut); + } + + return {controlsOut, targetsOut}; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index d3e065b950..eaa15f2481 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -206,6 +206,120 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, getMatrixSWAP()); } +//===----------------------------------------------------------------------===// +// Modifiers +//===----------------------------------------------------------------------===// + +UnitaryOpInterface CtrlOp::getBodyUnitary() { + return llvm::dyn_cast(&getBody().front().front()); +} + +size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } + +size_t CtrlOp::getNumTargets() { return getTargetsIn().size(); } + +size_t CtrlOp::getNumControls() { + return getNumPosControls() + getNumNegControls(); +} + +size_t CtrlOp::getNumPosControls() { return getControlsIn().size(); } + +size_t CtrlOp::getNumNegControls() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getNumNegControls(); +} + +Value CtrlOp::getInputQubit(size_t i) { + if (i < getNumTargets()) { + return getInputTarget(i); + } + if (getNumTargets() <= i && i < getNumPosControls()) { + return getInputPosControl(i - getNumTargets()); + } + if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { + return getInputNegControl(i - getNumTargets() - getNumPosControls()); + } + llvm_unreachable("Invalid qubit index"); +} + +Value CtrlOp::getOutputQubit(size_t i) { + if (i < getNumTargets()) { + return getOutputTarget(i); + } + if (getNumTargets() <= i && i < getNumPosControls()) { + return getOutputPosControl(i - getNumTargets()); + } + if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { + return getOutputNegControl(i - getNumTargets() - getNumPosControls()); + } + llvm_unreachable("Invalid qubit index"); +} + +Value CtrlOp::getInputTarget(size_t i) { return getTargetsIn()[i]; } + +Value CtrlOp::getOutputTarget(size_t i) { return getTargetsOut()[i]; } + +Value CtrlOp::getInputPosControl(size_t i) { return getControlsIn()[i]; } + +Value CtrlOp::getOutputPosControl(size_t i) { return getControlsOut()[i]; } + +Value CtrlOp::getInputNegControl(size_t i) { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getInputNegControl(i); +} + +Value CtrlOp::getOutputNegControl(size_t i) { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getOutputNegControl(i); +} + +Value CtrlOp::getInputForOutput(Value output) { + for (auto i = 0; i < getNumPosControls(); ++i) { + if (output == getControlsOut()[i]) { + return getControlsIn()[i]; + } + } + for (auto i = 0; i < getNumTargets(); ++i) { + if (output == getTargetsOut()[i]) { + return getTargetsIn()[i]; + } + } + llvm_unreachable("Given qubit is not an output of the operation"); +} + +Value CtrlOp::getOutputForInput(Value input) { + for (auto i = 0; i < getNumPosControls(); ++i) { + if (input == getControlsIn()[i]) { + return getControlsOut()[i]; + } + } + for (auto i = 0; i < getNumTargets(); ++i) { + if (input == getTargetsIn()[i]) { + return getTargetsOut()[i]; + } + } + llvm_unreachable("Given qubit is not an input of the operation"); +} + +size_t CtrlOp::getNumParams() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getNumParams(); +} + +bool CtrlOp::hasStaticUnitary() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.hasStaticUnitary(); +} + +ParameterDescriptor CtrlOp::getParameter(size_t i) { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.getParameter(i); +} + +DenseElementsAttr CtrlOp::tryGetStaticMatrix() { + llvm_unreachable("Not implemented yet"); // TODO +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index d1620d4e6f..14b77de760 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1162,19 +1162,39 @@ TEST_F(SimpleConversionTest, CX) { qc.addQubitRegister(2, "q"); qc.cx(0, 1); - const auto module = translateQuantumComputationToQuartz(context.get(), qc); - ASSERT_TRUE(module); - runCanonicalizationPasses(module.get()); - const auto moduleIR = captureIR(module.get()); + const auto quartz = translateQuantumComputationToQuartz(context.get(), qc); + ASSERT_TRUE(quartz); + runCanonicalizationPasses(quartz.get()); + const auto quartzIR = captureIR(quartz.get()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl(q0, [&](auto& b) { b.x(q1); }); + }); + + EXPECT_TRUE(verify("Translation", quartzIR, quartzExpected.get())); + + PassManager pm(quartz.get().getContext()); + pm.addPass(createQuartzToFlux()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); + ASSERT_TRUE(pm.run(quartz.get()).succeeded()); + const auto fluxIR = captureIR(quartz.get()); + + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl(q0, [&](auto& b) { b.x(q1); }); + auto q0_0 = reg[0]; + auto q1_0 = reg[1]; + b.ctrl({q0_0}, {q1_0}, [&](auto& b) { + auto q1_1 = b.x(q1_0); + return SmallVector{q1_1}; + }); }); - EXPECT_TRUE(verify("Translation", moduleIR, expected.get())); + EXPECT_TRUE(verify("Quartz to Flux", fluxIR, fluxExpected.get())); } } // namespace From eae39bbb3acc76da9eaae3b3702915b84a941a65 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 31 Oct 2025 17:21:33 +0100 Subject: [PATCH 136/419] Minor Quartz fixes --- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 1 + mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index ef3359a1d8..84a132dd64 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -184,6 +184,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::ctrl( builder.setInsertionPointToStart(&ctrlOp.getBody().emplaceBlock()); body(*this); + builder.create(loc); return *this; diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 7bc8b3bedf..305da37651 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -220,7 +220,9 @@ size_t CtrlOp::getNumTargets() { return unitaryOp.getNumTargets(); } -size_t CtrlOp::getNumControls() { return getNumPosControls(); } +size_t CtrlOp::getNumControls() { + return getNumPosControls() + getNumNegControls(); +} size_t CtrlOp::getNumPosControls() { return getControls().size(); } From 3f5077c5864f5dbeb12ff3c0f64efe27539929fc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:31:11 +0100 Subject: [PATCH 137/419] Fix linter errors --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 17 +++++++++-------- .../Dialect/Flux/Builder/FluxProgramBuilder.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 9 +++++---- .../pipeline/test_compiler_pipeline.cpp | 10 +++++----- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 17ba46f777..f3a64025f0 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -526,7 +526,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::CtrlOp op, OpAdaptor adaptor, + matchAndRewrite(quartz::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { // Get Flux controls from state map const auto& quartzControls = op.getControls(); @@ -539,7 +539,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { // Get Flux targets from state map SmallVector fluxTargets; fluxTargets.reserve(op.getNumTargets()); - for (auto i = 0; i < op.getNumTargets(); ++i) { + for (size_t i = 0; i < op.getNumTargets(); ++i) { const auto& quartzTarget = op.getTarget(i); fluxTargets.push_back(getState().qubitMap[quartzTarget]); } @@ -550,18 +550,18 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { // Clone the body region from Quartz to Flux IRMapping regionMap; - for (auto i = 0; i < op.getNumTargets(); ++i) { + for (size_t i = 0; i < op.getNumTargets(); ++i) { regionMap.map(op.getTarget(i), fluxTargets[i]); } rewriter.cloneRegionBefore(op.getBody(), fluxOp.getBody(), fluxOp.getBody().end(), regionMap); // Update state map - for (auto i = 0; i < op.getNumPosControls(); ++i) { + for (size_t i = 0; i < op.getNumPosControls(); ++i) { const auto& quartzControl = quartzControls[i]; getState().qubitMap[quartzControl] = fluxOp.getControlsOut()[i]; } - for (auto i = 0; i < op.getNumTargets(); ++i) { + for (size_t i = 0; i < op.getNumTargets(); ++i) { const auto& quartzTarget = op.getTarget(i); getState().qubitMap[quartzTarget] = fluxOp.getTargetsOut()[i]; } @@ -578,15 +578,16 @@ struct ConvertQuartzYieldOp final using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::YieldOp op, OpAdaptor adaptor, + matchAndRewrite(quartz::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto ctrlOp = dyn_cast(op->getParentOp()); auto unitaryOp = ctrlOp.getBodyUnitary(); SmallVector targets; targets.reserve(unitaryOp.getNumTargets()); - for (auto i = 0; i < unitaryOp.getNumTargets(); ++i) { - auto yield = rewriter.getRemappedValue(unitaryOp.getOutputTarget(i)); + for (size_t i = 0; i < unitaryOp.getNumTargets(); ++i) { + const auto& yield = + rewriter.getRemappedValue(unitaryOp.getOutputTarget(i)); targets.push_back(yield); } diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index af3c6031df..51e77cd629 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index eaa15f2481..2371f80253 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -21,6 +21,7 @@ // IWYU pragma: end_keep #include +#include #include #include #include @@ -274,12 +275,12 @@ Value CtrlOp::getOutputNegControl(size_t i) { } Value CtrlOp::getInputForOutput(Value output) { - for (auto i = 0; i < getNumPosControls(); ++i) { + for (size_t i = 0; i < getNumPosControls(); ++i) { if (output == getControlsOut()[i]) { return getControlsIn()[i]; } } - for (auto i = 0; i < getNumTargets(); ++i) { + for (size_t i = 0; i < getNumTargets(); ++i) { if (output == getTargetsOut()[i]) { return getTargetsIn()[i]; } @@ -288,12 +289,12 @@ Value CtrlOp::getInputForOutput(Value output) { } Value CtrlOp::getOutputForInput(Value input) { - for (auto i = 0; i < getNumPosControls(); ++i) { + for (size_t i = 0; i < getNumPosControls(); ++i) { if (input == getControlsIn()[i]) { return getControlsOut()[i]; } } - for (auto i = 0; i < getNumTargets(); ++i) { + for (size_t i = 0; i < getNumTargets(); ++i) { if (input == getTargetsIn()[i]) { return getTargetsOut()[i]; } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 14b77de760..8058f04d70 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1186,11 +1186,11 @@ TEST_F(SimpleConversionTest, CX) { const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0_0 = reg[0]; - auto q1_0 = reg[1]; - b.ctrl({q0_0}, {q1_0}, [&](auto& b) { - auto q1_1 = b.x(q1_0); - return SmallVector{q1_1}; + auto q0a = reg[0]; + auto q1a = reg[1]; + b.ctrl({q0a}, {q1a}, [&](auto& b) { + auto q1b = b.x(q1a); + return SmallVector{q1b}; }); }); From 9ee77e6be9fae4a26448a4a5bb737b753fec596a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:35:38 +0100 Subject: [PATCH 138/419] Use llvm::isa instead of dyn_cast --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index f3a64025f0..45d89952c3 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -14,6 +14,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include +#include #include #include #include @@ -374,7 +375,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { const auto& quartzQubit = op.getQubit(); - auto inRegion = dyn_cast(op->getParentOp()) != nullptr; + auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit Value fluxQubitIn; From 991c15fdc5a154967358b7f5e8eaaee0b39d3eb0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:13:22 +0100 Subject: [PATCH 139/419] Add missing header --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 45d89952c3..e37384b52f 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include #include #include #include From 572e8691cfed70f826ec51cfe3d4fc100ee85059 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:50:16 +0100 Subject: [PATCH 140/419] Fix Quartz-to-Flux conversion --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index e37384b52f..83416ed3d2 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -374,15 +374,16 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubit(); - auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit - Value fluxQubitIn; + Value quartzQubit = nullptr; + Value fluxQubitIn = nullptr; if (inRegion) { + quartzQubit = op->getOperand(0); fluxQubitIn = rewriter.getRemappedValue(quartzQubit); } else { + quartzQubit = op.getQubit(); fluxQubitIn = getState().qubitMap[quartzQubit]; } From d2f80782e0c7d18bb0470fa31b3bca8e7040cae2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:08:37 +0100 Subject: [PATCH 141/419] Use replaceOpWithNewOp to hopefully make Windows happier --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 83416ed3d2..bebb55c298 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -388,16 +388,13 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { } // Create flux.x operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubitIn); + auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxQubitIn); // Update state map if (!inRegion) { getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); } - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getResult()); - return success(); } }; @@ -429,14 +426,11 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { // Create flux.rx operation (consumes input, produces output) auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, theta, thetaDyn); + rewriter.replaceOpWithNewOp(op, fluxQubit, theta, thetaDyn); // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getResult()); - return success(); } }; @@ -471,15 +465,12 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { const auto& lambdaDyn = op.getLambdaDyn(); // Create flux.u2 operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, phi, - phiDyn, lambda, lambdaDyn); + auto fluxOp = rewriter.replaceOpWithNewOp( + op, fluxQubit, phi, phiDyn, lambda, lambdaDyn); // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getResult()); - return success(); } }; @@ -512,15 +503,12 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { // Create flux.swap operation (consumes input, produces output) auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); + rewriter.replaceOpWithNewOp(op, fluxQubit0, fluxQubit1); // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getOperands()); - return success(); } }; @@ -548,8 +536,8 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { } // Create flux.ctrl operation - auto fluxOp = - rewriter.create(op.getLoc(), fluxControls, fluxTargets); + auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxControls, + fluxTargets); // Clone the body region from Quartz to Flux IRMapping regionMap; @@ -569,9 +557,6 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { getState().qubitMap[quartzTarget] = fluxOp.getTargetsOut()[i]; } - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getOperands()); - return success(); } }; From cfa2c967435003688edcc541f0d9b0428b26a606 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:45:04 +0100 Subject: [PATCH 142/419] Add debug statements to narrow down Windows failures --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 2371f80253..96aac4939a 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -343,8 +343,12 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { } // Remove the AllocOp and the DeallocOp + llvm::errs() << "Removing AllocOp...\n"; rewriter.eraseOp(allocOp); + llvm::errs() << "Removed AllocOp\n"; + llvm::errs() << "Removing DeallocOp...\n"; rewriter.eraseOp(deallocOp); + llvm::errs() << "Removed DeallocOp\n"; return success(); } }; From 436b4df3c5fa6b3125685e4c921ab8ffe84043d9 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:07:32 +0100 Subject: [PATCH 143/419] Remove DeallocOp before AllocOp --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 96aac4939a..a57ba9f0fd 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -343,12 +343,12 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { } // Remove the AllocOp and the DeallocOp - llvm::errs() << "Removing AllocOp...\n"; - rewriter.eraseOp(allocOp); - llvm::errs() << "Removed AllocOp\n"; llvm::errs() << "Removing DeallocOp...\n"; rewriter.eraseOp(deallocOp); llvm::errs() << "Removed DeallocOp\n"; + llvm::errs() << "Removing AllocOp...\n"; + rewriter.eraseOp(allocOp); + llvm::errs() << "Removed AllocOp\n"; return success(); } }; From 246f2297bc041767374e25e326d7bbb2f57a59dc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:04:27 +0100 Subject: [PATCH 144/419] Do not misuse llvm_unreachable() --- .../include/mlir/Dialect/Flux/IR/FluxDialect.h | 8 ++++---- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 18 +++++++++--------- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 4 ++-- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 8 ++++---- .../mlir/Dialect/Utils/ParameterDescriptor.h | 3 ++- .../Flux/Builder/FluxProgramBuilder.cpp | 2 +- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 14 +++++++------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 3 ++- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 8 ++++---- 9 files changed, 35 insertions(+), 33 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 97a33f56e9..7f81961cf0 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -63,19 +63,19 @@ template class TargetArityTrait { size_t getNumNegControls() { return 0; } Value getInputPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); + llvm::report_fatal_error("Operation does not have controls"); } Value getOutputPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); + llvm::report_fatal_error("Operation does not have controls"); } Value getInputNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); + llvm::report_fatal_error("Operation does not have controls"); } Value getOutputNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); + llvm::report_fatal_error("Operation does not have controls"); } }; }; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 3859faab26..c9608354b8 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -251,14 +251,14 @@ def OneTarget : TargetArityTrait<1> { if (i == 0) { return getQubitIn(); } - llvm_unreachable("Operation has one input qubit"); + emitError("Operation has one input qubit"); } Value getOutputQubit(size_t i) { if (i == 0) { return getQubitOut(); } - llvm_unreachable("Operation has one output qubit"); + emitError("Operation has one output qubit"); } Value getInputTarget(size_t i) { @@ -273,14 +273,14 @@ def OneTarget : TargetArityTrait<1> { if (output == getQubitOut()) { return getQubitIn(); } - llvm_unreachable("Given qubit is not an output of the operation"); + emitError("Given qubit is not an output of the operation"); } Value getOutputForInput(Value input) { if (input == getQubitIn()) { return getQubitOut(); } - llvm_unreachable("Given qubit is not an input of the operation"); + emitError("Given qubit is not an input of the operation"); } }]; } @@ -303,7 +303,7 @@ def TwoTarget : TargetArityTrait<2> { if (i == 1) { return getQubit1Out(); } - llvm_unreachable("Operation has two output qubits"); + emitError("Operation has two output qubits"); } Value getInputTarget(size_t i) { @@ -321,7 +321,7 @@ def TwoTarget : TargetArityTrait<2> { if (output == getQubit1Out()) { return getQubit1In(); } - llvm_unreachable("Given qubit is not an output of the operation"); + emitError("Given qubit is not an output of the operation"); } Value getOutputForInput(Value input) { @@ -331,7 +331,7 @@ def TwoTarget : TargetArityTrait<2> { if (input == getQubit1In()) { return getQubit1Out(); } - llvm_unreachable("Given qubit is not an input of the operation"); + emitError("Given qubit is not an input of the operation"); } }]; } @@ -343,7 +343,7 @@ class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !ca def ZeroParameter : ParameterArityTrait<0> { let extraConcreteClassDeclaration = [{ ::mlir::utils::ParameterDescriptor getParameter(size_t i) { - llvm_unreachable("Operation does not have parameters"); + emitError("Operation does not have parameters"); } bool hasStaticUnitary() { return true; } @@ -547,7 +547,7 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen size_t getNumParams(); ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); - static DenseElementsAttr tryGetStaticMatrix(); + DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 8883661f01..407d940f45 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -63,11 +63,11 @@ template class TargetArityTrait { size_t getNumNegControls() { return 0; } Value getPosControl(size_t i) { - llvm_unreachable("Operation does not have controls"); + llvm::report_fatal_error("Operation does not have controls"); } Value getNegControl(size_t i) { - llvm_unreachable("Operation does not have controls"); + llvm::report_fatal_error("Operation does not have controls"); } }; }; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 53fe20313b..064f02255d 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -239,7 +239,7 @@ def OneTarget : TargetArityTrait<1> { if (i == 0) { return getQubit(); } - llvm_unreachable("Operation has one input qubit"); + emitError("Operation has one input qubit"); } Value getTarget(size_t i) { @@ -257,7 +257,7 @@ def TwoTarget : TargetArityTrait<2> { if (i == 1) { return getQubit1(); } - llvm_unreachable("Operation has two input qubits"); + emitError("Operation has two input qubits"); } Value getTarget(size_t i) { @@ -273,7 +273,7 @@ class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !ca def ZeroParameter : ParameterArityTrait<0> { let extraConcreteClassDeclaration = [{ ::mlir::utils::ParameterDescriptor getParameter(size_t i) { - llvm_unreachable("Operation does not have parameters"); + emitError("Operation does not have parameters"); } bool hasStaticUnitary() { return true; } @@ -462,7 +462,7 @@ def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { size_t getNumParams(); ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); - static DenseElementsAttr tryGetStaticMatrix(); + DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; } diff --git a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h index 999db444af..f68c110cc1 100644 --- a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h +++ b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h @@ -57,7 +57,8 @@ class ParameterDescriptor { */ double getValueDouble() const { if (isDynamic()) { - llvm_unreachable("Cannot get double value from dynamic parameter"); + llvm::report_fatal_error( + "Cannot get double value from dynamic parameter"); } return valueAttr.getValueAsDouble(); } diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 51e77cd629..617b66ced3 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -113,7 +113,7 @@ void FluxProgramBuilder::validateQubitValue(const Value qubit) const { llvm::errs() << "Error: Attempting to use an invalid qubit SSA value. " << "The value may have been consumed by a previous operation " << "or was never created through this builder.\n"; - llvm_unreachable( + llvm::report_fatal_error( "Invalid qubit value used (either consumed or not tracked)"); } } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index a57ba9f0fd..3963f11912 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -137,7 +137,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { return {getThetaAttr(), getThetaDyn()}; } - llvm_unreachable("RXOp has one parameter"); + emitError("RXOp has one parameter"); } bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } @@ -169,7 +169,7 @@ ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 1) { return {getLambdaAttr(), getLambdaDyn()}; } - llvm_unreachable("U2Op has two parameters"); + emitError("U2Op has two parameters"); } bool U2Op::hasStaticUnitary() { @@ -240,7 +240,7 @@ Value CtrlOp::getInputQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getInputNegControl(i - getNumTargets() - getNumPosControls()); } - llvm_unreachable("Invalid qubit index"); + emitError("Invalid qubit index"); } Value CtrlOp::getOutputQubit(size_t i) { @@ -253,7 +253,7 @@ Value CtrlOp::getOutputQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getOutputNegControl(i - getNumTargets() - getNumPosControls()); } - llvm_unreachable("Invalid qubit index"); + emitError("Invalid qubit index"); } Value CtrlOp::getInputTarget(size_t i) { return getTargetsIn()[i]; } @@ -285,7 +285,7 @@ Value CtrlOp::getInputForOutput(Value output) { return getTargetsIn()[i]; } } - llvm_unreachable("Given qubit is not an output of the operation"); + emitError("Given qubit is not an output of the operation"); } Value CtrlOp::getOutputForInput(Value input) { @@ -299,7 +299,7 @@ Value CtrlOp::getOutputForInput(Value input) { return getTargetsOut()[i]; } } - llvm_unreachable("Given qubit is not an input of the operation"); + emitError("Given qubit is not an input of the operation"); } size_t CtrlOp::getNumParams() { @@ -318,7 +318,7 @@ ParameterDescriptor CtrlOp::getParameter(size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - llvm_unreachable("Not implemented yet"); // TODO + emitError("Not implemented yet"); // TODO } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 84a132dd64..c0f7314f8e 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -201,7 +201,8 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { // deallocated llvm::errs() << "Error: Attempting to deallocate a qubit that was not " "allocated or has already been deallocated\n"; - llvm_unreachable("Double deallocation or invalid qubit deallocation"); + llvm::report_fatal_error( + "Double deallocation or invalid qubit deallocation"); } // Create the DeallocOp diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 305da37651..9dffbcf5d9 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -135,7 +135,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { return {getThetaAttr(), getThetaDyn()}; } - llvm_unreachable("RXOp has one parameter"); + emitError("RXOp has one parameter"); } bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } @@ -167,7 +167,7 @@ ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 1) { return {getLambdaAttr(), getLambdaDyn()}; } - llvm_unreachable("U2Op has two parameters"); + emitError("U2Op has two parameters"); } bool U2Op::hasStaticUnitary() { @@ -241,7 +241,7 @@ Value CtrlOp::getQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getNegControl(i - getNumTargets() - getNumPosControls()); } - llvm_unreachable("Invalid qubit index"); + emitError("Invalid qubit index"); } Value CtrlOp::getTarget(size_t i) { @@ -272,5 +272,5 @@ ParameterDescriptor CtrlOp::getParameter(size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - llvm_unreachable("Not implemented yet"); // TODO + emitError("Not implemented yet"); // TODO } From 68427eeed8ff238c071fbf5883eade8f3008e8ef Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:19:51 +0100 Subject: [PATCH 145/419] Enable Flux-to-Quartz conversion of CtrlOp --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 59 +++++++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 26 +++++--- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 6d7ce47c0f..feff854154 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -368,6 +368,63 @@ struct ConvertFluxSWAPOp final : OpConversionPattern { } }; +struct ConvertFluxCtrlOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::CtrlOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Get Quartz controls + const auto& quartzControls = adaptor.getControlsIn(); + + // Get Quartz targets + const auto& quartzTargets = adaptor.getTargetsIn(); + + // Create quartz.ctrl operation + auto fluxOp = rewriter.create(op.getLoc(), quartzControls); + + // Clone the body region from Flux to Quartz + rewriter.cloneRegionBefore(op.getBody(), fluxOp.getBody(), + fluxOp.getBody().end()); + + SmallVector quartzQubits; + quartzQubits.reserve(quartzControls.size() + quartzTargets.size()); + quartzQubits.append(quartzControls.begin(), quartzControls.end()); + quartzQubits.append(quartzTargets.begin(), quartzTargets.end()); + + // Replace the output qubits with the same quartz references + rewriter.replaceOp(op, quartzQubits); + + return success(); + } +}; + +struct ConvertFluxYieldOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::YieldOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto ctrlOp = dyn_cast(op->getParentOp()); + auto unitaryOp = ctrlOp.getBodyUnitary(); + + SmallVector targets; + targets.reserve(unitaryOp.getNumTargets()); + for (size_t i = 0; i < unitaryOp.getNumTargets(); ++i) { + const auto& yield = rewriter.getRemappedValue(unitaryOp.getTarget(i)); + targets.push_back(yield); + } + + // Create quartz.yield + rewriter.create(op.getLoc()); + + // Replace the output qubit with the same quartz references + rewriter.replaceOp(op, targets); + + return success(); + } +}; + /** * @brief Pass implementation for Flux-to-Quartz conversion * @@ -421,6 +478,8 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); + patterns.add(typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 8058f04d70..d1b392f826 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -10,6 +10,7 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" +#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" #include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" #include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" #include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" @@ -1162,10 +1163,10 @@ TEST_F(SimpleConversionTest, CX) { qc.addQubitRegister(2, "q"); qc.cx(0, 1); - const auto quartz = translateQuantumComputationToQuartz(context.get(), qc); - ASSERT_TRUE(quartz); - runCanonicalizationPasses(quartz.get()); - const auto quartzIR = captureIR(quartz.get()); + const auto module = translateQuantumComputationToQuartz(context.get(), qc); + ASSERT_TRUE(module); + runCanonicalizationPasses(module.get()); + const auto quartzIRInit = captureIR(module.get()); const auto quartzExpected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { @@ -1175,14 +1176,14 @@ TEST_F(SimpleConversionTest, CX) { b.ctrl(q0, [&](auto& b) { b.x(q1); }); }); - EXPECT_TRUE(verify("Translation", quartzIR, quartzExpected.get())); + EXPECT_TRUE(verify("Translation", quartzIRInit, quartzExpected.get())); - PassManager pm(quartz.get().getContext()); + PassManager pm(module.get().getContext()); pm.addPass(createQuartzToFlux()); pm.addPass(createCanonicalizerPass()); pm.addPass(createRemoveDeadValuesPass()); - ASSERT_TRUE(pm.run(quartz.get()).succeeded()); - const auto fluxIR = captureIR(quartz.get()); + ASSERT_TRUE(pm.run(module.get()).succeeded()); + const auto fluxIR = captureIR(module.get()); const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); @@ -1195,6 +1196,15 @@ TEST_F(SimpleConversionTest, CX) { }); EXPECT_TRUE(verify("Quartz to Flux", fluxIR, fluxExpected.get())); + + pm.clear(); + pm.addPass(createFluxToQuartz()); + pm.addPass(createCanonicalizerPass()); + pm.addPass(createRemoveDeadValuesPass()); + ASSERT_TRUE(pm.run(module.get()).succeeded()); + const auto quartzIRConv = captureIR(module.get()); + + EXPECT_TRUE(verify("Flux to Quartz", quartzIRConv, quartzExpected.get())); } } // namespace From e71bcfb55ef7b2f324d48a259ce6c5384ff9354b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:20:10 +0100 Subject: [PATCH 146/419] Streamline code annotations --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index bebb55c298..12ee8fb123 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -286,7 +286,7 @@ struct ConvertQuartzMeasureOp final // Get the latest Flux qubit value from the state map const auto& fluxQubit = getState().qubitMap[quartzQubit]; - // Create flux.measure operation (returns both output qubit and bit result) + // Create flux.measure (returns both output qubit and bit result) auto fluxOp = rewriter.create( op.getLoc(), fluxQubit, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); @@ -340,7 +340,7 @@ struct ConvertQuartzResetOp final // Get the latest Flux qubit value from the state map const auto& fluxQubit = getState().qubitMap[quartzQubit]; - // Create flux.reset operation (consumes input, produces output) + // Create flux.reset (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), qubitType, fluxQubit); @@ -349,7 +349,7 @@ struct ConvertQuartzResetOp final // Update mapping: the Quartz qubit now corresponds to the reset output getState().qubitMap[quartzQubit] = outFluxQubit; - // Erase the old operation (it has no results to replace) + // Erase the old (it has no results to replace) rewriter.eraseOp(op); return success(); @@ -387,7 +387,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { fluxQubitIn = getState().qubitMap[quartzQubit]; } - // Create flux.x operation (consumes input, produces output) + // Create flux.x (consumes input, produces output) auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxQubitIn); // Update state map @@ -424,7 +424,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { const auto& theta = op.getThetaAttr(); const auto& thetaDyn = op.getThetaDyn(); - // Create flux.rx operation (consumes input, produces output) + // Create flux.rx (consumes input, produces output) auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxQubit, theta, thetaDyn); @@ -464,7 +464,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { const auto& lambda = op.getLambdaAttr(); const auto& lambdaDyn = op.getLambdaDyn(); - // Create flux.u2 operation (consumes input, produces output) + // Create flux.u2 (consumes input, produces output) auto fluxOp = rewriter.replaceOpWithNewOp( op, fluxQubit, phi, phiDyn, lambda, lambdaDyn); @@ -501,7 +501,7 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { const auto& fluxQubit0 = getState().qubitMap[quartzQubit0]; const auto& fluxQubit1 = getState().qubitMap[quartzQubit1]; - // Create flux.swap operation (consumes input, produces output) + // Create flux.swap (consumes input, produces output) auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxQubit0, fluxQubit1); @@ -535,7 +535,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { fluxTargets.push_back(getState().qubitMap[quartzTarget]); } - // Create flux.ctrl operation + // Create flux.ctrl auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxControls, fluxTargets); @@ -579,6 +579,7 @@ struct ConvertQuartzYieldOp final targets.push_back(yield); } + // Create quartz.yield rewriter.replaceOpWithNewOp(op, targets); return success(); From 54f2da7e96b2babb6f8e070ad64caccc3d062251 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:22:30 +0100 Subject: [PATCH 147/419] Use report_fatal_error() --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 16 ++++++++-------- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 6 +++--- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 14 +++++++------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index c9608354b8..233965652e 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -251,14 +251,14 @@ def OneTarget : TargetArityTrait<1> { if (i == 0) { return getQubitIn(); } - emitError("Operation has one input qubit"); + llvm::report_fatal_error("Operation has one input qubit"); } Value getOutputQubit(size_t i) { if (i == 0) { return getQubitOut(); } - emitError("Operation has one output qubit"); + llvm::report_fatal_error("Operation has one output qubit"); } Value getInputTarget(size_t i) { @@ -273,14 +273,14 @@ def OneTarget : TargetArityTrait<1> { if (output == getQubitOut()) { return getQubitIn(); } - emitError("Given qubit is not an output of the operation"); + llvm::report_fatal_error("Given qubit is not an output of the operation"); } Value getOutputForInput(Value input) { if (input == getQubitIn()) { return getQubitOut(); } - emitError("Given qubit is not an input of the operation"); + llvm::report_fatal_error("Given qubit is not an input of the operation"); } }]; } @@ -303,7 +303,7 @@ def TwoTarget : TargetArityTrait<2> { if (i == 1) { return getQubit1Out(); } - emitError("Operation has two output qubits"); + llvm::report_fatal_error("Operation has two output qubits"); } Value getInputTarget(size_t i) { @@ -321,7 +321,7 @@ def TwoTarget : TargetArityTrait<2> { if (output == getQubit1Out()) { return getQubit1In(); } - emitError("Given qubit is not an output of the operation"); + llvm::report_fatal_error("Given qubit is not an output of the operation"); } Value getOutputForInput(Value input) { @@ -331,7 +331,7 @@ def TwoTarget : TargetArityTrait<2> { if (input == getQubit1In()) { return getQubit1Out(); } - emitError("Given qubit is not an input of the operation"); + llvm::report_fatal_error("Given qubit is not an input of the operation"); } }]; } @@ -343,7 +343,7 @@ class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !ca def ZeroParameter : ParameterArityTrait<0> { let extraConcreteClassDeclaration = [{ ::mlir::utils::ParameterDescriptor getParameter(size_t i) { - emitError("Operation does not have parameters"); + llvm::report_fatal_error("Operation does not have parameters"); } bool hasStaticUnitary() { return true; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 064f02255d..486027ce7a 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -239,7 +239,7 @@ def OneTarget : TargetArityTrait<1> { if (i == 0) { return getQubit(); } - emitError("Operation has one input qubit"); + llvm::report_fatal_error("Operation has one input qubit"); } Value getTarget(size_t i) { @@ -257,7 +257,7 @@ def TwoTarget : TargetArityTrait<2> { if (i == 1) { return getQubit1(); } - emitError("Operation has two input qubits"); + llvm::report_fatal_error("Operation has two input qubits"); } Value getTarget(size_t i) { @@ -273,7 +273,7 @@ class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !ca def ZeroParameter : ParameterArityTrait<0> { let extraConcreteClassDeclaration = [{ ::mlir::utils::ParameterDescriptor getParameter(size_t i) { - emitError("Operation does not have parameters"); + llvm::report_fatal_error("Operation does not have parameters"); } bool hasStaticUnitary() { return true; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 3963f11912..89b7a64ae6 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -137,7 +137,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { return {getThetaAttr(), getThetaDyn()}; } - emitError("RXOp has one parameter"); + llvm::report_fatal_error("RXOp has one parameter"); } bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } @@ -169,7 +169,7 @@ ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 1) { return {getLambdaAttr(), getLambdaDyn()}; } - emitError("U2Op has two parameters"); + llvm::report_fatal_error("U2Op has two parameters"); } bool U2Op::hasStaticUnitary() { @@ -240,7 +240,7 @@ Value CtrlOp::getInputQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getInputNegControl(i - getNumTargets() - getNumPosControls()); } - emitError("Invalid qubit index"); + llvm::report_fatal_error("Invalid qubit index"); } Value CtrlOp::getOutputQubit(size_t i) { @@ -253,7 +253,7 @@ Value CtrlOp::getOutputQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getOutputNegControl(i - getNumTargets() - getNumPosControls()); } - emitError("Invalid qubit index"); + llvm::report_fatal_error("Invalid qubit index"); } Value CtrlOp::getInputTarget(size_t i) { return getTargetsIn()[i]; } @@ -285,7 +285,7 @@ Value CtrlOp::getInputForOutput(Value output) { return getTargetsIn()[i]; } } - emitError("Given qubit is not an output of the operation"); + llvm::report_fatal_error("Given qubit is not an output of the operation"); } Value CtrlOp::getOutputForInput(Value input) { @@ -299,7 +299,7 @@ Value CtrlOp::getOutputForInput(Value input) { return getTargetsOut()[i]; } } - emitError("Given qubit is not an input of the operation"); + llvm::report_fatal_error("Given qubit is not an input of the operation"); } size_t CtrlOp::getNumParams() { @@ -318,7 +318,7 @@ ParameterDescriptor CtrlOp::getParameter(size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - emitError("Not implemented yet"); // TODO + llvm::report_fatal_error("Not implemented yet"); // TODO } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 9dffbcf5d9..18873248ea 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -135,7 +135,7 @@ ParameterDescriptor RXOp::getParameter(size_t i) { if (i == 0) { return {getThetaAttr(), getThetaDyn()}; } - emitError("RXOp has one parameter"); + llvm::report_fatal_error("RXOp has one parameter"); } bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } @@ -167,7 +167,7 @@ ParameterDescriptor U2Op::getParameter(size_t i) { if (i == 1) { return {getLambdaAttr(), getLambdaDyn()}; } - emitError("U2Op has two parameters"); + llvm::report_fatal_error("U2Op has two parameters"); } bool U2Op::hasStaticUnitary() { @@ -241,7 +241,7 @@ Value CtrlOp::getQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getNegControl(i - getNumTargets() - getNumPosControls()); } - emitError("Invalid qubit index"); + llvm::report_fatal_error("Invalid qubit index"); } Value CtrlOp::getTarget(size_t i) { @@ -272,5 +272,5 @@ ParameterDescriptor CtrlOp::getParameter(size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - emitError("Not implemented yet"); // TODO + llvm::report_fatal_error("Not implemented yet"); // TODO } From 3e7f2248c12754e184d44e21033e50eb84cfc12d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:47:39 +0100 Subject: [PATCH 148/419] Revert "Use replaceOpWithNewOp to hopefully make Windows happier" This reverts commit d2f80782e0c7d18bb0470fa31b3bca8e7040cae2. --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 12ee8fb123..a42b1d5da7 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -388,13 +388,16 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { } // Create flux.x (consumes input, produces output) - auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxQubitIn); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubitIn); // Update state map if (!inRegion) { getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); } + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getResult()); + return success(); } }; @@ -426,11 +429,14 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { // Create flux.rx (consumes input, produces output) auto fluxOp = - rewriter.replaceOpWithNewOp(op, fluxQubit, theta, thetaDyn); + rewriter.create(op.getLoc(), fluxQubit, theta, thetaDyn); // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getResult()); + return success(); } }; @@ -465,12 +471,15 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { const auto& lambdaDyn = op.getLambdaDyn(); // Create flux.u2 (consumes input, produces output) - auto fluxOp = rewriter.replaceOpWithNewOp( - op, fluxQubit, phi, phiDyn, lambda, lambdaDyn); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, phi, + phiDyn, lambda, lambdaDyn); // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getResult()); + return success(); } }; @@ -503,12 +512,15 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { // Create flux.swap (consumes input, produces output) auto fluxOp = - rewriter.replaceOpWithNewOp(op, fluxQubit0, fluxQubit1); + rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getOperands()); + return success(); } }; @@ -536,8 +548,8 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { } // Create flux.ctrl - auto fluxOp = rewriter.replaceOpWithNewOp(op, fluxControls, - fluxTargets); + auto fluxOp = + rewriter.create(op.getLoc(), fluxControls, fluxTargets); // Clone the body region from Quartz to Flux IRMapping regionMap; @@ -557,6 +569,9 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { getState().qubitMap[quartzTarget] = fluxOp.getTargetsOut()[i]; } + // Replace the Quartz operation with the Flux operation + rewriter.replaceOp(op, fluxOp.getOperands()); + return success(); } }; From 80538c76563b1cc10d8d2d469bb291da0fbd356f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:48:54 +0100 Subject: [PATCH 149/419] Use eraseOp() instead of replaceOp() --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index a42b1d5da7..32d10f15f9 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -395,8 +395,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); } - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getResult()); + rewriter.eraseOp(op); return success(); } @@ -434,8 +433,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getResult()); + rewriter.eraseOp(op); return success(); } @@ -477,8 +475,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { // Update state map: the Quartz qubit now corresponds to the output qubit getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getResult()); + rewriter.eraseOp(op); return success(); } @@ -518,8 +515,7 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getOperands()); + rewriter.eraseOp(op); return success(); } @@ -569,8 +565,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { getState().qubitMap[quartzTarget] = fluxOp.getTargetsOut()[i]; } - // Replace the Quartz operation with the Flux operation - rewriter.replaceOp(op, fluxOp.getOperands()); + rewriter.eraseOp(op); return success(); } From 8a0183b2628011fc0abda79e9b97da9e0caadc49 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:43:27 +0100 Subject: [PATCH 150/419] Remove debug statements again --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 89b7a64ae6..bf4eba7aba 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -343,12 +343,8 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { } // Remove the AllocOp and the DeallocOp - llvm::errs() << "Removing DeallocOp...\n"; rewriter.eraseOp(deallocOp); - llvm::errs() << "Removed DeallocOp\n"; - llvm::errs() << "Removing AllocOp...\n"; rewriter.eraseOp(allocOp); - llvm::errs() << "Removed AllocOp\n"; return success(); } }; From 3e747e08da1bb566048a58674305af32f7e330eb Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:44:15 +0100 Subject: [PATCH 151/419] Fix linter errors --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 +- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 233965652e..4044c98dec 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -547,7 +547,7 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen size_t getNumParams(); ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); + static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 486027ce7a..4482ccd71a 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -462,7 +462,7 @@ def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { size_t getNumParams(); ::mlir::utils::ParameterDescriptor getParameter(size_t i); bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); + static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; } diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index feff854154..8a62537872 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include #include #include #include From 306c7b64ed070ba448cf912e4f2637cf1e6093af Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:46:46 +0100 Subject: [PATCH 152/419] Maybe hopefully fix conversion of YieldOp --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 32d10f15f9..63149fd25c 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -590,7 +590,9 @@ struct ConvertQuartzYieldOp final } // Create quartz.yield - rewriter.replaceOpWithNewOp(op, targets); + rewriter.create(op.getLoc(), targets); + + rewriter.eraseOp(op); return success(); } From 18557da9e8002ead702859811a5f626114c12a1c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:55:12 +0100 Subject: [PATCH 153/419] Move ParameterArityTrait class to common location --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 10 +------ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 +- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 10 +------ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- .../mlir/Dialect/Utils/ParameterArityTrait.h | 27 +++++++++++++++++++ 5 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 7f81961cf0..ed2fe79b65 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -10,6 +10,7 @@ #pragma once +#include "mlir/Dialect/Utils/ParameterArityTrait.h" #include "mlir/Dialect/Utils/ParameterDescriptor.h" #include @@ -80,15 +81,6 @@ template class TargetArityTrait { }; }; -template class ParameterArityTrait { -public: - template - class Impl : public mlir::OpTrait::TraitBase { - public: - size_t getNumParams() { return n; } - }; -}; - } // namespace mlir::flux #include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 4044c98dec..924a8db397 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -337,7 +337,7 @@ def TwoTarget : TargetArityTrait<2> { } class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { - let cppNamespace = "::mlir::flux"; + let cppNamespace = "::mlir::utils"; } def ZeroParameter : ParameterArityTrait<0> { diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 407d940f45..ee6396abde 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -10,6 +10,7 @@ #pragma once +#include "mlir/Dialect/Utils/ParameterArityTrait.h" #include "mlir/Dialect/Utils/ParameterDescriptor.h" #include @@ -72,15 +73,6 @@ template class TargetArityTrait { }; }; -template class ParameterArityTrait { -public: - template - class Impl : public mlir::OpTrait::TraitBase { - public: - size_t getNumParams() { return n; } - }; -}; - } // namespace mlir::quartz #include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 4482ccd71a..5ce21926c7 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -267,7 +267,7 @@ def TwoTarget : TargetArityTrait<2> { } class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { - let cppNamespace = "::mlir::quartz"; + let cppNamespace = "::mlir::utils"; } def ZeroParameter : ParameterArityTrait<0> { diff --git a/mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h b/mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h new file mode 100644 index 0000000000..f1045ef6d1 --- /dev/null +++ b/mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include + +namespace mlir::utils { + +template class ParameterArityTrait { +public: + template + class Impl : public mlir::OpTrait::TraitBase { + public: + size_t getNumParams() { return n; } + }; +}; + +} // namespace mlir::utils From df4bbe7c9c7165ff679ab76a321d5a5c2c2e9686 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:56:41 +0100 Subject: [PATCH 154/419] Revert "Maybe hopefully fix conversion of YieldOp" This reverts commit 306c7b64ed070ba448cf912e4f2637cf1e6093af. --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 63149fd25c..32d10f15f9 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -590,9 +590,7 @@ struct ConvertQuartzYieldOp final } // Create quartz.yield - rewriter.create(op.getLoc(), targets); - - rewriter.eraseOp(op); + rewriter.replaceOpWithNewOp(op, targets); return success(); } From 753b992c740382cf8704bad08d1f1198e0b80fe1 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:58:50 +0100 Subject: [PATCH 155/419] Add other debug statements to narrow down final Windows error --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index d1b392f826..fa208fa61b 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1178,6 +1178,8 @@ TEST_F(SimpleConversionTest, CX) { EXPECT_TRUE(verify("Translation", quartzIRInit, quartzExpected.get())); + llvm::errs() << "Converting Quartz to Flux...\n"; + PassManager pm(module.get().getContext()); pm.addPass(createQuartzToFlux()); pm.addPass(createCanonicalizerPass()); @@ -1185,6 +1187,8 @@ TEST_F(SimpleConversionTest, CX) { ASSERT_TRUE(pm.run(module.get()).succeeded()); const auto fluxIR = captureIR(module.get()); + llvm::errs() << "Converted Quartz to Flux\n"; + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0a = reg[0]; @@ -1197,6 +1201,8 @@ TEST_F(SimpleConversionTest, CX) { EXPECT_TRUE(verify("Quartz to Flux", fluxIR, fluxExpected.get())); + llvm::errs() << "Converting Flux to Quartz...\n"; + pm.clear(); pm.addPass(createFluxToQuartz()); pm.addPass(createCanonicalizerPass()); @@ -1204,6 +1210,8 @@ TEST_F(SimpleConversionTest, CX) { ASSERT_TRUE(pm.run(module.get()).succeeded()); const auto quartzIRConv = captureIR(module.get()); + llvm::errs() << "Converted Flux to Quartz\n"; + EXPECT_TRUE(verify("Flux to Quartz", quartzIRConv, quartzExpected.get())); } From a5dd665325d17f2527fb39189a6ea51934d125b4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:12:46 +0100 Subject: [PATCH 156/419] Fix YieldOp conversion --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 8a62537872..70fd0e1826 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -406,22 +406,7 @@ struct ConvertFluxYieldOp final : OpConversionPattern { LogicalResult matchAndRewrite(flux::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto ctrlOp = dyn_cast(op->getParentOp()); - auto unitaryOp = ctrlOp.getBodyUnitary(); - - SmallVector targets; - targets.reserve(unitaryOp.getNumTargets()); - for (size_t i = 0; i < unitaryOp.getNumTargets(); ++i) { - const auto& yield = rewriter.getRemappedValue(unitaryOp.getTarget(i)); - targets.push_back(yield); - } - - // Create quartz.yield - rewriter.create(op.getLoc()); - - // Replace the output qubit with the same quartz references - rewriter.replaceOp(op, targets); - + rewriter.replaceOpWithNewOp(op); return success(); } }; From 83d414614e3e441458cec1c4177697c3c01e8270 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:21:00 +0100 Subject: [PATCH 157/419] Revert "Add other debug statements to narrow down final Windows error" This reverts commit 753b992c740382cf8704bad08d1f1198e0b80fe1. --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index fa208fa61b..d1b392f826 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1178,8 +1178,6 @@ TEST_F(SimpleConversionTest, CX) { EXPECT_TRUE(verify("Translation", quartzIRInit, quartzExpected.get())); - llvm::errs() << "Converting Quartz to Flux...\n"; - PassManager pm(module.get().getContext()); pm.addPass(createQuartzToFlux()); pm.addPass(createCanonicalizerPass()); @@ -1187,8 +1185,6 @@ TEST_F(SimpleConversionTest, CX) { ASSERT_TRUE(pm.run(module.get()).succeeded()); const auto fluxIR = captureIR(module.get()); - llvm::errs() << "Converted Quartz to Flux\n"; - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0a = reg[0]; @@ -1201,8 +1197,6 @@ TEST_F(SimpleConversionTest, CX) { EXPECT_TRUE(verify("Quartz to Flux", fluxIR, fluxExpected.get())); - llvm::errs() << "Converting Flux to Quartz...\n"; - pm.clear(); pm.addPass(createFluxToQuartz()); pm.addPass(createCanonicalizerPass()); @@ -1210,8 +1204,6 @@ TEST_F(SimpleConversionTest, CX) { ASSERT_TRUE(pm.run(module.get()).succeeded()); const auto quartzIRConv = captureIR(module.get()); - llvm::errs() << "Converted Flux to Quartz\n"; - EXPECT_TRUE(verify("Flux to Quartz", quartzIRConv, quartzExpected.get())); } From 2fd0d45dee7a51a730ecff8c0cbcf7dbe7806c7e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:40:11 +0100 Subject: [PATCH 158/419] Remove superfluous header include --- mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 70fd0e1826..8a2c9053db 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -13,7 +13,6 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include #include #include #include From 73a3a2103f25295c72ce4e7e5254d57ab1b3f45b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:58:08 +0100 Subject: [PATCH 159/419] Do not create a new RXOp --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index bf4eba7aba..5dfa7b0b3b 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -414,13 +414,13 @@ struct MergeSubsequentRX final : OpRewritePattern { return failure(); } - // Merge the two RXOps + // Compute and set new theta const auto newTheta = theta.getValueDouble() + prevTheta.getValueDouble(); - auto newOp = - rewriter.create(rxOp.getLoc(), rxOp.getQubitIn(), newTheta); + rxOp.setThetaAttr(rewriter.getF64FloatAttr(static_cast(newTheta))); + // Trivialize previous RXOp rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(rxOp, newOp.getResult()); + return success(); } }; From d6e7469c73454b5e61a411e1041e9a79ea9e3a62 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:01:11 +0100 Subject: [PATCH 160/419] Merge RXOps for all parameter types --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 31 ++++++-- .../pipeline/test_compiler_pipeline.cpp | 70 +++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 5dfa7b0b3b..185eb49101 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -407,19 +408,37 @@ struct MergeSubsequentRX final : OpRewritePattern { return failure(); } - // Check if both RXOps have static angles + // Compute and set new theta const auto& theta = prevOp.getParameter(0); const auto& prevTheta = rxOp.getParameter(0); - if (!theta.isStatic() || !prevTheta.isStatic()) { + if (theta.isStatic() && prevTheta.isStatic()) { + const auto& newTheta = + theta.getValueDouble() + prevTheta.getValueDouble(); + rxOp.setThetaAttr(rewriter.getF64FloatAttr(newTheta)); + } else if (theta.isStatic() && prevTheta.isDynamic()) { + auto constantOp = rewriter.create( + rxOp.getLoc(), rewriter.getF64FloatAttr(theta.getValueDouble())); + auto newTheta = rewriter.create( + rxOp.getLoc(), constantOp.getResult(), prevTheta.getValueOperand()); + rxOp->setOperand(1, newTheta.getResult()); + } else if (theta.isDynamic() && prevTheta.isStatic()) { + auto constantOp = rewriter.create( + rxOp.getLoc(), rewriter.getF64FloatAttr(prevTheta.getValueDouble())); + auto newTheta = rewriter.create( + rxOp.getLoc(), theta.getValueOperand(), constantOp.getResult()); + rewriter.replaceOpWithNewOp(rxOp, rxOp.getQubitIn(), + newTheta.getResult()); + } else if (theta.isDynamic() && prevTheta.isDynamic()) { + auto newTheta = rewriter.create( + rxOp.getLoc(), theta.getValueOperand(), prevTheta.getValueOperand()); + rxOp->setOperand(1, newTheta.getResult()); + } else { return failure(); } - // Compute and set new theta - const auto newTheta = theta.getValueDouble() + prevTheta.getValueDouble(); - rxOp.setThetaAttr(rewriter.getF64FloatAttr(static_cast(newTheta))); - // Trivialize previous RXOp rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + llvm::errs() << "Trivialized previous RXOp\n"; return success(); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index d1b392f826..1be83d2f0c 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1158,6 +1158,76 @@ TEST_F(SimpleConversionTest, RXQuartzToQIR) { // EXPECT_TRUE(verify("Quartz to QIR", qirResult, qirExpected.get())); } +TEST_F(SimpleConversionTest, RXSimplifyAttrValue) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.0); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + auto q1 = b.rx(0.5, q0); + b.rx(thetaOperand, q1); + }); + const auto fluxInitIR = captureIR(fluxInit.get()); + + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.5); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + b.rx(thetaOperand, q0); + }); + + EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); +} + +TEST_F(SimpleConversionTest, RXSimplifyValueAttr) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.0); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + auto q1 = b.rx(thetaOperand, q0); + b.rx(0.5, q1); + }); + const auto fluxInitIR = captureIR(fluxInit.get()); + + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.5); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + b.rx(thetaOperand, q0); + }); + + EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); +} + +TEST_F(SimpleConversionTest, RXSimplifyValueValue) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr10 = b.builder.getF64FloatAttr(1.0); + auto thetaOperand10 = + b.builder.create(b.loc, thetaAttr10); + auto thetaAttr05 = b.builder.getF64FloatAttr(0.5); + auto thetaOperand05 = + b.builder.create(b.loc, thetaAttr05); + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + auto q1 = b.rx(thetaOperand10, q0); + b.rx(thetaOperand05, q1); + }); + const auto fluxInitIR = captureIR(fluxInit.get()); + + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto thetaAttr = b.builder.getF64FloatAttr(1.5); + auto thetaOperand = b.builder.create(b.loc, thetaAttr); + auto reg = b.allocQubitRegister(1, "q"); + auto q0 = reg[0]; + b.rx(thetaOperand, q0); + }); + + EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); +} + TEST_F(SimpleConversionTest, CX) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); From 2351e0e6a101713dc717476c6d0580f7fbc529af Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:12:16 +0100 Subject: [PATCH 161/419] Add initial constant-folding pattern for RXOp --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 27 ++++++++++++++++++- .../pipeline/test_compiler_pipeline.cpp | 16 +++-------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 185eb49101..9fb1ec885a 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -444,6 +444,31 @@ struct MergeSubsequentRX final : OpRewritePattern { } }; +struct ConstantFoldingRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXOp rxOp, + PatternRewriter& rewriter) const override { + if (!rxOp.getThetaDyn()) { + return failure(); + } + + auto constantOp = rxOp.getThetaDyn().getDefiningOp(); + if (!constantOp) { + return failure(); + } + + const auto& thetaAttr = dyn_cast(constantOp.getValue()); + if (!thetaAttr) { + return failure(); + } + + rewriter.replaceOpWithNewOp(rxOp, rxOp.getQubitIn(), thetaAttr); + + return success(); + } +}; + } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -463,5 +488,5 @@ void XOp::getCanonicalizationPatterns(RewritePatternSet& results, void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 1be83d2f0c..a34ea15ec8 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1124,10 +1124,8 @@ TEST_F(SimpleConversionTest, RXQuartzToFlux) { const auto fluxResult = captureIR(module.get()); const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.0); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); auto q = b.allocQubitRegister(1, "q"); - b.rx(thetaOperand, q[0]); + b.rx(1.0, q[0]); }); EXPECT_TRUE(verify("Quartz to Flux", fluxResult, fluxExpected.get())); @@ -1170,11 +1168,9 @@ TEST_F(SimpleConversionTest, RXSimplifyAttrValue) { const auto fluxInitIR = captureIR(fluxInit.get()); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.5); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); auto reg = b.allocQubitRegister(1, "q"); auto q0 = reg[0]; - b.rx(thetaOperand, q0); + b.rx(1.5, q0); }); EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); @@ -1192,11 +1188,9 @@ TEST_F(SimpleConversionTest, RXSimplifyValueAttr) { const auto fluxInitIR = captureIR(fluxInit.get()); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.5); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); auto reg = b.allocQubitRegister(1, "q"); auto q0 = reg[0]; - b.rx(thetaOperand, q0); + b.rx(1.5, q0); }); EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); @@ -1218,11 +1212,9 @@ TEST_F(SimpleConversionTest, RXSimplifyValueValue) { const auto fluxInitIR = captureIR(fluxInit.get()); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.5); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); auto reg = b.allocQubitRegister(1, "q"); auto q0 = reg[0]; - b.rx(thetaOperand, q0); + b.rx(1.5, q0); }); EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); From 66e399dac47385c1ccc3f2e7f4ab703377675aa3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:18:33 +0100 Subject: [PATCH 162/419] Fix linter errors --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 9fb1ec885a..1566bb8bee 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -438,7 +438,6 @@ struct MergeSubsequentRX final : OpRewritePattern { // Trivialize previous RXOp rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - llvm::errs() << "Trivialized previous RXOp\n"; return success(); } @@ -458,7 +457,7 @@ struct ConstantFoldingRX final : OpRewritePattern { return failure(); } - const auto& thetaAttr = dyn_cast(constantOp.getValue()); + const auto& thetaAttr = llvm::dyn_cast(constantOp.getValue()); if (!thetaAttr) { return failure(); } From cbe85087db0bc6cac94587233465ea604e3e5064 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:28:44 +0100 Subject: [PATCH 163/419] Define builders in C++ --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 6 +-- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 45 +------------------ .../Quartz/Builder/QuartzProgramBuilder.h | 8 ++-- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 45 +------------------ .../Flux/Builder/FluxProgramBuilder.cpp | 36 +++------------ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 42 +++++++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 33 +++----------- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 41 +++++++++++++++++ 8 files changed, 109 insertions(+), 147 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 722a2d1b5d..608b22d6e8 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -279,7 +279,7 @@ class FluxProgramBuilder { * %q_out = flux.rx(1.0) %q_in : !flux.qubit -> !flux.qubit * ``` */ - Value rx(std::variant theta, Value qubit); + Value rx(std::variant theta, Value qubit); /** * @brief Apply a U2 gate to a qubit @@ -301,8 +301,8 @@ class FluxProgramBuilder { * %q_out = flux.u2(1.0, 0.5) %q_in : !flux.qubit -> !flux.qubit * ``` */ - Value u2(std::variant phi, std::variant lambda, - Value qubit); + Value u2(std::variant phi, + std::variant lambda, Value qubit); /** * @brief Apply a SWAP gate to two qubits diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 924a8db397..5fcbb5cf12 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -409,16 +409,7 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "double":$theta_double), [{ - auto theta_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), theta_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, theta_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$theta_attr), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, theta_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$theta_operand), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, theta_operand); - }]>, + OpBuilder<(ins "Value":$qubit_in, "std::variant":$theta)> ]; let hasCanonicalizer = 1; @@ -453,39 +444,7 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, A }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "double":$lambda_double), [{ - auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "FloatAttr":$lambda_attr), [{ - auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "double":$phi_double, "Value":$lambda_operand), [{ - auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, nullptr, lambda_operand); - }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "double":$lambda_double), [{ - auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "FloatAttr":$lambda_attr), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "FloatAttr":$phi_attr, "Value":$lambda_operand), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, phi_attr, nullptr, nullptr, lambda_operand); - }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "double":$lambda_double), [{ - auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, phi_operand, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "FloatAttr":$lambda_attr), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, phi_operand, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit_in, "Value":$phi_operand, "Value":$lambda_operand), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), qubit_in, nullptr, phi_operand, nullptr, lambda_operand); - }]>, + OpBuilder<(ins "Value":$qubit_in, "std::variant":$phi, "std::variant":$lambda)> ]; let hasVerifier = 1; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index ec6cd3761e..bf360b9a18 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -259,7 +259,8 @@ class QuartzProgramBuilder { * quartz.rx(1.0) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& rx(std::variant theta, Value qubit); + QuartzProgramBuilder& rx(std::variant theta, + Value qubit); /** * @brief Apply a U2 gate to a qubit @@ -277,8 +278,9 @@ class QuartzProgramBuilder { * quartz.u2(1.0, 0.5) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& u2(std::variant phi, - std::variant lambda, Value qubit); + QuartzProgramBuilder& u2(std::variant phi, + std::variant lambda, + Value qubit); /** * @brief Apply a SWAP gate to two qubits diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 5ce21926c7..43ffc6d3e5 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -335,16 +335,7 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] }]; let builders = [ - OpBuilder<(ins "Value":$qubit, "double":$theta_double), [{ - auto theta_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), theta_double); - build($_builder, $_state, qubit, theta_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "FloatAttr":$theta_attr), [{ - build($_builder, $_state, qubit, theta_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "Value":$theta_operand), [{ - build($_builder, $_state, qubit, nullptr, theta_operand); - }]>, + OpBuilder<(ins "Value":$qubit_in, "std::variant":$theta)> ]; let hasVerifier = 1; @@ -377,39 +368,7 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, }]; let builders = [ - OpBuilder<(ins "Value":$qubit, "double":$phi_double, "double":$lambda_double), [{ - auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "double":$phi_double, "FloatAttr":$lambda_attr), [{ - auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "double":$phi_double, "Value":$lambda_operand), [{ - auto phi_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), phi_double); - build($_builder, $_state, qubit, phi_attr, nullptr, nullptr, lambda_operand); - }]>, - OpBuilder<(ins "Value":$qubit, "FloatAttr":$phi_attr, "double":$lambda_double), [{ - auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "FloatAttr":$phi_attr, "FloatAttr":$lambda_attr), [{ - build($_builder, $_state, qubit, phi_attr, nullptr, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "FloatAttr":$phi_attr, "Value":$lambda_operand), [{ - build($_builder, $_state, qubit, phi_attr, nullptr, nullptr, lambda_operand); - }]>, - OpBuilder<(ins "Value":$qubit, "Value":$phi_operand, "double":$lambda_double), [{ - auto lambda_attr = FloatAttr::get(Float64Type::get($_builder.getContext()), lambda_double); - build($_builder, $_state, qubit, nullptr, phi_operand, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "Value":$phi_operand, "FloatAttr":$lambda_attr), [{ - build($_builder, $_state, qubit, nullptr, phi_operand, lambda_attr, nullptr); - }]>, - OpBuilder<(ins "Value":$qubit, "Value":$phi_operand, "Value":$lambda_operand), [{ - build($_builder, $_state, qubit, nullptr, phi_operand, nullptr, lambda_operand); - }]>, + OpBuilder<(ins "Value":$qubit_in, "std::variant":$phi, "std::variant":$lambda)> ]; let hasVerifier = 1; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 617b66ced3..eabbf41f33 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -185,14 +185,9 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } -Value FluxProgramBuilder::rx(std::variant theta, Value qubit) { - RXOp rxOp; - if (std::holds_alternative(theta)) { - rxOp = builder.create(loc, qubit, std::get(theta)); - } else { - rxOp = builder.create(loc, qubit, std::get(theta)); - } - +Value FluxProgramBuilder::rx(std::variant theta, + Value qubit) { + auto rxOp = builder.create(loc, qubit, theta); const auto& qubitOut = rxOp.getQubitOut(); // Update tracking @@ -201,27 +196,10 @@ Value FluxProgramBuilder::rx(std::variant theta, Value qubit) { return qubitOut; } -Value FluxProgramBuilder::u2(std::variant phi, - std::variant lambda, Value qubit) { - FloatAttr phiAttr = nullptr; - Value phiValue = nullptr; - if (std::holds_alternative(phi)) { - phiAttr = builder.getF64FloatAttr(std::get(phi)); - } else { - phiValue = std::get(phi); - } - - FloatAttr lambdaAttr = nullptr; - Value lambdaValue = nullptr; - if (std::holds_alternative(lambda)) { - lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); - } else { - lambdaValue = std::get(lambda); - } - - auto u2Op = builder.create(loc, qubit, phiAttr, phiValue, lambdaAttr, - lambdaValue); - +Value FluxProgramBuilder::u2(std::variant phi, + std::variant lambda, + Value qubit) { + auto u2Op = builder.create(loc, qubit, phi, lambda); const auto& qubitOut = u2Op.getQubitOut(); // Update tracking diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 1566bb8bee..6b94fbdc8c 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -154,6 +154,21 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, getMatrixRX(theta)); } +void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + std::variant theta) { + FloatAttr thetaAttr = nullptr; + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaAttr = builder.getF64FloatAttr(std::get(theta)); + } else if (std::holds_alternative(theta)) { + thetaAttr = std::get(theta); + } else { + thetaOperand = std::get(theta); + } + build(builder, state, QubitType::get(builder.getContext()), qubitIn, + thetaAttr, thetaOperand); +} + LogicalResult RXOp::verify() { if (getTheta().has_value() == (getThetaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic theta"); @@ -189,6 +204,33 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { return DenseElementsAttr::get(type, getMatrixU2(phi, lambda)); } +void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, + std::variant phi, + std::variant lambda) { + FloatAttr phiAttr = nullptr; + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiAttr = builder.getF64FloatAttr(std::get(phi)); + } else if (std::holds_alternative(phi)) { + phiAttr = std::get(phi); + } else { + phiOperand = std::get(phi); + } + + FloatAttr lambdaAttr = nullptr; + Value lambdaOperand = nullptr; + if (std::holds_alternative(lambda)) { + lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); + } else if (std::holds_alternative(lambda)) { + lambdaAttr = std::get(lambda); + } else { + lambdaOperand = std::get(lambda); + } + + build(builder, state, QubitType::get(builder.getContext()), qubitIn, phiAttr, + phiOperand, lambdaAttr, lambdaOperand); +} + LogicalResult U2Op::verify() { if (getPhi().has_value() == (getPhiDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic phi"); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index c0f7314f8e..6accdd117b 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -133,36 +133,17 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { } QuartzProgramBuilder& -QuartzProgramBuilder::rx(std::variant theta, Value qubit) { - if (std::holds_alternative(theta)) { - builder.create(loc, qubit, std::get(theta)); - } else { - builder.create(loc, qubit, std::get(theta)); - } +QuartzProgramBuilder::rx(std::variant theta, + Value qubit) { + builder.create(loc, qubit, theta); return *this; } QuartzProgramBuilder& -QuartzProgramBuilder::u2(std::variant phi, - std::variant lambda, Value qubit) { - FloatAttr phiAttr = nullptr; - Value phiValue = nullptr; - if (std::holds_alternative(phi)) { - phiAttr = builder.getF64FloatAttr(std::get(phi)); - } else { - phiValue = std::get(phi); - } - - FloatAttr lambdaAttr = nullptr; - Value lambdaValue = nullptr; - if (std::holds_alternative(lambda)) { - lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); - } else { - lambdaValue = std::get(lambda); - } - - builder.create(loc, qubit, phiAttr, phiValue, lambdaAttr, lambdaValue); - +QuartzProgramBuilder::u2(std::variant phi, + std::variant lambda, + Value qubit) { + builder.create(loc, qubit, phi, lambda); return *this; } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 18873248ea..da142d6c6e 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -151,6 +151,20 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { return DenseElementsAttr::get(type, getMatrixRX(theta)); } +void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, + std::variant theta) { + FloatAttr thetaAttr = nullptr; + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaAttr = builder.getF64FloatAttr(std::get(theta)); + } else if (std::holds_alternative(theta)) { + thetaAttr = std::get(theta); + } else { + thetaOperand = std::get(theta); + } + build(builder, state, qubitIn, thetaAttr, thetaOperand); +} + LogicalResult RXOp::verify() { if (getTheta().has_value() == (getThetaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic theta"); @@ -186,6 +200,33 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { return DenseElementsAttr::get(type, getMatrixU2(phi, lambda)); } +void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, + std::variant phi, + std::variant lambda) { + FloatAttr phiAttr = nullptr; + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiAttr = builder.getF64FloatAttr(std::get(phi)); + } else if (std::holds_alternative(phi)) { + phiAttr = std::get(phi); + } else { + phiOperand = std::get(phi); + } + + FloatAttr lambdaAttr = nullptr; + Value lambdaOperand = nullptr; + if (std::holds_alternative(lambda)) { + lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); + } else if (std::holds_alternative(lambda)) { + lambdaAttr = std::get(lambda); + } else { + lambdaOperand = std::get(lambda); + } + + build(builder, state, qubitIn, phiAttr, phiOperand, lambdaAttr, + lambdaOperand); +} + LogicalResult U2Op::verify() { if (getPhi().has_value() == (getPhiDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic phi"); From 147b553c5ec1871818436ceacaf1b5ce4521bc53 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:51:26 +0100 Subject: [PATCH 164/419] Move remaining logic from target definition to class --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 56 ++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 91 +------------------ .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 4 + .../mlir/Dialect/Quartz/IR/QuartzOps.td | 22 +---- 4 files changed, 63 insertions(+), 110 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index ed2fe79b65..1ddad3434e 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -63,6 +63,18 @@ template class TargetArityTrait { size_t getNumPosControls() { return 0; } size_t getNumNegControls() { return 0; } + Value getInputQubit(size_t i) { + return this->getOperation()->getOperand(i); + } + + Value getOutputQubit(size_t i) { + return this->getOperation()->getResult(i); + } + + Value getInputTarget(size_t i) { return getInputQubit(i); } + + Value getOutputTarget(size_t i) { return getOutputQubit(i); } + Value getInputPosControl(size_t i) { llvm::report_fatal_error("Operation does not have controls"); } @@ -78,6 +90,50 @@ template class TargetArityTrait { Value getOutputNegControl(size_t i) { llvm::report_fatal_error("Operation does not have controls"); } + + Value getInputForOutput(Value output) { + switch (n) { + case 1: + if (output == this->getOperation()->getResult(0)) { + return this->getOperation()->getOperand(0); + } + llvm::report_fatal_error( + "Given qubit is not an output of the operation"); + case 2: + if (output == this->getOperation()->getResult(0)) { + return this->getOperation()->getOperand(0); + } + if (output == this->getOperation()->getResult(1)) { + return this->getOperation()->getOperand(1); + } + llvm::report_fatal_error( + "Given qubit is not an output of the operation"); + default: + llvm::report_fatal_error("Unsupported number of qubits"); + } + } + + Value getOutputForInput(Value input) { + switch (n) { + case 1: + if (input == this->getOperation()->getOperand(0)) { + return this->getOperation()->getResult(0); + } + llvm::report_fatal_error( + "Given qubit is not an input of the operation"); + case 2: + if (input == this->getOperation()->getOperand(0)) { + return this->getOperation()->getResult(0); + } + if (input == this->getOperation()->getOperand(1)) { + return this->getOperation()->getResult(1); + } + llvm::report_fatal_error( + "Given qubit is not an input of the operation"); + default: + llvm::report_fatal_error("Unsupported number of qubits"); + } + } }; }; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 5fcbb5cf12..dee57d69a8 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -245,96 +245,9 @@ class TargetArityTrait : ParamNativeOpTrait<"TargetArityTrait", !cast { - let extraConcreteClassDeclaration = [{ - Value getInputQubit(size_t i) { - if (i == 0) { - return getQubitIn(); - } - llvm::report_fatal_error("Operation has one input qubit"); - } - - Value getOutputQubit(size_t i) { - if (i == 0) { - return getQubitOut(); - } - llvm::report_fatal_error("Operation has one output qubit"); - } - - Value getInputTarget(size_t i) { - return getInputQubit(i); - } - - Value getOutputTarget(size_t i) { - return getOutputQubit(i); - } - - Value getInputForOutput(Value output) { - if (output == getQubitOut()) { - return getQubitIn(); - } - llvm::report_fatal_error("Given qubit is not an output of the operation"); - } - - Value getOutputForInput(Value input) { - if (input == getQubitIn()) { - return getQubitOut(); - } - llvm::report_fatal_error("Given qubit is not an input of the operation"); - } - }]; -} +def OneTarget : TargetArityTrait<1>; -def TwoTarget : TargetArityTrait<2> { - let extraConcreteClassDeclaration = [{ - Value getInputQubit(size_t i) { - if (i == 0) { - return getQubit0In(); - } - if (i == 1) { - return getQubit1In(); - } - } - - Value getOutputQubit(size_t i) { - if (i == 0) { - return getQubit0Out(); - } - if (i == 1) { - return getQubit1Out(); - } - llvm::report_fatal_error("Operation has two output qubits"); - } - - Value getInputTarget(size_t i) { - return getInputQubit(i); - } - - Value getOutputTarget(size_t i) { - return getOutputQubit(i); - } - - Value getInputForOutput(Value output) { - if (output == getQubit0Out()) { - return getQubit0In(); - } - if (output == getQubit1Out()) { - return getQubit1In(); - } - llvm::report_fatal_error("Given qubit is not an output of the operation"); - } - - Value getOutputForInput(Value input) { - if (input == getQubit0In()) { - return getQubit0Out(); - } - if (input == getQubit1In()) { - return getQubit1Out(); - } - llvm::report_fatal_error("Given qubit is not an input of the operation"); - } - }]; -} +def TwoTarget : TargetArityTrait<2>; class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { let cppNamespace = "::mlir::utils"; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index ee6396abde..bcd104a660 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -63,6 +63,10 @@ template class TargetArityTrait { size_t getNumPosControls() { return 0; } size_t getNumNegControls() { return 0; } + Value getQubit(size_t i) { return this->getOperation()->getOperand(i); } + + Value getTarget(size_t i) { return this->getOperation()->getOperand(i); } + Value getPosControl(size_t i) { llvm::report_fatal_error("Operation does not have controls"); } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 43ffc6d3e5..81768d23c5 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -241,30 +241,10 @@ def OneTarget : TargetArityTrait<1> { } llvm::report_fatal_error("Operation has one input qubit"); } - - Value getTarget(size_t i) { - return getQubit(i); - } }]; } -def TwoTarget : TargetArityTrait<2> { - let extraConcreteClassDeclaration = [{ - Value getQubit(size_t i) { - if (i == 0) { - return getQubit0(); - } - if (i == 1) { - return getQubit1(); - } - llvm::report_fatal_error("Operation has two input qubits"); - } - - Value getTarget(size_t i) { - return getQubit(i); - } - }]; -} +def TwoTarget : TargetArityTrait<2>; class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { let cppNamespace = "::mlir::utils"; From a06e40b07c7bf710e0fb7dd2f1dda366611a80a0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:06:28 +0100 Subject: [PATCH 165/419] Add missing headers --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 1 + mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 6b94fbdc8c..e7a5118471 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -29,6 +29,7 @@ #include #include #include +#include using namespace mlir; using namespace mlir::flux; diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index da142d6c6e..982b91b861 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace mlir; using namespace mlir::quartz; From 348ec7252704a7bdca8d63b94fcc790f979808d0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:22:32 +0100 Subject: [PATCH 166/419] Add header also in header files --- mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h | 1 + mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h | 1 + 2 files changed, 2 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 1ddad3434e..4fc3ff5765 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -19,6 +19,7 @@ #include #include #include +#include // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index bcd104a660..9eb04cbf75 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -19,6 +19,7 @@ #include #include #include +#include // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) From b601af6a71a0c369f99c8836caee7408a145e16d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:03:05 +0100 Subject: [PATCH 167/419] Always modify RXOp in place instead of replacing it --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index e7a5118471..2fe51c20a4 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -431,6 +431,7 @@ struct RemoveSubsequentX final : OpRewritePattern { // Remove both XOps rewriter.replaceOp(prevOp, prevOp.getQubitIn()); rewriter.replaceOp(xOp, xOp.getQubitIn()); + return success(); } }; @@ -469,8 +470,8 @@ struct MergeSubsequentRX final : OpRewritePattern { rxOp.getLoc(), rewriter.getF64FloatAttr(prevTheta.getValueDouble())); auto newTheta = rewriter.create( rxOp.getLoc(), theta.getValueOperand(), constantOp.getResult()); - rewriter.replaceOpWithNewOp(rxOp, rxOp.getQubitIn(), - newTheta.getResult()); + rxOp.removeThetaAttr(); + rxOp->setOperand(1, newTheta.getResult()); } else if (theta.isDynamic() && prevTheta.isDynamic()) { auto newTheta = rewriter.create( rxOp.getLoc(), theta.getValueOperand(), prevTheta.getValueOperand()); @@ -505,7 +506,8 @@ struct ConstantFoldingRX final : OpRewritePattern { return failure(); } - rewriter.replaceOpWithNewOp(rxOp, rxOp.getQubitIn(), thetaAttr); + rxOp.setThetaAttr(thetaAttr); + rxOp->eraseOperand(1); return success(); } From ca11a1ae6d3089c2a3585e3f2cf06979486c1771 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:05:22 +0100 Subject: [PATCH 168/419] Add missing headers --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 1 + mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 2fe51c20a4..647738761b 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 982b91b861..1c50c467b0 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include From dca2e83a5102c23a170f81940045ff1d43ae6c6b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:28:59 +0100 Subject: [PATCH 169/419] Implement ConstantFolding as fold --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 1 + mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 51 +++++++++----------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index dee57d69a8..f7c8ee00e9 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -326,6 +326,7 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> ]; let hasCanonicalizer = 1; + let hasFolder = 1; let hasVerifier = 1; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 647738761b..166c91beca 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -171,6 +171,29 @@ void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, thetaAttr, thetaOperand); } +OpFoldResult RXOp::fold(FoldAdaptor adaptor) { + const auto& theta = getParameter(0); + + if (theta.isStatic()) { + return nullptr; + } + + auto constantOp = theta.getValueOperand().getDefiningOp(); + if (!constantOp) { + return nullptr; + } + + const auto& thetaAttr = llvm::dyn_cast(constantOp.getValue()); + if (!thetaAttr) { + return nullptr; + } + + getOperation()->setAttr("theta", thetaAttr); + getOperation()->eraseOperand(1); + + return nullptr; +} + LogicalResult RXOp::verify() { if (getTheta().has_value() == (getThetaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic theta"); @@ -488,32 +511,6 @@ struct MergeSubsequentRX final : OpRewritePattern { } }; -struct ConstantFoldingRX final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(RXOp rxOp, - PatternRewriter& rewriter) const override { - if (!rxOp.getThetaDyn()) { - return failure(); - } - - auto constantOp = rxOp.getThetaDyn().getDefiningOp(); - if (!constantOp) { - return failure(); - } - - const auto& thetaAttr = llvm::dyn_cast(constantOp.getValue()); - if (!thetaAttr) { - return failure(); - } - - rxOp.setThetaAttr(thetaAttr); - rxOp->eraseOperand(1); - - return success(); - } -}; - } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -533,5 +530,5 @@ void XOp::getCanonicalizationPatterns(RewritePatternSet& results, void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } From e007675ee2581db78ac80866e3059d5dda8f77f7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:23:35 +0100 Subject: [PATCH 170/419] Move fold to trait --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 20 +++- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 4 + mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 10 +- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 12 ++- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- .../mlir/Dialect/Utils/ParameterArityTrait.h | 27 ----- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 99 ++++++++++++++----- 7 files changed, 113 insertions(+), 61 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 4fc3ff5765..64481dc128 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -10,10 +10,10 @@ #pragma once -#include "mlir/Dialect/Utils/ParameterArityTrait.h" #include "mlir/Dialect/Utils/ParameterDescriptor.h" #include +#include #include #include #include @@ -56,7 +56,7 @@ namespace mlir::flux { template class TargetArityTrait { public: template - class Impl : public mlir::OpTrait::TraitBase { + class Impl : public OpTrait::TraitBase { public: size_t getNumQubits() { return n; } size_t getNumTargets() { return n; } @@ -138,6 +138,22 @@ template class TargetArityTrait { }; }; +LogicalResult foldParameterArityTrait(Operation* op); + +template class ParameterArityTrait { +public: + template + class Impl : public OpTrait::TraitBase { + public: + size_t getNumParams() { return n; } + + static LogicalResult foldTrait(Operation* op, ArrayRef operands, + SmallVectorImpl& results) { + return foldParameterArityTrait(op); + } + }; +}; + } // namespace mlir::flux #include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index efce833cc6..69fb84c5d2 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -100,6 +100,10 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the i-th parameter.", "::mlir::utils::ParameterDescriptor", "getParameter", (ins "size_t":$i) >, + InterfaceMethod< + "For the i-th parameter, replace the operand with an attribute.", + "void", "replaceOperandWithAttr", (ins "size_t":$i, "FloatAttr":$attr) + >, /// TODO: Add convenience methods as necessary // Convenience methods diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index f7c8ee00e9..506585e1ab 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -250,7 +250,7 @@ def OneTarget : TargetArityTrait<1>; def TwoTarget : TargetArityTrait<2>; class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { - let cppNamespace = "::mlir::utils"; + let cppNamespace = "::mlir::flux"; } def ZeroParameter : ParameterArityTrait<0> { @@ -260,6 +260,10 @@ def ZeroParameter : ParameterArityTrait<0> { } bool hasStaticUnitary() { return true; } + + void replaceOperandWithAttr(size_t i, FloatAttr attr) { + llvm::report_fatal_error("Operation does not have parameters"); + } }]; } @@ -316,6 +320,7 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> let extraClassDeclaration = [{ ::mlir::utils::ParameterDescriptor getParameter(size_t i); + void replaceOperandWithAttr(size_t i, FloatAttr attr); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } @@ -326,7 +331,6 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> ]; let hasCanonicalizer = 1; - let hasFolder = 1; let hasVerifier = 1; } @@ -352,6 +356,7 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, A let extraClassDeclaration = [{ ::mlir::utils::ParameterDescriptor getParameter(size_t i); + void replaceOperandWithAttr(size_t i, FloatAttr attr); bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } @@ -419,6 +424,7 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen Value getOutputForInput(Value input); size_t getNumParams(); ::mlir::utils::ParameterDescriptor getParameter(size_t i); + void replaceOperandWithAttr(size_t i, FloatAttr attr); bool hasStaticUnitary(); static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 9eb04cbf75..1e991f3f86 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -10,7 +10,6 @@ #pragma once -#include "mlir/Dialect/Utils/ParameterArityTrait.h" #include "mlir/Dialect/Utils/ParameterDescriptor.h" #include @@ -56,7 +55,7 @@ namespace mlir::quartz { template class TargetArityTrait { public: template - class Impl : public mlir::OpTrait::TraitBase { + class Impl : public OpTrait::TraitBase { public: size_t getNumQubits() { return n; } size_t getNumTargets() { return n; } @@ -78,6 +77,15 @@ template class TargetArityTrait { }; }; +template class ParameterArityTrait { +public: + template + class Impl : public OpTrait::TraitBase { + public: + size_t getNumParams() { return n; } + }; +}; + } // namespace mlir::quartz #include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 81768d23c5..cc54e6a0f7 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -247,7 +247,7 @@ def OneTarget : TargetArityTrait<1> { def TwoTarget : TargetArityTrait<2>; class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { - let cppNamespace = "::mlir::utils"; + let cppNamespace = "::mlir::quartz"; } def ZeroParameter : ParameterArityTrait<0> { diff --git a/mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h b/mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h deleted file mode 100644 index f1045ef6d1..0000000000 --- a/mlir/include/mlir/Dialect/Utils/ParameterArityTrait.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include -#include - -namespace mlir::utils { - -template class ParameterArityTrait { -public: - template - class Impl : public mlir::OpTrait::TraitBase { - public: - size_t getNumParams() { return n; } - }; -}; - -} // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 166c91beca..64e0f53594 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -68,6 +68,46 @@ void FluxDialect::initialize() { #include "mlir/Dialect/Flux/IR/FluxInterfaces.cpp.inc" +//===----------------------------------------------------------------------===// +// Traits +//===----------------------------------------------------------------------===// + +namespace mlir::flux { + +LogicalResult foldParameterArityTrait(Operation* op) { + auto concreteOp = llvm::dyn_cast(op); + if (!concreteOp) { + return failure(); + } + + LogicalResult succeeded = failure(); + for (size_t i = 0; i < concreteOp.getNumParams(); ++i) { + const auto& parameter = concreteOp.getParameter(i); + + if (parameter.isStatic()) { + continue; + } + + auto constantOp = + parameter.getValueOperand().getDefiningOp(); + if (!constantOp) { + continue; + } + + const auto& thetaAttr = llvm::dyn_cast(constantOp.getValue()); + if (!thetaAttr) { + continue; + } + + concreteOp.replaceOperandWithAttr(i, thetaAttr); + succeeded = success(); + } + + return succeeded; +} + +} // namespace mlir::flux + //===----------------------------------------------------------------------===// // Operations //===----------------------------------------------------------------------===// @@ -143,6 +183,15 @@ ParameterDescriptor RXOp::getParameter(size_t i) { llvm::report_fatal_error("RXOp has one parameter"); } +void RXOp::replaceOperandWithAttr(size_t i, FloatAttr attr) { + if (i == 0) { + getOperation()->setAttr("theta", attr); + getOperation()->eraseOperand(1); + return; + } + llvm::report_fatal_error("RXOp has one parameter"); +} + bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } DenseElementsAttr RXOp::tryGetStaticMatrix() { @@ -171,29 +220,6 @@ void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, thetaAttr, thetaOperand); } -OpFoldResult RXOp::fold(FoldAdaptor adaptor) { - const auto& theta = getParameter(0); - - if (theta.isStatic()) { - return nullptr; - } - - auto constantOp = theta.getValueOperand().getDefiningOp(); - if (!constantOp) { - return nullptr; - } - - const auto& thetaAttr = llvm::dyn_cast(constantOp.getValue()); - if (!thetaAttr) { - return nullptr; - } - - getOperation()->setAttr("theta", thetaAttr); - getOperation()->eraseOperand(1); - - return nullptr; -} - LogicalResult RXOp::verify() { if (getTheta().has_value() == (getThetaDyn() != nullptr)) { return emitOpError("must specify exactly one of static or dynamic theta"); @@ -213,6 +239,20 @@ ParameterDescriptor U2Op::getParameter(size_t i) { llvm::report_fatal_error("U2Op has two parameters"); } +void U2Op::replaceOperandWithAttr(size_t i, FloatAttr attr) { + if (i == 0) { + getOperation()->setAttr("phi", attr); + getOperation()->eraseOperand(1); + return; + } + if (i == 1) { + getOperation()->setAttr("lambda", attr); + getOperation()->eraseOperand(3); + return; + } + llvm::report_fatal_error("U2Op has two parameter"); +} + bool U2Op::hasStaticUnitary() { return (getParameter(0).isStatic() && getParameter(1).isStatic()); } @@ -375,14 +415,19 @@ size_t CtrlOp::getNumParams() { return unitaryOp.getNumParams(); } -bool CtrlOp::hasStaticUnitary() { +ParameterDescriptor CtrlOp::getParameter(size_t i) { auto unitaryOp = getBodyUnitary(); - return unitaryOp.hasStaticUnitary(); + return unitaryOp.getParameter(i); } -ParameterDescriptor CtrlOp::getParameter(size_t i) { +void CtrlOp::replaceOperandWithAttr(size_t i, FloatAttr attr) { auto unitaryOp = getBodyUnitary(); - return unitaryOp.getParameter(i); + unitaryOp.replaceOperandWithAttr(i, attr); +} + +bool CtrlOp::hasStaticUnitary() { + auto unitaryOp = getBodyUnitary(); + return unitaryOp.hasStaticUnitary(); } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { From 3fefb4e07420741d6541ea5b61db4276b7ddf4e5 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:10:20 +0100 Subject: [PATCH 171/419] Add canonicalization pattern for merging nested CtrlOps --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 2 +- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 6 ++- .../Flux/Builder/FluxProgramBuilder.cpp | 22 ++++----- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 45 +++++++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 30 +++++++++++++ 5 files changed, 93 insertions(+), 12 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 608b22d6e8..853d7399a0 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -380,7 +380,7 @@ class FluxProgramBuilder { Location loc; private: - bool inRegion = false; + int inRegion = 0; //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 506585e1ab..f8a1b417f4 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -433,9 +433,13 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen let builders = [ OpBuilder<(ins "ValueRange":$controls_in, "ValueRange":$targets_in), [{ auto qubit_type = QubitType::get($_builder.getContext()); - build($_builder, $_state, qubit_type, qubit_type, controls_in, targets_in); + SmallVector control_types(controls_in.size(), qubit_type); + SmallVector target_types(targets_in.size(), qubit_type); + build($_builder, $_state, TypeRange(control_types), TypeRange(target_types), controls_in, targets_in); }]>, ]; + + let hasCanonicalizer = 1; } #endif // FluxOPS diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index eabbf41f33..917b9a46ff 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -178,7 +178,7 @@ Value FluxProgramBuilder::x(Value qubit) { const auto& qubitOut = xOp.getQubitOut(); // Update tracking - if (!inRegion) { + if (inRegion == 0) { updateQubitTracking(qubit, qubitOut); } @@ -232,21 +232,23 @@ std::pair, SmallVector> FluxProgramBuilder::ctrl( const mlir::OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointToStart(&ctrlOp.getBody().emplaceBlock()); - inRegion = true; + inRegion++; auto targetsYield = body(*this); - inRegion = false; + inRegion--; builder.create(loc, targetsYield); - // Update tracking const auto& controlsOut = ctrlOp.getControlsOut(); - for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { - updateQubitTracking(control, controlOut); - } - const auto& targetsOut = ctrlOp.getTargetsOut(); - for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { - updateQubitTracking(target, targetOut); + + // Update tracking + if (inRegion == 0) { + for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + updateQubitTracking(control, controlOut); + } + for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { + updateQubitTracking(target, targetOut); + } } return {controlsOut, targetsOut}; diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 64e0f53594..9a02eea745 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -556,6 +556,46 @@ struct MergeSubsequentRX final : OpRewritePattern { } }; +struct MergeNestedCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + auto bodyUnitary = ctrlOp.getBodyUnitary(); + auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); + + // Check if the body unitary is a CtrlOp + if (!bodyCtrlOp) { + return failure(); + } + + // Merge controls + SmallVector newControls; + newControls.append(ctrlOp.getControlsIn().begin(), + ctrlOp.getControlsIn().end()); + for (auto control : bodyCtrlOp.getControlsIn()) { + if (llvm::is_contained(newControls, control)) { + continue; + } + newControls.push_back(control); + } + + // Create new CtrlOp + auto newCtrlOp = rewriter.create(ctrlOp.getLoc(), newControls, + bodyCtrlOp.getTargetsIn()); + + // Clone block + rewriter.cloneRegionBefore(bodyCtrlOp.getBody(), newCtrlOp.getBody(), + newCtrlOp.getBody().end()); + + // Replace CtrlOps + rewriter.eraseOp(bodyCtrlOp); + rewriter.replaceOp(ctrlOp, newCtrlOp.getResults()); + + return success(); + } +}; + } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -577,3 +617,8 @@ void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); } + +void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index a34ea15ec8..bf8a47b37b 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1269,4 +1269,34 @@ TEST_F(SimpleConversionTest, CX) { EXPECT_TRUE(verify("Flux to Quartz", quartzIRConv, quartzExpected.get())); } +TEST_F(SimpleConversionTest, CXMerge) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto q0a = reg[0]; + auto q1a = reg[1]; + auto q2a = reg[2]; + b.ctrl({q0a}, {q1a, q2a}, [&](auto& b) { + auto q12b = b.ctrl({q1a}, {q2a}, [&](auto& b) { + auto q2b = b.x(q2a); + return SmallVector{q2b}; + }); + return SmallVector{q12b.first[0], q12b.second[0]}; + }); + }); + const auto fluxInitIR = captureIR(fluxInit.get()); + + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto q0a = reg[0]; + auto q1a = reg[1]; + auto q2a = reg[2]; + b.ctrl({q0a, q1a}, {q2a}, [&](auto& b) { + auto q2b = b.x(q2a); + return SmallVector{q2b}; + }); + }); + + EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); +} + } // namespace From 080a65b182689d55d7af88aee77eedf8f194d493 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:22:45 +0100 Subject: [PATCH 172/419] Do not explicitely erase body CtrlOp --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 9a02eea745..4324979c76 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -589,7 +589,6 @@ struct MergeNestedCtrl final : OpRewritePattern { newCtrlOp.getBody().end()); // Replace CtrlOps - rewriter.eraseOp(bodyCtrlOp); rewriter.replaceOp(ctrlOp, newCtrlOp.getResults()); return success(); From e78a90f4192b0e20aab51e75da08ae801ac015fa Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:24:17 +0100 Subject: [PATCH 173/419] Add missing headers --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 4324979c76..cd0a3f4bd9 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -21,6 +21,8 @@ // IWYU pragma: end_keep #include +#include +#include #include #include #include @@ -570,7 +572,7 @@ struct MergeNestedCtrl final : OpRewritePattern { } // Merge controls - SmallVector newControls; + llvm::SmallVector newControls; newControls.append(ctrlOp.getControlsIn().begin(), ctrlOp.getControlsIn().end()); for (auto control : bodyCtrlOp.getControlsIn()) { From 319efc077555c3ca5e79d1793994dd951fe2c2df Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:31:58 +0100 Subject: [PATCH 174/419] Add canonicalization pattern for removing trivial CtrlOps --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 26 ++++++++++++++++++- .../pipeline/test_compiler_pipeline.cpp | 22 +++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index cd0a3f4bd9..726958f42b 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -558,6 +558,9 @@ struct MergeSubsequentRX final : OpRewritePattern { } }; +/** + * @brief Merge nested control modifiers into a single one. + */ struct MergeNestedCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; @@ -597,6 +600,27 @@ struct MergeNestedCtrl final : OpRewritePattern { } }; +/** + * @brief Remove control modifiers without controls. + */ +struct RemoveTrivialCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + if (ctrlOp.getNumControls() != 0) { + return failure(); + } + + auto bodyUnitary = ctrlOp.getBodyUnitary(); + + bodyUnitary->moveBefore(ctrlOp); + rewriter.replaceOp(ctrlOp, bodyUnitary->getResults()); + + return success(); + } +}; + } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -621,5 +645,5 @@ void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index bf8a47b37b..a5b33d2402 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1269,7 +1269,7 @@ TEST_F(SimpleConversionTest, CX) { EXPECT_TRUE(verify("Flux to Quartz", quartzIRConv, quartzExpected.get())); } -TEST_F(SimpleConversionTest, CXMerge) { +TEST_F(SimpleConversionTest, CXMergeNested) { const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); auto q0a = reg[0]; @@ -1299,4 +1299,24 @@ TEST_F(SimpleConversionTest, CXMerge) { EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); } +TEST_F(SimpleConversionTest, CXRemoveTrivial) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q0a = reg[0]; + b.ctrl({}, {q0a}, [&](auto& b) { + auto q0b = b.x(q0a); + return SmallVector{q0b}; + }); + }); + const auto fluxInitIR = captureIR(fluxInit.get()); + + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q0a = reg[0]; + b.x(q0a); + }); + + EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); +} + } // namespace From 9184ab8ba91b2509598d09ad9c4bdda48e15946f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:40:22 +0100 Subject: [PATCH 175/419] Explicitly insert constants in entry block --- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index e79e4a88af..43cb61e762 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -255,10 +255,10 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::rx(std::variant theta, const Value qubit) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard entryGuard(builder); - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); Value thetaOperand; if (std::holds_alternative(theta)) { @@ -271,6 +271,12 @@ QIRProgramBuilder& QIRProgramBuilder::rx(std::variant theta, thetaOperand = std::get(theta); } + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + // Create rx call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), @@ -287,10 +293,10 @@ QIRProgramBuilder& QIRProgramBuilder::u2(std::variant phi, std::variant lambda, const Value qubit) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard entryGuard(builder); - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); Value phiOperand; if (std::holds_alternative(phi)) { @@ -300,7 +306,6 @@ QIRProgramBuilder& QIRProgramBuilder::u2(std::variant phi, .getResult(); } else { phiOperand = std::get(phi); - ; } Value lambdaOperand; @@ -312,9 +317,14 @@ QIRProgramBuilder& QIRProgramBuilder::u2(std::variant phi, .getResult(); } else { lambdaOperand = std::get(lambda); - ; } + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + // Create u2 call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), From a1608ac87755e85f7fda8f04efd40391a3c1acef Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:26:15 +0100 Subject: [PATCH 176/419] Add docstrings to ctrl builder methods --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 22 +++++++++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.h | 18 +++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 853d7399a0..1cefc6df1b 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -330,6 +330,28 @@ class FluxProgramBuilder { // Modifiers //===--------------------------------------------------------------------===// + /** + * @brief Apply a controlled operation + * + * @param controls Control qubits + * @param targets Target qubits + * @param body Function that builds the body containing the target operation + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * controls_out, targets_out = builder.ctrl(q0_in, q1_in, [&](auto& b) { + * auto q1_res = b.x(q1_in); + * return SmallVector{q1_res}; + * }); + * ``` + * ```mlir + * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { + * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + * ``` + */ std::pair, SmallVector> ctrl(SmallVector controls, SmallVector targets, const std::function(FluxProgramBuilder&)>& body); diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index bf360b9a18..b4a958b8ea 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -303,6 +303,24 @@ class QuartzProgramBuilder { // Modifiers //===--------------------------------------------------------------------===// + /** + * @brief Apply a controlled operation + * + * @param controls Control qubits + * @param body Function that builds the body containing the target operation + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ctrl(q0, [&](auto& b) { b.x(q1); }); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.x %q1 + * quartz.yield + * } + * ``` + */ QuartzProgramBuilder& ctrl(ValueRange controls, const std::function& body); From 3c719ccb0e169efc8a42fd6edae707c6f90a8422 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:31:27 +0100 Subject: [PATCH 177/419] Add docstrings to operations --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 19 +++++++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index f8a1b417f4..a17b96c48c 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -395,11 +395,30 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParamet //===----------------------------------------------------------------------===// def YieldOp : FluxOp<"yield", traits = [Terminator]> { + let summary = "Yield from a modifier region"; + let description = [{ + Terminates a modifier region, yielding control back to the enclosing operation. + }]; + let arguments = (ins Variadic:$targets); let assemblyFormat = "$targets attr-dict"; } def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegments, AttrSizedResultSegments]> { + let summary = "Apply a controlled operation"; + let description = [{ + Modifier to make an operation controlled by one or more control qubits. + The transformed qubits are returned. + + Example: + ```mlir + %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { + %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit + flux.yield %q1_res + } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + ``` + }]; + let arguments = (ins Variadic:$controls_in, Variadic:$targets_in); let results = (outs Variadic:$controls_out, Variadic:$targets_out); let regions = (region SizedRegion<1>:$body); diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index cc54e6a0f7..4ca7b9d95c 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -379,10 +379,28 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParam //===----------------------------------------------------------------------===// def YieldOp : QuartzOp<"yield", traits = [Terminator]> { + let summary = "Yield from a modifier region"; + let description = [{ + Terminates a modifier region, yielding control back to the enclosing operation. + }]; + let assemblyFormat = "attr-dict"; } def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { + let summary = "Apply a controlled operation"; + let description = [{ + Modifier to make an operation controlled by one or more control qubits. + + Example: + ```mlir + quartz.ctrl(%q0) { + quartz.x %q1 + quartz.yield + } + ``` + }]; + let arguments = (ins Variadic:$controls); let regions = (region SizedRegion<1>:$body); let assemblyFormat = "`(` $controls `)` $body attr-dict"; From dc80d1ccf5f54967c25ea29ab563c8fe0541a4e3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:35:33 +0100 Subject: [PATCH 178/419] Improve assembly format of flux.ctrl --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index a17b96c48c..524dc5f1b8 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -422,7 +422,13 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen let arguments = (ins Variadic:$controls_in, Variadic:$targets_in); let results = (outs Variadic:$controls_out, Variadic:$targets_out); let regions = (region SizedRegion<1>:$body); - let assemblyFormat = "`(` $controls_in `_` $targets_in `)` $body attr-dict `:` type($controls_in) `_` type($targets_in) `->` type($controls_out) `_` type($targets_out)"; + let assemblyFormat = [{ + `(` `{` $controls_in `}` `,` `{` $targets_in `}` `)` + $body attr-dict `:` + `{` type($controls_in) `}` `,` `{` type($targets_in) `}` + `->` + `{` type($controls_out) `}` `,` `{` type($targets_out) `}` + }]; let extraClassDeclaration = [{ UnitaryOpInterface getBodyUnitary(); From e71d02ecfbe0a066d8d2134bd20820ec4a65bf48 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:43:23 +0100 Subject: [PATCH 179/419] Add docstrings to conversion patterns --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 32 +++++++++++++++++++ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 32 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 8a2c9053db..b3944eb6b2 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -368,6 +368,25 @@ struct ConvertFluxSWAPOp final : OpConversionPattern { } }; +/** + * @brief Converts quartz.ctrl to flux.ctrl + * + * @details + * Example: + * ```mlir + * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { + * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + * ``` + * is converted to + * ```mlir + * quartz.ctrl(%q0) { + * quartz.x %q1 + * quartz.yield + * } + * ``` + */ struct ConvertFluxCtrlOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -399,6 +418,19 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.yield to quartz.yield + * + * @details + * Example: + * ```mlir + * flux.yield %targets + * ``` + * is converted to + * ```mlir + * quartz.yield + * ``` + */ struct ConvertFluxYieldOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 32d10f15f9..7e63b9d35c 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -521,6 +521,25 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.ctrl to flux.ctrl + * + * @details + * Example: + * ```mlir + * quartz.ctrl(%q0) { + * quartz.x %q1 + * quartz.yield + * } + * ``` + * is converted to + * ```mlir + * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { + * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + * ``` + */ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -571,6 +590,19 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.yield to flux.yield + * + * @details + * Example: + * ```mlir + * quartz.yield + * ``` + * is converted to + * ```mlir + * flux.yield %targets + * ``` + */ struct ConvertQuartzYieldOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; From e1da5a3c78490bd183ac0bb6a61b7b4eb06369dc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:47:37 +0100 Subject: [PATCH 180/419] Add docstring to foldParameterArityTrait --- mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 64481dc128..e58177d017 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -138,6 +138,16 @@ template class TargetArityTrait { }; }; +/** + * @brief Replace an operand parameter with an attribute + * + * @details + * This function tries replace an operand parameter of with an attribute, + * if the operand is defined by a constant operation. + * The function is called by the foldTrait method of the ParameterArityTrait. + * + * @param op The operation to fold + */ LogicalResult foldParameterArityTrait(Operation* op); template class ParameterArityTrait { From b63e248931faa5c7cb532c3ec8b436e166100e51 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:59:08 +0100 Subject: [PATCH 181/419] Put translation on a more permanent footing --- .../TranslateQuantumComputationToQuartz.cpp | 113 ++++++++++++++---- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 5f25ebdc29..75f31c2ce0 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -220,47 +220,120 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } -// Temporary implementation of XOp translation +/** + * @brief Extracts positive control qubits from an operation + * + * @details + * Iterates through the controls of the given operation and collects + * the qubit values corresponding to positive controls. + * + * @param operation The operation containing controls + * @param qubits Flat vector of qubit values indexed by physical qubit index + * @return Vector of qubit values corresponding to positive controls + */ +llvm::SmallVector +getPosControls(const qc::Operation& operation, + const llvm::SmallVector& qubits) { + llvm::SmallVector controls; + for (const auto& [control, type] : operation.getControls()) { + if (type == qc::Control::Type::Neg) { + continue; + } + controls.push_back(qubits[control]); + } + return controls; +} + +/** + * @brief Adds X operations + * + * @details + * Translates X operations from the QuantumComputation to quartz.x operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The X operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { const auto& target = qubits[operation.getTargets()[0]]; - if (operation.getControls().empty()) { + const auto& posControls = getPosControls(operation, qubits); + if (posControls.empty()) { builder.x(target); } else { - llvm::SmallVector controls; - for (const auto& [control, type] : operation.getControls()) { - if (type == qc::Control::Type::Neg) { - continue; - } - controls.push_back(qubits[control]); - } - builder.ctrl(controls, [&](auto& builder) { builder.x(target); }); + builder.ctrl(posControls, [&](auto& builder) { builder.x(target); }); } } -// Temporary implementation of RXOp translation +/** + * @brief Adds RX operations + * + * @details + * Translates RX operations from the QuantumComputation to quartz.rx operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RX operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { const auto& theta = operation.getParameter()[0]; - const auto& qubit = qubits[operation.getTargets()[0]]; - builder.rx(theta, qubit); + const auto& target = qubits[operation.getTargets()[0]]; + const auto& posControls = getPosControls(operation, qubits); + if (posControls.empty()) { + builder.rx(theta, target); + } else { + builder.ctrl(posControls, + [&](auto& builder) { builder.rx(theta, target); }); + } } -// Temporary implementation of U2Op translation +/** + * @brief Adds U2 operations + * + * @details + * Translates U2 operations from the QuantumComputation to quartz.u2 operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The U2 operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { const auto& phi = operation.getParameter()[0]; const auto& lambda = operation.getParameter()[1]; - const auto& qubit = qubits[operation.getTargets()[0]]; - builder.u2(phi, lambda, qubit); + const auto& target = qubits[operation.getTargets()[0]]; + const auto& posControls = getPosControls(operation, qubits); + if (posControls.empty()) { + builder.u2(phi, lambda, target); + } else { + builder.ctrl(posControls, + [&](auto& builder) { builder.u2(phi, lambda, target); }); + } } -// Temporary implementation of SWAPOp translation +/** + * @brief Adds SWAP operations + * + * @details + * Translates SWAP operations from the QuantumComputation to quartz.swap + * operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The SWAP operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { - const auto& qubit0 = qubits[operation.getTargets()[0]]; - const auto& qubit1 = qubits[operation.getTargets()[1]]; - builder.swap(qubit0, qubit1); + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + const auto& posControls = getPosControls(operation, qubits); + if (posControls.empty()) { + builder.swap(target0, target1); + } else { + builder.ctrl(posControls, + [&](auto& builder) { builder.swap(target0, target1); }); + } } /** From b570fb89514b06a92d49495ab12755e40115a495 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:06:34 +0100 Subject: [PATCH 182/419] Make all operations controllable in Quartz-to-Flux conversion --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 80 +++++++++++++------ 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 7e63b9d35c..85c7d62af4 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -374,21 +374,21 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto inRegion = llvm::isa(op->getParentOp()); + const auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit Value quartzQubit = nullptr; - Value fluxQubitIn = nullptr; + Value fluxQubit = nullptr; if (inRegion) { quartzQubit = op->getOperand(0); - fluxQubitIn = rewriter.getRemappedValue(quartzQubit); + fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { quartzQubit = op.getQubit(); - fluxQubitIn = getState().qubitMap[quartzQubit]; + fluxQubit = getState().qubitMap[quartzQubit]; } // Create flux.x (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubitIn); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); // Update state map if (!inRegion) { @@ -418,10 +418,18 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::RXOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubit(); + const auto inRegion = llvm::isa(op->getParentOp()); - // Get the latest Flux qubit value from the state map - const auto fluxQubit = getState().qubitMap[quartzQubit]; + // Get the latest Flux qubit + Value quartzQubit = nullptr; + Value fluxQubit = nullptr; + if (inRegion) { + quartzQubit = op->getOperand(0); + fluxQubit = rewriter.getRemappedValue(quartzQubit); + } else { + quartzQubit = op.getQubit(); + fluxQubit = getState().qubitMap[quartzQubit]; + } const auto& theta = op.getThetaAttr(); const auto& thetaDyn = op.getThetaDyn(); @@ -430,8 +438,10 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, theta, thetaDyn); - // Update state map: the Quartz qubit now corresponds to the output qubit - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + // Update state map + if (!inRegion) { + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } rewriter.eraseOp(op); @@ -457,10 +467,18 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::U2Op op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit = op.getQubit(); + const auto inRegion = llvm::isa(op->getParentOp()); - // Get the latest Flux qubit value from the state map - const auto fluxQubit = getState().qubitMap[quartzQubit]; + // Get the latest Flux qubit + Value quartzQubit = nullptr; + Value fluxQubit = nullptr; + if (inRegion) { + quartzQubit = op->getOperand(0); + fluxQubit = rewriter.getRemappedValue(quartzQubit); + } else { + quartzQubit = op.getQubit(); + fluxQubit = getState().qubitMap[quartzQubit]; + } const auto& phi = op.getPhiAttr(); const auto& phiDyn = op.getPhiDyn(); @@ -472,8 +490,10 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, phi, phiDyn, lambda, lambdaDyn); - // Update state map: the Quartz qubit now corresponds to the output qubit - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + // Update state map + if (!inRegion) { + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } rewriter.eraseOp(op); @@ -500,20 +520,34 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto& quartzQubit0 = op.getQubit0(); - const auto& quartzQubit1 = op.getQubit1(); + const auto inRegion = llvm::isa(op->getParentOp()); - // Get the latest Flux qubit values from the state map - const auto& fluxQubit0 = getState().qubitMap[quartzQubit0]; - const auto& fluxQubit1 = getState().qubitMap[quartzQubit1]; + // Get the latest Flux qubit + Value quartzQubit0 = nullptr; + Value quartzQubit1 = nullptr; + Value fluxQubit0 = nullptr; + Value fluxQubit1 = nullptr; + if (inRegion) { + quartzQubit0 = op->getOperand(0); + quartzQubit1 = op->getOperand(1); + fluxQubit0 = rewriter.getRemappedValue(quartzQubit0); + fluxQubit1 = rewriter.getRemappedValue(quartzQubit1); + } else { + quartzQubit0 = op.getQubit0(); + quartzQubit1 = op.getQubit1(); + fluxQubit0 = getState().qubitMap[quartzQubit0]; + fluxQubit1 = getState().qubitMap[quartzQubit1]; + } // Create flux.swap (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); - // Update state map: the Quartz qubit now corresponds to the output qubit - getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + // Update state map + if (!inRegion) { + getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + } rewriter.eraseOp(op); From 5281f15b6af20b6bc2692a8205f32300fdd5d532 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:10:20 +0100 Subject: [PATCH 183/419] Make all operations controllable in Flux builder --- .../Flux/Builder/FluxProgramBuilder.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 917b9a46ff..5409454fad 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -120,6 +120,11 @@ void FluxProgramBuilder::validateQubitValue(const Value qubit) const { void FluxProgramBuilder::updateQubitTracking(const Value inputQubit, const Value outputQubit) { + if (inRegion > 0) { + // Do not update tracking within regions + return; + } + // Validate the input qubit validateQubitValue(inputQubit); @@ -178,9 +183,7 @@ Value FluxProgramBuilder::x(Value qubit) { const auto& qubitOut = xOp.getQubitOut(); // Update tracking - if (inRegion == 0) { - updateQubitTracking(qubit, qubitOut); - } + updateQubitTracking(qubit, qubitOut); return qubitOut; } @@ -242,13 +245,11 @@ std::pair, SmallVector> FluxProgramBuilder::ctrl( const auto& targetsOut = ctrlOp.getTargetsOut(); // Update tracking - if (inRegion == 0) { - for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { - updateQubitTracking(control, controlOut); - } - for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { - updateQubitTracking(target, targetOut); - } + for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + updateQubitTracking(control, controlOut); + } + for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { + updateQubitTracking(target, targetOut); } return {controlsOut, targetsOut}; From c3e9d2320bda923c35128abccc5a1cafbe12b80c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:15:29 +0100 Subject: [PATCH 184/419] WIP: Convert CtrlOp to QIR --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 18 +++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 1 + .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 30 +++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 20 ++++ .../pipeline/test_compiler_pipeline.cpp | 103 ++++++++++++++---- 5 files changed, 149 insertions(+), 23 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 3569f75bc4..52bd1cfe63 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -243,6 +243,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& x(const Value qubit); + /** + * @brief Apply the CX gate + * + * @param control Control qubit + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cx(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cx__body(%control, %target) : (!llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& cx(const Value control, const Value target); + /** * @brief Apply the RX gate to a qubit * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 83ce88baa5..449a6419b8 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -27,6 +27,7 @@ static constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; static constexpr auto QIR_X = "__quantum__qis__x__body"; +static constexpr auto QIR_CX = "__quantum__qis__cx__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index d03e154bdb..91a5373242 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -525,6 +525,34 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { } }; +/// TODO: After inlining, replace function call with controlled call +struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(CtrlOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), + op->getIterator()); + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Erases quartz.yield operation + */ +struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(YieldOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + rewriter.eraseOp(op); + return success(); + } +}; + /** * @brief Converts quartz.swap operation to QIR SWAP * @@ -890,6 +918,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 43cb61e762..7a7339badc 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -252,6 +252,26 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { return *this; } +QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, + const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create cx call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_CX, qirSignature); + builder.create(loc, fnDecl, ValueRange{control, qubit}); + + return *this; +} + QIRProgramBuilder& QIRProgramBuilder::rx(std::variant theta, const Value qubit) { // Save current insertion point diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index a5b33d2402..32ddbb8b2b 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -916,6 +916,63 @@ TEST_F(CompilerPipelineTest, X) { }); } +TEST_F(CompilerPipelineTest, CX) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.cx(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl(q0, [&](auto& b) { b.x(q1); }); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl({q0}, {q1}, [&](auto& b) { + auto q1res = b.x(q1); + return SmallVector{q1res}; + }); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl({q0}, {q1}, [&](auto& b) { + auto q1res = b.x(q1); + return SmallVector{q1res}; + }); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl(q0, [&](auto& b) { b.x(q1); }); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + auto q0 = reg[0]; + /// TODO: Replace uncomment this when CX can be converted to QIR + // auto q1 = reg[1]; + // b.cx(q0, q1); + b.x(q0); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, RX) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); @@ -1249,11 +1306,11 @@ TEST_F(SimpleConversionTest, CX) { const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0a = reg[0]; - auto q1a = reg[1]; - b.ctrl({q0a}, {q1a}, [&](auto& b) { - auto q1b = b.x(q1a); - return SmallVector{q1b}; + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ctrl({q0}, {q1}, [&](auto& b) { + auto q1res = b.x(q1); + return SmallVector{q1res}; }); }); @@ -1272,27 +1329,27 @@ TEST_F(SimpleConversionTest, CX) { TEST_F(SimpleConversionTest, CXMergeNested) { const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); - auto q0a = reg[0]; - auto q1a = reg[1]; - auto q2a = reg[2]; - b.ctrl({q0a}, {q1a, q2a}, [&](auto& b) { - auto q12b = b.ctrl({q1a}, {q2a}, [&](auto& b) { - auto q2b = b.x(q2a); - return SmallVector{q2b}; + auto q0 = reg[0]; + auto q1 = reg[1]; + auto q2 = reg[2]; + b.ctrl({q0}, {q1, q2}, [&](auto& b) { + auto q12res = b.ctrl({q1}, {q2}, [&](auto& b) { + auto q2res = b.x(q2); + return SmallVector{q2res}; }); - return SmallVector{q12b.first[0], q12b.second[0]}; + return SmallVector{q12res.first[0], q12res.second[0]}; }); }); const auto fluxInitIR = captureIR(fluxInit.get()); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); - auto q0a = reg[0]; - auto q1a = reg[1]; - auto q2a = reg[2]; - b.ctrl({q0a, q1a}, {q2a}, [&](auto& b) { - auto q2b = b.x(q2a); - return SmallVector{q2b}; + auto q0 = reg[0]; + auto q1 = reg[1]; + auto q2 = reg[2]; + b.ctrl({q0, q1}, {q2}, [&](auto& b) { + auto q2res = b.x(q2); + return SmallVector{q2res}; }); }); @@ -1302,10 +1359,10 @@ TEST_F(SimpleConversionTest, CXMergeNested) { TEST_F(SimpleConversionTest, CXRemoveTrivial) { const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q0a = reg[0]; - b.ctrl({}, {q0a}, [&](auto& b) { - auto q0b = b.x(q0a); - return SmallVector{q0b}; + auto q0 = reg[0]; + b.ctrl({}, {q0}, [&](auto& b) { + auto q0res = b.x(q0); + return SmallVector{q0res}; }); }); const auto fluxInitIR = captureIR(fluxInit.get()); From 95c77cfa610f7065950dc88f8fbcafdca197cfc7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:28:59 +0100 Subject: [PATCH 185/419] Fix linter errors --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 91a5373242..3ddafdb1c1 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -530,7 +530,7 @@ struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(CtrlOp op, OpAdaptor adaptor, + matchAndRewrite(CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), op->getIterator()); @@ -546,7 +546,7 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(YieldOp op, OpAdaptor adaptor, + matchAndRewrite(YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { rewriter.eraseOp(op); return success(); From 5c6acd452962e76b2e72ff051b2db78cfad83896 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:37:53 +0100 Subject: [PATCH 186/419] Add verifiers to CtrlOps --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 1 + .../mlir/Dialect/Quartz/IR/QuartzOps.td | 2 ++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 18 ++++++++++++++++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 15 +++++++++++++++ 4 files changed, 36 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 524dc5f1b8..bd7befdee8 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -465,6 +465,7 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen ]; let hasCanonicalizer = 1; + let hasVerifier = 1; } #endif // FluxOPS diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 4ca7b9d95c..85973dd61d 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -422,6 +422,8 @@ def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; + + let hasVerifier = 1; } #endif // QUARTZ_OPS diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 726958f42b..d3dadce732 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -436,6 +436,24 @@ DenseElementsAttr CtrlOp::tryGetStaticMatrix() { llvm::report_fatal_error("Not implemented yet"); // TODO } +LogicalResult CtrlOp::verify() { + if (getBody().front().getOperations().size() != 2) { + return emitOpError("body region must have exactly two operations"); + } + if (!llvm::isa(getBody().front().front())) { + return emitOpError( + "first operation in body region must be a unitary operation"); + } + if (!llvm::isa(getBody().front().back())) { + return emitOpError( + "second operation in body region must be a yield operation"); + } + if (getBody().front().back().getNumOperands() != getNumTargets()) { + return emitOpError("yield operation must yield all target qubits"); + } + return success(); +} + //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 1c50c467b0..dddc1de331 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -317,3 +317,18 @@ ParameterDescriptor CtrlOp::getParameter(size_t i) { DenseElementsAttr CtrlOp::tryGetStaticMatrix() { llvm::report_fatal_error("Not implemented yet"); // TODO } + +LogicalResult CtrlOp::verify() { + if (getBody().front().getOperations().size() != 2) { + return emitOpError("body region must have exactly two operations"); + } + if (!llvm::isa(getBody().front().front())) { + return emitOpError( + "first operation in body region must be a unitary operation"); + } + if (!llvm::isa(getBody().front().back())) { + return emitOpError( + "second operation in body region must be a yield operation"); + } + return success(); +} From 7440f12020d22e95fdef7e5eaeba707ec33b2462 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 5 Nov 2025 13:28:57 +0100 Subject: [PATCH 187/419] =?UTF-8?q?=F0=9F=9A=A8=20silence=20a=20compiler?= =?UTF-8?q?=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 22 +++++++++---------- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 20 ++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index e58177d017..8ca964c30d 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -10,17 +10,6 @@ #pragma once -#include "mlir/Dialect/Utils/ParameterDescriptor.h" - -#include -#include -#include -#include -#include -#include -#include -#include - // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) #if defined(__clang__) @@ -32,6 +21,17 @@ #pragma clang diagnostic pop #endif +#include "mlir/Dialect/Utils/ParameterDescriptor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + #define DIALECT_NAME_FLUX "flux" //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 1e991f3f86..e42cb1b688 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -10,16 +10,6 @@ #pragma once -#include "mlir/Dialect/Utils/ParameterDescriptor.h" - -#include -#include -#include -#include -#include -#include -#include - // Suppress warnings about ambiguous reversed operators in MLIR // (see https://github.com/llvm/llvm-project/issues/45853) #if defined(__clang__) @@ -31,6 +21,16 @@ #pragma clang diagnostic pop #endif +#include "mlir/Dialect/Utils/ParameterDescriptor.h" + +#include +#include +#include +#include +#include +#include +#include + #define DIALECT_NAME_QUARTZ "quartz" //===----------------------------------------------------------------------===// From 37de9e7ef4d08a9bea46c6669c7583cbbda00134 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 6 Nov 2025 16:00:05 +0100 Subject: [PATCH 188/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20paramet?= =?UTF-8?q?er=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit operation parameters are now solely based on operands. There was simply no value in keeping the option of storing as an attribute. It just made the code more complicated and harder to follow. Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 6 +- .../mlir/Dialect/Flux/IR/FluxDialect.h | 151 +++++++------ .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 6 +- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 84 +++----- .../Quartz/Builder/QuartzProgramBuilder.h | 6 +- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 84 ++++++-- .../Dialect/Quartz/IR/QuartzInterfaces.td | 2 +- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 90 +++----- .../mlir/Dialect/Utils/ParameterDescriptor.h | 81 ------- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 15 +- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 25 +-- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 36 +--- .../Flux/Builder/FluxProgramBuilder.cpp | 16 +- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 200 +++--------------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 14 +- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 100 +++------ 16 files changed, 280 insertions(+), 636 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 1cefc6df1b..e13b90a377 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -279,7 +279,7 @@ class FluxProgramBuilder { * %q_out = flux.rx(1.0) %q_in : !flux.qubit -> !flux.qubit * ``` */ - Value rx(std::variant theta, Value qubit); + Value rx(const std::variant& theta, Value qubit); /** * @brief Apply a U2 gate to a qubit @@ -301,8 +301,8 @@ class FluxProgramBuilder { * %q_out = flux.u2(1.0, 0.5) %q_in : !flux.qubit -> !flux.qubit * ``` */ - Value u2(std::variant phi, - std::variant lambda, Value qubit); + Value u2(const std::variant& phi, + const std::variant& lambda, Value qubit); /** * @brief Apply a SWAP gate to two qubits diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 8ca964c30d..417b756356 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -21,8 +21,6 @@ #pragma clang diagnostic pop #endif -#include "mlir/Dialect/Utils/ParameterDescriptor.h" - #include #include #include @@ -53,113 +51,106 @@ namespace mlir::flux { -template class TargetArityTrait { +/** + * @brief Trait for operations with a fixed number of target qubits and + * parameters + * @details This trait indicates that an operation has a fixed number of target + * qubits and parameters, specified by the template parameters T and P. This is + * helpful for defining operations with known arities, allowing for static + * verification and code generation optimizations. + * @tparam T The target arity. + * @tparam P The parameter arity. + */ +template class TargetAndParameterArityTrait { public: template class Impl : public OpTrait::TraitBase { public: - size_t getNumQubits() { return n; } - size_t getNumTargets() { return n; } - size_t getNumControls() { return 0; } - size_t getNumPosControls() { return 0; } - size_t getNumNegControls() { return 0; } + static size_t getNumQubits() { return T; } + static size_t getNumTargets() { return T; } + static size_t getNumControls() { return 0; } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } Value getInputQubit(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have qubits"); + } return this->getOperation()->getOperand(i); } - Value getOutputQubit(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have qubits"); + } return this->getOperation()->getResult(i); } - Value getInputTarget(size_t i) { return getInputQubit(i); } + Value getInputTarget(const size_t i) { return getInputQubit(i); } + Value getOutputTarget(const size_t i) { return getOutputQubit(i); } - Value getOutputTarget(size_t i) { return getOutputQubit(i); } + static Value getInputPosControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } + static Value getOutputPosControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } + static Value getInputNegControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } - Value getInputPosControl(size_t i) { - llvm::report_fatal_error("Operation does not have controls"); + static Value getOutputNegControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); } - Value getOutputPosControl(size_t i) { - llvm::report_fatal_error("Operation does not have controls"); + static size_t getNumParams() { return P; } + + Value getParameter(const size_t i) { + if (i >= P) { + llvm::reportFatalUsageError("Parameter index out of bounds"); + } + return this->getOperation()->getOperand(T + i); } - Value getInputNegControl(size_t i) { - llvm::report_fatal_error("Operation does not have controls"); + [[nodiscard]] static FloatAttr getStaticParameter(const Value param) { + auto constantOp = param.getDefiningOp(); + if (!constantOp) { + return nullptr; + } + return dyn_cast(constantOp.getValue()); } - Value getOutputNegControl(size_t i) { - llvm::report_fatal_error("Operation does not have controls"); + bool hasStaticUnitary() { + if constexpr (P == 0) { + return true; + } + const auto& op = this->getOperation(); + for (size_t i = 0; i < P; ++i) { + if (!getStaticParameter(op->getOperand(T + i))) { + return false; + } + } + return true; } Value getInputForOutput(Value output) { - switch (n) { - case 1: - if (output == this->getOperation()->getResult(0)) { - return this->getOperation()->getOperand(0); - } - llvm::report_fatal_error( - "Given qubit is not an output of the operation"); - case 2: - if (output == this->getOperation()->getResult(0)) { - return this->getOperation()->getOperand(0); - } - if (output == this->getOperation()->getResult(1)) { - return this->getOperation()->getOperand(1); + const auto& op = this->getOperation(); + for (size_t i = 0; i < T; ++i) { + if (output == op->getResult(i)) { + return op->getOperand(i); } - llvm::report_fatal_error( - "Given qubit is not an output of the operation"); - default: - llvm::report_fatal_error("Unsupported number of qubits"); } + llvm::reportFatalUsageError( + "Given qubit is not an output of the operation"); } - Value getOutputForInput(Value input) { - switch (n) { - case 1: - if (input == this->getOperation()->getOperand(0)) { - return this->getOperation()->getResult(0); - } - llvm::report_fatal_error( - "Given qubit is not an input of the operation"); - case 2: - if (input == this->getOperation()->getOperand(0)) { - return this->getOperation()->getResult(0); - } - if (input == this->getOperation()->getOperand(1)) { - return this->getOperation()->getResult(1); + const auto& op = this->getOperation(); + for (size_t i = 0; i < T; ++i) { + if (input == op->getOperand(i)) { + return op->getResult(i); } - llvm::report_fatal_error( - "Given qubit is not an input of the operation"); - default: - llvm::report_fatal_error("Unsupported number of qubits"); } - } - }; -}; - -/** - * @brief Replace an operand parameter with an attribute - * - * @details - * This function tries replace an operand parameter of with an attribute, - * if the operand is defined by a constant operation. - * The function is called by the foldTrait method of the ParameterArityTrait. - * - * @param op The operation to fold - */ -LogicalResult foldParameterArityTrait(Operation* op); - -template class ParameterArityTrait { -public: - template - class Impl : public OpTrait::TraitBase { - public: - size_t getNumParams() { return n; } - - static LogicalResult foldTrait(Operation* op, ArrayRef operands, - SmallVectorImpl& results) { - return foldParameterArityTrait(op); + llvm::reportFatalUsageError( + "Given qubit is not an input of the operation"); } }; }; diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 69fb84c5d2..16611b0bc0 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -98,11 +98,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "::mlir::utils::ParameterDescriptor", "getParameter", (ins "size_t":$i) - >, - InterfaceMethod< - "For the i-th parameter, replace the operand with an attribute.", - "void", "replaceOperandWithAttr", (ins "size_t":$i, "FloatAttr":$attr) + "Value", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index bd7befdee8..c7a141d715 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -241,43 +241,21 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { // Traits //===----------------------------------------------------------------------===// -class TargetArityTrait : ParamNativeOpTrait<"TargetArityTrait", !cast(n)> { +class TargetAndParameterArityTrait + : ParamNativeOpTrait<"TargetAndParameterArityTrait", !strconcat(!cast(T), ",", !cast(P))> { let cppNamespace = "::mlir::flux"; } -def OneTarget : TargetArityTrait<1>; - -def TwoTarget : TargetArityTrait<2>; - -class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { - let cppNamespace = "::mlir::flux"; -} - -def ZeroParameter : ParameterArityTrait<0> { - let extraConcreteClassDeclaration = [{ - ::mlir::utils::ParameterDescriptor getParameter(size_t i) { - llvm::report_fatal_error("Operation does not have parameters"); - } - - bool hasStaticUnitary() { return true; } - - void replaceOperandWithAttr(size_t i, FloatAttr attr) { - llvm::report_fatal_error("Operation does not have parameters"); - } - }]; -} - -def OneParameter : ParameterArityTrait<1>; - -def TwoParameter : ParameterArityTrait<2>; - -def ThreeParameter : ParameterArityTrait<3>; +def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; +def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; +def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; +def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; //===----------------------------------------------------------------------===// // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { +def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit and returns the transformed qubit. @@ -288,7 +266,7 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { ``` }]; - let arguments = (ins QubitType:$qubit_in); + let arguments = (ins Arg:$qubit_in); let results = (outs QubitType:$qubit_out); let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -300,11 +278,10 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { let hasCanonicalizer = 1; } -def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { +def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit and returns the transformed qubit. - The angle theta can be provided as a static attribute or as a dynamic operand. Example: ```mlir @@ -312,33 +289,27 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> ``` }]; - let arguments = (ins QubitType:$qubit_in, - OptionalAttr:$theta, - Optional:$theta_dyn); + let arguments = (ins Arg:$qubit_in, + Arg:$theta); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($theta^)? (`` $theta_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - ::mlir::utils::ParameterDescriptor getParameter(size_t i); - void replaceOperandWithAttr(size_t i, FloatAttr attr); - bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "std::variant":$theta)> + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> ]; let hasCanonicalizer = 1; - let hasVerifier = 1; } -def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { +def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit and returns the transformed qubit. - The angles phi and lambda can be provided as static attributes or as dynamic operands. Example: ```mlir @@ -346,30 +317,23 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, A ``` }]; - let arguments = (ins QubitType:$qubit_in, - OptionalAttr:$phi, - Optional:$phi_dyn, - OptionalAttr:$lambda, - Optional:$lambda_dyn); + let arguments = (ins Arg:$qubit_in, + Arg:$phi, + Arg:$lambda); let results = (outs QubitType:$qubit_out); - let assemblyFormat = "`(` ($phi^)? (`` $phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - ::mlir::utils::ParameterDescriptor getParameter(size_t i); - void replaceOperandWithAttr(size_t i, FloatAttr attr); - bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "std::variant":$phi, "std::variant":$lambda)> + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$phi, "const std::variant&":$lambda)> ]; - - let hasVerifier = 1; } -def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { +def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits and returns the transformed qubits. @@ -380,9 +344,10 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParamet ``` }]; - let arguments = (ins QubitType:$qubit0_in, QubitType:$qubit1_in); + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); - let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + let assemblyFormat = "$qubit0_in $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -448,8 +413,7 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen Value getInputForOutput(Value output); Value getOutputForInput(Value input); size_t getNumParams(); - ::mlir::utils::ParameterDescriptor getParameter(size_t i); - void replaceOperandWithAttr(size_t i, FloatAttr attr); + Value getParameter(size_t i); bool hasStaticUnitary(); static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index b4a958b8ea..8caf2ad76e 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -259,7 +259,7 @@ class QuartzProgramBuilder { * quartz.rx(1.0) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& rx(std::variant theta, + QuartzProgramBuilder& rx(const std::variant& theta, Value qubit); /** @@ -278,8 +278,8 @@ class QuartzProgramBuilder { * quartz.u2(1.0, 0.5) %q : !quartz.qubit * ``` */ - QuartzProgramBuilder& u2(std::variant phi, - std::variant lambda, + QuartzProgramBuilder& u2(const std::variant& phi, + const std::variant& lambda, Value qubit); /** diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index e42cb1b688..3a3928a371 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -21,9 +21,9 @@ #pragma clang diagnostic pop #endif -#include "mlir/Dialect/Utils/ParameterDescriptor.h" - #include +#include +#include #include #include #include @@ -52,37 +52,77 @@ namespace mlir::quartz { -template class TargetArityTrait { +/** + * @brief Trait for operations with a fixed number of target qubits and + * parameters + * @details This trait indicates that an operation has a fixed number of target + * qubits and parameters, specified by the template parameters T and P. This is + * helpful for defining operations with known arities, allowing for static + * verification and code generation optimizations. + * @tparam T The target arity. + * @tparam P The parameter arity. + */ +template class TargetAndParameterArityTrait { public: template class Impl : public OpTrait::TraitBase { public: - size_t getNumQubits() { return n; } - size_t getNumTargets() { return n; } - size_t getNumControls() { return 0; } - size_t getNumPosControls() { return 0; } - size_t getNumNegControls() { return 0; } + static size_t getNumQubits() { return T; } + static size_t getNumTargets() { return T; } + static size_t getNumControls() { return 0; } + static size_t getNumPosControls() { return 0; } + static size_t getNumNegControls() { return 0; } + + Value getQubit(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have qubits"); + } + return this->getOperation()->getOperand(i); + } + Value getTarget(size_t i) { + if constexpr (T == 0) { + llvm::reportFatalUsageError("Operation does not have targets"); + } + return this->getOperation()->getOperand(i); + } - Value getQubit(size_t i) { return this->getOperation()->getOperand(i); } + static Value getPosControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } + + static Value getNegControl([[maybe_unused]] size_t i) { + llvm::reportFatalUsageError("Operation does not have controls"); + } - Value getTarget(size_t i) { return this->getOperation()->getOperand(i); } + static size_t getNumParams() { return P; } - Value getPosControl(size_t i) { - llvm::report_fatal_error("Operation does not have controls"); + Value getParameter(const size_t i) { + if (i >= P) { + llvm::reportFatalUsageError("Parameter index out of bounds"); + } + return this->getOperation()->getOperand(T + i); } - Value getNegControl(size_t i) { - llvm::report_fatal_error("Operation does not have controls"); + [[nodiscard]] static FloatAttr getStaticParameter(const Value param) { + auto constantOp = param.getDefiningOp(); + if (!constantOp) { + return nullptr; + } + return dyn_cast(constantOp.getValue()); } - }; -}; -template class ParameterArityTrait { -public: - template - class Impl : public OpTrait::TraitBase { - public: - size_t getNumParams() { return n; } + bool hasStaticUnitary() { + if constexpr (P == 0) { + return true; + } + const auto& op = this->getOperation(); + for (size_t i = 0; i < P; ++i) { + if (!getStaticParameter(op->getOperand(T + i))) { + return false; + } + } + return true; + } }; }; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 3dcda1096c..0333698614 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -73,7 +73,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { >, InterfaceMethod< "Returns the i-th parameter.", - "::mlir::utils::ParameterDescriptor", "getParameter", (ins "size_t":$i) + "Value", "getParameter", (ins "size_t":$i) >, /// TODO: Add convenience methods as necessary diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 85973dd61d..b5ff1ac252 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -229,48 +229,21 @@ def ResetOp : QuartzOp<"reset"> { // Traits //===----------------------------------------------------------------------===// -class TargetArityTrait : ParamNativeOpTrait<"TargetArityTrait", !cast(n)> { +class TargetAndParameterArityTrait + : ParamNativeOpTrait<"TargetAndParameterArityTrait", !strconcat(!cast(T), ",", !cast(P))> { let cppNamespace = "::mlir::quartz"; } -def OneTarget : TargetArityTrait<1> { - let extraConcreteClassDeclaration = [{ - Value getQubit(size_t i) { - if (i == 0) { - return getQubit(); - } - llvm::report_fatal_error("Operation has one input qubit"); - } - }]; -} - -def TwoTarget : TargetArityTrait<2>; - -class ParameterArityTrait : ParamNativeOpTrait<"ParameterArityTrait", !cast(n)> { - let cppNamespace = "::mlir::quartz"; -} - -def ZeroParameter : ParameterArityTrait<0> { - let extraConcreteClassDeclaration = [{ - ::mlir::utils::ParameterDescriptor getParameter(size_t i) { - llvm::report_fatal_error("Operation does not have parameters"); - } - - bool hasStaticUnitary() { return true; } - }]; -} - -def OneParameter : ParameterArityTrait<1>; - -def TwoParameter : ParameterArityTrait<2>; - -def ThreeParameter : ParameterArityTrait<2>; +def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; +def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; +def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; +def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; //===----------------------------------------------------------------------===// // Unitary Operations //===----------------------------------------------------------------------===// -def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> { +def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit, modifying it in place. @@ -281,8 +254,8 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> ``` }]; - let arguments = (ins QubitType:$qubit); - let assemblyFormat = "$qubit attr-dict"; + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -290,11 +263,10 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTarget, ZeroParameter]> }]; } -def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter]> { +def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit, modifying it in place. - The angle theta can be provided as a static attribute or as a dynamic operand. Example: ```mlir @@ -302,30 +274,24 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTarget, OneParameter] ``` }]; - let arguments = (ins QubitType:$qubit, - OptionalAttr:$theta, - Optional:$theta_dyn); - let assemblyFormat = "`(` ($theta^)? (`` $theta_dyn^)? `)` $qubit attr-dict"; + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - ::mlir::utils::ParameterDescriptor getParameter(size_t i); - bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "std::variant":$theta)> + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> ]; - - let hasVerifier = 1; } -def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, AttrSizedOperandSegments]> { +def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit, modifying it in place. - The angles phi and lambda can be provided as static attributes or as dynamic operands. Example: ```mlir @@ -333,28 +299,21 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTarget, TwoParameter, ``` }]; - let arguments = (ins QubitType:$qubit, - OptionalAttr:$phi, - Optional:$phi_dyn, - OptionalAttr:$lambda, - Optional:$lambda_dyn); - let assemblyFormat = "`(` ($phi^)? (`` $phi_dyn^)? `,` ($lambda^)? ($lambda_dyn^)? `)` $qubit attr-dict"; - + let arguments = (ins Arg:$qubit_in, + Arg:$phi, + Arg:$lambda); + let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - ::mlir::utils::ParameterDescriptor getParameter(size_t i); - bool hasStaticUnitary(); DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } }]; let builders = [ - OpBuilder<(ins "Value":$qubit_in, "std::variant":$phi, "std::variant":$lambda)> + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$phi, "const std::variant&":$lambda)> ]; - - let hasVerifier = 1; } -def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParameter]> { +def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits, modifying them in place. @@ -365,8 +324,9 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTarget, ZeroParam ``` }]; - let arguments = (ins QubitType:$qubit0, QubitType:$qubit1); - let assemblyFormat = "$qubit0 `,` $qubit1 attr-dict"; + let arguments = (ins Arg:$qubit0, + Arg:$qubit1); + let assemblyFormat = "$qubit0 $qubit1 attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -417,7 +377,7 @@ def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { Value getPosControl(size_t i); Value getNegControl(size_t i); size_t getNumParams(); - ::mlir::utils::ParameterDescriptor getParameter(size_t i); + Value getParameter(size_t i); bool hasStaticUnitary(); static DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } diff --git a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h b/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h deleted file mode 100644 index f68c110cc1..0000000000 --- a/mlir/include/mlir/Dialect/Utils/ParameterDescriptor.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include -#include - -namespace mlir::utils { - -/** - * @brief Descriptor for a parameter that can be either static or dynamic - */ -class ParameterDescriptor { - mlir::FloatAttr valueAttr; - mlir::Value valueOperand; - -public: - /** - * @brief Construct a new ParameterDescriptor object - * - * @param attr Static float attribute (optional) - * @param operand Dynamic value operand (optional) - */ - ParameterDescriptor(mlir::FloatAttr attr = nullptr, - mlir::Value operand = nullptr) { - assert(!(attr && operand) && "Cannot have both static and dynamic values"); - valueAttr = attr; - valueOperand = operand; - } - - /** - * @brief Check if the parameter is static - * - * @return true if static, false if dynamic - */ - bool isStatic() const { return valueAttr != nullptr; } - - /** - * @brief Check if the parameter is dynamic - * - * @return true if dynamic, false if static - */ - bool isDynamic() const { return valueOperand != nullptr; } - - /** - * @brief Try to get the double value of the parameter - * - * @return double value - */ - double getValueDouble() const { - if (isDynamic()) { - llvm::report_fatal_error( - "Cannot get double value from dynamic parameter"); - } - return valueAttr.getValueAsDouble(); - } - - /** - * @brief Get the static float attribute - * - * @return mlir::FloatAttr Static float attribute (nullptr if dynamic) - */ - mlir::FloatAttr getValueAttr() const { return valueAttr; } - - /** - * @brief Get the dynamic value operand - * - * @return mlir::Value Dynamic value operand (nullptr if static) - */ - mlir::Value getValueOperand() const { return valueOperand; } -}; - -} // namespace mlir::utils diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index b3944eb6b2..91325e3205 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -285,11 +285,8 @@ struct ConvertFluxRXOp final : OpConversionPattern { // OpAdaptor provides the already type-converted input qubit const auto& quartzQubit = adaptor.getQubitIn(); - const auto& theta = op.getThetaAttr(); - const auto& thetaDyn = op.getThetaDyn(); - // Create quartz.rx (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, theta, thetaDyn); + rewriter.create(op.getLoc(), quartzQubit, adaptor.getTheta()); // Replace the output qubit with the same quartz reference rewriter.replaceOp(op, quartzQubit); @@ -319,15 +316,9 @@ struct ConvertFluxU2Op final : OpConversionPattern { // OpAdaptor provides the already type-converted input qubit const auto& quartzQubit = adaptor.getQubitIn(); - const auto& phi = op.getPhiAttr(); - const auto& phiDyn = op.getPhiDyn(); - - const auto& lambda = op.getLambdaAttr(); - const auto& lambdaDyn = op.getLambdaDyn(); - // Create quartz.u2 (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, phi, phiDyn, lambda, - lambdaDyn); + rewriter.create(op.getLoc(), quartzQubit, adaptor.getPhi(), + adaptor.getLambda()); // Replace the output qubit with the same quartz reference rewriter.replaceOp(op, quartzQubit); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 85c7d62af4..5422e4a2b4 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -383,7 +383,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { quartzQubit = op->getOperand(0); fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { - quartzQubit = op.getQubit(); + quartzQubit = op.getQubitIn(); fluxQubit = getState().qubitMap[quartzQubit]; } @@ -416,7 +416,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::RXOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(quartz::RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { const auto inRegion = llvm::isa(op->getParentOp()); @@ -427,16 +427,13 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { quartzQubit = op->getOperand(0); fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { - quartzQubit = op.getQubit(); + quartzQubit = op.getQubitIn(); fluxQubit = getState().qubitMap[quartzQubit]; } - const auto& theta = op.getThetaAttr(); - const auto& thetaDyn = op.getThetaDyn(); - // Create flux.rx (consumes input, produces output) auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, theta, thetaDyn); + rewriter.create(op.getLoc(), fluxQubit, adaptor.getTheta()); // Update state map if (!inRegion) { @@ -465,7 +462,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::U2Op op, OpAdaptor /*adaptor*/, + matchAndRewrite(quartz::U2Op op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { const auto inRegion = llvm::isa(op->getParentOp()); @@ -476,19 +473,13 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { quartzQubit = op->getOperand(0); fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { - quartzQubit = op.getQubit(); + quartzQubit = op.getQubitIn(); fluxQubit = getState().qubitMap[quartzQubit]; } - const auto& phi = op.getPhiAttr(); - const auto& phiDyn = op.getPhiDyn(); - - const auto& lambda = op.getLambdaAttr(); - const auto& lambdaDyn = op.getLambdaDyn(); - // Create flux.u2 (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, phi, - phiDyn, lambda, lambdaDyn); + auto fluxOp = rewriter.create( + op.getLoc(), fluxQubit, adaptor.getPhi(), adaptor.getLambda()); // Update state map if (!inRegion) { diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 3ddafdb1c1..e37d4fc108 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -238,7 +238,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); - const auto index = op.getIndex(); + const auto index = static_cast(op.getIndex()); // Get or create a pointer to the qubit if (getState().ptrMap.contains(index)) { @@ -256,7 +256,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { } // Track maximum qubit index - if (index >= getState().numQubits) { + if (std::cmp_greater_equal(index, getState().numQubits)) { getState().numQubits = index + 1; } @@ -454,18 +454,9 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { const auto fnDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_RX, qirSignature); - Value thetaDyn; - if (op.getTheta().has_value()) { - const auto& theta = op.getThetaAttr(); - auto constantOp = rewriter.create(op.getLoc(), theta); - thetaDyn = constantOp.getResult(); - } else { - thetaDyn = op.getThetaDyn(); - } - // Replace with call to RX rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubit(), thetaDyn}); + op, fnDecl, ValueRange{adaptor.getQubitIn(), op.getTheta()}); return success(); } }; @@ -500,27 +491,10 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { const auto fnDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_U2, qirSignature); - Value phiDyn; - if (op.getPhi().has_value()) { - const auto& phi = op.getPhiAttr(); - auto constantOp = rewriter.create(op.getLoc(), phi); - phiDyn = constantOp.getResult(); - } else { - phiDyn = op.getPhiDyn(); - } - - Value lambdaDyn; - if (op.getLambda().has_value()) { - const auto& lambda = op.getLambdaAttr(); - auto constantOp = rewriter.create(op.getLoc(), lambda); - lambdaDyn = constantOp.getResult(); - } else { - lambdaDyn = op.getLambdaDyn(); - } - // Replace with call to U2 rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubit(), phiDyn, lambdaDyn}); + op, fnDecl, + ValueRange{adaptor.getQubitIn(), op.getPhi(), op.getLambda()}); return success(); } }; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 5409454fad..e3a7a9252a 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -79,10 +79,10 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { return qubit; } -llvm::SmallVector +SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { - llvm::SmallVector qubits; + SmallVector qubits; qubits.reserve(static_cast(size)); auto nameAttr = builder.getStringAttr(name); @@ -113,7 +113,7 @@ void FluxProgramBuilder::validateQubitValue(const Value qubit) const { llvm::errs() << "Error: Attempting to use an invalid qubit SSA value. " << "The value may have been consumed by a previous operation " << "or was never created through this builder.\n"; - llvm::report_fatal_error( + llvm::reportFatalUsageError( "Invalid qubit value used (either consumed or not tracked)"); } } @@ -188,7 +188,7 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } -Value FluxProgramBuilder::rx(std::variant theta, +Value FluxProgramBuilder::rx(const std::variant& theta, Value qubit) { auto rxOp = builder.create(loc, qubit, theta); const auto& qubitOut = rxOp.getQubitOut(); @@ -199,8 +199,8 @@ Value FluxProgramBuilder::rx(std::variant theta, return qubitOut; } -Value FluxProgramBuilder::u2(std::variant phi, - std::variant lambda, +Value FluxProgramBuilder::u2(const std::variant& phi, + const std::variant& lambda, Value qubit) { auto u2Op = builder.create(loc, qubit, phi, lambda); const auto& qubitOut = u2Op.getQubitOut(); @@ -245,10 +245,10 @@ std::pair, SmallVector> FluxProgramBuilder::ctrl( const auto& targetsOut = ctrlOp.getTargetsOut(); // Update tracking - for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + for (const auto& [control, controlOut] : zip(controls, controlsOut)) { updateQubitTracking(control, controlOut); } - for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { + for (const auto& [target, targetOut] : zip(targets, targetsOut)) { updateQubitTracking(target, targetOut); } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index d3dadce732..5ea0afb269 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" // IWYU pragma: associated #include "mlir/Dialect/Utils/MatrixUtils.h" -#include "mlir/Dialect/Utils/ParameterDescriptor.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -70,46 +69,6 @@ void FluxDialect::initialize() { #include "mlir/Dialect/Flux/IR/FluxInterfaces.cpp.inc" -//===----------------------------------------------------------------------===// -// Traits -//===----------------------------------------------------------------------===// - -namespace mlir::flux { - -LogicalResult foldParameterArityTrait(Operation* op) { - auto concreteOp = llvm::dyn_cast(op); - if (!concreteOp) { - return failure(); - } - - LogicalResult succeeded = failure(); - for (size_t i = 0; i < concreteOp.getNumParams(); ++i) { - const auto& parameter = concreteOp.getParameter(i); - - if (parameter.isStatic()) { - continue; - } - - auto constantOp = - parameter.getValueOperand().getDefiningOp(); - if (!constantOp) { - continue; - } - - const auto& thetaAttr = llvm::dyn_cast(constantOp.getValue()); - if (!thetaAttr) { - continue; - } - - concreteOp.replaceOperandWithAttr(i, thetaAttr); - succeeded = success(); - } - - return succeeded; -} - -} // namespace mlir::flux - //===----------------------------------------------------------------------===// // Operations //===----------------------------------------------------------------------===// @@ -178,134 +137,69 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { // RXOp -ParameterDescriptor RXOp::getParameter(size_t i) { - if (i == 0) { - return {getThetaAttr(), getThetaDyn()}; - } - llvm::report_fatal_error("RXOp has one parameter"); -} - -void RXOp::replaceOperandWithAttr(size_t i, FloatAttr attr) { - if (i == 0) { - getOperation()->setAttr("theta", attr); - getOperation()->eraseOperand(1); - return; - } - llvm::report_fatal_error("RXOp has one parameter"); -} - -bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } - DenseElementsAttr RXOp::tryGetStaticMatrix() { - if (!hasStaticUnitary()) { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { return nullptr; } + const auto thetaValue = theta.getValueAsDouble(); auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& theta = getTheta().value().convertToDouble(); - return DenseElementsAttr::get(type, getMatrixRX(theta)); + return DenseElementsAttr::get(type, getMatrixRX(thetaValue)); } -void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, - std::variant theta) { - FloatAttr thetaAttr = nullptr; +void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& theta) { Value thetaOperand = nullptr; if (std::holds_alternative(theta)) { - thetaAttr = builder.getF64FloatAttr(std::get(theta)); - } else if (std::holds_alternative(theta)) { - thetaAttr = std::get(theta); + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); } else { thetaOperand = std::get(theta); } - build(builder, state, QubitType::get(builder.getContext()), qubitIn, - thetaAttr, thetaOperand); -} - -LogicalResult RXOp::verify() { - if (getTheta().has_value() == (getThetaDyn() != nullptr)) { - return emitOpError("must specify exactly one of static or dynamic theta"); - } - return success(); + build(odsBuilder, odsState, qubit_in, thetaOperand); } // U2Op -ParameterDescriptor U2Op::getParameter(size_t i) { - if (i == 0) { - return {getPhiAttr(), getPhiDyn()}; - } - if (i == 1) { - return {getLambdaAttr(), getLambdaDyn()}; - } - llvm::report_fatal_error("U2Op has two parameters"); -} - -void U2Op::replaceOperandWithAttr(size_t i, FloatAttr attr) { - if (i == 0) { - getOperation()->setAttr("phi", attr); - getOperation()->eraseOperand(1); - return; - } - if (i == 1) { - getOperation()->setAttr("lambda", attr); - getOperation()->eraseOperand(3); - return; - } - llvm::report_fatal_error("U2Op has two parameter"); -} - -bool U2Op::hasStaticUnitary() { - return (getParameter(0).isStatic() && getParameter(1).isStatic()); -} - DenseElementsAttr U2Op::tryGetStaticMatrix() { - if (!hasStaticUnitary()) { + const auto phi = getStaticParameter(getPhi()); + const auto lambda = getStaticParameter(getLambda()); + if (!phi || !lambda) { return nullptr; } + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& phi = getPhi().value().convertToDouble(); - const auto& lambda = getLambda().value().convertToDouble(); - return DenseElementsAttr::get(type, getMatrixU2(phi, lambda)); + return DenseElementsAttr::get(type, getMatrixU2(phiValue, lambdaValue)); } -void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, - std::variant phi, - std::variant lambda) { - FloatAttr phiAttr = nullptr; +void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& phi, + const std::variant& lambda) { Value phiOperand = nullptr; if (std::holds_alternative(phi)) { - phiAttr = builder.getF64FloatAttr(std::get(phi)); - } else if (std::holds_alternative(phi)) { - phiAttr = std::get(phi); + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); } else { phiOperand = std::get(phi); } - FloatAttr lambdaAttr = nullptr; Value lambdaOperand = nullptr; if (std::holds_alternative(lambda)) { - lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); - } else if (std::holds_alternative(lambda)) { - lambdaAttr = std::get(lambda); + lambdaOperand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(lambda))); } else { lambdaOperand = std::get(lambda); } - build(builder, state, QubitType::get(builder.getContext()), qubitIn, phiAttr, - phiOperand, lambdaAttr, lambdaOperand); -} - -LogicalResult U2Op::verify() { - if (getPhi().has_value() == (getPhiDyn() != nullptr)) { - return emitOpError("must specify exactly one of static or dynamic phi"); - } - if (getLambda().has_value() == (getLambdaDyn() != nullptr)) { - return emitOpError("must specify exactly one of static or dynamic lambda"); - } - return success(); + build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); } // SWAPOp @@ -417,16 +311,11 @@ size_t CtrlOp::getNumParams() { return unitaryOp.getNumParams(); } -ParameterDescriptor CtrlOp::getParameter(size_t i) { +Value CtrlOp::getParameter(size_t i) { auto unitaryOp = getBodyUnitary(); return unitaryOp.getParameter(i); } -void CtrlOp::replaceOperandWithAttr(size_t i, FloatAttr attr) { - auto unitaryOp = getBodyUnitary(); - unitaryOp.replaceOperandWithAttr(i, attr); -} - bool CtrlOp::hasStaticUnitary() { auto unitaryOp = getBodyUnitary(); return unitaryOp.hasStaticUnitary(); @@ -510,9 +399,8 @@ struct RemoveSubsequentX final : OpRewritePattern { LogicalResult matchAndRewrite(XOp xOp, PatternRewriter& rewriter) const override { - auto prevOp = xOp.getQubitIn().getDefiningOp(); - // Check if the predecessor is an XOp + auto prevOp = xOp.getQubitIn().getDefiningOp(); if (!prevOp) { return failure(); } @@ -534,40 +422,16 @@ struct MergeSubsequentRX final : OpRewritePattern { LogicalResult matchAndRewrite(RXOp rxOp, PatternRewriter& rewriter) const override { - auto prevOp = rxOp.getQubitIn().getDefiningOp(); - // Check if the predecessor is an RXOp + auto prevOp = rxOp.getQubitIn().getDefiningOp(); if (!prevOp) { return failure(); } // Compute and set new theta - const auto& theta = prevOp.getParameter(0); - const auto& prevTheta = rxOp.getParameter(0); - if (theta.isStatic() && prevTheta.isStatic()) { - const auto& newTheta = - theta.getValueDouble() + prevTheta.getValueDouble(); - rxOp.setThetaAttr(rewriter.getF64FloatAttr(newTheta)); - } else if (theta.isStatic() && prevTheta.isDynamic()) { - auto constantOp = rewriter.create( - rxOp.getLoc(), rewriter.getF64FloatAttr(theta.getValueDouble())); - auto newTheta = rewriter.create( - rxOp.getLoc(), constantOp.getResult(), prevTheta.getValueOperand()); - rxOp->setOperand(1, newTheta.getResult()); - } else if (theta.isDynamic() && prevTheta.isStatic()) { - auto constantOp = rewriter.create( - rxOp.getLoc(), rewriter.getF64FloatAttr(prevTheta.getValueDouble())); - auto newTheta = rewriter.create( - rxOp.getLoc(), theta.getValueOperand(), constantOp.getResult()); - rxOp.removeThetaAttr(); - rxOp->setOperand(1, newTheta.getResult()); - } else if (theta.isDynamic() && prevTheta.isDynamic()) { - auto newTheta = rewriter.create( - rxOp.getLoc(), theta.getValueOperand(), prevTheta.getValueOperand()); - rxOp->setOperand(1, newTheta.getResult()); - } else { - return failure(); - } + auto newTheta = rewriter.create( + rxOp.getLoc(), rxOp.getTheta(), prevOp.getTheta()); + rxOp->setOperand(1, newTheta.getResult()); // Trivialize previous RXOp rewriter.replaceOp(prevOp, prevOp.getQubitIn()); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 6accdd117b..52b3ee7a39 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -74,11 +74,11 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { return staticOp.getQubit(); } -llvm::SmallVector +SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { // Allocate a sequence of qubits with register metadata - llvm::SmallVector qubits; + SmallVector qubits; qubits.reserve(size); auto nameAttr = builder.getStringAttr(name); @@ -133,15 +133,15 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { } QuartzProgramBuilder& -QuartzProgramBuilder::rx(std::variant theta, +QuartzProgramBuilder::rx(const std::variant& theta, Value qubit) { builder.create(loc, qubit, theta); return *this; } QuartzProgramBuilder& -QuartzProgramBuilder::u2(std::variant phi, - std::variant lambda, +QuartzProgramBuilder::u2(const std::variant& phi, + const std::variant& lambda, Value qubit) { builder.create(loc, qubit, phi, lambda); return *this; @@ -180,9 +180,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { if (!allocatedQubits.erase(qubit)) { // Qubit was not found in the set - either never allocated or already // deallocated - llvm::errs() << "Error: Attempting to deallocate a qubit that was not " - "allocated or has already been deallocated\n"; - llvm::report_fatal_error( + llvm::reportFatalUsageError( "Double deallocation or invalid qubit deallocation"); } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index dddc1de331..1418fdd4d4 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" // IWYU pragma: associated #include "mlir/Dialect/Utils/MatrixUtils.h" -#include "mlir/Dialect/Utils/ParameterDescriptor.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -132,111 +131,68 @@ DenseElementsAttr XOp::tryGetStaticMatrix() { } // RXOp - -ParameterDescriptor RXOp::getParameter(size_t i) { - if (i == 0) { - return {getThetaAttr(), getThetaDyn()}; - } - llvm::report_fatal_error("RXOp has one parameter"); -} - -bool RXOp::hasStaticUnitary() { return getParameter(0).isStatic(); } - DenseElementsAttr RXOp::tryGetStaticMatrix() { - if (!hasStaticUnitary()) { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { return nullptr; } + const auto thetaValue = theta.getValueAsDouble(); auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& theta = getTheta().value().convertToDouble(); - return DenseElementsAttr::get(type, getMatrixRX(theta)); + return DenseElementsAttr::get(type, getMatrixRX(thetaValue)); } -void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, - std::variant theta) { - FloatAttr thetaAttr = nullptr; +void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, + const std::variant& theta) { Value thetaOperand = nullptr; if (std::holds_alternative(theta)) { - thetaAttr = builder.getF64FloatAttr(std::get(theta)); - } else if (std::holds_alternative(theta)) { - thetaAttr = std::get(theta); + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); } else { thetaOperand = std::get(theta); } - build(builder, state, qubitIn, thetaAttr, thetaOperand); -} - -LogicalResult RXOp::verify() { - if (getTheta().has_value() == (getThetaDyn() != nullptr)) { - return emitOpError("must specify exactly one of static or dynamic theta"); - } - return success(); + build(odsBuilder, odsState, qubit_in, thetaOperand); } // U2Op -ParameterDescriptor U2Op::getParameter(size_t i) { - if (i == 0) { - return {getPhiAttr(), getPhiDyn()}; - } - if (i == 1) { - return {getLambdaAttr(), getLambdaDyn()}; - } - llvm::report_fatal_error("U2Op has two parameters"); -} - -bool U2Op::hasStaticUnitary() { - return (getParameter(0).isStatic() && getParameter(1).isStatic()); -} - DenseElementsAttr U2Op::tryGetStaticMatrix() { - if (!hasStaticUnitary()) { + const auto phi = getStaticParameter(getPhi()); + const auto lambda = getStaticParameter(getLambda()); + if (!phi || !lambda) { return nullptr; } + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); auto* ctx = getContext(); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& phi = getPhi().value().convertToDouble(); - const auto& lambda = getLambda().value().convertToDouble(); - return DenseElementsAttr::get(type, getMatrixU2(phi, lambda)); + return DenseElementsAttr::get(type, getMatrixU2(phiValue, lambdaValue)); } -void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, - std::variant phi, - std::variant lambda) { - FloatAttr phiAttr = nullptr; +void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, const std::variant& phi, + const std::variant& lambda) { Value phiOperand = nullptr; if (std::holds_alternative(phi)) { - phiAttr = builder.getF64FloatAttr(std::get(phi)); - } else if (std::holds_alternative(phi)) { - phiAttr = std::get(phi); + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); } else { phiOperand = std::get(phi); } - FloatAttr lambdaAttr = nullptr; Value lambdaOperand = nullptr; if (std::holds_alternative(lambda)) { - lambdaAttr = builder.getF64FloatAttr(std::get(lambda)); - } else if (std::holds_alternative(lambda)) { - lambdaAttr = std::get(lambda); + lambdaOperand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(lambda))); } else { lambdaOperand = std::get(lambda); } - build(builder, state, qubitIn, phiAttr, phiOperand, lambdaAttr, - lambdaOperand); -} - -LogicalResult U2Op::verify() { - if (getPhi().has_value() == (getPhiDyn() != nullptr)) { - return emitOpError("must specify exactly one of static or dynamic phi"); - } - if (getLambda().has_value() == (getLambdaDyn() != nullptr)) { - return emitOpError("must specify exactly one of static or dynamic lambda"); - } - return success(); + build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); } // SWAPOp @@ -284,7 +240,7 @@ Value CtrlOp::getQubit(size_t i) { if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { return getNegControl(i - getNumTargets() - getNumPosControls()); } - llvm::report_fatal_error("Invalid qubit index"); + llvm::reportFatalUsageError("Invalid qubit index"); } Value CtrlOp::getTarget(size_t i) { @@ -309,13 +265,13 @@ bool CtrlOp::hasStaticUnitary() { return unitaryOp.hasStaticUnitary(); } -ParameterDescriptor CtrlOp::getParameter(size_t i) { +Value CtrlOp::getParameter(size_t i) { auto unitaryOp = getBodyUnitary(); return unitaryOp.getParameter(i); } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - llvm::report_fatal_error("Not implemented yet"); // TODO + llvm::reportFatalUsageError("Not implemented yet"); // TODO } LogicalResult CtrlOp::verify() { From bda023772540c41d06a825a0982e65334562129b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 6 Nov 2025 17:16:26 +0100 Subject: [PATCH 189/419] =?UTF-8?q?=F0=9F=9A=A7=20WIP=20better=20module=20?= =?UTF-8?q?equivalence=20checking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../pipeline/test_compiler_pipeline.cpp | 393 +++++++++++++++++- 1 file changed, 387 insertions(+), 6 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 32ddbb8b2b..7c3cada710 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -21,8 +21,12 @@ #include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" #include "mlir/Support/PrettyPrinting.h" +#include #include #include +#include +#include +#include #include #include #include @@ -31,16 +35,21 @@ #include #include #include +#include #include #include +#include #include #include +#include +#include #include #include #include #include #include #include +#include namespace { @@ -50,6 +59,383 @@ using namespace mlir; // Stage Verification Utility //===----------------------------------------------------------------------===// +/// Compute a structural hash for an operation (excluding SSA value identities). +/// This hash is based on operation name, types, and attributes only. +struct OperationStructuralHash { + size_t operator()(Operation* op) const { + size_t hash = llvm::hash_value(op->getName().getStringRef()); + + // Hash result types + for (const Type type : op->getResultTypes()) { + hash = llvm::hash_combine(hash, type.getAsOpaquePointer()); + } + + // Hash operand types (not values) + for (const Value operand : op->getOperands()) { + hash = llvm::hash_combine(hash, operand.getType().getAsOpaquePointer()); + } + + // Hash attributes + // for (const auto& attr : op->getAttrDictionary()) { + // hash = llvm::hash_combine(hash, attr.getName().str()); + // hash = llvm::hash_combine(hash, attr.getValue().getAsOpaquePointer()); + // } + + return hash; + } +}; + +/// Check if two operations are structurally equivalent (excluding SSA value +/// identities). +struct OperationStructuralEquality { + bool operator()(Operation* lhs, Operation* rhs) const { + // Check operation name + if (lhs->getName() != rhs->getName()) { + return false; + } + + // Check result types + if (lhs->getResultTypes() != rhs->getResultTypes()) { + return false; + } + + // Check operand types (not values) + auto lhsOperandTypes = lhs->getOperandTypes(); + auto rhsOperandTypes = rhs->getOperandTypes(); + if (!std::equal(lhsOperandTypes.begin(), lhsOperandTypes.end(), + rhsOperandTypes.begin(), rhsOperandTypes.end())) { + return false; + } + + // Note: Attributes are intentionally not checked here to allow relaxed + // comparison. Attributes like function names, parameter names, etc. may + // differ while operations are still structurally equivalent. + + return true; + } +}; + +/// Map to track value equivalence between two modules. +using ValueEquivalenceMap = DenseMap; + +/// Compare two operations for structural equivalence. +/// Updates valueMap to track corresponding SSA values. +bool areOperationsEquivalent(Operation* lhs, Operation* rhs, + ValueEquivalenceMap& valueMap) { + // Check operation name + if (lhs->getName() != rhs->getName()) { + return false; + } + + // Check number of operands and results + if (lhs->getNumOperands() != rhs->getNumOperands() || + lhs->getNumResults() != rhs->getNumResults() || + lhs->getNumRegions() != rhs->getNumRegions()) { + return false; + } + + // Note: Attributes are intentionally not checked to allow relaxed comparison + + // Check result types + if (lhs->getResultTypes() != rhs->getResultTypes()) { + return false; + } + + // Check operands according to value mapping + for (auto [lhsOperand, rhsOperand] : + llvm::zip(lhs->getOperands(), rhs->getOperands())) { + if (auto it = valueMap.find(lhsOperand); it != valueMap.end()) { + // Value already mapped, must match + if (it->second != rhsOperand) { + return false; + } + } else { + // Establish new mapping + valueMap[lhsOperand] = rhsOperand; + } + } + + // Update value mapping for results + for (auto [lhsResult, rhsResult] : + llvm::zip(lhs->getResults(), rhs->getResults())) { + valueMap[lhsResult] = rhsResult; + } + + return true; +} + +/// Forward declaration for mutual recursion. +bool areBlocksEquivalent(Block& lhs, Block& rhs, ValueEquivalenceMap& valueMap); + +/// Compare two regions for structural equivalence. +bool areRegionsEquivalent(Region& lhs, Region& rhs, + ValueEquivalenceMap& valueMap) { + if (lhs.getBlocks().size() != rhs.getBlocks().size()) { + return false; + } + + for (auto [lhsBlock, rhsBlock] : llvm::zip(lhs, rhs)) { + if (!areBlocksEquivalent(lhsBlock, rhsBlock, valueMap)) { + return false; + } + } + + return true; +} + +/// Check if an operation has memory effects or control flow side effects +/// that would prevent reordering. +bool hasOrderingConstraints(Operation* op) { + // Terminators must maintain their position + if (op->hasTrait()) { + return true; + } + + // Symbol-defining operations (like function declarations) can be reordered + if (op->hasTrait() || + isa(op)) { + return false; + } + + // Check for memory effects that enforce ordering + if (auto memInterface = dyn_cast(op)) { + SmallVector effects; + memInterface.getEffects(effects); + + bool hasNonAllocFreeEffects = false; + for (const auto& effect : effects) { + // Allow operations with no effects or pure allocation/free effects + if (!isa( + effect.getEffect())) { + hasNonAllocFreeEffects = true; + break; + } + } + + if (hasNonAllocFreeEffects) { + return true; + } + } + + return false; +} + +/// Build a dependence graph for operations. +/// Returns a map from each operation to the set of operations it depends on. +DenseMap> +buildDependenceGraph(ArrayRef ops) { + DenseMap> dependsOn; + DenseMap valueProducers; + + // Build value-to-producer map and dependence relationships + for (Operation* op : ops) { + dependsOn[op] = DenseSet(); + + // This operation depends on the producers of its operands + for (Value operand : op->getOperands()) { + if (auto it = valueProducers.find(operand); it != valueProducers.end()) { + dependsOn[op].insert(it->second); + } + } + + // Register this operation as the producer of its results + for (Value result : op->getResults()) { + valueProducers[result] = op; + } + } + + return dependsOn; +} + +/// Partition operations into groups that can be compared as multisets. +/// Operations in the same group are independent and can be reordered. +std::vector> +partitionIndependentGroups(ArrayRef ops) { + std::vector> groups; + if (ops.empty()) { + return groups; + } + + auto dependsOn = buildDependenceGraph(ops); + DenseSet processed; + SmallVector currentGroup; + + for (Operation* op : ops) { + bool dependsOnCurrent = false; + + // Check if this operation depends on any operation in the current group + for (Operation* groupOp : currentGroup) { + if (dependsOn[op].contains(groupOp)) { + dependsOnCurrent = true; + break; + } + } + + // Check if this operation has ordering constraints + bool hasConstraints = hasOrderingConstraints(op); + + // If it depends on current group or has ordering constraints, + // finalize the current group and start a new one + if (dependsOnCurrent || (hasConstraints && !currentGroup.empty())) { + if (!currentGroup.empty()) { + groups.push_back(std::move(currentGroup)); + currentGroup.clear(); + } + } + + currentGroup.push_back(op); + + // If this operation has ordering constraints, finalize the group + if (hasConstraints) { + groups.push_back(std::move(currentGroup)); + currentGroup.clear(); + } + } + + // Add any remaining operations + if (!currentGroup.empty()) { + groups.push_back(std::move(currentGroup)); + } + + return groups; +} + +/// Compare two groups of independent operations using multiset equivalence. +bool areIndependentGroupsEquivalent(ArrayRef lhsOps, + ArrayRef rhsOps) { + if (lhsOps.size() != rhsOps.size()) { + return false; + } + + // Build frequency maps for both groups + std::unordered_map + lhsFrequencyMap; + std::unordered_map + rhsFrequencyMap; + + for (Operation* op : lhsOps) { + lhsFrequencyMap[op]++; + } + + for (Operation* op : rhsOps) { + rhsFrequencyMap[op]++; + } + + // Check structural equivalence + if (lhsFrequencyMap.size() != rhsFrequencyMap.size()) { + return false; + } + + for (const auto& [lhsOp, lhsCount] : lhsFrequencyMap) { + auto it = rhsFrequencyMap.find(lhsOp); + if (it == rhsFrequencyMap.end() || it->second != lhsCount) { + return false; + } + } + + return true; +} + +/// Compare two blocks for structural equivalence, allowing permutations +/// of independent operations. +bool areBlocksEquivalent(Block& lhs, Block& rhs, + ValueEquivalenceMap& valueMap) { + // Check block arguments + if (lhs.getNumArguments() != rhs.getNumArguments()) { + return false; + } + + for (auto [lhsArg, rhsArg] : + llvm::zip(lhs.getArguments(), rhs.getArguments())) { + if (lhsArg.getType() != rhsArg.getType()) { + return false; + } + valueMap[lhsArg] = rhsArg; + } + + // Collect all operations + SmallVector lhsOps; + SmallVector rhsOps; + + for (Operation& op : lhs) { + lhsOps.push_back(&op); + } + + for (Operation& op : rhs) { + rhsOps.push_back(&op); + } + + if (lhsOps.size() != rhsOps.size()) { + return false; + } + + // Partition operations into independent groups + auto lhsGroups = partitionIndependentGroups(lhsOps); + auto rhsGroups = partitionIndependentGroups(rhsOps); + + if (lhsGroups.size() != rhsGroups.size()) { + return false; + } + + // Compare each group + for (size_t groupIdx = 0; groupIdx < lhsGroups.size(); ++groupIdx) { + auto& lhsGroup = lhsGroups[groupIdx]; + auto& rhsGroup = rhsGroups[groupIdx]; + + if (!areIndependentGroupsEquivalent(lhsGroup, rhsGroup)) { + return false; + } + + // Update value mappings for operations in this group + // We need to match operations and update the value map + // Since they are structurally equivalent, we can match them + // by trying all permutations (for small groups) or use a greedy approach + + // Use a simple greedy matching + DenseSet matchedRhs; + for (Operation* lhsOp : lhsGroup) { + bool matched = false; + for (Operation* rhsOp : rhsGroup) { + if (matchedRhs.contains(rhsOp)) { + continue; + } + + ValueEquivalenceMap tempMap = valueMap; + if (areOperationsEquivalent(lhsOp, rhsOp, tempMap)) { + valueMap = std::move(tempMap); + matchedRhs.insert(rhsOp); + matched = true; + + // Recursively compare regions + for (auto [lhsRegion, rhsRegion] : + llvm::zip(lhsOp->getRegions(), rhsOp->getRegions())) { + if (!areRegionsEquivalent(lhsRegion, rhsRegion, valueMap)) { + return false; + } + } + break; + } + } + + if (!matched) { + return false; + } + } + } + + return true; +} + +/// Compare two MLIR modules for structural equivalence, allowing permutations +/// of speculatable operations. +bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { + ValueEquivalenceMap valueMap; + return areRegionsEquivalent(lhs.getBodyRegion(), rhs.getBodyRegion(), + valueMap); +} + /** * @brief Verify a stage matches expected module * @@ -69,12 +455,7 @@ using namespace mlir; << stageName << " failed to parse actual IR"; } - if (!OperationEquivalence::isEquivalentTo( - actualModule.get(), expectedModule, - OperationEquivalence::ignoreValueEquivalence, nullptr, - OperationEquivalence::IgnoreLocations | - OperationEquivalence::IgnoreDiscardableAttrs | - OperationEquivalence::IgnoreProperties)) { + if (!areModulesEquivalentWithPermutations(*actualModule, expectedModule)) { std::ostringstream msg; msg << stageName << " IR does not match expected structure\n\n"; From ebd73f939a6c9bd75f351a83c4933c63fefce1b6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 7 Nov 2025 23:20:07 +0100 Subject: [PATCH 190/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20QIR=20c?= =?UTF-8?q?onversion=20and=20builder=20to=20comply=20with=20QIR=202.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 135 +++++++------ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 - .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 190 ++++++++++-------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 173 +++++++--------- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 3 +- .../pipeline/test_compiler_pipeline.cpp | 24 ++- 6 files changed, 265 insertions(+), 262 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 52bd1cfe63..7a65c47ff9 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -86,20 +86,6 @@ class QIRProgramBuilder { // Memory Management //===--------------------------------------------------------------------===// - /** - * @brief Allocate a single qubit dynamically - * @return An LLVM pointer representing the qubit - * - * @par Example: - * ```c++ - * auto q = builder.allocQubit(); - * ``` - * ```mlir - * %q = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr - * ``` - */ - Value allocQubit(); - /** * @brief Get a static qubit by index * @param index The qubit index (must be non-negative) @@ -117,7 +103,7 @@ class QIRProgramBuilder { Value staticQubit(int64_t index); /** - * @brief Allocate a qubit register dynamically + * @brief Allocate an array of (static) qubits * @param size Number of qubits (must be positive) * @return Vector of LLVM pointers representing the qubits * @@ -126,13 +112,64 @@ class QIRProgramBuilder { * auto q = builder.allocQubitRegister(3); * ``` * ```mlir - * %q0 = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr - * %q1 = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr - * %q2 = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr + * %c1 = llvm.mlir.constant(1 : i64) : i64 + * %q1 = llvm.inttoptr %c1 : i64 to !llvm.ptr + * %c2 = llvm.mlir.constant(2 : i64) : i64 + * %q2 = llvm.inttoptr %c2 : i64 to !llvm.ptr * ``` */ SmallVector allocQubitRegister(int64_t size); + /** + * @brief A small structure representing a single classical bit within a + * classical register. + */ + struct Bit { + /// Name of the register containing this bit + StringRef registerName; + /// Size of the register containing this bit + int64_t registerSize{}; + /// Index of this bit within the register + int64_t registerIndex{}; + }; + + /** + * @brief A small structure representing a classical bit register. + */ + struct ClassicalRegister { + /// Name of the classical register + StringRef name; + /// Size of the classical register + int64_t size; + + /** + * @brief Access a specific bit in the classical register + * @param index The index of the bit to access (must be less than size) + * @return A Bit structure representing the specified bit + */ + Bit operator[](const int64_t index) const { + assert(index < size); + return { + .registerName = name, .registerSize = size, .registerIndex = index}; + } + }; + + /** + * @brief Allocate a classical bit register + * @param size Number of bits + * @param name Register name (default: "c") + * @return A reference to a ClassicalRegister structure + * + * @par Example: + * ```c++ + * auto& c = builder.allocClassicalBitRegister(3, "c"); + * ``` + */ + ClassicalRegister& allocClassicalBitRegister(int64_t size, + StringRef name = "c"); + //===--------------------------------------------------------------------===// // Measurement and Reset //===--------------------------------------------------------------------===// @@ -177,14 +214,14 @@ class QIRProgramBuilder { * 2. __quantum__rt__result_record_output for each measurement in the register * * @param qubit The qubit to measure - * @param registerName The name of the classical register (e.g., "c") - * @param registerIndex The index within the register for this measurement + * @param bit The classical bit to store the result * @return Reference to this builder for method chaining * * @par Example: * ```c++ - * builder.measure(q0, "c", 0); - * builder.measure(q1, "c", 1); + * auto& c = builder.allocClassicalBitRegister(2, "c"); + * builder.measure(q0, c[0]); + * builder.measure(q1, c[1]); * ``` * ```mlir * // In measurements block: @@ -201,8 +238,7 @@ class QIRProgramBuilder { * llvm.call @__quantum__rt__result_record_output(ptr %r1, ptr @2) * ``` */ - QIRProgramBuilder& measure(Value qubit, StringRef registerName, - int64_t registerIndex); + QIRProgramBuilder& measure(Value qubit, const Bit& bit); /** * @brief Reset a qubit to |0⟩ state @@ -241,13 +277,13 @@ class QIRProgramBuilder { * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () * ``` */ - QIRProgramBuilder& x(const Value qubit); + QIRProgramBuilder& x(Value qubit); /** * @brief Apply the CX gate * * @param control Control qubit - * @param qubit Target qubit + * @param target Target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -259,7 +295,7 @@ class QIRProgramBuilder { * !llvm.ptr) -> () * ``` */ - QIRProgramBuilder& cx(const Value control, const Value target); + QIRProgramBuilder& cx(Value control, Value target); /** * @brief Apply the RX gate to a qubit @@ -276,7 +312,7 @@ class QIRProgramBuilder { * llvm.call @__quantum__qis__rx__body(%q, %c) : (!llvm.ptr, f64) -> () * ``` */ - QIRProgramBuilder& rx(std::variant theta, const Value qubit); + QIRProgramBuilder& rx(const std::variant& theta, Value qubit); /** * @brief Apply the U2 gate to a qubit @@ -295,8 +331,8 @@ class QIRProgramBuilder { * -> () * ``` */ - QIRProgramBuilder& u2(std::variant phi, - std::variant lambda, const Value qubit); + QIRProgramBuilder& u2(const std::variant& phi, + const std::variant& lambda, Value qubit); /** * @brief Apply the SWAP gate to two qubits @@ -314,31 +350,7 @@ class QIRProgramBuilder { * () * ``` */ - QIRProgramBuilder& swap(const Value qubit0, const Value qubit1); - - //===--------------------------------------------------------------------===// - // Deallocation - //===--------------------------------------------------------------------===// - - /** - * @brief Explicitly deallocate a qubit - * - * @details - * Deallocates a qubit using __quantum__rt__qubit_release. Optional - - * finalize() automatically deallocates all remaining allocated qubits. - * - * @param qubit The qubit to deallocate - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.dealloc(q); - * ``` - * ```mlir - * llvm.call @__quantum__rt__qubit_release(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& dealloc(Value qubit); + QIRProgramBuilder& swap(Value qubit0, Value qubit1); //===--------------------------------------------------------------------===// // Finalization @@ -363,6 +375,9 @@ class QIRProgramBuilder { Location loc; private: + /// Track allocated classical Registers + SmallVector allocatedClassicalRegisters; + LLVM::LLVMFuncOp mainFunc; /// Entry block: constants and initialization @@ -377,14 +392,8 @@ class QIRProgramBuilder { /// Exit code constant (created in entry block, used in output block) LLVM::ConstantOp exitCode; - /// Track allocated qubits for automatic deallocation - DenseSet allocatedQubits; - - /// Cache static qubit pointers for reuse - DenseMap staticQubitCache; - - /// Cache result pointers for reuse (separate from qubits) - DenseMap resultPointerCache; + /// Cache static pointers for reuse + DenseMap ptrCache; /// Map from (register_name, register_index) to result pointer DenseMap, Value> registerResultMap; diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 449a6419b8..fd72628aa4 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -19,8 +19,6 @@ namespace mlir::qir { // QIR function name constants static constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; -static constexpr auto QIR_QUBIT_ALLOCATE = "__quantum__rt__qubit_allocate"; -static constexpr auto QIR_QUBIT_RELEASE = "__quantum__rt__qubit_release"; static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; static constexpr auto QIR_ARRAY_RECORD_OUTPUT = diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index e37d4fc108..1b6a2f299c 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -69,11 +69,11 @@ namespace { * - Sequence of measurements for output recording */ struct LoweringState : QIRMetadata { - /// Map from qubit index to pointer value for reuse - DenseMap ptrMap; + /// Map from register name to register start index + DenseMap registerStartIndexMap; - /// Map from classical result index to pointer value for reuse - DenseMap resultPtrMap; + /// Map from index to pointer value for reuse + DenseMap ptrMap; /// Map from (register_name, register_index) to result pointer /// This allows caching result pointers for measurements with register info @@ -128,14 +128,15 @@ struct QuartzToQIRTypeConverter final : LLVMTypeConverter { namespace { /** - * @brief Converts quartz.alloc operation to QIR qubit_allocate + * @brief Converts quartz.alloc operation to static QIR qubit allocations * * @details - * Converts dynamic qubit allocation to a call to the QIR - * __quantum__rt__qubit_allocate function. + * QIR 2.0 does not support dynamic qubit allocation. Therefore, quartz.alloc + * operations are converted to static qubit references using inttoptr with a + * constant index. * - * Register metadata (register_name, register_size, register_index) is ignored - * during QIR conversion as it is used for analysis and readability only. + * Register metadata (register_name, register_size, register_index) is used to + * provide a reasonable guess for a static qubit index that is still free. * * @par Example: * ```mlir @@ -143,7 +144,8 @@ namespace { * ``` * becomes: * ```mlir - * %q = llvm.call @__quantum__rt__qubit_allocate() : () -> !llvm.ptr + * %c0 = llvm.mlir.constant(0 : i64) : i64 + * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr * ``` */ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { @@ -152,34 +154,68 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); - - // Create QIR function signature: () -> ptr - const auto qirSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMPointerType::get(ctx), {}); - - // Get or create function declaration - const auto fnDecl = getOrCreateFunctionDeclaration( - rewriter, op, QIR_QUBIT_ALLOCATE, qirSignature); - - // Replace with call to qubit_allocate - rewriter.replaceOpWithNewOp(op, fnDecl, ValueRange{}); + auto& state = getState(); + const auto numQubits = static_cast(state.numQubits); + auto& ptrMap = state.ptrMap; + auto& registerMap = state.registerStartIndexMap; - // Track qubit count and mark as using dynamic allocation - getState().numQubits++; - getState().useDynamicQubit = true; + // Get or create pointer value + if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { + const auto registerName = op.getRegisterName().value(); + const auto registerSize = + static_cast(op.getRegisterSize().value()); + const auto registerIndex = + static_cast(op.getRegisterIndex().value()); + + if (const auto it = registerMap.find(registerName); + it != registerMap.end()) { + // register is already tracked, the corresponding ptr was already + // created + const auto globalIndex = it->second + registerIndex; + assert(ptrMap.contains(globalIndex)); + rewriter.replaceOp(op, ptrMap.at(globalIndex)); + return success(); + } + // Allocate the entire register as static qubits + registerMap[registerName] = numQubits; + SmallVector resultValues; + resultValues.reserve(registerSize); + for (int64_t i = 0; i < registerSize; ++i) { + Value val{}; + if (const auto it = ptrMap.find(numQubits + i); it != ptrMap.end()) { + val = it->second; + } else { + val = createPointerFromIndex(rewriter, op.getLoc(), numQubits + i); + ptrMap[numQubits + i] = val; + } + resultValues.push_back(val); + } + state.numQubits += registerSize; + rewriter.replaceOp(op, resultValues[registerIndex]); + return success(); + } + // no register info, check if ptr has already been allocated (as a Result) + Value val{}; + if (const auto it = ptrMap.find(numQubits); it != ptrMap.end()) { + val = it->second; + } else { + val = createPointerFromIndex(rewriter, op.getLoc(), numQubits); + ptrMap[numQubits] = val; + } + rewriter.replaceOp(op, val); + state.numQubits++; return success(); } }; /** - * @brief Converts quartz.dealloc operation to QIR qubit_release + * @brief Erases quartz.dealloc operations * * @details - * Converts dynamic qubit deallocation to a call to the QIR - * __quantum__rt__qubit_release function, which releases a dynamically - * allocated qubit. + * Since QIR 2.0 does not support dynamic qubit allocation, dynamic allocations + * are converted to static allocations. Therefore, deallocation operations + * become no-ops and are simply removed from the IR. * * @par Example: * ```mlir @@ -187,28 +223,16 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { * ``` * becomes: * ```mlir - * llvm.call @__quantum__rt__qubit_release(%q) : (!llvm.ptr) -> () + * // (removed) * ``` */ struct ConvertQuartzDeallocQIR final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(DeallocOp op, OpAdaptor adaptor, + matchAndRewrite(DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); - - // Create QIR function signature: (ptr) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); - - // Get or create function declaration - const auto fnDecl = getOrCreateFunctionDeclaration( - rewriter, op, QIR_QUBIT_RELEASE, qirSignature); - - // Replace with call to qubit_release - rewriter.replaceOpWithNewOp(op, fnDecl, - adaptor.getOperands()); + rewriter.eraseOp(op); return success(); } }; @@ -237,27 +261,23 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); const auto index = static_cast(op.getIndex()); - + auto& state = getState(); // Get or create a pointer to the qubit - if (getState().ptrMap.contains(index)) { + Value val{}; + if (const auto it = state.ptrMap.find(index); it != state.ptrMap.end()) { // Reuse existing pointer - rewriter.replaceOp(op, getState().ptrMap.at(index)); + val = it->second; } else { - // Create constant and inttoptr operations - const auto constantOp = rewriter.create( - op.getLoc(), rewriter.getI64IntegerAttr(index)); - const auto intToPtrOp = rewriter.replaceOpWithNewOp( - op, LLVM::LLVMPointerType::get(ctx), constantOp->getResult(0)); - - // Cache for reuse - getState().ptrMap.try_emplace(index, intToPtrOp->getResult(0)); + // Create and cache for reuse + val = createPointerFromIndex(rewriter, op.getLoc(), index); + state.ptrMap.try_emplace(index, val); } + rewriter.replaceOp(op, val); // Track maximum qubit index - if (std::cmp_greater_equal(index, getState().numQubits)) { - getState().numQubits = index + 1; + if (std::cmp_greater_equal(index, state.numQubits)) { + state.numQubits = index + 1; } return success(); @@ -299,42 +319,46 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { const auto ptrType = LLVM::LLVMPointerType::get(ctx); auto& state = getState(); const auto numResults = static_cast(state.numResults); - auto& resultPtrMap = state.resultPtrMap; + auto& ptrMap = state.ptrMap; auto& registerResultMap = state.registerResultMap; // Get or create result pointer value Value resultValue; if (op.getRegisterName() && op.getRegisterSize() && op.getRegisterIndex()) { const auto registerName = op.getRegisterName().value(); - const auto registerIndex = op.getRegisterIndex().value(); + const auto registerSize = + static_cast(op.getRegisterSize().value()); + const auto registerIndex = + static_cast(op.getRegisterIndex().value()); const auto key = std::make_pair(registerName, registerIndex); if (const auto it = registerResultMap.find(key); it != registerResultMap.end()) { resultValue = it->second; } else { - const auto constantOp = rewriter.create( - op.getLoc(), - rewriter.getI64IntegerAttr( - static_cast(numResults))); // Sequential result index - resultValue = rewriter - .create(op.getLoc(), ptrType, - constantOp->getResult(0)) - .getResult(); - resultPtrMap[numResults] = resultValue; - registerResultMap.insert({key, resultValue}); - state.numResults++; + // Allocate the entire register as static results + for (int64_t i = 0; i < registerSize; ++i) { + Value val{}; + if (const auto it2 = ptrMap.find(numResults + i); + it2 != ptrMap.end()) { + val = it2->second; + } else { + val = createPointerFromIndex(rewriter, op.getLoc(), numResults + i); + ptrMap[numResults + i] = val; + } + registerResultMap.try_emplace({registerName, i}, val); + } + state.numResults += registerSize; + resultValue = registerResultMap.at(key); } } else { - // No register info - assign sequential result pointer - const auto constantOp = rewriter.create( - op.getLoc(), - rewriter.getI64IntegerAttr(numResults)); // Sequential result index - resultValue = rewriter - .create(op.getLoc(), ptrType, - constantOp->getResult(0)) - .getResult(); - resultPtrMap[numResults] = resultValue; + // no register info, check if ptr has already been allocated (as a Qubit) + if (const auto it = ptrMap.find(numResults); it != ptrMap.end()) { + resultValue = it->second; + } else { + resultValue = createPointerFromIndex(rewriter, op.getLoc(), numResults); + ptrMap[numResults] = resultValue; + } registerResultMap.insert({{"c", numResults}, resultValue}); state.numResults++; } @@ -344,10 +368,8 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); const auto mzDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_MEASURE, mzSignature); - rewriter.create(op.getLoc(), mzDecl, - ValueRange{adaptor.getQubit(), resultValue}); - - rewriter.eraseOp(op); + rewriter.replaceOpWithNewOp( + op, mzDecl, ValueRange{adaptor.getQubit(), resultValue}); return success(); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 7a7339badc..b161d6097b 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -91,60 +91,62 @@ void QIRProgramBuilder::initialize() { builder.setInsertionPointToStart(bodyBlock); } -Value QIRProgramBuilder::allocQubit() { - // Create function signature: () -> ptr - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMPointerType::get(builder.getContext()), {}); - - auto fnDecl = getOrCreateFunctionDeclaration( - builder, module, QIR_QUBIT_ALLOCATE, qirSignature); - - // Call qubit_allocate - auto callOp = builder.create(loc, fnDecl, ValueRange{}); - const auto qubit = callOp.getResult(); - - // Track for automatic deallocation - allocatedQubits.insert(qubit); - - // Update counts - metadata_.numQubits++; - metadata_.useDynamicQubit = true; - - return qubit; -} - Value QIRProgramBuilder::staticQubit(const int64_t index) { // Check cache - if (staticQubitCache.contains(index)) { - return staticQubitCache.at(index); + Value val{}; + if (const auto it = ptrCache.find(index); it != ptrCache.end()) { + val = it->second; + } else { + val = createPointerFromIndex(builder, loc, index); + // Cache for reuse + ptrCache[index] = val; } - // Use common utility function to create pointer from index - const auto qubit = createPointerFromIndex(builder, loc, index); - - // Cache for reuse - staticQubitCache[index] = qubit; - // Update qubit count if (std::cmp_greater_equal(index, metadata_.numQubits)) { metadata_.numQubits = static_cast(index) + 1; } - return qubit; + return val; } -llvm::SmallVector -QIRProgramBuilder::allocQubitRegister(const int64_t size) { - llvm::SmallVector qubits; +SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { + SmallVector qubits; qubits.reserve(size); for (int64_t i = 0; i < size; ++i) { - qubits.push_back(allocQubit()); + qubits.push_back(staticQubit(static_cast(metadata_.numQubits))); } return qubits; } +QIRProgramBuilder::ClassicalRegister& +QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, + StringRef name) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in measurements block (before branch) + builder.setInsertionPoint(measurementsBlock->getTerminator()); + + const auto numResults = static_cast(metadata_.numResults); + auto& reg = allocatedClassicalRegisters.emplace_back(name, size); + for (int64_t i = 0; i < size; ++i) { + Value val{}; + if (const auto it = ptrCache.find(numResults + i); it != ptrCache.end()) { + val = it->second; + } else { + val = createPointerFromIndex(builder, loc, numResults + i); + // Cache for reuse + ptrCache[numResults + i] = val; + } + registerResultMap.insert({{name, i}, val}); + } + metadata_.numResults += size; + return reg; +} + Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -152,20 +154,28 @@ Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); - auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto key = std::make_pair("c", resultIndex); + if (const auto it = registerResultMap.find(key); + it != registerResultMap.end()) { + return it->second; + } - // Get or create result pointer (separate from qubit pointers) - Value resultValue = nullptr; - if (resultPointerCache.contains(resultIndex)) { - resultValue = resultPointerCache.at(resultIndex); + Value resultValue{}; + if (const auto it = ptrCache.find(resultIndex); it != ptrCache.end()) { + resultValue = it->second; } else { resultValue = createPointerFromIndex(builder, loc, resultIndex); - resultPointerCache[resultIndex] = resultValue; - registerResultMap.insert({{"c", resultIndex}, resultValue}); - metadata_.numResults++; + ptrCache[resultIndex] = resultValue; + registerResultMap.insert({key, resultValue}); + } + + // Update result count + if (std::cmp_greater_equal(resultIndex, metadata_.numResults)) { + metadata_.numResults = static_cast(resultIndex) + 1; } // Create mz call + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); const auto mzSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); auto mzDecl = @@ -176,33 +186,22 @@ Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { } QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, - const StringRef registerName, - const int64_t registerIndex) { + const Bit& bit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); - auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - // Check if we already have a result pointer for this register slot + const auto& registerName = bit.registerName; + const auto registerIndex = bit.registerIndex; const auto key = std::make_pair(registerName, registerIndex); - - Value resultValue = nullptr; - if (const auto it = registerResultMap.find(key); - it != registerResultMap.end()) { - resultValue = it->second; - } else { - resultValue = createPointerFromIndex( - builder, loc, static_cast(metadata_.numResults)); - // Cache for reuse - resultPointerCache[metadata_.numResults] = resultValue; - registerResultMap.insert({key, resultValue}); - metadata_.numResults++; - } + assert(registerResultMap.contains(key)); + const auto resultValue = registerResultMap.at(key); // Create mz call + auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); const auto mzSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {ptrType, ptrType}); auto mzDecl = @@ -272,8 +271,9 @@ QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, return *this; } -QIRProgramBuilder& QIRProgramBuilder::rx(std::variant theta, - const Value qubit) { +QIRProgramBuilder& +QIRProgramBuilder::rx(const std::variant& theta, + const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard entryGuard(builder); @@ -309,9 +309,10 @@ QIRProgramBuilder& QIRProgramBuilder::rx(std::variant theta, return *this; } -QIRProgramBuilder& QIRProgramBuilder::u2(std::variant phi, - std::variant lambda, - const Value qubit) { +QIRProgramBuilder& +QIRProgramBuilder::u2(const std::variant& phi, + const std::variant& lambda, + const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard entryGuard(builder); @@ -379,30 +380,6 @@ QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, return *this; } -//===----------------------------------------------------------------------===// -// Deallocation -//===----------------------------------------------------------------------===// - -QIRProgramBuilder& QIRProgramBuilder::dealloc(const Value qubit) { - allocatedQubits.erase(qubit); - - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in measurements block (before branch) - builder.setInsertionPoint(measurementsBlock->getTerminator()); - - // Create release call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())); - auto fnDecl = getOrCreateFunctionDeclaration(builder, module, - QIR_QUBIT_RELEASE, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit}); - - return *this; -} - //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// @@ -421,21 +398,20 @@ void QIRProgramBuilder::generateOutputRecording() { auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); // Group measurements by register - llvm::StringMap>> registerGroups; + llvm::StringMap>> registerGroups; for (const auto& [key, resultPtr] : registerResultMap) { const auto& [regName, regIdx] = key; registerGroups[regName].emplace_back(regIdx, resultPtr); } // Sort registers by name for deterministic output - llvm::SmallVector< - std::pair>>> + SmallVector>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); } - llvm::sort(sortedRegisters, - [](const auto& a, const auto& b) { return a.first < b.first; }); + sort(sortedRegisters, + [](const auto& a, const auto& b) { return a.first < b.first; }); // Create array_record_output call const auto arrayRecordSig = @@ -453,8 +429,8 @@ void QIRProgramBuilder::generateOutputRecording() { // Generate output recording for each register for (auto& [registerName, measurements] : sortedRegisters) { // Sort measurements by register index - llvm::sort(measurements, - [](const auto& a, const auto& b) { return a.first < b.first; }); + sort(measurements, + [](const auto& a, const auto& b) { return a.first < b.first; }); const auto arraySize = measurements.size(); auto arrayLabelOp = createResultLabel(builder, module, registerName); @@ -479,11 +455,6 @@ void QIRProgramBuilder::generateOutputRecording() { } OwningOpRef QIRProgramBuilder::finalize() { - for (const Value qubit : allocatedQubits) { - dealloc(qubit); - } - allocatedQubits.clear(); - // Generate output recording in the output block generateOutputRecording(); diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index e3cbf5a8c3..8e5f130e1f 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -103,8 +103,7 @@ LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, fnDecl = builder.create(op->getLoc(), fnName, fnType); // Add irreversible attribute to irreversible quantum operations - if (fnName == QIR_MEASURE || fnName == QIR_QUBIT_RELEASE || - fnName == QIR_RESET) { + if (fnName == QIR_MEASURE || fnName == QIR_RESET) { fnDecl->setAttr("passthrough", builder.getStrArrayAttr({"irreversible"})); } } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 7c3cada710..fc615f27d5 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1099,7 +1099,8 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.measure(q[0], 0); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); }); verifyAllStages({ @@ -1139,8 +1140,9 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.measure(q[0], 0); - b.measure(q[0], 0); + const auto& c = b.allocClassicalBitRegister(1); + b.measure(q[0], c[0]); + b.measure(q[0], c[0]); }); verifyAllStages({ @@ -1184,9 +1186,10 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.measure(q[0], 0); - b.measure(q[0], 1); - b.measure(q[0], 2); + const auto& c = b.allocClassicalBitRegister(3); + b.measure(q[0], c[0]); + b.measure(q[0], c[1]); + b.measure(q[0], c[2]); }); verifyAllStages({ @@ -1230,8 +1233,10 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.measure(q[0], "c1", 0); - b.measure(q[1], "c2", 0); + const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); + const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); + b.measure(q[0], creg1[0]); + b.measure(q[1], creg2[0]); }); verifyAllStages({ @@ -1338,11 +1343,10 @@ TEST_F(CompilerPipelineTest, CX) { }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - auto q0 = reg[0]; /// TODO: Replace uncomment this when CX can be converted to QIR // auto q1 = reg[1]; // b.cx(q0, q1); - b.x(q0); + b.x(reg[1]); }); verifyAllStages({ From 0684b5439028db0f5e0f7e79e27d4e1ca3009ae1 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 10 Nov 2025 16:13:47 +0100 Subject: [PATCH 191/419] =?UTF-8?q?=F0=9F=9A=A7=20WIP=20on=20ctrl=20modifi?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 197 ++++++++- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 45 ++- .../Quartz/Builder/QuartzProgramBuilder.h | 188 ++++++++- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 26 +- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 52 ++- .../Flux/Builder/FluxProgramBuilder.cpp | 113 ++++-- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 174 ++++---- .../Quartz/Builder/QuartzProgramBuilder.cpp | 72 +++- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 148 +++++-- .../TranslateQuantumComputationToQuartz.cpp | 67 ++-- .../pipeline/test_compiler_pipeline.cpp | 373 +++--------------- 11 files changed, 869 insertions(+), 586 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index e13b90a377..a0b1b24ec5 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -260,6 +260,45 @@ class FluxProgramBuilder { */ Value x(Value qubit); + /** + * @brief Apply a CX gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cx(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * ``` + */ + std::pair cx(Value control, Value target); + + /** + * @brief Apply a multi-controlled X gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcx({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.x %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, + * !flux.qubit + * ``` + */ + std::pair mcx(ValueRange controls, Value target); + /** * @brief Apply an RX gate to a qubit * @@ -267,7 +306,7 @@ class FluxProgramBuilder { * Consumes the input qubit and produces a new output qubit SSA value. * The input is validated and the tracking is updated. * - * @param theta Rotation angle + * @param theta Rotation angle in radians * @param qubit Input qubit (must be valid/unconsumed) * @return Output qubit * @@ -281,6 +320,49 @@ class FluxProgramBuilder { */ Value rx(const std::variant& theta, Value qubit); + /** + * @brief Apply a controlled RX gate + * + * @param theta Rotation angle in radians + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.crx(1.0, q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.rx(1.0) %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * ``` + */ + std::pair crx(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled RX gate + * + * @param theta Rotation angle in radians + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcrx(1.0, {q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.rx(1.0) %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, + * !flux.qubit + * ``` + */ + std::pair mcrx(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -288,8 +370,8 @@ class FluxProgramBuilder { * Consumes the input qubit and produces a new output qubit SSA value. * The input is validated and the tracking is updated. * - * @param phi Rotation angle - * @param lambda Rotation angle + * @param phi Rotation angle in radians + * @param lambda Rotation angle in radians * @param qubit Input qubit (must be valid/unconsumed) * @return Output qubit * @@ -304,6 +386,53 @@ class FluxProgramBuilder { Value u2(const std::variant& phi, const std::variant& lambda, Value qubit); + /** + * @brief Apply a controlled U2 gate + * + * @param phi Rotation angle in radians + * @param lambda Rotation angle in radians + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cu2(1.0, 0.5, q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.u2(1.0, 0.5) %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * ``` + */ + std::pair cu2(const std::variant& phi, + const std::variant& lambda, + Value control, Value target); + + /** + * @brief Apply a multi-controlled U2 gate + * + * @param phi Rotation angle in radians + * @param lambda Rotation angle in radians + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcu2(1.0, 0.5, {q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.u2(1.0, 0.5) %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, + * !flux.qubit + * ``` + */ + std::pair mcu2(const std::variant& phi, + const std::variant& lambda, + ValueRange controls, Value target); + /** * @brief Apply a SWAP gate to two qubits * @@ -311,8 +440,8 @@ class FluxProgramBuilder { * Consumes the input qubits and produces new output qubit SSA values. * The inputs are validated and the tracking is updated. * - * @param qubit0 Input qubit (must be valid/unconsumed) - * @param qubit1 Input qubit (must be valid/unconsumed) + * @param qubit0 First input qubit (must be valid/unconsumed) + * @param qubit1 Second input qubit (must be valid/unconsumed) * @return Output qubits * * @par Example: @@ -326,6 +455,50 @@ class FluxProgramBuilder { */ std::pair swap(Value qubit0, Value qubit1); + /** + * @brief Apply a controlled SWAP gate + * @param control Input control qubit (must be valid/unconsumed) + * @param qubit0 First target qubit (must be valid/unconsumed) + * @param qubit1 Second target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) + * @par Example: + * ```c++ + * {q0_out, {q1_out, q2_out}} = builder.cswap(q0_in, q1_in, q2_in); + * ``` + * ```mlir + * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { + * %q1_res, %q2_res = flux.swap %q1_in, %q2_in : !flux.qubit, + * !flux.qubit -> !flux.qubit, !flux.qubit + * flux.yield %q1_res, %q2_res + * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, + * !flux.qubit + * ``` + */ + std::pair> cswap(Value control, Value qubit0, + Value qubit1); + + /** + * @brief Apply a multi-controlled SWAP gate + * @param controls Input control qubits (must be valid/unconsumed) + * @param qubit0 First target qubit (must be valid/unconsumed) + * @param qubit1 Second target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) + * @par Example: + * ```c++ + * {controls_out, {q1_out, q2_out}} = builder.mcswap({q0_in, q1_in}, q2_in, + * q3_in); + * ``` + * ```mlir + * %controls_out, %q2_out, %q3_out = flux.ctrl(%q0_in, %q1_in) %q2_in, %q3_in + * { %q2_res, %q3_res = flux.swap %q2_in, %q3_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit flux.yield %q2_res, %q3_res } : !flux.qubit, + * !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, + * !flux.qubit, !flux.qubit + * ``` + */ + std::pair> + mcswap(ValueRange controls, Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// @@ -342,19 +515,19 @@ class FluxProgramBuilder { * ```c++ * controls_out, targets_out = builder.ctrl(q0_in, q1_in, [&](auto& b) { * auto q1_res = b.x(q1_in); - * return SmallVector{q1_res}; + * return {q1_res}; * }); * ``` * ```mlir - * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { + * %controls_out, %targets_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit * ``` */ - std::pair, SmallVector> - ctrl(SmallVector controls, SmallVector targets, - const std::function(FluxProgramBuilder&)>& body); + std::pair + ctrl(ValueRange controls, ValueRange targets, + const std::function& body); //===--------------------------------------------------------------------===// // Deallocation @@ -402,8 +575,6 @@ class FluxProgramBuilder { Location loc; private: - int inRegion = 0; - //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index c7a141d715..5a523f609d 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -369,30 +369,43 @@ def YieldOp : FluxOp<"yield", traits = [Terminator]> { let assemblyFormat = "$targets attr-dict"; } -def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegments, AttrSizedResultSegments]> { - let summary = "Apply a controlled operation"; +def CtrlOp : FluxOp<"ctrl", traits = + [ + UnitaryOpInterface, + AttrSizedOperandSegments, + AttrSizedResultSegments, + SameOperandsAndResultType, + SameOperandsAndResultShape, + SingleBlock + ]> { + let summary = "Add control qubits to a unitary operation"; let description = [{ - Modifier to make an operation controlled by one or more control qubits. - The transformed qubits are returned. + A modifier operation that adds control qubits to the unitary operation + defined in its body region. The controlled operation applies the + underlying unitary only when all control qubits are in the |1⟩ state. + The operation takes a variadic number of control and target qubits as + inputs and produces corresponding output qubits. Control qubits are not + modified by the operation and simply pass through to the outputs. Example: ```mlir - %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { - %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit - flux.yield %q1_res - } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + %ctrl_q_out, %tgt_q_out = flux.ctrl(%ctrl_q_in) %tgt_q_in { + %tgt_q_out = flux.h %tgt_q_in : !flux.qubit -> !flux.qubit + flux.yield %tgt_q_out : !flux.qubit + } : (!flux.qubit, !flux.qubit) -> (!flux.qubit, !flux.qubit) ``` }]; - let arguments = (ins Variadic:$controls_in, Variadic:$targets_in); + let arguments = (ins Arg, "the control qubits", [MemRead]>:$controls_in, + Arg, "the target qubits", [MemRead]>:$targets_in); let results = (outs Variadic:$controls_out, Variadic:$targets_out); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - `(` `{` $controls_in `}` `,` `{` $targets_in `}` `)` + `(` $controls_in `)` $targets_in $body attr-dict `:` - `{` type($controls_in) `}` `,` `{` type($targets_in) `}` + type($controls_in) `,` type($targets_in) `->` - `{` type($controls_out) `}` `,` `{` type($targets_out) `}` + type($controls_out) `,` type($targets_out) }]; let extraClassDeclaration = [{ @@ -420,12 +433,8 @@ def CtrlOp : FluxOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegmen }]; let builders = [ - OpBuilder<(ins "ValueRange":$controls_in, "ValueRange":$targets_in), [{ - auto qubit_type = QubitType::get($_builder.getContext()); - SmallVector control_types(controls_in.size(), qubit_type); - SmallVector target_types(targets_in.size(), qubit_type); - build($_builder, $_state, TypeRange(control_types), TypeRange(target_types), controls_in, targets_in); - }]>, + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, "UnitaryOpInterface":$bodyUnitary)>, + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, "const std::function&":$bodyBuilder)> ]; let hasCanonicalizer = 1; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 8caf2ad76e..414ac2f1e6 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -46,7 +46,7 @@ namespace mlir::quartz { * auto module = builder.finalize(); * ``` */ -class QuartzProgramBuilder { +class QuartzProgramBuilder { /// TODO: Consider inheriting from OpBuilder public: /** * @brief Construct a new QuartzProgramBuilder @@ -225,13 +225,13 @@ class QuartzProgramBuilder { QuartzProgramBuilder& reset(Value qubit); //===--------------------------------------------------------------------===// - // Unitry Operations + // Unitary Operations //===--------------------------------------------------------------------===// /** * @brief Apply an X gate to a qubit * - * @param qubit Input qubit + * @param qubit Target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -244,11 +244,49 @@ class QuartzProgramBuilder { */ QuartzProgramBuilder& x(Value qubit); + /** + * @brief Apply a CX gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cx(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.x %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cx(Value control, Value target); + + /** + * @brief Apply a multi-controlled X gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcx({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.x %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcx(ValueRange controls, Value target); + /** * @brief Apply an RX gate to a qubit * - * @param theta Rotation angle - * @param qubit Input qubit + * @param theta Rotation angle in radians + * @param qubit Target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -262,12 +300,52 @@ class QuartzProgramBuilder { QuartzProgramBuilder& rx(const std::variant& theta, Value qubit); + /** + * @brief Apply a CRX gate + * + * @param theta Rotation angle in radians + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.crx(1.0, q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.rx(1.0) %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& crx(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled RX gate + * + * @param theta Rotation angle in radians + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.mcrx(1.0, {q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.rx(1.0) %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcrx(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * - * @param phi Rotation angle - * @param lambda Rotation angle - * @param qubit Input qubit + * @param phi Rotation angle in radians + * @param lambda Rotation angle in radians + * @param qubit Target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -282,11 +360,55 @@ class QuartzProgramBuilder { const std::variant& lambda, Value qubit); + /** + * @brief Apply a controlled U2 gate + * + * @param phi Rotation angle in radians + * @param lambda Rotation angle in radians + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.cu2(1.0, 0.5, q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.u2(1.0, 0.5) %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cu2(const std::variant& phi, + const std::variant& lambda, + Value control, Value target); + + /** + * @brief Apply a multi-controlled U2 gate + * + * @param phi Rotation angle in radians + * @param lambda Rotation angle in radians + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.mcu2(1.0, 0.5, {q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.u2(1.0, 0.5) %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcu2(const std::variant& phi, + const std::variant& lambda, + ValueRange controls, Value target); + /** * @brief Apply a SWAP gate to two qubits * - * @param qubit0 Input qubit - * @param qubit1 Input qubit + * @param qubit0 First target qubit + * @param qubit1 Second target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -299,6 +421,44 @@ class QuartzProgramBuilder { */ QuartzProgramBuilder& swap(Value qubit0, Value qubit1); + /** + * @brief Apply a controlled SWAP gate + * + * @param control Control qubit + * @param qubit0 First target qubit + * @param qubit1 Second target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.cswap(q0, q1, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.swap %q1, %q2 : !quartz.qubit, !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cswap(Value control, Value qubit0, Value qubit1); + + /** + * @brief Apply a multi-controlled SWAP gate + * + * @param controls Control qubits + * @param qubit0 First target qubit + * @param qubit1 Second target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.mcswap({q0, q1}, q2, q3); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.swap %q2, %q3 : !quartz.qubit, !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcswap(ValueRange controls, Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// @@ -316,14 +476,12 @@ class QuartzProgramBuilder { * ``` * ```mlir * quartz.ctrl(%q0) { - * quartz.x %q1 - * quartz.yield + * quartz.x %q1 : !quartz.qubit * } * ``` */ - QuartzProgramBuilder& - ctrl(ValueRange controls, - const std::function& body); + QuartzProgramBuilder& ctrl(ValueRange controls, + const std::function& body); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index b5ff1ac252..e34b81e431 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -347,26 +347,37 @@ def YieldOp : QuartzOp<"yield", traits = [Terminator]> { let assemblyFormat = "attr-dict"; } -def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { - let summary = "Apply a controlled operation"; +def CtrlOp : QuartzOp<"ctrl", + traits = [ + UnitaryOpInterface, + SingleBlockImplicitTerminator<"::mlir::quartz::YieldOp"> + ]> { + let summary = "Add control qubits to a unitary operation"; let description = [{ - Modifier to make an operation controlled by one or more control qubits. + A modifier operation that adds control qubits to the unitary operation + defined in its body region. The controlled operation applies the + underlying unitary only when all control qubits are in the |1⟩ state. + The control qubits are not modified by this operation. Example: ```mlir quartz.ctrl(%q0) { - quartz.x %q1 - quartz.yield + quartz.x %q1 : !quartz.qubit } ``` }]; - let arguments = (ins Variadic:$controls); + let arguments = (ins Arg, "the control qubits", [MemRead,MemWrite]>:$controls); let regions = (region SizedRegion<1>:$body); let assemblyFormat = "`(` $controls `)` $body attr-dict"; + let builders = [ + OpBuilder<(ins "ValueRange":$controls, "UnitaryOpInterface":$bodyUnitary)>, + OpBuilder<(ins "ValueRange":$controls, "const std::function&":$bodyBuilder)> + ]; + let extraClassDeclaration = [{ - UnitaryOpInterface getBodyUnitary(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(); size_t getNumQubits(); size_t getNumTargets(); size_t getNumControls(); @@ -383,6 +394,7 @@ def CtrlOp : QuartzOp<"ctrl", traits = [UnitaryOpInterface]> { static StringRef getBaseSymbol() { return "ctrl"; } }]; + let hasCanonicalizer = 1; let hasVerifier = 1; } diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 5422e4a2b4..25b89ad96c 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -332,22 +332,17 @@ struct ConvertQuartzResetOp final matchAndRewrite(quartz::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - // Prepare result type - const auto& qubitType = flux::QubitType::get(rewriter.getContext()); - + // Track the Quartz qubit being reset const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map const auto& fluxQubit = getState().qubitMap[quartzQubit]; // Create flux.reset (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), qubitType, fluxQubit); - - auto outFluxQubit = fluxOp.getQubitOut(); + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); // Update mapping: the Quartz qubit now corresponds to the reset output - getState().qubitMap[quartzQubit] = outFluxQubit; + getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); // Erase the old (it has no results to replace) rewriter.eraseOp(op); @@ -559,54 +554,53 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { * ``` * is converted to * ```mlir - * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { + * %controls_out, %targets_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit * ``` */ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::CtrlOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(quartz::CtrlOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); // Get Flux controls from state map const auto& quartzControls = op.getControls(); SmallVector fluxControls; fluxControls.reserve(quartzControls.size()); for (const auto& quartzControl : quartzControls) { - fluxControls.push_back(getState().qubitMap[quartzControl]); + fluxControls.push_back(qubitMap[quartzControl]); } // Get Flux targets from state map + const auto numTargets = op.getNumTargets(); SmallVector fluxTargets; - fluxTargets.reserve(op.getNumTargets()); - for (size_t i = 0; i < op.getNumTargets(); ++i) { - const auto& quartzTarget = op.getTarget(i); - fluxTargets.push_back(getState().qubitMap[quartzTarget]); + fluxTargets.reserve(numTargets); + for (size_t i = 0; i < numTargets; ++i) { + fluxTargets.push_back(qubitMap[op.getTarget(i)]); } // Create flux.ctrl + /// TODO this does not work yet. auto fluxOp = - rewriter.create(op.getLoc(), fluxControls, fluxTargets); - - // Clone the body region from Quartz to Flux - IRMapping regionMap; - for (size_t i = 0; i < op.getNumTargets(); ++i) { - regionMap.map(op.getTarget(i), fluxTargets[i]); - } - rewriter.cloneRegionBefore(op.getBody(), fluxOp.getBody(), - fluxOp.getBody().end(), regionMap); + rewriter.create(op.getLoc(), fluxControls, fluxTargets, + llvm::dyn_cast( + &adaptor.getBody().front().front())); // Update state map - for (size_t i = 0; i < op.getNumPosControls(); ++i) { + const auto numPosControls = op.getNumPosControls(); + const auto controlsOut = fluxOp.getControlsOut(); + for (size_t i = 0; i < numPosControls; ++i) { const auto& quartzControl = quartzControls[i]; - getState().qubitMap[quartzControl] = fluxOp.getControlsOut()[i]; + qubitMap[quartzControl] = controlsOut[i]; } - for (size_t i = 0; i < op.getNumTargets(); ++i) { + const auto targetsOut = fluxOp.getTargetsOut(); + for (size_t i = 0; i < numTargets; ++i) { const auto& quartzTarget = op.getTarget(i); - getState().qubitMap[quartzTarget] = fluxOp.getTargetsOut()[i]; + qubitMap[quartzTarget] = targetsOut[i]; } rewriter.eraseOp(op); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index e3a7a9252a..35e7ba62d1 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -120,11 +120,6 @@ void FluxProgramBuilder::validateQubitValue(const Value qubit) const { void FluxProgramBuilder::updateQubitTracking(const Value inputQubit, const Value outputQubit) { - if (inRegion > 0) { - // Do not update tracking within regions - return; - } - // Validate the input qubit validateQubitValue(inputQubit); @@ -188,6 +183,27 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } +std::pair FluxProgramBuilder::cx(Value control, Value target) { + const auto [controlsOut, targetsOut] = + ctrl({control}, {target}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto x = b.create(loc, targets[0]); + return {x.getQubitOut()}; + }); + return {controlsOut[0], targetsOut[0]}; +} + +std::pair FluxProgramBuilder::mcx(const ValueRange controls, + Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {target}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto x = b.create(loc, targets[0]); + return {x.getQubitOut()}; + }); + return {controlsOut, targetsOut[0]}; +} + Value FluxProgramBuilder::rx(const std::variant& theta, Value qubit) { auto rxOp = builder.create(loc, qubit, theta); @@ -199,6 +215,30 @@ Value FluxProgramBuilder::rx(const std::variant& theta, return qubitOut; } +std::pair +FluxProgramBuilder::crx(const std::variant& theta, Value control, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl({control}, {target}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto rx = b.create(loc, targets[0], theta); + return {rx.getQubitOut()}; + }); + return {controlsOut[0], targetsOut[0]}; +} + +std::pair +FluxProgramBuilder::mcrx(const std::variant& theta, + const ValueRange controls, Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {target}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto rx = b.create(loc, targets[0], theta); + return {rx.getQubitOut()}; + }); + return {controlsOut, targetsOut[0]}; +} + Value FluxProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, Value qubit) { @@ -211,6 +251,32 @@ Value FluxProgramBuilder::u2(const std::variant& phi, return qubitOut; } +std::pair +FluxProgramBuilder::cu2(const std::variant& phi, + const std::variant& lambda, + Value control, const Value target) { + const auto [controlsOut, targetsOut] = + ctrl({control}, {target}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto u2 = b.create(loc, targets[0], phi, lambda); + return {u2.getQubitOut()}; + }); + return {controlsOut[0], targetsOut[0]}; +} + +std::pair +FluxProgramBuilder::mcu2(const std::variant& phi, + const std::variant& lambda, + const ValueRange controls, Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {target}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto u2 = b.create(loc, targets[0], phi, lambda); + return {u2.getQubitOut()}; + }); + return {controlsOut, targetsOut[0]}; +} + std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { auto swapOp = builder.create(loc, qubit0, qubit1); const auto& qubit0Out = swapOp.getQubit0Out(); @@ -223,32 +289,33 @@ std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { return {qubit0Out, qubit1Out}; } +std::pair> +FluxProgramBuilder::cswap(Value control, Value qubit0, Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl({control}, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + auto swap = b.create(loc, targets[0], targets[1]); + return {swap.getQubit0Out(), swap.getQubit1Out()}; + }); + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// -std::pair, SmallVector> FluxProgramBuilder::ctrl( - SmallVector controls, SmallVector targets, - const std::function(FluxProgramBuilder&)>& body) { - auto ctrlOp = builder.create(loc, controls, targets); - - const mlir::OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&ctrlOp.getBody().emplaceBlock()); - - inRegion++; - auto targetsYield = body(*this); - inRegion--; - - builder.create(loc, targetsYield); - - const auto& controlsOut = ctrlOp.getControlsOut(); - const auto& targetsOut = ctrlOp.getTargetsOut(); +std::pair FluxProgramBuilder::ctrl( + const ValueRange controls, const ValueRange targets, + const std::function& body) { + auto ctrlOp = builder.create(loc, controls, targets, body); // Update tracking - for (const auto& [control, controlOut] : zip(controls, controlsOut)) { + const auto& controlsOut = ctrlOp.getControlsOut(); + for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { updateQubitTracking(control, controlOut); } - for (const auto& [target, targetOut] : zip(targets, targetsOut)) { + const auto& targetsOut = ctrlOp.getTargetsOut(); + for (const auto& [target, targetOut] : llvm::zip(targets, targetsOut)) { updateQubitTracking(target, targetOut); } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 5ea0afb269..98a69a6172 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -215,6 +215,35 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { // Modifiers //===----------------------------------------------------------------------===// +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, const ValueRange targets, + UnitaryOpInterface bodyUnitary) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + odsState.addOperands(targets); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + // Move the unitary op into the block + odsBuilder.setInsertionPointToStart(&block); + auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location, op->getResults()); +} + +void CtrlOp::build( + OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, + const ValueRange targets, + const std::function& bodyBuilder) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + odsState.addOperands(targets); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + odsBuilder.setInsertionPointToStart(&block); + auto targetsOut = bodyBuilder(odsBuilder, block.getArguments()); + odsBuilder.create(odsState.location, targetsOut); +} + UnitaryOpInterface CtrlOp::getBodyUnitary() { return llvm::dyn_cast(&getBody().front().front()); } @@ -230,55 +259,50 @@ size_t CtrlOp::getNumControls() { size_t CtrlOp::getNumPosControls() { return getControlsIn().size(); } size_t CtrlOp::getNumNegControls() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getNumNegControls(); + return getBodyUnitary().getNumNegControls(); } -Value CtrlOp::getInputQubit(size_t i) { - if (i < getNumTargets()) { - return getInputTarget(i); +Value CtrlOp::getInputQubit(const size_t i) { + const auto numPosControls = getNumPosControls(); + if (i < numPosControls) { + return getControlsIn()[i]; } - if (getNumTargets() <= i && i < getNumPosControls()) { - return getInputPosControl(i - getNumTargets()); - } - if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { - return getInputNegControl(i - getNumTargets() - getNumPosControls()); + if (numPosControls <= i && i < getNumQubits()) { + return getBodyUnitary().getInputQubit(i - numPosControls); } llvm::report_fatal_error("Invalid qubit index"); } -Value CtrlOp::getOutputQubit(size_t i) { - if (i < getNumTargets()) { - return getOutputTarget(i); - } - if (getNumTargets() <= i && i < getNumPosControls()) { - return getOutputPosControl(i - getNumTargets()); +Value CtrlOp::getOutputQubit(const size_t i) { + const auto numPosControls = getNumPosControls(); + if (i < numPosControls) { + return getControlsOut()[i]; } - if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { - return getOutputNegControl(i - getNumTargets() - getNumPosControls()); + if (numPosControls <= i && i < getNumQubits()) { + return getBodyUnitary().getOutputQubit(i - numPosControls); } llvm::report_fatal_error("Invalid qubit index"); } -Value CtrlOp::getInputTarget(size_t i) { return getTargetsIn()[i]; } +Value CtrlOp::getInputTarget(const size_t i) { return getTargetsIn()[i]; } -Value CtrlOp::getOutputTarget(size_t i) { return getTargetsOut()[i]; } +Value CtrlOp::getOutputTarget(const size_t i) { return getTargetsOut()[i]; } -Value CtrlOp::getInputPosControl(size_t i) { return getControlsIn()[i]; } +Value CtrlOp::getInputPosControl(const size_t i) { return getControlsIn()[i]; } -Value CtrlOp::getOutputPosControl(size_t i) { return getControlsOut()[i]; } +Value CtrlOp::getOutputPosControl(const size_t i) { + return getControlsOut()[i]; +} -Value CtrlOp::getInputNegControl(size_t i) { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getInputNegControl(i); +Value CtrlOp::getInputNegControl(const size_t i) { + return getBodyUnitary().getInputNegControl(i); } -Value CtrlOp::getOutputNegControl(size_t i) { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getOutputNegControl(i); +Value CtrlOp::getOutputNegControl(const size_t i) { + return getBodyUnitary().getOutputNegControl(i); } -Value CtrlOp::getInputForOutput(Value output) { +Value CtrlOp::getInputForOutput(const Value output) { for (size_t i = 0; i < getNumPosControls(); ++i) { if (output == getControlsOut()[i]) { return getControlsIn()[i]; @@ -292,7 +316,7 @@ Value CtrlOp::getInputForOutput(Value output) { llvm::report_fatal_error("Given qubit is not an output of the operation"); } -Value CtrlOp::getOutputForInput(Value input) { +Value CtrlOp::getOutputForInput(const Value input) { for (size_t i = 0; i < getNumPosControls(); ++i) { if (input == getControlsIn()[i]) { return getControlsOut()[i]; @@ -306,39 +330,61 @@ Value CtrlOp::getOutputForInput(Value input) { llvm::report_fatal_error("Given qubit is not an input of the operation"); } -size_t CtrlOp::getNumParams() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getNumParams(); -} +size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } -Value CtrlOp::getParameter(size_t i) { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getParameter(i); -} +bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } -bool CtrlOp::hasStaticUnitary() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.hasStaticUnitary(); +Value CtrlOp::getParameter(const size_t i) { + return getBodyUnitary().getParameter(i); } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - llvm::report_fatal_error("Not implemented yet"); // TODO + llvm::reportFatalUsageError("Not implemented yet"); // TODO } LogicalResult CtrlOp::verify() { - if (getBody().front().getOperations().size() != 2) { + auto& block = getBody().front(); + if (block.getOperations().size() != 2) { return emitOpError("body region must have exactly two operations"); } - if (!llvm::isa(getBody().front().front())) { + if (!llvm::isa(block.front())) { return emitOpError( "first operation in body region must be a unitary operation"); } - if (!llvm::isa(getBody().front().back())) { + if (!llvm::isa(block.back())) { return emitOpError( "second operation in body region must be a yield operation"); } - if (getBody().front().back().getNumOperands() != getNumTargets()) { - return emitOpError("yield operation must yield all target qubits"); + // the yield operation must yield as many values as there are targets + if (block.back().getNumOperands() != getNumTargets()) { + return emitOpError("yield operation must yield ") + << getNumTargets() << " values, but found " + << block.back().getNumOperands(); + } + + SmallPtrSet uniqueQubitsIn; + for (const auto& control : getControlsIn()) { + if (!uniqueQubitsIn.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + auto bodyUnitary = getBodyUnitary(); + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubitsIn.insert(bodyUnitary.getInputQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + SmallPtrSet uniqueQubitsOut; + for (const auto& control : getControlsOut()) { + if (!uniqueQubitsOut.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubitsOut.insert(bodyUnitary.getOutputQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } } return success(); } @@ -450,33 +496,20 @@ struct MergeNestedCtrl final : OpRewritePattern { PatternRewriter& rewriter) const override { auto bodyUnitary = ctrlOp.getBodyUnitary(); auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); - - // Check if the body unitary is a CtrlOp if (!bodyCtrlOp) { return failure(); } // Merge controls - llvm::SmallVector newControls; - newControls.append(ctrlOp.getControlsIn().begin(), - ctrlOp.getControlsIn().end()); - for (auto control : bodyCtrlOp.getControlsIn()) { - if (llvm::is_contained(newControls, control)) { - continue; - } + SmallVector newControls(ctrlOp.getControlsIn()); + for (const auto control : bodyCtrlOp.getControlsIn()) { newControls.push_back(control); } - // Create new CtrlOp - auto newCtrlOp = rewriter.create(ctrlOp.getLoc(), newControls, - bodyCtrlOp.getTargetsIn()); - - // Clone block - rewriter.cloneRegionBefore(bodyCtrlOp.getBody(), newCtrlOp.getBody(), - newCtrlOp.getBody().end()); - - // Replace CtrlOps - rewriter.replaceOp(ctrlOp, newCtrlOp.getResults()); + rewriter.replaceOpWithNewOp(ctrlOp, newControls, + ctrlOp.getTargetsIn(), + bodyCtrlOp.getBodyUnitary()); + rewriter.eraseOp(bodyCtrlOp); return success(); } @@ -490,15 +523,10 @@ struct RemoveTrivialCtrl final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp ctrlOp, PatternRewriter& rewriter) const override { - if (ctrlOp.getNumControls() != 0) { + if (ctrlOp.getNumControls() > 0) { return failure(); } - - auto bodyUnitary = ctrlOp.getBodyUnitary(); - - bodyUnitary->moveBefore(ctrlOp); - rewriter.replaceOp(ctrlOp, bodyUnitary->getResults()); - + rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); return success(); } }; diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 52b3ee7a39..85b1ec8f05 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -132,6 +132,17 @@ QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { return *this; } +QuartzProgramBuilder& QuartzProgramBuilder::cx(Value control, Value target) { + return mcx({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcx(ValueRange controls, + Value target) { + builder.create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + QuartzProgramBuilder& QuartzProgramBuilder::rx(const std::variant& theta, Value qubit) { @@ -139,6 +150,20 @@ QuartzProgramBuilder::rx(const std::variant& theta, return *this; } +QuartzProgramBuilder& +QuartzProgramBuilder::crx(const std::variant& theta, + Value control, const Value target) { + return mcrx(theta, {control}, target); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mcrx(const std::variant& theta, + ValueRange controls, Value target) { + builder.create( + loc, controls, [&](OpBuilder& b) { b.create(loc, target, theta); }); + return *this; +} + QuartzProgramBuilder& QuartzProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, @@ -147,27 +172,50 @@ QuartzProgramBuilder::u2(const std::variant& phi, return *this; } +QuartzProgramBuilder& +QuartzProgramBuilder::cu2(const std::variant& phi, + const std::variant& lambda, + Value control, const Value target) { + return mcu2(phi, lambda, {control}, target); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mcu2(const std::variant& phi, + const std::variant& lambda, + ValueRange controls, Value target) { + builder.create(loc, controls, [&](OpBuilder& b) { + b.create(loc, target, phi, lambda); + }); + return *this; +} + QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { builder.create(loc, qubit0, qubit1); return *this; } +QuartzProgramBuilder& QuartzProgramBuilder::cswap(Value control, + const Value qubit0, + const Value qubit1) { + return mcswap({control}, qubit0, qubit1); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcswap(ValueRange controls, + Value qubit0, Value qubit1) { + builder.create(loc, controls, [&](OpBuilder& b) { + b.create(loc, qubit0, qubit1); + }); + return *this; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// -QuartzProgramBuilder& QuartzProgramBuilder::ctrl( - ValueRange controls, - const std::function& body) { - auto ctrlOp = builder.create(loc, controls); - - const mlir::OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&ctrlOp.getBody().emplaceBlock()); - - body(*this); - - builder.create(loc); - +QuartzProgramBuilder& +QuartzProgramBuilder::ctrl(ValueRange controls, + const std::function& body) { + builder.create(loc, controls, body); return *this; } diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 1418fdd4d4..6d8957dc83 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -43,11 +44,13 @@ void QuartzDialect::initialize() { addTypes< #define GET_TYPEDEF_LIST #include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" + >(); addOperations< #define GET_OP_LIST #include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" + >(); } @@ -144,7 +147,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, + const Value qubit_in, // NOLINT(*-identifier-naming) const std::variant& theta) { Value thetaOperand = nullptr; if (std::holds_alternative(theta)) { @@ -173,7 +176,8 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, const std::variant& phi, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& phi, const std::variant& lambda) { Value phiOperand = nullptr; if (std::holds_alternative(phi)) { @@ -208,16 +212,38 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { // Modifiers //===----------------------------------------------------------------------===// +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, UnitaryOpInterface bodyUnitary) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + // Move the unitary op into the block + odsBuilder.setInsertionPointToStart(&block); + odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location); +} + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, + const std::function& bodyBuilder) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(odsBuilder); + odsBuilder.create(odsState.location); +} + UnitaryOpInterface CtrlOp::getBodyUnitary() { return llvm::dyn_cast(&getBody().front().front()); } size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } -size_t CtrlOp::getNumTargets() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getNumTargets(); -} +size_t CtrlOp::getNumTargets() { return getBodyUnitary().getNumTargets(); } size_t CtrlOp::getNumControls() { return getNumPosControls() + getNumNegControls(); @@ -226,48 +252,36 @@ size_t CtrlOp::getNumControls() { size_t CtrlOp::getNumPosControls() { return getControls().size(); } size_t CtrlOp::getNumNegControls() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getNumNegControls(); + return getBodyUnitary().getNumNegControls(); } -Value CtrlOp::getQubit(size_t i) { - if (i < getNumTargets()) { - return getTarget(i); +Value CtrlOp::getQubit(const size_t i) { + const auto numPosControls = getNumPosControls(); + if (i < numPosControls) { + return getControls()[i]; } - if (getNumTargets() <= i && i < getNumPosControls()) { - return getPosControl(i - getNumTargets()); - } - if (getNumTargets() + getNumPosControls() <= i && i < getNumQubits()) { - return getNegControl(i - getNumTargets() - getNumPosControls()); + if (numPosControls <= i && i < getNumQubits()) { + return getBodyUnitary().getQubit(i - numPosControls); } llvm::reportFatalUsageError("Invalid qubit index"); } -Value CtrlOp::getTarget(size_t i) { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getTarget(i); +Value CtrlOp::getTarget(const size_t i) { + return getBodyUnitary().getTarget(i); } -Value CtrlOp::getPosControl(size_t i) { return getControls()[i]; } +Value CtrlOp::getPosControl(const size_t i) { return getControls()[i]; } -Value CtrlOp::getNegControl(size_t i) { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getNegControl(i); +Value CtrlOp::getNegControl(const size_t i) { + return getBodyUnitary().getNegControl(i); } -size_t CtrlOp::getNumParams() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getNumParams(); -} +size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } -bool CtrlOp::hasStaticUnitary() { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.hasStaticUnitary(); -} +bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } -Value CtrlOp::getParameter(size_t i) { - auto unitaryOp = getBodyUnitary(); - return unitaryOp.getParameter(i); +Value CtrlOp::getParameter(const size_t i) { + return getBodyUnitary().getParameter(i); } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { @@ -275,16 +289,76 @@ DenseElementsAttr CtrlOp::tryGetStaticMatrix() { } LogicalResult CtrlOp::verify() { - if (getBody().front().getOperations().size() != 2) { + auto& block = getBody().front(); + if (block.getOperations().size() != 2) { return emitOpError("body region must have exactly two operations"); } - if (!llvm::isa(getBody().front().front())) { + if (!llvm::isa(block.front())) { return emitOpError( "first operation in body region must be a unitary operation"); } - if (!llvm::isa(getBody().front().back())) { + if (!llvm::isa(block.back())) { return emitOpError( "second operation in body region must be a yield operation"); } + SmallPtrSet uniqueQubits; + for (const auto& control : getControls()) { + if (!uniqueQubits.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + auto bodyUnitary = getBodyUnitary(); + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubits.insert(bodyUnitary.getQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } return success(); } + +/** + * @brief A rewrite pattern for merging nested control modifiers. + */ +struct MergeNestedCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + auto bodyUnitary = ctrlOp.getBodyUnitary(); + auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); + if (!bodyCtrlOp) { + return failure(); + } + + SmallVector newControls(ctrlOp.getControls()); + for (const auto control : bodyCtrlOp.getControls()) { + newControls.push_back(control); + } + + rewriter.replaceOpWithNewOp(ctrlOp, newControls, + bodyCtrlOp.getBodyUnitary()); + rewriter.eraseOp(bodyCtrlOp); + + return success(); + } +}; + +/** + * @brief A rewrite pattern for removing control modifiers without controls. + */ +struct RemoveTrivialCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + if (ctrlOp.getNumControls() > 0) { + return failure(); + } + rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); + return success(); + } +}; + +void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 75f31c2ce0..3509c2c037 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -45,12 +45,12 @@ namespace { */ struct QregInfo { const qc::QuantumRegister* qregPtr; - llvm::SmallVector qubits; + SmallVector qubits; }; using BitMemInfo = std::pair; // (register ref, localIdx) -using BitIndexVec = llvm::SmallVector; +using BitIndexVec = SmallVector; /** * @brief Allocates quantum registers using the QuartzProgramBuilder @@ -65,11 +65,11 @@ using BitIndexVec = llvm::SmallVector; * @param quantumComputation The quantum computation to translate * @return Vector containing information about all quantum registers */ -llvm::SmallVector +SmallVector allocateQregs(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - llvm::SmallVector qregPtrs; + SmallVector qregPtrs; qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + quantumComputation.getAncillaRegisters().size()); for (const auto& qreg : @@ -88,7 +88,7 @@ allocateQregs(QuartzProgramBuilder& builder, }); // Allocate quantum registers using the builder - llvm::SmallVector qregs; + SmallVector qregs; for (const auto* qregPtr : qregPtrs) { auto qubits = builder.allocQubitRegister(qregPtr->getSize(), qregPtr->getName()); @@ -110,10 +110,10 @@ allocateQregs(QuartzProgramBuilder& builder, * @param qregs Vector containing information about all quantum registers * @return Flat vector of qubit values indexed by physical qubit index */ -llvm::SmallVector +SmallVector buildQubitMap(const qc::QuantumComputation& quantumComputation, - const llvm::SmallVector& qregs) { - llvm::SmallVector flatQubits; + const SmallVector& qregs) { + SmallVector flatQubits; const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); flatQubits.resize(static_cast(maxPhys) + 1); @@ -143,7 +143,7 @@ BitIndexVec allocateClassicalRegisters(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - llvm::SmallVector cregPtrs; + SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); for (const auto& reg : quantumComputation.getClassicalRegisters() | std::views::values) { @@ -184,8 +184,7 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, * @param bitMap Mapping from global bit index to (register, local_index) */ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { + const SmallVector& qubits, const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -213,7 +212,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { + const SmallVector& qubits) { for (const auto& target : operation.getTargets()) { const Value qubit = qubits[target]; builder.reset(qubit); @@ -231,10 +230,9 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index * @return Vector of qubit values corresponding to positive controls */ -llvm::SmallVector -getPosControls(const qc::Operation& operation, - const llvm::SmallVector& qubits) { - llvm::SmallVector controls; +SmallVector getPosControls(const qc::Operation& operation, + const SmallVector& qubits) { + SmallVector controls; for (const auto& [control, type] : operation.getControls()) { if (type == qc::Control::Type::Neg) { continue; @@ -255,13 +253,13 @@ getPosControls(const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { + const SmallVector& qubits) { const auto& target = qubits[operation.getTargets()[0]]; - const auto& posControls = getPosControls(operation, qubits); - if (posControls.empty()) { + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { builder.x(target); } else { - builder.ctrl(posControls, [&](auto& builder) { builder.x(target); }); + builder.mcx(posControls, target); } } @@ -276,15 +274,14 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { + const SmallVector& qubits) { const auto& theta = operation.getParameter()[0]; const auto& target = qubits[operation.getTargets()[0]]; - const auto& posControls = getPosControls(operation, qubits); - if (posControls.empty()) { + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { builder.rx(theta, target); } else { - builder.ctrl(posControls, - [&](auto& builder) { builder.rx(theta, target); }); + builder.mcrx(theta, posControls, target); } } @@ -299,16 +296,15 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { + const SmallVector& qubits) { const auto& phi = operation.getParameter()[0]; const auto& lambda = operation.getParameter()[1]; const auto& target = qubits[operation.getTargets()[0]]; - const auto& posControls = getPosControls(operation, qubits); - if (posControls.empty()) { + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { builder.u2(phi, lambda, target); } else { - builder.ctrl(posControls, - [&](auto& builder) { builder.u2(phi, lambda, target); }); + builder.mcu2(phi, lambda, posControls, target); } } @@ -324,15 +320,14 @@ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { + const SmallVector& qubits) { const auto& target0 = qubits[operation.getTargets()[0]]; const auto& target1 = qubits[operation.getTargets()[1]]; - const auto& posControls = getPosControls(operation, qubits); - if (posControls.empty()) { + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { builder.swap(target0, target1); } else { - builder.ctrl(posControls, - [&](auto& builder) { builder.swap(target0, target1); }); + builder.mcswap(posControls, target0, target1); } } @@ -357,7 +352,7 @@ void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, LogicalResult translateOperations(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation, - const llvm::SmallVector& qubits, + const SmallVector& qubits, const BitIndexVec& bitMap) { for (const auto& operation : quantumComputation) { switch (operation->getType()) { diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index fc615f27d5..ad72898f66 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1311,35 +1311,17 @@ TEST_F(CompilerPipelineTest, CX) { ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl(q0, [&](auto& b) { b.x(q1); }); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl({q0}, {q1}, [&](auto& b) { - auto q1res = b.x(q1); - return SmallVector{q1res}; - }); - }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl({q0}, {q1}, [&](auto& b) { - auto q1res = b.x(q1); - return SmallVector{q1res}; - }); - }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl(q0, [&](auto& b) { b.x(q1); }); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); @@ -1350,10 +1332,10 @@ TEST_F(CompilerPipelineTest, CX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), .qirConversion = qirOpt.get(), }); } @@ -1404,6 +1386,44 @@ TEST_F(CompilerPipelineTest, RX) { }); } +TEST_F(CompilerPipelineTest, CRX) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.crx(1.0, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.crx(1.0, q0, q1); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.crx(1.0, q0, q1); + }); + + // const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + // auto reg = b.allocQubitRegister(2); + // const auto q0 = reg[0]; + // const auto q1 = reg[1]; + // b.rx(1.0, q0); + // }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = nullptr, + }); +} + TEST_F(CompilerPipelineTest, U2) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); @@ -1468,297 +1488,4 @@ TEST_F(CompilerPipelineTest, SWAP) { }); } -// ################################################## -// # Temporary Simple Conversion Tests -// ################################################## - -class SimpleConversionTest : public testing::Test { -protected: - std::unique_ptr context; - - void SetUp() override { - // Register all dialects needed for the full compilation pipeline - DialectRegistry registry; - registry - .insert(); - - context = std::make_unique(); - context->appendDialectRegistry(registry); - context->loadAllAvailableDialects(); - } - - /** - * @brief Run canonicalization - */ - static void runCanonicalizationPasses(ModuleOp module) { - PassManager pm(module.getContext()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createCSEPass()); - if (pm.run(module).failed()) { - llvm::errs() << "Failed to run canonicalization passes\n"; - } - } - - /** - * @brief Build expected Quartz IR programmatically and run canonicalization - */ - [[nodiscard]] OwningOpRef buildQuartzIR( - const std::function& buildFunc) - const { - quartz::QuartzProgramBuilder builder(context.get()); - builder.initialize(); - buildFunc(builder); - auto module = builder.finalize(); - runCanonicalizationPasses(module.get()); - return module; - } - - /** - * @brief Build expected Flux IR programmatically and run canonicalization - */ - [[nodiscard]] OwningOpRef buildFluxIR( - const std::function& buildFunc) const { - flux::FluxProgramBuilder builder(context.get()); - builder.initialize(); - buildFunc(builder); - auto module = builder.finalize(); - runCanonicalizationPasses(module.get()); - return module; - } - - /** - * @brief Build expected QIR programmatically and run canonicalization - */ - [[nodiscard]] OwningOpRef buildQIR( - const std::function& buildFunc) const { - qir::QIRProgramBuilder builder(context.get()); - builder.initialize(); - buildFunc(builder); - auto module = builder.finalize(); - runCanonicalizationPasses(module.get()); - return module; - } - - static std::string captureIR(ModuleOp module) { - std::string result; - llvm::raw_string_ostream os(result); - module.print(os); - os.flush(); - return result; - } -}; - -TEST_F(SimpleConversionTest, RXQuartzToFlux) { - auto module = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.0); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); - auto q = b.allocQubitRegister(1, "q"); - b.rx(thetaOperand, q[0]); - }); - - PassManager pm(module.get().getContext()); - pm.addPass(createQuartzToFlux()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createRemoveDeadValuesPass()); - ASSERT_TRUE(pm.run(module.get()).succeeded()); - - const auto fluxResult = captureIR(module.get()); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.rx(1.0, q[0]); - }); - - EXPECT_TRUE(verify("Quartz to Flux", fluxResult, fluxExpected.get())); -} - -TEST_F(SimpleConversionTest, RXQuartzToQIR) { - auto module = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.0); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); - auto q = b.allocQubitRegister(1, "q"); - b.rx(thetaOperand, q[0]); - }); - - PassManager pm(module.get().getContext()); - pm.addPass(createQuartzToQIR()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createRemoveDeadValuesPass()); - ASSERT_TRUE(pm.run(module.get()).succeeded()); - - // const auto qirResult = captureIR(module.get()); - // const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { - // auto thetaAttr = b.builder.getF64FloatAttr(1.0); - // auto thetaOperand = b.builder.create(b.loc, thetaAttr); - // auto q = b.allocQubitRegister(1); - // b.rx(thetaOperand, q[0]); - // }); - - // EXPECT_TRUE(verify("Quartz to QIR", qirResult, qirExpected.get())); -} - -TEST_F(SimpleConversionTest, RXSimplifyAttrValue) { - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.0); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - auto q1 = b.rx(0.5, q0); - b.rx(thetaOperand, q1); - }); - const auto fluxInitIR = captureIR(fluxInit.get()); - - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - b.rx(1.5, q0); - }); - - EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); -} - -TEST_F(SimpleConversionTest, RXSimplifyValueAttr) { - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr = b.builder.getF64FloatAttr(1.0); - auto thetaOperand = b.builder.create(b.loc, thetaAttr); - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - auto q1 = b.rx(thetaOperand, q0); - b.rx(0.5, q1); - }); - const auto fluxInitIR = captureIR(fluxInit.get()); - - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - b.rx(1.5, q0); - }); - - EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); -} - -TEST_F(SimpleConversionTest, RXSimplifyValueValue) { - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto thetaAttr10 = b.builder.getF64FloatAttr(1.0); - auto thetaOperand10 = - b.builder.create(b.loc, thetaAttr10); - auto thetaAttr05 = b.builder.getF64FloatAttr(0.5); - auto thetaOperand05 = - b.builder.create(b.loc, thetaAttr05); - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - auto q1 = b.rx(thetaOperand10, q0); - b.rx(thetaOperand05, q1); - }); - const auto fluxInitIR = captureIR(fluxInit.get()); - - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - b.rx(1.5, q0); - }); - - EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); -} - -TEST_F(SimpleConversionTest, CX) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.cx(0, 1); - - const auto module = translateQuantumComputationToQuartz(context.get(), qc); - ASSERT_TRUE(module); - runCanonicalizationPasses(module.get()); - const auto quartzIRInit = captureIR(module.get()); - - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl(q0, [&](auto& b) { b.x(q1); }); - }); - - EXPECT_TRUE(verify("Translation", quartzIRInit, quartzExpected.get())); - - PassManager pm(module.get().getContext()); - pm.addPass(createQuartzToFlux()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createRemoveDeadValuesPass()); - ASSERT_TRUE(pm.run(module.get()).succeeded()); - const auto fluxIR = captureIR(module.get()); - - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - b.ctrl({q0}, {q1}, [&](auto& b) { - auto q1res = b.x(q1); - return SmallVector{q1res}; - }); - }); - - EXPECT_TRUE(verify("Quartz to Flux", fluxIR, fluxExpected.get())); - - pm.clear(); - pm.addPass(createFluxToQuartz()); - pm.addPass(createCanonicalizerPass()); - pm.addPass(createRemoveDeadValuesPass()); - ASSERT_TRUE(pm.run(module.get()).succeeded()); - const auto quartzIRConv = captureIR(module.get()); - - EXPECT_TRUE(verify("Flux to Quartz", quartzIRConv, quartzExpected.get())); -} - -TEST_F(SimpleConversionTest, CXMergeNested) { - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(3, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - auto q2 = reg[2]; - b.ctrl({q0}, {q1, q2}, [&](auto& b) { - auto q12res = b.ctrl({q1}, {q2}, [&](auto& b) { - auto q2res = b.x(q2); - return SmallVector{q2res}; - }); - return SmallVector{q12res.first[0], q12res.second[0]}; - }); - }); - const auto fluxInitIR = captureIR(fluxInit.get()); - - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(3, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; - auto q2 = reg[2]; - b.ctrl({q0, q1}, {q2}, [&](auto& b) { - auto q2res = b.x(q2); - return SmallVector{q2res}; - }); - }); - - EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); -} - -TEST_F(SimpleConversionTest, CXRemoveTrivial) { - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - b.ctrl({}, {q0}, [&](auto& b) { - auto q0res = b.x(q0); - return SmallVector{q0res}; - }); - }); - const auto fluxInitIR = captureIR(fluxInit.get()); - - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q0a = reg[0]; - b.x(q0a); - }); - - EXPECT_TRUE(verify("Flux Canonicalization", fluxInitIR, fluxOpt.get())); -} - } // namespace From 49a830cd475cee16ae4017ff7f2043685ba9c929 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 10 Nov 2025 18:52:04 +0100 Subject: [PATCH 192/419] =?UTF-8?q?=F0=9F=9A=A8=20fix=20compiler=20warning?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Flux/Builder/FluxProgramBuilder.cpp | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 35e7ba62d1..490d1d9bd8 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -187,8 +187,8 @@ std::pair FluxProgramBuilder::cx(Value control, Value target) { const auto [controlsOut, targetsOut] = ctrl({control}, {target}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto x = b.create(loc, targets[0]); - return {x.getQubitOut()}; + const auto x = b.create(loc, targets[0]); + return x->getResults(); }); return {controlsOut[0], targetsOut[0]}; } @@ -198,8 +198,8 @@ std::pair FluxProgramBuilder::mcx(const ValueRange controls, const auto [controlsOut, targetsOut] = ctrl(controls, {target}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto x = b.create(loc, targets[0]); - return {x.getQubitOut()}; + const auto x = b.create(loc, targets[0]); + return x->getResults(); }); return {controlsOut, targetsOut[0]}; } @@ -221,8 +221,8 @@ FluxProgramBuilder::crx(const std::variant& theta, Value control, const auto [controlsOut, targetsOut] = ctrl({control}, {target}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto rx = b.create(loc, targets[0], theta); - return {rx.getQubitOut()}; + const auto rx = b.create(loc, targets[0], theta); + return rx->getResults(); }); return {controlsOut[0], targetsOut[0]}; } @@ -233,8 +233,8 @@ FluxProgramBuilder::mcrx(const std::variant& theta, const auto [controlsOut, targetsOut] = ctrl(controls, {target}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto rx = b.create(loc, targets[0], theta); - return {rx.getQubitOut()}; + const auto rx = b.create(loc, targets[0], theta); + return rx->getResults(); }); return {controlsOut, targetsOut[0]}; } @@ -258,8 +258,8 @@ FluxProgramBuilder::cu2(const std::variant& phi, const auto [controlsOut, targetsOut] = ctrl({control}, {target}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto u2 = b.create(loc, targets[0], phi, lambda); - return {u2.getQubitOut()}; + const auto u2 = b.create(loc, targets[0], phi, lambda); + return u2->getResults(); }); return {controlsOut[0], targetsOut[0]}; } @@ -271,8 +271,8 @@ FluxProgramBuilder::mcu2(const std::variant& phi, const auto [controlsOut, targetsOut] = ctrl(controls, {target}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto u2 = b.create(loc, targets[0], phi, lambda); - return {u2.getQubitOut()}; + const auto u2 = b.create(loc, targets[0], phi, lambda); + return u2->getResults(); }); return {controlsOut, targetsOut[0]}; } @@ -294,8 +294,20 @@ FluxProgramBuilder::cswap(Value control, Value qubit0, Value qubit1) { const auto [controlsOut, targetsOut] = ctrl({control}, {qubit0, qubit1}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - auto swap = b.create(loc, targets[0], targets[1]); - return {swap.getQubit0Out(), swap.getQubit1Out()}; + const auto swap = b.create(loc, targets[0], targets[1]); + return swap->getResults(); + }); + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; +} + +std::pair> +FluxProgramBuilder::mcswap(const ValueRange controls, Value qubit0, + Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto swap = b.create(loc, targets[0], targets[1]); + return swap->getResults(); }); return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; } From 04ce9f9d6311bfdb301108a5db4e7d850834c92d Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 11 Nov 2025 17:31:36 -0500 Subject: [PATCH 193/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20fix=20CtrlOp=20han?= =?UTF-8?q?dling=20and=20improve=20conversions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 26 ++-- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 11 +- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 29 +--- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 137 ++++++++---------- .../Flux/Builder/FluxProgramBuilder.cpp | 31 ++-- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 18 +-- .../pipeline/test_compiler_pipeline.cpp | 69 +++++++++ 7 files changed, 179 insertions(+), 142 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index a0b1b24ec5..cc2b782aae 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -274,7 +274,7 @@ class FluxProgramBuilder { * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` */ std::pair cx(Value control, Value target); @@ -293,8 +293,8 @@ class FluxProgramBuilder { * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { * %q2_res = flux.x %q2_in : !flux.qubit -> !flux.qubit * flux.yield %q2_res - * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, - * !flux.qubit + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) * ``` */ std::pair mcx(ValueRange controls, Value target); @@ -335,7 +335,7 @@ class FluxProgramBuilder { * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.rx(1.0) %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` */ std::pair crx(const std::variant& theta, @@ -356,8 +356,8 @@ class FluxProgramBuilder { * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { * %q2_res = flux.rx(1.0) %q2_in : !flux.qubit -> !flux.qubit * flux.yield %q2_res - * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, - * !flux.qubit + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) * ``` */ std::pair mcrx(const std::variant& theta, @@ -402,7 +402,7 @@ class FluxProgramBuilder { * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.u2(1.0, 0.5) %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` */ std::pair cu2(const std::variant& phi, @@ -425,8 +425,8 @@ class FluxProgramBuilder { * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { * %q2_res = flux.u2(1.0, 0.5) %q2_in : !flux.qubit -> !flux.qubit * flux.yield %q2_res - * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, - * !flux.qubit + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) * ``` */ std::pair mcu2(const std::variant& phi, @@ -491,9 +491,9 @@ class FluxProgramBuilder { * ```mlir * %controls_out, %q2_out, %q3_out = flux.ctrl(%q0_in, %q1_in) %q2_in, %q3_in * { %q2_res, %q3_res = flux.swap %q2_in, %q3_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit flux.yield %q2_res, %q3_res } : !flux.qubit, - * !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, - * !flux.qubit, !flux.qubit + * !flux.qubit, !flux.qubit flux.yield %q2_res, %q3_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> + * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) * ``` */ std::pair> @@ -522,7 +522,7 @@ class FluxProgramBuilder { * %controls_out, %targets_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` */ std::pair diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 5a523f609d..442e161b19 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -347,7 +347,7 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); - let assemblyFormat = "$qubit0_in $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -392,7 +392,7 @@ def CtrlOp : FluxOp<"ctrl", traits = %ctrl_q_out, %tgt_q_out = flux.ctrl(%ctrl_q_in) %tgt_q_in { %tgt_q_out = flux.h %tgt_q_in : !flux.qubit -> !flux.qubit flux.yield %tgt_q_out : !flux.qubit - } : (!flux.qubit, !flux.qubit) -> (!flux.qubit, !flux.qubit) + } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) ``` }]; @@ -403,9 +403,9 @@ def CtrlOp : FluxOp<"ctrl", traits = let assemblyFormat = [{ `(` $controls_in `)` $targets_in $body attr-dict `:` - type($controls_in) `,` type($targets_in) + `(` `{` type($controls_in) `}` `,` `{` type($targets_in) `}` `)` `->` - type($controls_out) `,` type($targets_out) + `(` `{` type($controls_out) `}` `,` `{` type($targets_out) `}` `)` }]; let extraClassDeclaration = [{ @@ -433,6 +433,9 @@ def CtrlOp : FluxOp<"ctrl", traits = }]; let builders = [ + OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets), [{ + build($_builder, $_state, controls.getTypes(), targets.getTypes(), controls, targets); + }]>, OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, "UnitaryOpInterface":$bodyUnitary)>, OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, "const std::function&":$bodyBuilder)> ]; diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 91325e3205..2c183fbc5d 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -353,7 +353,7 @@ struct ConvertFluxSWAPOp final : OpConversionPattern { rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1); // Replace the output qubit with the same quartz references - rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + rewriter.replaceOp(op, adaptor.getOperands()); return success(); } @@ -368,7 +368,7 @@ struct ConvertFluxSWAPOp final : OpConversionPattern { * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : {!flux.qubit}, {!flux.qubit} -> {!flux.qubit}, {!flux.qubit} + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` * is converted to * ```mlir @@ -387,9 +387,6 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { // Get Quartz controls const auto& quartzControls = adaptor.getControlsIn(); - // Get Quartz targets - const auto& quartzTargets = adaptor.getTargetsIn(); - // Create quartz.ctrl operation auto fluxOp = rewriter.create(op.getLoc(), quartzControls); @@ -397,13 +394,8 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { rewriter.cloneRegionBefore(op.getBody(), fluxOp.getBody(), fluxOp.getBody().end()); - SmallVector quartzQubits; - quartzQubits.reserve(quartzControls.size() + quartzTargets.size()); - quartzQubits.append(quartzControls.begin(), quartzControls.end()); - quartzQubits.append(quartzTargets.begin(), quartzTargets.end()); - // Replace the output qubits with the same quartz references - rewriter.replaceOp(op, quartzQubits); + rewriter.replaceOp(op, adaptor.getOperands()); return success(); } @@ -477,17 +469,10 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); - patterns.add(typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 25b89ad96c..5c75db5813 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -153,6 +153,7 @@ struct ConvertQuartzAllocOp final LogicalResult matchAndRewrite(quartz::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); const auto& quartzQubit = op.getResult(); // Create the flux.alloc operation with preserved register metadata @@ -164,7 +165,7 @@ struct ConvertQuartzAllocOp final // Establish initial mapping: this Quartz qubit reference now corresponds // to this Flux SSA value - getState().qubitMap.try_emplace(quartzQubit, fluxQubit); + qubitMap.try_emplace(quartzQubit, fluxQubit); return success(); } @@ -192,16 +193,17 @@ struct ConvertQuartzDeallocOp final LogicalResult matchAndRewrite(quartz::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); const auto& quartzQubit = op.getQubit(); // Look up the latest Flux value for this Quartz qubit - const auto& fluxQubit = getState().qubitMap[quartzQubit]; + const auto& fluxQubit = qubitMap[quartzQubit]; // Create the dealloc operation rewriter.replaceOpWithNewOp(op, fluxQubit); // Remove from state as qubit is no longer in use - getState().qubitMap.erase(quartzQubit); + qubitMap.erase(quartzQubit); return success(); } @@ -229,19 +231,17 @@ struct ConvertQuartzStaticOp final LogicalResult matchAndRewrite(quartz::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - // Prepare result type - const auto& qubitType = flux::QubitType::get(rewriter.getContext()); + auto& [qubitMap] = getState(); + const auto& quartzQubit = op.getQubit(); // Create new flux.static operation with the same index - auto fluxOp = - rewriter.create(op.getLoc(), qubitType, op.getIndex()); + auto fluxOp = rewriter.create(op.getLoc(), op.getIndex()); - // Collect Quartz and Flux SSA values - const auto& quartzQubit = op.getQubit(); + // Collect Flux qubit SSA value const auto& fluxQubit = fluxOp.getQubit(); // Establish mapping from Quartz reference to Flux value - getState().qubitMap[quartzQubit] = fluxQubit; + qubitMap[quartzQubit] = fluxQubit; // Replace the old operation result with the new result rewriter.replaceOp(op, fluxQubit); @@ -280,11 +280,11 @@ struct ConvertQuartzMeasureOp final LogicalResult matchAndRewrite(quartz::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - + auto& [qubitMap] = getState(); const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map - const auto& fluxQubit = getState().qubitMap[quartzQubit]; + const auto& fluxQubit = qubitMap[quartzQubit]; // Create flux.measure (returns both output qubit and bit result) auto fluxOp = rewriter.create( @@ -295,7 +295,7 @@ struct ConvertQuartzMeasureOp final const auto newBit = fluxOp.getResult(); // Update mapping: the Quartz qubit now corresponds to the output qubit - getState().qubitMap[quartzQubit] = outFluxQubit; + qubitMap[quartzQubit] = outFluxQubit; // Replace the Quartz operation's bit result with the Flux bit result rewriter.replaceOp(op, newBit); @@ -331,18 +331,18 @@ struct ConvertQuartzResetOp final LogicalResult matchAndRewrite(quartz::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - + auto& [qubitMap] = getState(); // Track the Quartz qubit being reset const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map - const auto& fluxQubit = getState().qubitMap[quartzQubit]; + const auto& fluxQubit = qubitMap[quartzQubit]; // Create flux.reset (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); // Update mapping: the Quartz qubit now corresponds to the reset output - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[quartzQubit] = fluxOp.getQubitOut(); // Erase the old (it has no results to replace) rewriter.eraseOp(op); @@ -369,17 +369,16 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); const auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit - Value quartzQubit = nullptr; + const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; if (inRegion) { - quartzQubit = op->getOperand(0); fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { - quartzQubit = op.getQubitIn(); - fluxQubit = getState().qubitMap[quartzQubit]; + fluxQubit = qubitMap[quartzQubit]; } // Create flux.x (consumes input, produces output) @@ -387,7 +386,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { // Update state map if (!inRegion) { - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[quartzQubit] = fluxOp.getQubitOut(); } rewriter.eraseOp(op); @@ -413,17 +412,16 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); const auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit - Value quartzQubit = nullptr; + const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; if (inRegion) { - quartzQubit = op->getOperand(0); fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { - quartzQubit = op.getQubitIn(); - fluxQubit = getState().qubitMap[quartzQubit]; + fluxQubit = qubitMap[quartzQubit]; } // Create flux.rx (consumes input, produces output) @@ -432,7 +430,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { // Update state map if (!inRegion) { - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[quartzQubit] = fluxOp.getQubitOut(); } rewriter.eraseOp(op); @@ -459,17 +457,16 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::U2Op op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); const auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit - Value quartzQubit = nullptr; + const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; if (inRegion) { - quartzQubit = op->getOperand(0); fluxQubit = rewriter.getRemappedValue(quartzQubit); } else { - quartzQubit = op.getQubitIn(); - fluxQubit = getState().qubitMap[quartzQubit]; + fluxQubit = qubitMap[quartzQubit]; } // Create flux.u2 (consumes input, produces output) @@ -478,7 +475,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { // Update state map if (!inRegion) { - getState().qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[quartzQubit] = fluxOp.getQubitOut(); } rewriter.eraseOp(op); @@ -506,23 +503,20 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); const auto inRegion = llvm::isa(op->getParentOp()); // Get the latest Flux qubit - Value quartzQubit0 = nullptr; - Value quartzQubit1 = nullptr; + const auto quartzQubit0 = op->getOperand(0); + const auto quartzQubit1 = op->getOperand(1); Value fluxQubit0 = nullptr; Value fluxQubit1 = nullptr; if (inRegion) { - quartzQubit0 = op->getOperand(0); - quartzQubit1 = op->getOperand(1); fluxQubit0 = rewriter.getRemappedValue(quartzQubit0); fluxQubit1 = rewriter.getRemappedValue(quartzQubit1); } else { - quartzQubit0 = op.getQubit0(); - quartzQubit1 = op.getQubit1(); - fluxQubit0 = getState().qubitMap[quartzQubit0]; - fluxQubit1 = getState().qubitMap[quartzQubit1]; + fluxQubit0 = qubitMap[quartzQubit0]; + fluxQubit1 = qubitMap[quartzQubit1]; } // Create flux.swap (consumes input, produces output) @@ -531,8 +525,8 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { // Update state map if (!inRegion) { - getState().qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - getState().qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); } rewriter.eraseOp(op); @@ -564,7 +558,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::CtrlOp op, OpAdaptor adaptor, + matchAndRewrite(quartz::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& [qubitMap] = getState(); // Get Flux controls from state map @@ -575,27 +569,26 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { fluxControls.push_back(qubitMap[quartzControl]); } - // Get Flux targets from state map + // Get Flux targets from state map and build mapping for region + IRMapping regionMap; const auto numTargets = op.getNumTargets(); SmallVector fluxTargets; fluxTargets.reserve(numTargets); for (size_t i = 0; i < numTargets; ++i) { - fluxTargets.push_back(qubitMap[op.getTarget(i)]); + const auto& quartzTarget = op.getTarget(i); + const auto& fluxTarget = qubitMap[quartzTarget]; + fluxTargets.push_back(fluxTarget); + regionMap.map(quartzTarget, fluxTarget); } // Create flux.ctrl - /// TODO this does not work yet. auto fluxOp = - rewriter.create(op.getLoc(), fluxControls, fluxTargets, - llvm::dyn_cast( - &adaptor.getBody().front().front())); + rewriter.create(op.getLoc(), fluxControls, fluxTargets); // Update state map - const auto numPosControls = op.getNumPosControls(); - const auto controlsOut = fluxOp.getControlsOut(); - for (size_t i = 0; i < numPosControls; ++i) { - const auto& quartzControl = quartzControls[i]; - qubitMap[quartzControl] = controlsOut[i]; + for (const auto& [quartzControl, fluxControl] : + llvm::zip(quartzControls, fluxOp.getControlsOut())) { + qubitMap[quartzControl] = fluxControl; } const auto targetsOut = fluxOp.getTargetsOut(); for (size_t i = 0; i < numTargets; ++i) { @@ -603,8 +596,12 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { qubitMap[quartzTarget] = targetsOut[i]; } - rewriter.eraseOp(op); + // clone region + auto& dstRegion = fluxOp.getBody(); + rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end(), + regionMap); + rewriter.eraseOp(op); return success(); } }; @@ -630,19 +627,9 @@ struct ConvertQuartzYieldOp final matchAndRewrite(quartz::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto ctrlOp = dyn_cast(op->getParentOp()); - auto unitaryOp = ctrlOp.getBodyUnitary(); - - SmallVector targets; - targets.reserve(unitaryOp.getNumTargets()); - for (size_t i = 0; i < unitaryOp.getNumTargets(); ++i) { - const auto& yield = - rewriter.getRemappedValue(unitaryOp.getOutputTarget(i)); - targets.push_back(yield); - } - - // Create quartz.yield - rewriter.replaceOpWithNewOp(op, targets); - + assert(ctrlOp != nullptr); + rewriter.replaceOpWithNewOp( + op, ctrlOp.getBodyUnitary()->getResults()); return success(); } }; @@ -685,17 +672,11 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); - patterns.add(typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 490d1d9bd8..5f1b46ba97 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -183,9 +183,10 @@ Value FluxProgramBuilder::x(Value qubit) { return qubitOut; } -std::pair FluxProgramBuilder::cx(Value control, Value target) { +std::pair FluxProgramBuilder::cx(const Value control, + const Value target) { const auto [controlsOut, targetsOut] = - ctrl({control}, {target}, + ctrl(control, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto x = b.create(loc, targets[0]); return x->getResults(); @@ -194,9 +195,9 @@ std::pair FluxProgramBuilder::cx(Value control, Value target) { } std::pair FluxProgramBuilder::mcx(const ValueRange controls, - Value target) { + const Value target) { const auto [controlsOut, targetsOut] = - ctrl(controls, {target}, + ctrl(controls, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto x = b.create(loc, targets[0]); return x->getResults(); @@ -216,10 +217,10 @@ Value FluxProgramBuilder::rx(const std::variant& theta, } std::pair -FluxProgramBuilder::crx(const std::variant& theta, Value control, - const Value target) { +FluxProgramBuilder::crx(const std::variant& theta, + const Value control, const Value target) { const auto [controlsOut, targetsOut] = - ctrl({control}, {target}, + ctrl(control, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto rx = b.create(loc, targets[0], theta); return rx->getResults(); @@ -229,9 +230,9 @@ FluxProgramBuilder::crx(const std::variant& theta, Value control, std::pair FluxProgramBuilder::mcrx(const std::variant& theta, - const ValueRange controls, Value target) { + const ValueRange controls, const Value target) { const auto [controlsOut, targetsOut] = - ctrl(controls, {target}, + ctrl(controls, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto rx = b.create(loc, targets[0], theta); return rx->getResults(); @@ -254,9 +255,9 @@ Value FluxProgramBuilder::u2(const std::variant& phi, std::pair FluxProgramBuilder::cu2(const std::variant& phi, const std::variant& lambda, - Value control, const Value target) { + const Value control, const Value target) { const auto [controlsOut, targetsOut] = - ctrl({control}, {target}, + ctrl(control, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto u2 = b.create(loc, targets[0], phi, lambda); return u2->getResults(); @@ -267,9 +268,9 @@ FluxProgramBuilder::cu2(const std::variant& phi, std::pair FluxProgramBuilder::mcu2(const std::variant& phi, const std::variant& lambda, - const ValueRange controls, Value target) { + const ValueRange controls, const Value target) { const auto [controlsOut, targetsOut] = - ctrl(controls, {target}, + ctrl(controls, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto u2 = b.create(loc, targets[0], phi, lambda); return u2->getResults(); @@ -290,9 +291,9 @@ std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { } std::pair> -FluxProgramBuilder::cswap(Value control, Value qubit0, Value qubit1) { +FluxProgramBuilder::cswap(const Value control, Value qubit0, Value qubit1) { const auto [controlsOut, targetsOut] = - ctrl({control}, {qubit0, qubit1}, + ctrl(control, {qubit0, qubit1}, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { const auto swap = b.create(loc, targets[0], targets[1]); return swap->getResults(); diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 98a69a6172..48043ecb6a 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -218,13 +218,11 @@ DenseElementsAttr SWAPOp::tryGetStaticMatrix() { void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, const ValueRange targets, UnitaryOpInterface bodyUnitary) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - odsState.addOperands(targets); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); odsBuilder.setInsertionPointToStart(&block); auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); odsBuilder.create(odsState.location, op->getResults()); @@ -234,13 +232,13 @@ void CtrlOp::build( OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, const ValueRange targets, const std::function& bodyBuilder) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - odsState.addOperands(targets); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); odsBuilder.setInsertionPointToStart(&block); - auto targetsOut = bodyBuilder(odsBuilder, block.getArguments()); + auto targetsOut = bodyBuilder(odsBuilder, targets); odsBuilder.create(odsState.location, targetsOut); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index ad72898f66..696d841d2e 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1340,6 +1340,43 @@ TEST_F(CompilerPipelineTest, CX) { }); } +TEST_F(CompilerPipelineTest, CX3) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.cx(0, 1); + qc.cx(1, 0); + qc.cx(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); + b.cx(q1, q0); + b.cx(q0, q1); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.cx(q0, q1); + std::tie(q1, q0) = b.cx(q1, q0); + std::tie(q0, q1) = b.cx(q0, q1); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = nullptr, + }); +} + TEST_F(CompilerPipelineTest, RX) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); @@ -1488,4 +1525,36 @@ TEST_F(CompilerPipelineTest, SWAP) { }); } +TEST_F(CompilerPipelineTest, CSWAP) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.cswap(0, 1, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzExpected = + buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto q = b.allocQubitRegister(3, "q"); + b.cswap(q[0], q[1], q[2]); + }); + const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto q = b.allocQubitRegister(3, "q"); + b.cswap(q[0], q[1], q[2]); + }); + // const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { + // auto q = b.allocQubitRegister(3); + // b.swap(q[0], q[1]); + // }); + + verifyAllStages({ + .quartzImport = quartzExpected.get(), + .fluxConversion = fluxExpected.get(), + .optimization = fluxExpected.get(), + .quartzConversion = quartzExpected.get(), + .qirConversion = nullptr, + }); +} + } // namespace From 0d1a5824b24d9c6cd86a46fcb81bc2b5528e2555 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 11 Nov 2025 17:53:26 -0500 Subject: [PATCH 194/419] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20make=20Quartz=20an?= =?UTF-8?q?d=20Flux=20builder=20directly=20inherit=20from=20OpBuilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 8 +- .../Quartz/Builder/QuartzProgramBuilder.h | 8 +- .../Flux/Builder/FluxProgramBuilder.cpp | 68 ++++++++-------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 80 +++++++++---------- 4 files changed, 78 insertions(+), 86 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index cc2b782aae..ba0f6cb74d 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -54,7 +54,7 @@ namespace mlir::flux { * auto module = builder.finalize(); * ``` */ -class FluxProgramBuilder { +class FluxProgramBuilder final : public OpBuilder { public: /** * @brief Construct a new FluxProgramBuilder @@ -570,11 +570,11 @@ class FluxProgramBuilder { */ OwningOpRef finalize(); - OpBuilder builder; - ModuleOp module; +private: + MLIRContext* ctx{}; Location loc; + ModuleOp module; -private: //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 414ac2f1e6..204d0862b5 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -46,7 +46,7 @@ namespace mlir::quartz { * auto module = builder.finalize(); * ``` */ -class QuartzProgramBuilder { /// TODO: Consider inheriting from OpBuilder +class QuartzProgramBuilder final : public OpBuilder { public: /** * @brief Construct a new QuartzProgramBuilder @@ -524,11 +524,11 @@ class QuartzProgramBuilder { /// TODO: Consider inheriting from OpBuilder */ OwningOpRef finalize(); - OpBuilder builder; - ModuleOp module; +private: + MLIRContext* ctx{}; Location loc; + ModuleOp module; -private: /// Track allocated qubits for automatic deallocation llvm::DenseSet allocatedQubits; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 5f1b46ba97..f96499c720 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -33,33 +33,31 @@ namespace mlir::flux { FluxProgramBuilder::FluxProgramBuilder(MLIRContext* context) - : builder(context), - module(builder.create(UnknownLoc::get(context))), - loc(UnknownLoc::get(context)) {} + : OpBuilder(context), ctx(context), loc(getUnknownLoc()), + module(create(loc)) {} void FluxProgramBuilder::initialize() { // Ensure the Flux dialect is loaded - builder.getContext()->loadDialect(); + ctx->loadDialect(); // Set insertion point to the module body - builder.setInsertionPointToStart(module.getBody()); + setInsertionPointToStart(module.getBody()); // Create main function as entry point - auto funcType = builder.getFunctionType({}, {builder.getI64Type()}); - auto mainFunc = builder.create(loc, "main", funcType); + auto funcType = getFunctionType({}, {getI64Type()}); + auto mainFunc = create(loc, "main", funcType); // Add entry_point attribute to identify the main function - auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); - mainFunc->setAttr("passthrough", - ArrayAttr::get(builder.getContext(), {entryPointAttr})); + auto entryPointAttr = getStringAttr("entry_point"); + mainFunc->setAttr("passthrough", getArrayAttr({entryPointAttr})); // Create entry block and set insertion point auto& entryBlock = mainFunc.getBody().emplaceBlock(); - builder.setInsertionPointToStart(&entryBlock); + setInsertionPointToStart(&entryBlock); } Value FluxProgramBuilder::allocQubit() { - auto allocOp = builder.create(loc); + auto allocOp = create(loc); const auto qubit = allocOp.getResult(); // Track the allocated qubit as valid @@ -69,8 +67,8 @@ Value FluxProgramBuilder::allocQubit() { } Value FluxProgramBuilder::staticQubit(const int64_t index) { - auto indexAttr = builder.getI64IntegerAttr(index); - auto staticOp = builder.create(loc, indexAttr); + auto indexAttr = getI64IntegerAttr(index); + auto staticOp = create(loc, indexAttr); const auto qubit = staticOp.getQubit(); // Track the static qubit as valid @@ -85,12 +83,12 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, SmallVector qubits; qubits.reserve(static_cast(size)); - auto nameAttr = builder.getStringAttr(name); - auto sizeAttr = builder.getI64IntegerAttr(size); + auto nameAttr = getStringAttr(name); + auto sizeAttr = getI64IntegerAttr(size); for (int64_t i = 0; i < size; ++i) { - auto indexAttr = builder.getI64IntegerAttr(i); - auto allocOp = builder.create(loc, nameAttr, sizeAttr, indexAttr); + auto indexAttr = getI64IntegerAttr(i); + auto allocOp = create(loc, nameAttr, sizeAttr, indexAttr); const auto& qubit = qubits.emplace_back(allocOp.getResult()); // Track the allocated qubit as valid validQubits.insert(qubit); @@ -112,7 +110,7 @@ void FluxProgramBuilder::validateQubitValue(const Value qubit) const { if (!validQubits.contains(qubit)) { llvm::errs() << "Error: Attempting to use an invalid qubit SSA value. " << "The value may have been consumed by a previous operation " - << "or was never created through this builder.\n"; + << "or was never created through this \n"; llvm::reportFatalUsageError( "Invalid qubit value used (either consumed or not tracked)"); } @@ -135,7 +133,7 @@ void FluxProgramBuilder::updateQubitTracking(const Value inputQubit, //===----------------------------------------------------------------------===// std::pair FluxProgramBuilder::measure(Value qubit) { - auto measureOp = builder.create(loc, qubit); + auto measureOp = create(loc, qubit); auto qubitOut = measureOp.getQubitOut(); auto result = measureOp.getResult(); @@ -146,11 +144,10 @@ std::pair FluxProgramBuilder::measure(Value qubit) { } Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { - auto nameAttr = builder.getStringAttr(bit.registerName); - auto sizeAttr = builder.getI64IntegerAttr(bit.registerSize); - auto indexAttr = builder.getI64IntegerAttr(bit.registerIndex); - auto measureOp = - builder.create(loc, qubit, nameAttr, sizeAttr, indexAttr); + auto nameAttr = getStringAttr(bit.registerName); + auto sizeAttr = getI64IntegerAttr(bit.registerSize); + auto indexAttr = getI64IntegerAttr(bit.registerIndex); + auto measureOp = create(loc, qubit, nameAttr, sizeAttr, indexAttr); const auto qubitOut = measureOp.getQubitOut(); // Update tracking @@ -160,7 +157,7 @@ Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { } Value FluxProgramBuilder::reset(Value qubit) { - auto resetOp = builder.create(loc, qubit); + auto resetOp = create(loc, qubit); const auto qubitOut = resetOp.getQubitOut(); // Update tracking @@ -174,7 +171,7 @@ Value FluxProgramBuilder::reset(Value qubit) { //===----------------------------------------------------------------------===// Value FluxProgramBuilder::x(Value qubit) { - auto xOp = builder.create(loc, qubit); + auto xOp = create(loc, qubit); const auto& qubitOut = xOp.getQubitOut(); // Update tracking @@ -207,7 +204,7 @@ std::pair FluxProgramBuilder::mcx(const ValueRange controls, Value FluxProgramBuilder::rx(const std::variant& theta, Value qubit) { - auto rxOp = builder.create(loc, qubit, theta); + auto rxOp = create(loc, qubit, theta); const auto& qubitOut = rxOp.getQubitOut(); // Update tracking @@ -243,7 +240,7 @@ FluxProgramBuilder::mcrx(const std::variant& theta, Value FluxProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, Value qubit) { - auto u2Op = builder.create(loc, qubit, phi, lambda); + auto u2Op = create(loc, qubit, phi, lambda); const auto& qubitOut = u2Op.getQubitOut(); // Update tracking @@ -279,7 +276,7 @@ FluxProgramBuilder::mcu2(const std::variant& phi, } std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { - auto swapOp = builder.create(loc, qubit0, qubit1); + auto swapOp = create(loc, qubit0, qubit1); const auto& qubit0Out = swapOp.getQubit0Out(); const auto& qubit1Out = swapOp.getQubit1Out(); @@ -320,7 +317,7 @@ FluxProgramBuilder::mcswap(const ValueRange controls, Value qubit0, std::pair FluxProgramBuilder::ctrl( const ValueRange controls, const ValueRange targets, const std::function& body) { - auto ctrlOp = builder.create(loc, controls, targets, body); + auto ctrlOp = create(loc, controls, targets, body); // Update tracking const auto& controlsOut = ctrlOp.getControlsOut(); @@ -343,7 +340,7 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { validateQubitValue(qubit); validQubits.erase(qubit); - builder.create(loc, qubit); + create(loc, qubit); return *this; } @@ -355,17 +352,16 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { OwningOpRef FluxProgramBuilder::finalize() { // Automatically deallocate all remaining valid qubits for (const auto qubit : validQubits) { - builder.create(loc, qubit); + create(loc, qubit); } validQubits.clear(); // Create constant 0 for successful exit code - auto exitCode = - builder.create(loc, builder.getI64IntegerAttr(0)); + auto exitCode = create(loc, getI64IntegerAttr(0)); // Add return statement with exit code 0 to the main function - builder.create(loc, ValueRange{exitCode}); + create(loc, ValueRange{exitCode}); return module; } diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 85b1ec8f05..4df219d209 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -31,34 +31,32 @@ namespace mlir::quartz { QuartzProgramBuilder::QuartzProgramBuilder(MLIRContext* context) - : builder(context), - module(builder.create(UnknownLoc::get(context))), - loc(UnknownLoc::get(context)) {} + : OpBuilder(context), ctx(context), loc(getUnknownLoc()), + module(create(loc)) {} void QuartzProgramBuilder::initialize() { // Ensure the Quartz dialect is loaded - builder.getContext()->loadDialect(); + ctx->loadDialect(); // Set insertion point to the module body - builder.setInsertionPointToStart(module.getBody()); + setInsertionPointToStart(module.getBody()); // Create main function as entry point - auto funcType = builder.getFunctionType({}, {builder.getI64Type()}); - auto mainFunc = builder.create(loc, "main", funcType); + auto funcType = getFunctionType({}, {getI64Type()}); + auto mainFunc = create(loc, "main", funcType); // Add entry_point attribute to identify the main function - auto entryPointAttr = StringAttr::get(builder.getContext(), "entry_point"); - mainFunc->setAttr("passthrough", - ArrayAttr::get(builder.getContext(), {entryPointAttr})); + auto entryPointAttr = getStringAttr("entry_point"); + mainFunc->setAttr("passthrough", getArrayAttr({entryPointAttr})); // Create entry block and set insertion point auto& entryBlock = mainFunc.getBody().emplaceBlock(); - builder.setInsertionPointToStart(&entryBlock); + setInsertionPointToStart(&entryBlock); } Value QuartzProgramBuilder::allocQubit() { // Create the AllocOp without register metadata - auto allocOp = builder.create(loc); + auto allocOp = create(loc); const auto qubit = allocOp.getResult(); // Track the allocated qubit for automatic deallocation @@ -69,8 +67,8 @@ Value QuartzProgramBuilder::allocQubit() { Value QuartzProgramBuilder::staticQubit(const int64_t index) { // Create the StaticOp with the given index - auto indexAttr = builder.getI64IntegerAttr(index); - auto staticOp = builder.create(loc, indexAttr); + auto indexAttr = getI64IntegerAttr(index); + auto staticOp = create(loc, indexAttr); return staticOp.getQubit(); } @@ -81,12 +79,12 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, SmallVector qubits; qubits.reserve(size); - auto nameAttr = builder.getStringAttr(name); - auto sizeAttr = builder.getI64IntegerAttr(size); + auto nameAttr = getStringAttr(name); + auto sizeAttr = getI64IntegerAttr(size); for (int64_t i = 0; i < size; ++i) { - auto indexAttr = builder.getI64IntegerAttr(i); - auto allocOp = builder.create(loc, nameAttr, sizeAttr, indexAttr); + auto indexAttr = getI64IntegerAttr(i); + auto allocOp = create(loc, nameAttr, sizeAttr, indexAttr); const auto& qubit = qubits.emplace_back(allocOp.getResult()); // Track the allocated qubit for automatic deallocation allocatedQubits.insert(qubit); @@ -105,21 +103,21 @@ QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { //===----------------------------------------------------------------------===// Value QuartzProgramBuilder::measure(Value qubit) { - auto measureOp = builder.create(loc, qubit); + auto measureOp = create(loc, qubit); return measureOp.getResult(); } QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, const Bit& bit) { - auto nameAttr = builder.getStringAttr(bit.registerName); - auto sizeAttr = builder.getI64IntegerAttr(bit.registerSize); - auto indexAttr = builder.getI64IntegerAttr(bit.registerIndex); - builder.create(loc, qubit, nameAttr, sizeAttr, indexAttr); + auto nameAttr = getStringAttr(bit.registerName); + auto sizeAttr = getI64IntegerAttr(bit.registerSize); + auto indexAttr = getI64IntegerAttr(bit.registerIndex); + create(loc, qubit, nameAttr, sizeAttr, indexAttr); return *this; } QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { - builder.create(loc, qubit); + create(loc, qubit); return *this; } @@ -128,7 +126,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { //===----------------------------------------------------------------------===// QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { - builder.create(loc, qubit); + create(loc, qubit); return *this; } @@ -138,15 +136,15 @@ QuartzProgramBuilder& QuartzProgramBuilder::cx(Value control, Value target) { QuartzProgramBuilder& QuartzProgramBuilder::mcx(ValueRange controls, Value target) { - builder.create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); return *this; } QuartzProgramBuilder& QuartzProgramBuilder::rx(const std::variant& theta, Value qubit) { - builder.create(loc, qubit, theta); + create(loc, qubit, theta); return *this; } @@ -159,8 +157,8 @@ QuartzProgramBuilder::crx(const std::variant& theta, QuartzProgramBuilder& QuartzProgramBuilder::mcrx(const std::variant& theta, ValueRange controls, Value target) { - builder.create( - loc, controls, [&](OpBuilder& b) { b.create(loc, target, theta); }); + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target, theta); }); return *this; } @@ -168,7 +166,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, Value qubit) { - builder.create(loc, qubit, phi, lambda); + create(loc, qubit, phi, lambda); return *this; } @@ -183,14 +181,14 @@ QuartzProgramBuilder& QuartzProgramBuilder::mcu2(const std::variant& phi, const std::variant& lambda, ValueRange controls, Value target) { - builder.create(loc, controls, [&](OpBuilder& b) { + create(loc, controls, [&](OpBuilder& b) { b.create(loc, target, phi, lambda); }); return *this; } QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { - builder.create(loc, qubit0, qubit1); + create(loc, qubit0, qubit1); return *this; } @@ -202,9 +200,8 @@ QuartzProgramBuilder& QuartzProgramBuilder::cswap(Value control, QuartzProgramBuilder& QuartzProgramBuilder::mcswap(ValueRange controls, Value qubit0, Value qubit1) { - builder.create(loc, controls, [&](OpBuilder& b) { - b.create(loc, qubit0, qubit1); - }); + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, qubit0, qubit1); }); return *this; } @@ -215,7 +212,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::mcswap(ValueRange controls, QuartzProgramBuilder& QuartzProgramBuilder::ctrl(ValueRange controls, const std::function& body) { - builder.create(loc, controls, body); + create(loc, controls, body); return *this; } @@ -233,7 +230,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { } // Create the DeallocOp - builder.create(loc, qubit); + create(loc, qubit); return *this; } @@ -245,18 +242,17 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all remaining allocated qubits for (const Value qubit : allocatedQubits) { - builder.create(loc, qubit); + create(loc, qubit); } // Clear the tracking set allocatedQubits.clear(); // Create constant 0 for successful exit code - auto exitCode = - builder.create(loc, builder.getI64IntegerAttr(0)); + auto exitCode = create(loc, getI64IntegerAttr(0)); // Add return statement with exit code 0 to the main function - builder.create(loc, ValueRange{exitCode}); + create(loc, ValueRange{exitCode}); // Transfer ownership to the caller return module; From aba010396218d5268c953a2cc8e67367afc9ab2b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 01:20:27 +0100 Subject: [PATCH 195/419] Fix a good portion of the linter errors --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 2 + .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 1 + .../Flux/Builder/FluxProgramBuilder.cpp | 4 +- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 4 +- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 11 +++-- .../Quartz/Builder/QuartzProgramBuilder.cpp | 5 +-- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 9 +++- .../TranslateQuantumComputationToQuartz.cpp | 40 +++++++++-------- .../pipeline/test_compiler_pipeline.cpp | 44 +++++++++---------- 9 files changed, 67 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 5c75db5813..e7199281a3 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -13,8 +13,10 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include #include #include +#include #include #include #include diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 1b6a2f299c..c12c1cb8f4 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index f96499c720..f4c2464923 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -77,10 +77,10 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { return qubit; } -SmallVector +llvm::SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { - SmallVector qubits; + llvm::SmallVector qubits; qubits.reserve(static_cast(size)); auto nameAttr = getStringAttr(name); diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 48043ecb6a..7e1d534ca6 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -20,7 +20,9 @@ // IWYU pragma: end_keep #include +#include #include +#include #include #include #include @@ -360,7 +362,7 @@ LogicalResult CtrlOp::verify() { << block.back().getNumOperands(); } - SmallPtrSet uniqueQubitsIn; + llvm::SmallPtrSet uniqueQubitsIn; for (const auto& control : getControlsIn()) { if (!uniqueQubitsIn.insert(control).second) { return emitOpError("duplicate control qubit found"); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index b161d6097b..8e222bc0df 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" +#include #include #include #include @@ -110,8 +111,9 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { return val; } -SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { - SmallVector qubits; +llvm::SmallVector +QIRProgramBuilder::allocQubitRegister(const int64_t size) { + llvm::SmallVector qubits; qubits.reserve(size); for (int64_t i = 0; i < size; ++i) { @@ -398,14 +400,15 @@ void QIRProgramBuilder::generateOutputRecording() { auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); // Group measurements by register - llvm::StringMap>> registerGroups; + llvm::StringMap>> registerGroups; for (const auto& [key, resultPtr] : registerResultMap) { const auto& [regName, regIdx] = key; registerGroups[regName].emplace_back(regIdx, resultPtr); } // Sort registers by name for deterministic output - SmallVector>>> + llvm::SmallVector< + std::pair>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 4df219d209..cabc77928d 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -72,11 +71,11 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { return staticOp.getQubit(); } -SmallVector +llvm::SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { // Allocate a sequence of qubits with register metadata - SmallVector qubits; + llvm::SmallVector qubits; qubits.reserve(size); auto nameAttr = getStringAttr(name); diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 6d8957dc83..39577d6ea3 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -19,9 +19,14 @@ // IWYU pragma: end_keep #include +#include +#include +#include #include #include +#include #include +#include #include #include #include @@ -301,7 +306,7 @@ LogicalResult CtrlOp::verify() { return emitOpError( "second operation in body region must be a yield operation"); } - SmallPtrSet uniqueQubits; + llvm::SmallPtrSet uniqueQubits; for (const auto& control : getControls()) { if (!uniqueQubits.insert(control).second) { return emitOpError("duplicate control qubit found"); @@ -330,7 +335,7 @@ struct MergeNestedCtrl final : OpRewritePattern { return failure(); } - SmallVector newControls(ctrlOp.getControls()); + llvm::SmallVector newControls(ctrlOp.getControls()); for (const auto control : bodyCtrlOp.getControls()) { newControls.push_back(control); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 3509c2c037..5e114869ad 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -45,12 +45,12 @@ namespace { */ struct QregInfo { const qc::QuantumRegister* qregPtr; - SmallVector qubits; + llvm::SmallVector qubits; }; using BitMemInfo = std::pair; // (register ref, localIdx) -using BitIndexVec = SmallVector; +using BitIndexVec = llvm::SmallVector; /** * @brief Allocates quantum registers using the QuartzProgramBuilder @@ -65,11 +65,11 @@ using BitIndexVec = SmallVector; * @param quantumComputation The quantum computation to translate * @return Vector containing information about all quantum registers */ -SmallVector +llvm::SmallVector allocateQregs(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - SmallVector qregPtrs; + llvm::SmallVector qregPtrs; qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + quantumComputation.getAncillaRegisters().size()); for (const auto& qreg : @@ -88,7 +88,7 @@ allocateQregs(QuartzProgramBuilder& builder, }); // Allocate quantum registers using the builder - SmallVector qregs; + llvm::SmallVector qregs; for (const auto* qregPtr : qregPtrs) { auto qubits = builder.allocQubitRegister(qregPtr->getSize(), qregPtr->getName()); @@ -110,10 +110,10 @@ allocateQregs(QuartzProgramBuilder& builder, * @param qregs Vector containing information about all quantum registers * @return Flat vector of qubit values indexed by physical qubit index */ -SmallVector +llvm::SmallVector buildQubitMap(const qc::QuantumComputation& quantumComputation, - const SmallVector& qregs) { - SmallVector flatQubits; + const llvm::SmallVector& qregs) { + llvm::SmallVector flatQubits; const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); flatQubits.resize(static_cast(maxPhys) + 1); @@ -143,7 +143,7 @@ BitIndexVec allocateClassicalRegisters(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - SmallVector cregPtrs; + llvm::SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); for (const auto& reg : quantumComputation.getClassicalRegisters() | std::views::values) { @@ -184,7 +184,8 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, * @param bitMap Mapping from global bit index to (register, local_index) */ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits, const BitIndexVec& bitMap) { + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -212,7 +213,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits) { + const llvm::SmallVector& qubits) { for (const auto& target : operation.getTargets()) { const Value qubit = qubits[target]; builder.reset(qubit); @@ -230,9 +231,10 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index * @return Vector of qubit values corresponding to positive controls */ -SmallVector getPosControls(const qc::Operation& operation, - const SmallVector& qubits) { - SmallVector controls; +llvm::SmallVector +getPosControls(const qc::Operation& operation, + const llvm::SmallVector& qubits) { + llvm::SmallVector controls; for (const auto& [control, type] : operation.getControls()) { if (type == qc::Control::Type::Neg) { continue; @@ -253,7 +255,7 @@ SmallVector getPosControls(const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits) { + const llvm::SmallVector& qubits) { const auto& target = qubits[operation.getTargets()[0]]; if (const auto& posControls = getPosControls(operation, qubits); posControls.empty()) { @@ -274,7 +276,7 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits) { + const llvm::SmallVector& qubits) { const auto& theta = operation.getParameter()[0]; const auto& target = qubits[operation.getTargets()[0]]; if (const auto& posControls = getPosControls(operation, qubits); @@ -296,7 +298,7 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits) { + const llvm::SmallVector& qubits) { const auto& phi = operation.getParameter()[0]; const auto& lambda = operation.getParameter()[1]; const auto& target = qubits[operation.getTargets()[0]]; @@ -320,7 +322,7 @@ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const SmallVector& qubits) { + const llvm::SmallVector& qubits) { const auto& target0 = qubits[operation.getTargets()[0]]; const auto& target1 = qubits[operation.getTargets()[1]]; if (const auto& posControls = getPosControls(operation, qubits); @@ -352,7 +354,7 @@ void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, LogicalResult translateOperations(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation, - const SmallVector& qubits, + const llvm::SmallVector& qubits, const BitIndexVec& bitMap) { for (const auto& operation : quantumComputation) { switch (operation->getType()) { diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 696d841d2e..35e872eec3 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -10,9 +10,6 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" -#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" #include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" @@ -22,11 +19,15 @@ #include "mlir/Support/PrettyPrinting.h" #include +#include #include #include #include #include +#include +#include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -45,11 +47,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include namespace { @@ -102,21 +107,16 @@ struct OperationStructuralEquality { // Check operand types (not values) auto lhsOperandTypes = lhs->getOperandTypes(); auto rhsOperandTypes = rhs->getOperandTypes(); - if (!std::equal(lhsOperandTypes.begin(), lhsOperandTypes.end(), - rhsOperandTypes.begin(), rhsOperandTypes.end())) { - return false; - } + return llvm::equal(lhsOperandTypes, rhsOperandTypes); // Note: Attributes are intentionally not checked here to allow relaxed // comparison. Attributes like function names, parameter names, etc. may // differ while operations are still structurally equivalent. - - return true; } }; /// Map to track value equivalence between two modules. -using ValueEquivalenceMap = DenseMap; +using ValueEquivalenceMap = llvm::DenseMap; /// Compare two operations for structural equivalence. /// Updates valueMap to track corresponding SSA values. @@ -199,7 +199,7 @@ bool hasOrderingConstraints(Operation* op) { // Check for memory effects that enforce ordering if (auto memInterface = dyn_cast(op)) { - SmallVector effects; + llvm::SmallVector effects; memInterface.getEffects(effects); bool hasNonAllocFreeEffects = false; @@ -222,14 +222,14 @@ bool hasOrderingConstraints(Operation* op) { /// Build a dependence graph for operations. /// Returns a map from each operation to the set of operations it depends on. -DenseMap> +llvm::DenseMap> buildDependenceGraph(ArrayRef ops) { - DenseMap> dependsOn; - DenseMap valueProducers; + llvm::DenseMap> dependsOn; + llvm::DenseMap valueProducers; // Build value-to-producer map and dependence relationships for (Operation* op : ops) { - dependsOn[op] = DenseSet(); + dependsOn[op] = llvm::DenseSet(); // This operation depends on the producers of its operands for (Value operand : op->getOperands()) { @@ -249,16 +249,16 @@ buildDependenceGraph(ArrayRef ops) { /// Partition operations into groups that can be compared as multisets. /// Operations in the same group are independent and can be reordered. -std::vector> +std::vector> partitionIndependentGroups(ArrayRef ops) { - std::vector> groups; + std::vector> groups; if (ops.empty()) { return groups; } auto dependsOn = buildDependenceGraph(ops); - DenseSet processed; - SmallVector currentGroup; + llvm::DenseSet processed; + llvm::SmallVector currentGroup; for (Operation* op : ops) { bool dependsOnCurrent = false; @@ -356,8 +356,8 @@ bool areBlocksEquivalent(Block& lhs, Block& rhs, } // Collect all operations - SmallVector lhsOps; - SmallVector rhsOps; + llvm::SmallVector lhsOps; + llvm::SmallVector rhsOps; for (Operation& op : lhs) { lhsOps.push_back(&op); @@ -394,7 +394,7 @@ bool areBlocksEquivalent(Block& lhs, Block& rhs, // by trying all permutations (for small groups) or use a greedy approach // Use a simple greedy matching - DenseSet matchedRhs; + llvm::DenseSet matchedRhs; for (Operation* lhsOp : lhsGroup) { bool matched = false; for (Operation* rhsOp : rhsGroup) { From 02307e921fcf96ba52175b9ae74cc056cf7641d7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:27:06 +0100 Subject: [PATCH 196/419] Fix more linter errors --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 4 +-- .../pipeline/test_compiler_pipeline.cpp | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 7e1d534ca6..c226d21091 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -32,6 +31,7 @@ #include #include #include +#include #include #include @@ -362,7 +362,7 @@ LogicalResult CtrlOp::verify() { << block.back().getNumOperands(); } - llvm::SmallPtrSet uniqueQubitsIn; + SmallPtrSet uniqueQubitsIn; for (const auto& control : getControlsIn()) { if (!uniqueQubitsIn.insert(control).second) { return emitOpError("duplicate control qubit found"); diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 35e872eec3..024289a5ea 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -18,7 +18,6 @@ #include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" #include "mlir/Support/PrettyPrinting.h" -#include #include #include #include @@ -44,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -185,7 +186,7 @@ bool areRegionsEquivalent(Region& lhs, Region& rhs, /// Check if an operation has memory effects or control flow side effects /// that would prevent reordering. -bool hasOrderingConstraints(Operation* op) { +const bool hasOrderingConstraints(Operation* op) { // Terminators must maintain their position if (op->hasTrait()) { return true; @@ -193,19 +194,19 @@ bool hasOrderingConstraints(Operation* op) { // Symbol-defining operations (like function declarations) can be reordered if (op->hasTrait() || - isa(op)) { + llvm::isa(op)) { return false; } // Check for memory effects that enforce ordering - if (auto memInterface = dyn_cast(op)) { + if (auto memInterface = llvm::dyn_cast(op)) { llvm::SmallVector effects; memInterface.getEffects(effects); bool hasNonAllocFreeEffects = false; for (const auto& effect : effects) { // Allow operations with no effects or pure allocation/free effects - if (!isa( + if (!llvm::isa( effect.getEffect())) { hasNonAllocFreeEffects = true; break; @@ -232,14 +233,14 @@ buildDependenceGraph(ArrayRef ops) { dependsOn[op] = llvm::DenseSet(); // This operation depends on the producers of its operands - for (Value operand : op->getOperands()) { + for (const auto operand : op->getOperands()) { if (auto it = valueProducers.find(operand); it != valueProducers.end()) { dependsOn[op].insert(it->second); } } // Register this operation as the producer of its results - for (Value result : op->getResults()) { + for (const Value result : op->getResults()) { valueProducers[result] = op; } } @@ -257,14 +258,14 @@ partitionIndependentGroups(ArrayRef ops) { } auto dependsOn = buildDependenceGraph(ops); - llvm::DenseSet processed; + const llvm::DenseSet processed; llvm::SmallVector currentGroup; - for (Operation* op : ops) { + for (auto* op : ops) { bool dependsOnCurrent = false; // Check if this operation depends on any operation in the current group - for (Operation* groupOp : currentGroup) { + for (const auto* groupOp : currentGroup) { if (dependsOn[op].contains(groupOp)) { dependsOnCurrent = true; break; @@ -279,7 +280,7 @@ partitionIndependentGroups(ArrayRef ops) { if (dependsOnCurrent || (hasConstraints && !currentGroup.empty())) { if (!currentGroup.empty()) { groups.push_back(std::move(currentGroup)); - currentGroup.clear(); + currentGroup = {}; } } @@ -288,7 +289,7 @@ partitionIndependentGroups(ArrayRef ops) { // If this operation has ordering constraints, finalize the group if (hasConstraints) { groups.push_back(std::move(currentGroup)); - currentGroup.clear(); + currentGroup = {}; } } @@ -315,11 +316,11 @@ bool areIndependentGroupsEquivalent(ArrayRef lhsOps, OperationStructuralEquality> rhsFrequencyMap; - for (Operation* op : lhsOps) { + for (auto* op : lhsOps) { lhsFrequencyMap[op]++; } - for (Operation* op : rhsOps) { + for (auto* op : rhsOps) { rhsFrequencyMap[op]++; } From f168c0559b284d733d46ec1da1a369ad102cb46d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:00:34 +0100 Subject: [PATCH 197/419] Support CX conversion to QIR --- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 + .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 85 +++++++++++++++---- .../pipeline/test_compiler_pipeline.cpp | 7 +- 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index fd72628aa4..3239e14862 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -26,6 +26,8 @@ static constexpr auto QIR_ARRAY_RECORD_OUTPUT = static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; static constexpr auto QIR_X = "__quantum__qis__x__body"; static constexpr auto QIR_CX = "__quantum__qis__cx__body"; +static constexpr auto QIR_CCX = "__quantum__qis__ccx__body"; +static constexpr auto QIR_CCCX = "__quantum__qis__cccx__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index c12c1cb8f4..ba9baba60c 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -76,6 +76,9 @@ struct LoweringState : QIRMetadata { /// Map from index to pointer value for reuse DenseMap ptrMap; + /// Map from Quartz qubit value to pointer value + DenseMap qubitMap; + /// Map from (register_name, register_index) to result pointer /// This allows caching result pointers for measurements with register info DenseMap, Value> registerResultMap; @@ -158,6 +161,7 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { auto& state = getState(); const auto numQubits = static_cast(state.numQubits); auto& ptrMap = state.ptrMap; + auto& qubitMap = state.qubitMap; auto& registerMap = state.registerStartIndexMap; // Get or create pointer value @@ -170,18 +174,19 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { if (const auto it = registerMap.find(registerName); it != registerMap.end()) { - // register is already tracked, the corresponding ptr was already - // created + // Register is already tracked + // The pointer was created by the step below const auto globalIndex = it->second + registerIndex; assert(ptrMap.contains(globalIndex)); + qubitMap[op.getResult()] = ptrMap.at(globalIndex); rewriter.replaceOp(op, ptrMap.at(globalIndex)); return success(); } // Allocate the entire register as static qubits registerMap[registerName] = numQubits; - SmallVector resultValues; - resultValues.reserve(registerSize); + SmallVector pointers; + pointers.reserve(registerSize); for (int64_t i = 0; i < registerSize; ++i) { Value val{}; if (const auto it = ptrMap.find(numQubits + i); it != ptrMap.end()) { @@ -190,12 +195,14 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { val = createPointerFromIndex(rewriter, op.getLoc(), numQubits + i); ptrMap[numQubits + i] = val; } - resultValues.push_back(val); + pointers.push_back(val); } + qubitMap[op.getResult()] = ptrMap.at(numQubits + registerIndex); + rewriter.replaceOp(op, pointers[registerIndex]); state.numQubits += registerSize; - rewriter.replaceOp(op, resultValues[registerIndex]); return success(); } + // no register info, check if ptr has already been allocated (as a Result) Value val{}; if (const auto it = ptrMap.find(numQubits); it != ptrMap.end()) { @@ -204,6 +211,7 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { val = createPointerFromIndex(rewriter, op.getLoc(), numQubits); ptrMap[numQubits] = val; } + qubitMap[op.getResult()] = val; rewriter.replaceOp(op, val); state.numQubits++; return success(); @@ -274,6 +282,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { val = createPointerFromIndex(rewriter, op.getLoc(), index); state.ptrMap.try_emplace(index, val); } + state.qubitMap[op.getResult()] = val; rewriter.replaceOp(op, val); // Track maximum qubit index @@ -340,9 +349,8 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { // Allocate the entire register as static results for (int64_t i = 0; i < registerSize; ++i) { Value val{}; - if (const auto it2 = ptrMap.find(numResults + i); - it2 != ptrMap.end()) { - val = it2->second; + if (const auto it = ptrMap.find(numResults + i); it != ptrMap.end()) { + val = it->second; } else { val = createPointerFromIndex(rewriter, op.getLoc(), numResults + i); ptrMap[numResults + i] = val; @@ -433,18 +441,61 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { matchAndRewrite(XOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); + auto& state = getState(); - // Create QIR function signature: (ptr) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); + // Determine whether the operation is controlled + CtrlOp posCtrlOp = nullptr; + if (op->getNextNode() && op->getNextNode()->getNextNode()) { + posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); + } + + // Define function name + StringRef functionName; + if (posCtrlOp) { + if (posCtrlOp.getNumPosControls() == 1) { + functionName = QIR_CX; + } else if (posCtrlOp.getNumPosControls() == 2) { + functionName = QIR_CCX; + } else if (posCtrlOp.getNumPosControls() == 3) { + functionName = QIR_CCCX; + } else { + return failure(); + } + } else { + functionName = QIR_X; + } + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 1); + // Add control pointers + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + } + } + // Add target pointer + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + + // Create function signature + const auto functionSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), argumentTypes); // Get or create function declaration - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_X, qirSignature); + const auto fnDecl = getOrCreateFunctionDeclaration( + rewriter, op, functionName, functionSignature); + + SmallVector operands; + operands.reserve(argumentTypes.size()); + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); + } + } + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - // Replace with call to X - rewriter.replaceOpWithNewOp(op, fnDecl, - adaptor.getOperands()); + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); } }; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 024289a5ea..b86c07488c 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1326,10 +1326,9 @@ TEST_F(CompilerPipelineTest, CX) { }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - /// TODO: Replace uncomment this when CX can be converted to QIR - // auto q1 = reg[1]; - // b.cx(q0, q1); - b.x(reg[1]); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); }); verifyAllStages({ From 2a7eb06f843bdc3bb064d7eb66664b21ecca0fbf Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:30:51 +0100 Subject: [PATCH 198/419] Streamline unitary tests --- .../pipeline/test_compiler_pipeline.cpp | 167 +++++++++--------- 1 file changed, 79 insertions(+), 88 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index b86c07488c..6b22609009 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1266,31 +1266,31 @@ TEST_F(CompilerPipelineTest, X) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; + const auto q = reg[0]; b.x(q); b.x(q); b.x(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - auto q1 = b.x(q0); - auto q2 = b.x(q1); + const auto q0 = reg[0]; + const auto q1 = b.x(q0); + const auto q2 = b.x(q1); b.x(q2); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; + const auto q0 = reg[0]; b.x(q0); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; + const auto q = reg[0]; b.x(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - auto q = reg[0]; + const auto q = reg[0]; b.x(q); }); @@ -1314,21 +1314,15 @@ TEST_F(CompilerPipelineTest, CX) { const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - const auto q0 = reg[0]; - const auto q1 = reg[1]; - b.cx(q0, q1); + b.cx(reg[0], reg[1]); }); const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - const auto q0 = reg[0]; - const auto q1 = reg[1]; - b.cx(q0, q1); + b.cx(reg[0], reg[1]); }); - const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - const auto q0 = reg[0]; - const auto q1 = reg[1]; - b.cx(q0, q1); + b.cx(reg[0], reg[1]); }); verifyAllStages({ @@ -1336,7 +1330,7 @@ TEST_F(CompilerPipelineTest, CX) { .fluxConversion = flux.get(), .optimization = flux.get(), .quartzConversion = quartz.get(), - .qirConversion = qirOpt.get(), + .qirConversion = qir.get(), }); } @@ -1367,13 +1361,21 @@ TEST_F(CompilerPipelineTest, CX3) { std::tie(q1, q0) = b.cx(q1, q0); std::tie(q0, q1) = b.cx(q0, q1); }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.cx(q0, q1); + b.cx(q1, q0); + b.cx(q0, q1); + }); verifyAllStages({ .quartzImport = quartz.get(), .fluxConversion = flux.get(), .optimization = flux.get(), .quartzConversion = quartz.get(), - .qirConversion = nullptr, + .qirConversion = qir.get(), }); } @@ -1389,29 +1391,27 @@ TEST_F(CompilerPipelineTest, RX) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; + const auto q = reg[0]; b.rx(1.0, q); b.rx(0.5, q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - auto q1 = b.rx(1.0, q0); - b.rx(0.5, q1); + auto q = reg[0]; + q = b.rx(1.0, q); + b.rx(0.5, q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q0 = reg[0]; - b.rx(1.5, q0); + b.rx(1.5, reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; - b.rx(1.5, q); + b.rx(1.5, reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.rx(1.5, q[0]); + auto reg = b.allocQubitRegister(1); + b.rx(1.5, reg[0]); }); verifyAllStages({ @@ -1434,22 +1434,15 @@ TEST_F(CompilerPipelineTest, CRX) { const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - const auto q0 = reg[0]; - const auto q1 = reg[1]; - b.crx(1.0, q0, q1); + b.crx(1.0, reg[0], reg[1]); }); const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - const auto q0 = reg[0]; - const auto q1 = reg[1]; - b.crx(1.0, q0, q1); + b.crx(1.0, reg[0], reg[1]); }); - - // const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - // auto reg = b.allocQubitRegister(2); - // const auto q0 = reg[0]; - // const auto q1 = reg[1]; - // b.rx(1.0, q0); + // const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + // auto reg = b.allocQubitRegister(2); + // b.crx(1.0, reg[0], reg[1]); // }); verifyAllStages({ @@ -1470,26 +1463,26 @@ TEST_F(CompilerPipelineTest, U2) { ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.u2(1.0, 0.5, q[0]); - }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto q = b.allocQubitRegister(1, "q"); - b.u2(1.0, 0.5, q[0]); + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, reg[0]); }); - const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.u2(1.0, 0.5, q[0]); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.u2(1.0, 0.5, q); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = quartzExpected.get(), - .qirConversion = qirExpected.get(), + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), }); } @@ -1502,26 +1495,25 @@ TEST_F(CompilerPipelineTest, SWAP) { ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.allocQubitRegister(2, "q"); - b.swap(q[0], q[1]); - }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto q = b.allocQubitRegister(2, "q"); - b.swap(q[0], q[1]); + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.swap(reg[0], reg[1]); }); - const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { - auto q = b.allocQubitRegister(2); - b.swap(q[0], q[1]); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.swap(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.swap(reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = quartzExpected.get(), - .qirConversion = qirExpected.get(), + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), }); } @@ -1534,25 +1526,24 @@ TEST_F(CompilerPipelineTest, CSWAP) { ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto q = b.allocQubitRegister(3, "q"); - b.cswap(q[0], q[1], q[2]); - }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto q = b.allocQubitRegister(3, "q"); - b.cswap(q[0], q[1], q[2]); + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cswap(reg[0], reg[1], reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cswap(reg[0], reg[1], reg[2]); }); - // const auto qirExpected = buildQIR([](qir::QIRProgramBuilder& b) { - // auto q = b.allocQubitRegister(3); - // b.swap(q[0], q[1]); + // const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + // auto reg = b.allocQubitRegister(3); + // b.cswap(reg[0], reg[1], reg[2]); // }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = quartzExpected.get(), + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), .qirConversion = nullptr, }); } From be7f87b442293f9fb297871f03897ebdd78af3ac Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:54:54 +0100 Subject: [PATCH 199/419] Also support CRX, CU2, and CWAP conversions to QIR --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 79 +++++- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 9 + .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 241 ++++++++++++++---- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 119 ++++++++- .../pipeline/test_compiler_pipeline.cpp | 51 +++- 5 files changed, 424 insertions(+), 75 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 7a65c47ff9..d6acb70eba 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -271,7 +271,7 @@ class QIRProgramBuilder { * * @par Example: * ```c++ - * builder.x(q); + * builder.x(qubit); * ``` * ```mlir * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () @@ -291,8 +291,7 @@ class QIRProgramBuilder { * builder.cx(control, target); * ``` * ```mlir - * llvm.call @__quantum__qis__cx__body(%control, %target) : (!llvm.ptr, - * !llvm.ptr) -> () + * llvm.call @__quantum__qis__cx__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () * ``` */ QIRProgramBuilder& cx(Value control, Value target); @@ -306,14 +305,34 @@ class QIRProgramBuilder { * * @par Example: * ```c++ - * builder.rx(1.0, q); + * builder.rx(theta, qubit); * ``` * ```mlir - * llvm.call @__quantum__qis__rx__body(%q, %c) : (!llvm.ptr, f64) -> () + * llvm.call @__quantum__qis__rx__body(%q, %theta) : (!llvm.ptr, f64) -> () * ``` */ QIRProgramBuilder& rx(const std::variant& theta, Value qubit); + /** + * @brief Apply the CRX + * + * @param theta Rotation angle + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.crx(theta, control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__crx__body(%c, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& crx(const std::variant& theta, + Value control, Value target); + /** * @brief Apply the U2 gate to a qubit * @@ -324,16 +343,38 @@ class QIRProgramBuilder { * * @par Example: * ```c++ - * builder.u2(1.0, 0.5, q); + * builder.u2(phi, lambda, qubit); * ``` * ```mlir - * llvm.call @__quantum__qis__u2__body(%q, %c1, %c2) : (!llvm.ptr, f64, f64) - * -> () + * llvm.call @__quantum__qis__u2__body(%q, %phi, %lambda) : (!llvm.ptr, f64, + * f64) -> () * ``` */ QIRProgramBuilder& u2(const std::variant& phi, const std::variant& lambda, Value qubit); + /** + * @brief Apply the CU2 gate + * + * @param phi Rotation angle + * @param lambda Rotation angle + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cu2(phi, lambda, control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cu2__body(%c, %t, %phi, %lambda) : (!llvm.ptr, + * !llvm.ptr, f64, f64) -> () + * ``` + */ + QIRProgramBuilder& cu2(const std::variant& phi, + const std::variant& lambda, + Value control, Value target); + /** * @brief Apply the SWAP gate to two qubits * @@ -343,7 +384,7 @@ class QIRProgramBuilder { * * @par Example: * ```c++ - * builder.swap(q0, q1); + * builder.swap(qubit0, qubit1); * ``` * ```mlir * llvm.call @__quantum__qis__swap__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) -> @@ -352,6 +393,26 @@ class QIRProgramBuilder { */ QIRProgramBuilder& swap(Value qubit0, Value qubit1); + /** + * @brief Apply the CSWAP gate + * + * @param control Control qubit + * @param target1 Target qubit + * @param target2 Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cswap(control, target1, target2); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cswap__body(%c, %t0, %t1) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr) -> + * () + * ``` + */ + QIRProgramBuilder& cswap(Value control, Value target0, Value target1); + //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 3239e14862..785af0f559 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -29,8 +29,17 @@ static constexpr auto QIR_CX = "__quantum__qis__cx__body"; static constexpr auto QIR_CCX = "__quantum__qis__ccx__body"; static constexpr auto QIR_CCCX = "__quantum__qis__cccx__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; +static constexpr auto QIR_CRX = "__quantum__qis__crx__body"; +static constexpr auto QIR_CCRX = "__quantum__qis__ccrx__body"; +static constexpr auto QIR_CCCRX = "__quantum__qis__cccrx__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; +static constexpr auto QIR_CU2 = "__quantum__qis__cu2__body"; +static constexpr auto QIR_CCU2 = "__quantum__qis__ccu2__body"; +static constexpr auto QIR_CCCU2 = "__quantum__qis__cccu2__body"; static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; +static constexpr auto QIR_CSWAP = "__quantum__qis__cswap__body"; +static constexpr auto QIR_CCSWAP = "__quantum__qis__ccswap__body"; +static constexpr auto QIR_CCCSWAP = "__quantum__qis__cccswap__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index ba9baba60c..b95e1c4d82 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -518,19 +518,63 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { matchAndRewrite(RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); + auto& state = getState(); - // Create QIR function signature: (!llvm.ptr, f64) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), - {LLVM::LLVMPointerType::get(ctx), Float64Type::get(ctx)}); + // Determine whether the operation is controlled + CtrlOp posCtrlOp = nullptr; + if (op->getNextNode() && op->getNextNode()->getNextNode()) { + posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); + } + + // Define function name + StringRef functionName; + if (posCtrlOp) { + if (posCtrlOp.getNumPosControls() == 1) { + functionName = QIR_CRX; + } else if (posCtrlOp.getNumPosControls() == 2) { + functionName = QIR_CCRX; + } else if (posCtrlOp.getNumPosControls() == 3) { + functionName = QIR_CCCRX; + } else { + return failure(); + } + } else { + functionName = QIR_RX; + } + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 2); + // Add control pointers + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + } + } + // Add target pointer + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + // Add theta + argumentTypes.push_back(Float64Type::get(ctx)); + + // Create function signature + const auto functionSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), argumentTypes); // Get or create function declaration - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_RX, qirSignature); + const auto fnDecl = getOrCreateFunctionDeclaration( + rewriter, op, functionName, functionSignature); - // Replace with call to RX - rewriter.replaceOpWithNewOp( - op, fnDecl, ValueRange{adaptor.getQubitIn(), op.getTheta()}); + SmallVector operands; + operands.reserve(argumentTypes.size()); + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); + } + } + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); } }; @@ -554,49 +598,65 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { matchAndRewrite(U2Op op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); + auto& state = getState(); - // Create QIR function signature: (!llvm.ptr, f64, f64) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), - {LLVM::LLVMPointerType::get(ctx), Float64Type::get(ctx), - Float64Type::get(ctx)}); + // Determine whether the operation is controlled + CtrlOp posCtrlOp = nullptr; + if (op->getNextNode() && op->getNextNode()->getNextNode()) { + posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); + } - // Get or create function declaration - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_U2, qirSignature); + // Define function name + StringRef functionName; + if (posCtrlOp) { + if (posCtrlOp.getNumPosControls() == 1) { + functionName = QIR_CU2; + } else if (posCtrlOp.getNumPosControls() == 2) { + functionName = QIR_CCU2; + } else if (posCtrlOp.getNumPosControls() == 3) { + functionName = QIR_CCCU2; + } else { + return failure(); + } + } else { + functionName = QIR_U2; + } - // Replace with call to U2 - rewriter.replaceOpWithNewOp( - op, fnDecl, - ValueRange{adaptor.getQubitIn(), op.getPhi(), op.getLambda()}); - return success(); - } -}; + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 3); + // Add control pointers + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + } + } + // Add target pointer + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + // Add phi + argumentTypes.push_back(Float64Type::get(ctx)); + // Add lambda + argumentTypes.push_back(Float64Type::get(ctx)); -/// TODO: After inlining, replace function call with controlled call -struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; + // Create function signature + const auto functionSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), argumentTypes); - LogicalResult - matchAndRewrite(CtrlOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), - op->getIterator()); - rewriter.eraseOp(op); - return success(); - } -}; + // Get or create function declaration + const auto fnDecl = getOrCreateFunctionDeclaration( + rewriter, op, functionName, functionSignature); -/** - * @brief Erases quartz.yield operation - */ -struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; + SmallVector operands; + operands.reserve(argumentTypes.size()); + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); + } + } + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - LogicalResult - matchAndRewrite(YieldOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - rewriter.eraseOp(op); + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); } }; @@ -620,19 +680,92 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { matchAndRewrite(SWAPOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); + auto& state = getState(); - // Create QIR function signature: (ptr, ptr) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), - {LLVM::LLVMPointerType::get(ctx), LLVM::LLVMPointerType::get(ctx)}); + // Determine whether the operation is controlled + CtrlOp posCtrlOp = nullptr; + if (op->getNextNode() && op->getNextNode()->getNextNode()) { + posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); + } + + // Define function name + StringRef functionName; + if (posCtrlOp) { + if (posCtrlOp.getNumPosControls() == 1) { + functionName = QIR_CSWAP; + } else if (posCtrlOp.getNumPosControls() == 2) { + functionName = QIR_CCSWAP; + } else if (posCtrlOp.getNumPosControls() == 3) { + functionName = QIR_CCCSWAP; + } else { + return failure(); + } + } else { + functionName = QIR_SWAP; + } + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 2); + // Add control pointers + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + } + } + // Add target pointers + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + + // Create function signature + const auto functionSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), argumentTypes); // Get or create function declaration - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_SWAP, qirSignature); + const auto fnDecl = getOrCreateFunctionDeclaration( + rewriter, op, functionName, functionSignature); - // Replace with call to SWAP - rewriter.replaceOpWithNewOp(op, fnDecl, - adaptor.getOperands()); + SmallVector operands; + operands.reserve(argumentTypes.size()); + if (posCtrlOp) { + for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { + operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); + } + } + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); + } +}; + +/** + * @brief Inlines quartz.ctrl region removes the operation + */ +struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(CtrlOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), + op->getIterator()); + rewriter.eraseOp(op); + return success(); + } +}; + +/** + * @brief Erases quartz.yield operation + */ +struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(YieldOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + rewriter.eraseOp(op); return success(); } }; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 8e222bc0df..12c55117d9 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -254,7 +254,7 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { } QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, - const Value qubit) { + const Value target) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -268,7 +268,7 @@ QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, LLVM::LLVMPointerType::get(builder.getContext())}); auto fnDecl = getOrCreateFunctionDeclaration(builder, module, QIR_CX, qirSignature); - builder.create(loc, fnDecl, ValueRange{control, qubit}); + builder.create(loc, fnDecl, ValueRange{control, target}); return *this; } @@ -311,6 +311,46 @@ QIRProgramBuilder::rx(const std::variant& theta, return *this; } +QIRProgramBuilder& +QIRProgramBuilder::crx(const std::variant& theta, + const Value control, const Value target) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value thetaOperand; + if (std::holds_alternative(theta)) { + thetaOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(theta))) + .getResult(); + } else { + thetaOperand = std::get(theta); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create crx call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext()), + Float64Type::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_CRX, qirSignature); + builder.create(loc, fnDecl, + ValueRange{control, target, thetaOperand}); + + return *this; +} + QIRProgramBuilder& QIRProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, @@ -362,6 +402,58 @@ QIRProgramBuilder::u2(const std::variant& phi, return *this; } +QIRProgramBuilder& +QIRProgramBuilder::cu2(const std::variant& phi, + const std::variant& lambda, + const Value control, const Value target) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value phiOperand; + if (std::holds_alternative(phi)) { + phiOperand = builder + .create( + loc, builder.getF64FloatAttr(std::get(phi))) + .getResult(); + } else { + phiOperand = std::get(phi); + } + + Value lambdaOperand; + if (std::holds_alternative(lambda)) { + lambdaOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(lambda))) + .getResult(); + } else { + lambdaOperand = std::get(lambda); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create cu2 call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext()), + Float64Type::get(builder.getContext()), + Float64Type::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_CU2, qirSignature); + builder.create( + loc, fnDecl, ValueRange{control, target, phiOperand, lambdaOperand}); + + return *this; +} + QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, const Value qubit1) { // Save current insertion point @@ -382,6 +474,29 @@ QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, return *this; } +QIRProgramBuilder& QIRProgramBuilder::cswap(const Value control, + const Value target0, + const Value target1) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create cswap call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_CSWAP, qirSignature); + builder.create(loc, fnDecl, + ValueRange{control, target0, target1}); + + return *this; +} + //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 6b22609009..c3e3629699 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1440,17 +1440,17 @@ TEST_F(CompilerPipelineTest, CRX) { auto reg = b.allocQubitRegister(2, "q"); b.crx(1.0, reg[0], reg[1]); }); - // const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { - // auto reg = b.allocQubitRegister(2); - // b.crx(1.0, reg[0], reg[1]); - // }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.crx(1.0, reg[0], reg[1]); + }); verifyAllStages({ .quartzImport = quartz.get(), .fluxConversion = flux.get(), .optimization = flux.get(), .quartzConversion = quartz.get(), - .qirConversion = nullptr, + .qirConversion = qir.get(), }); } @@ -1486,6 +1486,37 @@ TEST_F(CompilerPipelineTest, U2) { }); } +TEST_F(CompilerPipelineTest, CU2) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.cu2(1.0, 0.5, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cu2(1.0, 0.5, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cu2(1.0, 0.5, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cu2(1.0, 0.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, SWAP) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); @@ -1534,17 +1565,17 @@ TEST_F(CompilerPipelineTest, CSWAP) { auto reg = b.allocQubitRegister(3, "q"); b.cswap(reg[0], reg[1], reg[2]); }); - // const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { - // auto reg = b.allocQubitRegister(3); - // b.cswap(reg[0], reg[1], reg[2]); - // }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.cswap(reg[0], reg[1], reg[2]); + }); verifyAllStages({ .quartzImport = quartz.get(), .fluxConversion = flux.get(), .optimization = flux.get(), .quartzConversion = quartz.get(), - .qirConversion = nullptr, + .qirConversion = qir.get(), }); } From 0f700c370020b803064091845f41dcfc724a9812 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:25:52 +0100 Subject: [PATCH 200/419] Fix linter errors --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 14 ++++++++++---- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index b95e1c4d82..919bcf9b03 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -14,11 +14,13 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include +#include #include #include #include #include #include +#include #include #include #include @@ -445,7 +447,8 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { // Determine whether the operation is controlled CtrlOp posCtrlOp = nullptr; - if (op->getNextNode() && op->getNextNode()->getNextNode()) { + if ((op->getNextNode() != nullptr) && + (op->getNextNode()->getNextNode() != nullptr)) { posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); } @@ -522,7 +525,8 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { // Determine whether the operation is controlled CtrlOp posCtrlOp = nullptr; - if (op->getNextNode() && op->getNextNode()->getNextNode()) { + if ((op->getNextNode() != nullptr) && + (op->getNextNode()->getNextNode() != nullptr)) { posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); } @@ -602,7 +606,8 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { // Determine whether the operation is controlled CtrlOp posCtrlOp = nullptr; - if (op->getNextNode() && op->getNextNode()->getNextNode()) { + if ((op->getNextNode() != nullptr) && + (op->getNextNode()->getNextNode() != nullptr)) { posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); } @@ -684,7 +689,8 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { // Determine whether the operation is controlled CtrlOp posCtrlOp = nullptr; - if (op->getNextNode() && op->getNextNode()->getNextNode()) { + if ((op->getNextNode() != nullptr) && + (op->getNextNode()->getNextNode() != nullptr)) { posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index c3e3629699..012fd21a93 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -186,7 +187,7 @@ bool areRegionsEquivalent(Region& lhs, Region& rhs, /// Check if an operation has memory effects or control flow side effects /// that would prevent reordering. -const bool hasOrderingConstraints(Operation* op) { +bool hasOrderingConstraints(Operation* op) { // Terminators must maintain their position if (op->hasTrait()) { return true; @@ -273,7 +274,7 @@ partitionIndependentGroups(ArrayRef ops) { } // Check if this operation has ordering constraints - bool hasConstraints = hasOrderingConstraints(op); + const auto hasConstraints = hasOrderingConstraints(op); // If it depends on current group or has ordering constraints, // finalize the current group and start a new one From 077360fed829840a0ea09a895c800cfbb8f878c8 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:28:46 +0100 Subject: [PATCH 201/419] Fix linter error about nondeterministic order --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 012fd21a93..14f4396f77 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -330,7 +330,13 @@ bool areIndependentGroupsEquivalent(ArrayRef lhsOps, return false; } - for (const auto& [lhsOp, lhsCount] : lhsFrequencyMap) { + std::vector keys; + keys.reserve(lhsFrequencyMap.size()); + for (const auto& pair : lhsFrequencyMap) { + keys.push_back(pair.first); + } + for (auto* lhsOp : keys) { + const auto lhsCount = lhsFrequencyMap.find(lhsOp)->second; auto it = rhsFrequencyMap.find(lhsOp); if (it == rhsFrequencyMap.end() || it->second != lhsCount) { return false; From a4b907532c1670d55e65a7c5e261222a1f0f0ca4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:50:15 +0100 Subject: [PATCH 202/419] Fix QIR of conversion of quartz.measure --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 919bcf9b03..57c7b9e76f 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -374,14 +374,16 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { state.numResults++; } - // Create mz (measure) call: mz(qubit, result) - const auto mzSignature = LLVM::LLVMFunctionType::get( + // Declare QIR function + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), {ptrType, ptrType}); - const auto mzDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_MEASURE, mzSignature); - rewriter.replaceOpWithNewOp( - op, mzDecl, ValueRange{adaptor.getQubit(), resultValue}); + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, QIR_MEASURE, fnSignature); + // Create CallOp and replace quartz.measure with result pointer + rewriter.create(op.getLoc(), fnDecl, + ValueRange{adaptor.getQubit(), resultValue}); + rewriter.replaceOp(op, resultValue); return success(); } }; From a30417b639a842efac3cf29ed13686d366bca39e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:50:38 +0100 Subject: [PATCH 203/419] Streamline QIR conversions --- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 109 +++++++++--------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 57c7b9e76f..4d21758282 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -412,15 +412,13 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto* ctx = getContext(); - // Create QIR function signature: (ptr) -> void - const auto qirSignature = LLVM::LLVMFunctionType::get( + // Declare QIR function + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); - - // Get or create function declaration const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, QIR_RESET, qirSignature); + getOrCreateFunctionDeclaration(rewriter, op, QIR_RESET, fnSignature); - // Replace with call to reset + // Replace operation with CallOp rewriter.replaceOpWithNewOp(op, fnDecl, adaptor.getOperands()); return success(); @@ -455,40 +453,39 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { } // Define function name - StringRef functionName; + StringRef fnName; if (posCtrlOp) { if (posCtrlOp.getNumPosControls() == 1) { - functionName = QIR_CX; + fnName = QIR_CX; } else if (posCtrlOp.getNumPosControls() == 2) { - functionName = QIR_CCX; + fnName = QIR_CCX; } else if (posCtrlOp.getNumPosControls() == 3) { - functionName = QIR_CCCX; + fnName = QIR_CCCX; } else { return failure(); } } else { - functionName = QIR_X; + fnName = QIR_X; } // Define function argument types SmallVector argumentTypes; argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 1); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers if (posCtrlOp) { for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); } } // Add target pointer - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); - - // Create function signature - const auto functionSignature = LLVM::LLVMFunctionType::get( + argumentTypes.push_back(ptrType); + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), argumentTypes); - // Get or create function declaration - const auto fnDecl = getOrCreateFunctionDeclaration( - rewriter, op, functionName, functionSignature); + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; operands.reserve(argumentTypes.size()); @@ -533,42 +530,42 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { } // Define function name - StringRef functionName; + StringRef fnName; if (posCtrlOp) { if (posCtrlOp.getNumPosControls() == 1) { - functionName = QIR_CRX; + fnName = QIR_CRX; } else if (posCtrlOp.getNumPosControls() == 2) { - functionName = QIR_CCRX; + fnName = QIR_CCRX; } else if (posCtrlOp.getNumPosControls() == 3) { - functionName = QIR_CCCRX; + fnName = QIR_CCCRX; } else { return failure(); } } else { - functionName = QIR_RX; + fnName = QIR_RX; } // Define function argument types SmallVector argumentTypes; argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 2); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers if (posCtrlOp) { for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); } } // Add target pointer - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); // Add theta argumentTypes.push_back(Float64Type::get(ctx)); - // Create function signature - const auto functionSignature = LLVM::LLVMFunctionType::get( + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), argumentTypes); - // Get or create function declaration - const auto fnDecl = getOrCreateFunctionDeclaration( - rewriter, op, functionName, functionSignature); + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; operands.reserve(argumentTypes.size()); @@ -614,44 +611,44 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { } // Define function name - StringRef functionName; + StringRef fnName; if (posCtrlOp) { if (posCtrlOp.getNumPosControls() == 1) { - functionName = QIR_CU2; + fnName = QIR_CU2; } else if (posCtrlOp.getNumPosControls() == 2) { - functionName = QIR_CCU2; + fnName = QIR_CCU2; } else if (posCtrlOp.getNumPosControls() == 3) { - functionName = QIR_CCCU2; + fnName = QIR_CCCU2; } else { return failure(); } } else { - functionName = QIR_U2; + fnName = QIR_U2; } // Define function argument types SmallVector argumentTypes; argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 3); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers if (posCtrlOp) { for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); } } // Add target pointer - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); // Add phi argumentTypes.push_back(Float64Type::get(ctx)); // Add lambda argumentTypes.push_back(Float64Type::get(ctx)); - // Create function signature - const auto functionSignature = LLVM::LLVMFunctionType::get( + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), argumentTypes); - // Get or create function declaration - const auto fnDecl = getOrCreateFunctionDeclaration( - rewriter, op, functionName, functionSignature); + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; operands.reserve(argumentTypes.size()); @@ -697,41 +694,41 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { } // Define function name - StringRef functionName; + StringRef fnName; if (posCtrlOp) { if (posCtrlOp.getNumPosControls() == 1) { - functionName = QIR_CSWAP; + fnName = QIR_CSWAP; } else if (posCtrlOp.getNumPosControls() == 2) { - functionName = QIR_CCSWAP; + fnName = QIR_CCSWAP; } else if (posCtrlOp.getNumPosControls() == 3) { - functionName = QIR_CCCSWAP; + fnName = QIR_CCCSWAP; } else { return failure(); } } else { - functionName = QIR_SWAP; + fnName = QIR_SWAP; } // Define function argument types SmallVector argumentTypes; argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 2); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers if (posCtrlOp) { for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); } } // Add target pointers - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); - argumentTypes.push_back(LLVM::LLVMPointerType::get(ctx)); + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); - // Create function signature - const auto functionSignature = LLVM::LLVMFunctionType::get( + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), argumentTypes); - // Get or create function declaration - const auto fnDecl = getOrCreateFunctionDeclaration( - rewriter, op, functionName, functionSignature); + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; operands.reserve(argumentTypes.size()); From 8d88a482dfd4b7e563bd96f63011d7742cf07c22 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:00:50 +0100 Subject: [PATCH 204/419] Revert "Fix linter error about nondeterministic order" This reverts commit 077360fed829840a0ea09a895c800cfbb8f878c8. --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 14f4396f77..012fd21a93 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -330,13 +330,7 @@ bool areIndependentGroupsEquivalent(ArrayRef lhsOps, return false; } - std::vector keys; - keys.reserve(lhsFrequencyMap.size()); - for (const auto& pair : lhsFrequencyMap) { - keys.push_back(pair.first); - } - for (auto* lhsOp : keys) { - const auto lhsCount = lhsFrequencyMap.find(lhsOp)->second; + for (const auto& [lhsOp, lhsCount] : lhsFrequencyMap) { auto it = rhsFrequencyMap.find(lhsOp); if (it == rhsFrequencyMap.end() || it->second != lhsCount) { return false; From c09b40f5d7792449c5156a3b9ef5e2fbf20c6a3e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:05:19 +0100 Subject: [PATCH 205/419] Include correct header --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 012fd21a93..1e8249c3e3 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -26,8 +26,8 @@ #include #include #include +#include #include -#include #include #include #include From f15c9a277e09e7af27c2b68d264051b8aae95cc6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:36:35 +0100 Subject: [PATCH 206/419] Use QIR conversion state for converting controlled operations --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 6 +- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 2 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 181 +++++++++--------- 3 files changed, 92 insertions(+), 97 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 2c183fbc5d..11c55d4d73 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -390,9 +390,9 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { // Create quartz.ctrl operation auto fluxOp = rewriter.create(op.getLoc(), quartzControls); - // Clone the body region from Flux to Quartz - rewriter.cloneRegionBefore(op.getBody(), fluxOp.getBody(), - fluxOp.getBody().end()); + // Clone body region from Flux to Quartz + auto& dstRegion = fluxOp.getBody(); + rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); // Replace the output qubits with the same quartz references rewriter.replaceOp(op, adaptor.getOperands()); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index e7199281a3..a676d2e32a 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -598,7 +598,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { qubitMap[quartzTarget] = targetsOut[i]; } - // clone region + // Clone body region from Quartz to Flux auto& dstRegion = fluxOp.getBody(); rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end(), regionMap); diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 4d21758282..060adc7eea 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -78,12 +78,13 @@ struct LoweringState : QIRMetadata { /// Map from index to pointer value for reuse DenseMap ptrMap; - /// Map from Quartz qubit value to pointer value - DenseMap qubitMap; - /// Map from (register_name, register_index) to result pointer /// This allows caching result pointers for measurements with register info DenseMap, Value> registerResultMap; + + /// Test + bool inCtrlOp = false; + SmallVector posCtrls; }; /** @@ -163,7 +164,6 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { auto& state = getState(); const auto numQubits = static_cast(state.numQubits); auto& ptrMap = state.ptrMap; - auto& qubitMap = state.qubitMap; auto& registerMap = state.registerStartIndexMap; // Get or create pointer value @@ -180,7 +180,6 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { // The pointer was created by the step below const auto globalIndex = it->second + registerIndex; assert(ptrMap.contains(globalIndex)); - qubitMap[op.getResult()] = ptrMap.at(globalIndex); rewriter.replaceOp(op, ptrMap.at(globalIndex)); return success(); } @@ -199,7 +198,6 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { } pointers.push_back(val); } - qubitMap[op.getResult()] = ptrMap.at(numQubits + registerIndex); rewriter.replaceOp(op, pointers[registerIndex]); state.numQubits += registerSize; return success(); @@ -213,7 +211,6 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { val = createPointerFromIndex(rewriter, op.getLoc(), numQubits); ptrMap[numQubits] = val; } - qubitMap[op.getResult()] = val; rewriter.replaceOp(op, val); state.numQubits++; return success(); @@ -284,7 +281,6 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { val = createPointerFromIndex(rewriter, op.getLoc(), index); state.ptrMap.try_emplace(index, val); } - state.qubitMap[op.getResult()] = val; rewriter.replaceOp(op, val); // Track maximum qubit index @@ -445,21 +441,19 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { auto* ctx = getContext(); auto& state = getState(); - // Determine whether the operation is controlled - CtrlOp posCtrlOp = nullptr; - if ((op->getNextNode() != nullptr) && - (op->getNextNode()->getNextNode() != nullptr)) { - posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); - } + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (posCtrlOp) { - if (posCtrlOp.getNumPosControls() == 1) { + if (inCtrlOp) { + if (numCtrls == 1) { fnName = QIR_CX; - } else if (posCtrlOp.getNumPosControls() == 2) { + } else if (numCtrls == 2) { fnName = QIR_CCX; - } else if (posCtrlOp.getNumPosControls() == 3) { + } else if (numCtrls == 3) { fnName = QIR_CCCX; } else { return failure(); @@ -470,13 +464,11 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { // Define function argument types SmallVector argumentTypes; - argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 1); + argumentTypes.reserve(numCtrls + 1); const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(ptrType); - } + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); } // Add target pointer argumentTypes.push_back(ptrType); @@ -488,14 +480,16 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; - operands.reserve(argumentTypes.size()); - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); - } - } + operands.reserve(numCtrls + 2); + operands.append(posCtrls.begin(), posCtrls.end()); operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + // Replace operation with CallOp rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); @@ -522,21 +516,19 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { auto* ctx = getContext(); auto& state = getState(); - // Determine whether the operation is controlled - CtrlOp posCtrlOp = nullptr; - if ((op->getNextNode() != nullptr) && - (op->getNextNode()->getNextNode() != nullptr)) { - posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); - } + // Query state for modifier information + const bool inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (posCtrlOp) { - if (posCtrlOp.getNumPosControls() == 1) { + if (inCtrlOp) { + if (numCtrls == 1) { fnName = QIR_CRX; - } else if (posCtrlOp.getNumPosControls() == 2) { + } else if (numCtrls == 2) { fnName = QIR_CCRX; - } else if (posCtrlOp.getNumPosControls() == 3) { + } else if (numCtrls == 3) { fnName = QIR_CCCRX; } else { return failure(); @@ -547,13 +539,11 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { // Define function argument types SmallVector argumentTypes; - argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 2); + argumentTypes.reserve(numCtrls + 2); const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(ptrType); - } + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); } // Add target pointer argumentTypes.push_back(ptrType); @@ -568,14 +558,16 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; - operands.reserve(argumentTypes.size()); - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); - } - } + operands.reserve(numCtrls + 2); + operands.append(posCtrls.begin(), posCtrls.end()); operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + // Replace operation with CallOp rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); @@ -603,21 +595,19 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { auto* ctx = getContext(); auto& state = getState(); - // Determine whether the operation is controlled - CtrlOp posCtrlOp = nullptr; - if ((op->getNextNode() != nullptr) && - (op->getNextNode()->getNextNode() != nullptr)) { - posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); - } + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (posCtrlOp) { - if (posCtrlOp.getNumPosControls() == 1) { + if (inCtrlOp) { + if (numCtrls == 1) { fnName = QIR_CU2; - } else if (posCtrlOp.getNumPosControls() == 2) { + } else if (numCtrls == 2) { fnName = QIR_CCU2; - } else if (posCtrlOp.getNumPosControls() == 3) { + } else if (numCtrls == 3) { fnName = QIR_CCCU2; } else { return failure(); @@ -628,13 +618,11 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { // Define function argument types SmallVector argumentTypes; - argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 3); + argumentTypes.reserve(numCtrls + 3); const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(ptrType); - } + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); } // Add target pointer argumentTypes.push_back(ptrType); @@ -651,14 +639,16 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; - operands.reserve(argumentTypes.size()); - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); - } - } + operands.reserve(numCtrls + 3); + operands.append(posCtrls.begin(), posCtrls.end()); operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + // Replace operation with CallOp rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); @@ -686,21 +676,19 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { auto* ctx = getContext(); auto& state = getState(); - // Determine whether the operation is controlled - CtrlOp posCtrlOp = nullptr; - if ((op->getNextNode() != nullptr) && - (op->getNextNode()->getNextNode() != nullptr)) { - posCtrlOp = llvm::dyn_cast(op->getNextNode()->getNextNode()); - } + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (posCtrlOp) { - if (posCtrlOp.getNumPosControls() == 1) { + if (inCtrlOp) { + if (numCtrls == 1) { fnName = QIR_CSWAP; - } else if (posCtrlOp.getNumPosControls() == 2) { + } else if (numCtrls == 2) { fnName = QIR_CCSWAP; - } else if (posCtrlOp.getNumPosControls() == 3) { + } else if (numCtrls == 3) { fnName = QIR_CCCSWAP; } else { return failure(); @@ -711,13 +699,11 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { // Define function argument types SmallVector argumentTypes; - argumentTypes.reserve((posCtrlOp ? posCtrlOp.getNumPosControls() : 0) + 2); + argumentTypes.reserve(numCtrls + 2); const auto ptrType = LLVM::LLVMPointerType::get(ctx); // Add control pointers - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - argumentTypes.push_back(ptrType); - } + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); } // Add target pointers argumentTypes.push_back(ptrType); @@ -731,14 +717,16 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; - operands.reserve(argumentTypes.size()); - if (posCtrlOp) { - for (size_t i = 0; i < posCtrlOp.getNumPosControls(); ++i) { - operands.push_back(state.qubitMap.at(posCtrlOp.getPosControl(i))); - } - } + operands.reserve(numCtrls + 2); + operands.append(posCtrls.begin(), posCtrls.end()); operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + // Replace operation with CallOp rewriter.replaceOpWithNewOp(op, fnDecl, operands); return success(); @@ -752,8 +740,15 @@ struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(CtrlOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(CtrlOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { + // Update modifier information + auto& state = getState(); + state.inCtrlOp = true; + state.posCtrls.append(adaptor.getControls().begin(), + adaptor.getControls().end()); + + // Inline region and remove operation rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), op->getIterator()); rewriter.eraseOp(op); From 5aebf4a4582adf96b0deb42decf46241b73bc5d7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:44:42 +0100 Subject: [PATCH 207/419] Fix static matrices and add initial support for controlled ones --- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 2 +- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 +- .../Dialect/Quartz/IR/QuartzInterfaces.td | 2 +- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 83 +++++++++++++++---- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 24 ++---- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 24 ++---- 7 files changed, 80 insertions(+), 59 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 16611b0bc0..9323746f90 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -128,7 +128,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { InterfaceMethod< "Attempts to extract the static unitary matrix. " "Returns std::nullopt if the operation is symbolic or dynamic.", - "std::optional", "tryGetStaticMatrix", (ins) + "DenseElementsAttr", "tryGetStaticMatrix", (ins) >, // Identification diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 442e161b19..e375595de2 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -428,7 +428,7 @@ def CtrlOp : FluxOp<"ctrl", traits = size_t getNumParams(); Value getParameter(size_t i); bool hasStaticUnitary(); - static DenseElementsAttr tryGetStaticMatrix(); + DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 0333698614..c1d720b1c9 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -103,7 +103,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { InterfaceMethod< "Attempts to extract the static unitary matrix. " "Returns std::nullopt if the operation is symbolic or dynamic.", - "std::optional", "tryGetStaticMatrix", (ins) + "DenseElementsAttr", "tryGetStaticMatrix", (ins) >, // Identification diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index e34b81e431..41ea8069ef 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -390,7 +390,7 @@ def CtrlOp : QuartzOp<"ctrl", size_t getNumParams(); Value getParameter(size_t i); bool hasStaticUnitary(); - static DenseElementsAttr tryGetStaticMatrix(); + DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 29cfaccaab..64e4b786ce 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -12,37 +12,86 @@ #include #include -#include using namespace std::complex_literals; namespace mlir::utils { -inline llvm::ArrayRef> getMatrixX() { - return {0.0, 1.0, // row 0 - 1.0, 0.0}; // row 1 +inline DenseElementsAttr getMatrixX(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const auto& matrix = {0.0 + 0i, 1.0 + 0i, // row 0 + 1.0 + 0i, 0.0 + 0i}; // row 1 + return DenseElementsAttr::get(type, matrix); } -inline llvm::ArrayRef> getMatrixRX(double theta) { - const std::complex m0(std::cos(theta / 2), 0); - const std::complex m1(0, -std::sin(theta / 2)); - return {m0, m1, m1, m0}; +inline DenseElementsAttr getMatrixRX(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m0 = std::cos(theta / 2) + 0i; + const std::complex m1 = -1i * std::sin(theta / 2); + return DenseElementsAttr::get(type, {m0, m1, m1, m0}); } -inline llvm::ArrayRef> getMatrixU2(double phi, - double lambda) { - const std::complex m00(1.0 / std::sqrt(2), 0.0); +inline DenseElementsAttr getMatrixU2(MLIRContext* ctx, double phi, + double lambda) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = 1.0 / std::sqrt(2) + 0i; const std::complex m01 = -std::exp(1i * lambda) / std::sqrt(2); const std::complex m10 = std::exp(1i * phi) / std::sqrt(2); const std::complex m11 = std::exp(1i * (phi + lambda)) / std::sqrt(2); - return {m00, m01, m10, m11}; + return DenseElementsAttr::get(type, {m00, m01, m10, m11}); } -inline llvm::ArrayRef> getMatrixSWAP() { - return {1.0, 0.0, 0.0, 0.0, // row 0 - 0.0, 0.0, 1.0, 0.0, // row 1 - 0.0, 1.0, 0.0, 0.0, // row 2 - 0.0, 0.0, 0.0, 1.0}; // row 3 +inline DenseElementsAttr getMatrixSWAP(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const auto matrix = {1.0 + 0i, 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, // row 1 + 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 2 + 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, 1.0 + 0i}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, + mlir::DenseElementsAttr target) { + // Get dimensions of target matrix + const auto& targetType = llvm::dyn_cast(target.getType()); + if (!targetType || targetType.getRank() != 2 || + targetType.getDimSize(0) != targetType.getDimSize(1)) { + llvm::report_fatal_error("Invalid target matrix"); + } + const auto targetDim = targetType.getDimSize(0); + + // Get values of target matrix + const auto& targetMatrix = target.getValues>(); + + // Define dimensions and type of output matrix + const auto dim = 2 * targetDim; + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({dim, dim}, complexType); + + // Allocate output matrix + std::vector> matrix; + matrix.reserve(dim * dim); + + // Fill output matrix + for (int64_t i = 0; i < dim; ++i) { + for (int64_t j = 0; j < dim; ++j) { + if (i < targetDim && j < targetDim) { + matrix.push_back((i == j) ? 1.0 : 0.0); + } else if (i >= targetDim && j >= targetDim) { + matrix.push_back( + targetMatrix[(i - targetDim) * targetDim + (j - targetDim)]); + } else { + matrix.push_back(0.0); + } + } + } + + ArrayRef> matrixRef(matrix); + return DenseElementsAttr::get(type, matrixRef); } } // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index c226d21091..7ff7e0d8ee 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -130,12 +130,7 @@ LogicalResult MeasureOp::verify() { // XOp -DenseElementsAttr XOp::tryGetStaticMatrix() { - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, getMatrixX()); -} +DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } // RXOp @@ -145,10 +140,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { return nullptr; } const auto thetaValue = theta.getValueAsDouble(); - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, getMatrixRX(thetaValue)); + return getMatrixRX(getContext(), thetaValue); } void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, @@ -174,10 +166,7 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, getMatrixU2(phiValue, lambdaValue)); + return getMatrixU2(getContext(), phiValue, lambdaValue); } void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, @@ -207,10 +196,7 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, // SWAPOp DenseElementsAttr SWAPOp::tryGetStaticMatrix() { - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - return DenseElementsAttr::get(type, getMatrixSWAP()); + return getMatrixSWAP(getContext()); } //===----------------------------------------------------------------------===// @@ -339,7 +325,7 @@ Value CtrlOp::getParameter(const size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - llvm::reportFatalUsageError("Not implemented yet"); // TODO + return getMatrixCtrl(getContext(), getBodyUnitary().tryGetStaticMatrix()); } LogicalResult CtrlOp::verify() { diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 39577d6ea3..00dcb9d712 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -131,12 +131,7 @@ LogicalResult MeasureOp::verify() { // XOp -DenseElementsAttr XOp::tryGetStaticMatrix() { - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, getMatrixX()); -} +DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } // RXOp DenseElementsAttr RXOp::tryGetStaticMatrix() { @@ -145,10 +140,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { return nullptr; } const auto thetaValue = theta.getValueAsDouble(); - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, getMatrixRX(thetaValue)); + return getMatrixRX(getContext(), thetaValue); } void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, @@ -174,10 +166,7 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - auto* ctx = getContext(); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - return DenseElementsAttr::get(type, getMatrixU2(phiValue, lambdaValue)); + return getMatrixU2(getContext(), phiValue, lambdaValue); } void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, @@ -207,10 +196,7 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, // SWAPOp DenseElementsAttr SWAPOp::tryGetStaticMatrix() { - auto* ctx = getContext(); - const auto& type = - RankedTensorType::get({4, 4}, ComplexType::get(Float64Type::get(ctx))); - return DenseElementsAttr::get(type, getMatrixSWAP()); + return getMatrixSWAP(getContext()); } //===----------------------------------------------------------------------===// @@ -290,7 +276,7 @@ Value CtrlOp::getParameter(const size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - llvm::reportFatalUsageError("Not implemented yet"); // TODO + return getMatrixCtrl(getContext(), getBodyUnitary().tryGetStaticMatrix()); } LogicalResult CtrlOp::verify() { From ce0353127ea0f5087858e22af99f0cebfdee8b42 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:46:01 +0100 Subject: [PATCH 208/419] Remove unnecessary header --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 060adc7eea..c70edf1e61 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include From 8ab3435354fbdec753919e92129db769452a5739 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:29:30 +0100 Subject: [PATCH 209/419] Remove unnecessary headers --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 2 -- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 7ff7e0d8ee..83213c8f4c 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -26,10 +26,8 @@ #include #include #include -#include #include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 00dcb9d712..e1d9995d67 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -25,11 +25,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include From 53ba8412d84e93e04880f6d3388b9c787d8ec3f4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 18 Nov 2025 21:08:20 +0100 Subject: [PATCH 210/419] Fix static matrices for multiple controls --- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 11 ++++++----- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 5 +++-- mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 64e4b786ce..79bd854da7 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -55,6 +55,7 @@ inline DenseElementsAttr getMatrixSWAP(MLIRContext* ctx) { } inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, + int64_t numControls, mlir::DenseElementsAttr target) { // Get dimensions of target matrix const auto& targetType = llvm::dyn_cast(target.getType()); @@ -68,7 +69,7 @@ inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, const auto& targetMatrix = target.getValues>(); // Define dimensions and type of output matrix - const auto dim = 2 * targetDim; + const auto dim = static_cast(std::pow(2, numControls) * targetDim); const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({dim, dim}, complexType); @@ -79,11 +80,11 @@ inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, // Fill output matrix for (int64_t i = 0; i < dim; ++i) { for (int64_t j = 0; j < dim; ++j) { - if (i < targetDim && j < targetDim) { + if (i < (dim - targetDim) && j < (dim - targetDim)) { matrix.push_back((i == j) ? 1.0 : 0.0); - } else if (i >= targetDim && j >= targetDim) { - matrix.push_back( - targetMatrix[(i - targetDim) * targetDim + (j - targetDim)]); + } else if (i >= (dim - targetDim) && j >= (dim - targetDim)) { + matrix.push_back(targetMatrix[(i - (dim - targetDim)) * targetDim + + (j - (dim - targetDim))]); } else { matrix.push_back(0.0); } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 83213c8f4c..ea590a50b3 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -323,7 +323,8 @@ Value CtrlOp::getParameter(const size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - return getMatrixCtrl(getContext(), getBodyUnitary().tryGetStaticMatrix()); + return getMatrixCtrl(getContext(), getNumPosControls(), + getBodyUnitary().tryGetStaticMatrix()); } LogicalResult CtrlOp::verify() { @@ -339,7 +340,7 @@ LogicalResult CtrlOp::verify() { return emitOpError( "second operation in body region must be a yield operation"); } - // the yield operation must yield as many values as there are targets + // The yield operation must yield as many values as there are targets if (block.back().getNumOperands() != getNumTargets()) { return emitOpError("yield operation must yield ") << getNumTargets() << " values, but found " diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index e1d9995d67..556484426e 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -274,7 +274,8 @@ Value CtrlOp::getParameter(const size_t i) { } DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - return getMatrixCtrl(getContext(), getBodyUnitary().tryGetStaticMatrix()); + return getMatrixCtrl(getContext(), getNumPosControls(), + getBodyUnitary().tryGetStaticMatrix()); } LogicalResult CtrlOp::verify() { From 3d4b8799cb7426a5449d02b73979b80c9be8550b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:49:19 +0100 Subject: [PATCH 211/419] Add support for S and Sdg without looking for commonalities yet --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 132 +++++++++++- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 46 +++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 83 +++++++- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 8 + .../Quartz/Builder/QuartzProgramBuilder.h | 110 +++++++++- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 40 ++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 16 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 113 ++++++++--- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 139 ++++++++++--- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 188 ++++++++++++++++-- .../Flux/Builder/FluxProgramBuilder.cpp | 82 ++++++-- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 64 ++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 88 ++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 44 ++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 10 + .../TranslateQuantumComputationToQuartz.cpp | 49 +++++ .../pipeline/test_compiler_pipeline.cpp | 162 +++++++++++++++ 17 files changed, 1287 insertions(+), 87 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index ba0f6cb74d..6b257e2571 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -261,11 +261,12 @@ class FluxProgramBuilder final : public OpBuilder { Value x(Value qubit); /** - * @brief Apply a CX gate + * @brief Apply a controlled X gate * * @param control Input control qubit (must be valid/unconsumed) * @param target Input target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubit, output_target_qubit) + * * @par Example: * ```c++ * {q0_out, q1_out} = builder.cx(q0_in, q1_in); @@ -285,6 +286,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param controls Input control qubits (must be valid/unconsumed) * @param target Input target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubits, output_target_qubit) + * * @par Example: * ```c++ * {controls_out, target_out} = builder.mcx({q0_in, q1_in}, q2_in); @@ -299,6 +301,128 @@ class FluxProgramBuilder final : public OpBuilder { */ std::pair mcx(ValueRange controls, Value target); + /** + * @brief Apply an S gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.s(q_in); + * ``` + * ```mlir + * %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value s(Value qubit); + + /** + * @brief Apply a controlled S gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cs(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.s %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cs(Value control, Value target); + + /** + * @brief Apply a multi-controlled S gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcs({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.s %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcs(ValueRange controls, Value target); + + /** + * @brief Apply an Sdg gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.sdg(q_in); + * ``` + * ```mlir + * %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value sdg(Value qubit); + + /** + * @brief Apply a controlled Sdg gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.csdg(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.sdg %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair csdg(Value control, Value target); + + /** + * @brief Apply a multi-controlled Sdg gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcsdg({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.sdg %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcsdg(ValueRange controls, Value target); + /** * @brief Apply an RX gate to a qubit * @@ -327,6 +451,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param control Input control qubit (must be valid/unconsumed) * @param target Input target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubit, output_target_qubit) + * * @par Example: * ```c++ * {q0_out, q1_out} = builder.crx(1.0, q0_in, q1_in); @@ -348,6 +473,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param controls Input control qubits (must be valid/unconsumed) * @param target Input target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubits, output_target_qubit) + * * @par Example: * ```c++ * {controls_out, target_out} = builder.mcrx(1.0, {q0_in, q1_in}, q2_in); @@ -394,6 +520,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param control Input control qubit (must be valid/unconsumed) * @param target Input target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubit, output_target_qubit) + * * @par Example: * ```c++ * {q0_out, q1_out} = builder.cu2(1.0, 0.5, q0_in, q1_in); @@ -417,6 +544,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param controls Input control qubits (must be valid/unconsumed) * @param target Input target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubits, output_target_qubit) + * * @par Example: * ```c++ * {controls_out, target_out} = builder.mcu2(1.0, 0.5, {q0_in, q1_in}, q2_in); @@ -461,6 +589,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param qubit0 First target qubit (must be valid/unconsumed) * @param qubit1 Second target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) + * * @par Example: * ```c++ * {q0_out, {q1_out, q2_out}} = builder.cswap(q0_in, q1_in, q2_in); @@ -483,6 +612,7 @@ class FluxProgramBuilder final : public OpBuilder { * @param qubit0 First target qubit (must be valid/unconsumed) * @param qubit1 Second target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) + * * @par Example: * ```c++ * {controls_out, {q1_out, q2_out}} = builder.mcswap({q0_in, q1_in}, q2_in, diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index e375595de2..8d05172611 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -278,6 +278,52 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } +def SOp : FluxOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an S gate to a qubit"; + let description = [{ + Applies an S gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "s"; } + }]; + + let hasCanonicalizer = 1; +} + +def SdgOp : FluxOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Sdg gate to a qubit"; + let description = [{ + Applies an Sdg gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "sdg"; } + }]; + + let hasCanonicalizer = 1; +} + def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index d6acb70eba..fbb683ec46 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -264,7 +264,7 @@ class QIRProgramBuilder { //===--------------------------------------------------------------------===// /** - * @brief Apply the X gate to a qubit + * @brief Apply an X gate to a qubit * * @param qubit Input qubit * @return Reference to this builder for method chaining @@ -280,7 +280,7 @@ class QIRProgramBuilder { QIRProgramBuilder& x(Value qubit); /** - * @brief Apply the CX gate + * @brief Apply a controlled X gate * * @param control Control qubit * @param target Target qubit @@ -297,7 +297,74 @@ class QIRProgramBuilder { QIRProgramBuilder& cx(Value control, Value target); /** - * @brief Apply the RX gate to a qubit + * @brief Apply an S gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.s(q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__s__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& s(Value qubit); + + /** + * @brief Apply a controlled S gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cs(q0, q1); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cs__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& cs(Value control, Value target); + + /** + * @brief Apply an Sdg gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.sdg(q); + * ``` + * ```mlir + * llvm.call @__quantum__qis__sdg__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& sdg(Value qubit); + + /** + * @brief Apply a controlled Sdg gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.csdg(q0, q1); + * ``` + * ```mlir + * llvm.call @__quantum__qis__csdg__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> + * () + * ``` + */ + QIRProgramBuilder& csdg(Value control, Value target); + + /** + * @brief Apply an RX gate to a qubit * * @param theta Rotation angle * @param qubit Input qubit @@ -314,7 +381,7 @@ class QIRProgramBuilder { QIRProgramBuilder& rx(const std::variant& theta, Value qubit); /** - * @brief Apply the CRX + * @brief Apply a controlled RX gate * * @param theta Rotation angle * @param control Control qubit @@ -334,7 +401,7 @@ class QIRProgramBuilder { Value control, Value target); /** - * @brief Apply the U2 gate to a qubit + * @brief Apply a U2 gate to a qubit * * @param phi Rotation angle * @param lambda Rotation angle @@ -354,7 +421,7 @@ class QIRProgramBuilder { const std::variant& lambda, Value qubit); /** - * @brief Apply the CU2 gate + * @brief Apply a controlled U2 gate * * @param phi Rotation angle * @param lambda Rotation angle @@ -376,7 +443,7 @@ class QIRProgramBuilder { Value control, Value target); /** - * @brief Apply the SWAP gate to two qubits + * @brief Apply a SWAP gate to two qubits * * @param qubit0 Input qubit * @param qubit1 Input qubit @@ -394,7 +461,7 @@ class QIRProgramBuilder { QIRProgramBuilder& swap(Value qubit0, Value qubit1); /** - * @brief Apply the CSWAP gate + * @brief Apply a controlled SWAP gate * * @param control Control qubit * @param target1 Target qubit diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 785af0f559..00ee0c2978 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -28,6 +28,14 @@ static constexpr auto QIR_X = "__quantum__qis__x__body"; static constexpr auto QIR_CX = "__quantum__qis__cx__body"; static constexpr auto QIR_CCX = "__quantum__qis__ccx__body"; static constexpr auto QIR_CCCX = "__quantum__qis__cccx__body"; +static constexpr auto QIR_S = "__quantum__qis__s__body"; +static constexpr auto QIR_CS = "__quantum__qis__cs__body"; +static constexpr auto QIR_CCS = "__quantum__qis__ccs__body"; +static constexpr auto QIR_CCCS = "__quantum__qis__cccs__body"; +static constexpr auto QIR_SDG = "__quantum__qis__sdg__body"; +static constexpr auto QIR_CSDG = "__quantum__qis__csdg__body"; +static constexpr auto QIR_CCSDG = "__quantum__qis__ccsdg__body"; +static constexpr auto QIR_CCCSDG = "__quantum__qis__cccsdg__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; static constexpr auto QIR_CRX = "__quantum__qis__crx__body"; static constexpr auto QIR_CCRX = "__quantum__qis__ccrx__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 204d0862b5..3ee308e710 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -245,7 +245,7 @@ class QuartzProgramBuilder final : public OpBuilder { QuartzProgramBuilder& x(Value qubit); /** - * @brief Apply a CX gate + * @brief Apply a controlled X gate * * @param control Control qubit * @param target Target qubit @@ -282,6 +282,114 @@ class QuartzProgramBuilder final : public OpBuilder { */ QuartzProgramBuilder& mcx(ValueRange controls, Value target); + /** + * @brief Apply an S gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.s(q); + * ``` + * ```mlir + * quartz.s %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& s(Value qubit); + + /** + * @brief Apply a controlled S gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cs(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.s %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cs(Value control, Value target); + + /** + * @brief Apply a multi-controlled S gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcs({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.s %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcs(ValueRange controls, Value target); + + /** + * @brief Apply an Sdg gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.sdg(q); + * ``` + * ```mlir + * quartz.sdg %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& sdg(Value qubit); + + /** + * @brief Apply a controlled Sdg gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.csdg(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.sdg %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& csdg(Value control, Value target); + + /** + * @brief Apply a multi-controlled Sdg gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcsdg({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.sdg %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcsdg(ValueRange controls, Value target); + /** * @brief Apply an RX gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 41ea8069ef..eb0e147b9f 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -263,6 +263,46 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } +def SOp : QuartzOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an S gate to a qubit"; + let description = [{ + Applies an S gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.s %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "s"; } + }]; +} + +def SdgOp : QuartzOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Sdg gate to a qubit"; + let description = [{ + Applies an Sdg gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.sdg %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "sdg"; } + }]; +} + def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 79bd854da7..4732902fb5 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -25,6 +25,22 @@ inline DenseElementsAttr getMatrixX(MLIRContext* ctx) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixS(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, 0.0 + 1i}; // row 1 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixSdg(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, 0.0 - 1i}; // row 1 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixRX(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 11c55d4d73..8965482ecb 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -236,13 +236,13 @@ struct ConvertFluxResetOp final : OpConversionPattern { /** * @brief Converts flux.x to quartz.x * - * @details - * Example transformation: + * @par Example: * ```mlir * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit - * // becomes: + * ``` + * is converted to + * ```mlir * quartz.x %q : !quartz.qubit - * // %q_out uses are replaced with %q * ``` */ struct ConvertFluxXOp final : OpConversionPattern { @@ -264,16 +264,78 @@ struct ConvertFluxXOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.s to quartz.s + * + * @par Example: + * ```mlir + * %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.s %q : !quartz.qubit + * ``` + */ +struct ConvertFluxSOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::SOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create quartz.s (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit); + + // Replace the output qubit with the same quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); + } +}; + +/** + * @brief Converts flux.sdg to quartz.sdg + * + * @par Example: + * ```mlir + * %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.sdg %q : !quartz.qubit + * ``` + */ +struct ConvertFluxSdgOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::SdgOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create quartz.sdg (in-place operation, no result) + rewriter.create(op.getLoc(), quartzQubit); + + // Replace the output qubit with the same quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); + } +}; + /** * @brief Converts flux.rx to quartz.rx * - * @details - * Example transformation: + * @par Example: * ```mlir * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit - * // becomes: + * ``` + * is converted to + * ```mlir * quartz.rx(%theta) %q : !quartz.qubit - * // %q_out uses are replaced with %q * ``` */ struct ConvertFluxRXOp final : OpConversionPattern { @@ -298,13 +360,13 @@ struct ConvertFluxRXOp final : OpConversionPattern { /** * @brief Converts flux.u2 to quartz.u2 * - * @details - * Example transformation: + * @par Example: * ```mlir * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit - * // becomes: + * ``` + * is converted to + * ```mlir * quartz.u2(%phi, %lambda) %q : !quartz.qubit - * // %q_out uses are replaced with %q * ``` */ struct ConvertFluxU2Op final : OpConversionPattern { @@ -330,13 +392,14 @@ struct ConvertFluxU2Op final : OpConversionPattern { /** * @brief Converts flux.swap to quartz.swap * - * @details - * Example transformation: + * @par Example: + * ```mlir + * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit + * ``` + * is converted to * ```mlir - * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit - * // becomes: * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit - * // {%q0_out, %q1_out} uses are replaced with {%q0, %q1} * ``` */ struct ConvertFluxSWAPOp final : OpConversionPattern { @@ -362,8 +425,7 @@ struct ConvertFluxSWAPOp final : OpConversionPattern { /** * @brief Converts quartz.ctrl to flux.ctrl * - * @details - * Example: + * @par Example: * ```mlir * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit @@ -404,8 +466,7 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { /** * @brief Converts flux.yield to quartz.yield * - * @details - * Example: + * @par Example: * ```mlir * flux.yield %targets * ``` @@ -469,10 +530,12 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add(typeConverter, context); + patterns + .add( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index a676d2e32a..f7cee2a0a1 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -356,13 +356,13 @@ struct ConvertQuartzResetOp final /** * @brief Converts quartz.x to flux.x * - * @details - * Example transformation: + * @par Example: * ```mlir * quartz.x %q : !quartz.qubit - * // becomes (where %q maps to %q_in): + * ``` + * is converted to + * ```mlir * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit - * // state updated: %q now maps to %q_out * ``` */ struct ConvertQuartzXOp final : StatefulOpConversionPattern { @@ -397,16 +397,105 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.s to flux.s + * + * @par Example: + * ```mlir + * quartz.s %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzSOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::SOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); + const auto inRegion = llvm::isa(op->getParentOp()); + + // Get the latest Flux qubit + const auto quartzQubit = op->getOperand(0); + Value fluxQubit = nullptr; + if (inRegion) { + fluxQubit = rewriter.getRemappedValue(quartzQubit); + } else { + fluxQubit = qubitMap[quartzQubit]; + } + + // Create flux.s (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + + // Update state map + if (!inRegion) { + qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } + + rewriter.eraseOp(op); + + return success(); + } +}; + +/** + * @brief Converts quartz.sdg to flux.sdg + * + * @par Example: + * ```mlir + * quartz.sdg %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzSdgOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::SdgOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& [qubitMap] = getState(); + const auto inRegion = llvm::isa(op->getParentOp()); + + // Get the latest Flux qubit + const auto quartzQubit = op->getOperand(0); + Value fluxQubit = nullptr; + if (inRegion) { + fluxQubit = rewriter.getRemappedValue(quartzQubit); + } else { + fluxQubit = qubitMap[quartzQubit]; + } + + // Create flux.sdg (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + + // Update state map + if (!inRegion) { + qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } + + rewriter.eraseOp(op); + + return success(); + } +}; + /** * @brief Converts quartz.rx to flux.rx * - * @details - * Example transformation: + * @par Example: * ```mlir * quartz.rx(%theta) %q : !quartz.qubit - * // becomes (where %q maps to %q_in): + * ``` + * is converted to + * ```mlir * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit - * // state updated: %q now maps to %q_out + * ``` */ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; @@ -444,13 +533,13 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { /** * @brief Converts quartz.u2 to flux.u2 * - * @details - * Example transformation: + * @par Example: * ```mlir * quartz.u2(%phi, %lambda) %q : !quartz.qubit - * // becomes (where %q maps to %q_in): + * ``` + * is converted to + * ```mlir * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit - * // state updated: %q now maps to %q_out * ``` */ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { @@ -489,14 +578,14 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { /** * @brief Converts quartz.swap to flux.swap * - * @details - * Example transformation: + * @par Example: * ```mlir * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit - * // becomes (where {%q0, %q1} map to {%q0_in, %q1_in}): + * ``` + * is converted to + * ```mlir * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> * !flux.qubit, !flux.qubit - * // state updated: {%q0, %q1} now map to {%q0_out, %q1_out} * ``` */ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { @@ -540,8 +629,7 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { /** * @brief Converts quartz.ctrl to flux.ctrl * - * @details - * Example: + * @par Example: * ```mlir * quartz.ctrl(%q0) { * quartz.x %q1 @@ -611,8 +699,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { /** * @brief Converts quartz.yield to flux.yield * - * @details - * Example: + * @par Example: * ```mlir * quartz.yield * ``` @@ -674,11 +761,13 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns.add(typeConverter, context, &state); + patterns + .add( + typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index c70edf1e61..96d40d1928 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -423,11 +423,12 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { /** * @brief Converts quartz.x operation to QIR x * - * @details - * Example transformation: + * @par Example: * ```mlir * quartz.x %q : !quartz.qubit - * // becomes: + * ``` + * is converted to + * ```mlir * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () * ``` */ @@ -479,7 +480,159 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; - operands.reserve(numCtrls + 2); + operands.reserve(numCtrls + 1); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); + } +}; + +/** + * @brief Converts quartz.s operation to QIR S + * + * @par Example: + * ```mlir + * quartz.s %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__s__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzSQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(SOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CS; + } else if (numCtrls == 2) { + fnName = QIR_CCS; + } else if (numCtrls == 3) { + fnName = QIR_CCCS; + } else { + return failure(); + } + } else { + fnName = QIR_S; + } + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 1); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 1); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); + } +}; + +/** + * @brief Converts quartz.sdg operation to QIR Sdg + * + * @par Example: + * ```mlir + * quartz.sdg %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__sdg__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(SdgOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto* ctx = getContext(); + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CSDG; + } else if (numCtrls == 2) { + fnName = QIR_CCSDG; + } else if (numCtrls == 3) { + fnName = QIR_CCCSDG; + } else { + return failure(); + } + } else { + fnName = QIR_SDG; + } + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 1); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 1); operands.append(posCtrls.begin(), posCtrls.end()); operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); @@ -498,11 +651,12 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { /** * @brief Converts quartz.rx operation to QIR RX * - * @details - * Example transformation: + * @par Example: + * ```mlir + * quartz.rx(%theta) %q : !quartz.qubit + * ``` + * is converted to * ```mlir - * quartz.rx %q(%theta) : !quartz.qubit - * // becomes: * llvm.call @__quantum__qis__rx__body(%q, %theta) : (!llvm.ptr, f64) -> () * ``` */ @@ -576,11 +730,12 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { /** * @brief Converts quartz.u2 operation to QIR U2 * - * @details - * Example transformation: + * @par Example: + * ```mlir + * quartz.u2(%phi, %lambda) %q : !quartz.qubit + * ``` + * is converted to * ```mlir - * quartz.u2 %q(%phi, %lambda) : !quartz.qubit - * // becomes: * llvm.call @__quantum__qis__u2__body(%q, %phi, %lambda) : (!llvm.ptr, f64, * f64) -> () * ``` @@ -657,11 +812,12 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { /** * @brief Converts quartz.swap operation to QIR SWAP * - * @details - * Example transformation: + * @par Example: * ```mlir * quartz.swap %q1, %q2 : !quartz.qubit, !quartz.qubit - * // becomes: + * ``` + * is converted to + * ```mlir * llvm.call @__quantum__qis__swap__body(%q1, %q2) : (!llvm.ptr, !llvm.ptr) -> * () * ``` @@ -1095,6 +1251,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index f4c2464923..0e2d47a30e 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -170,13 +170,12 @@ Value FluxProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// +// XOp + Value FluxProgramBuilder::x(Value qubit) { auto xOp = create(loc, qubit); const auto& qubitOut = xOp.getQubitOut(); - - // Update tracking updateQubitTracking(qubit, qubitOut); - return qubitOut; } @@ -202,14 +201,75 @@ std::pair FluxProgramBuilder::mcx(const ValueRange controls, return {controlsOut, targetsOut[0]}; } +// SOp + +Value FluxProgramBuilder::s(Value qubit) { + auto sOp = create(loc, qubit); + const auto& qubitOut = sOp.getQubitOut(); + updateQubitTracking(qubit, qubitOut); + return qubitOut; +} + +std::pair FluxProgramBuilder::cs(const Value control, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(control, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto s = b.create(loc, targets[0]); + return s->getResults(); + }); + return {controlsOut[0], targetsOut[0]}; +} + +std::pair FluxProgramBuilder::mcs(const ValueRange controls, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto s = b.create(loc, targets[0]); + return s->getResults(); + }); + return {controlsOut, targetsOut[0]}; +} + +// SdgOp + +Value FluxProgramBuilder::sdg(Value qubit) { + auto sdgOp = create(loc, qubit); + const auto& qubitOut = sdgOp.getQubitOut(); + updateQubitTracking(qubit, qubitOut); + return qubitOut; +} + +std::pair FluxProgramBuilder::csdg(const Value control, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(control, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto sdg = b.create(loc, targets[0]); + return sdg->getResults(); + }); + return {controlsOut[0], targetsOut[0]}; +} + +std::pair +FluxProgramBuilder::mcsdg(const ValueRange controls, const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto sdg = b.create(loc, targets[0]); + return sdg->getResults(); + }); + return {controlsOut, targetsOut[0]}; +} + +// RXOp + Value FluxProgramBuilder::rx(const std::variant& theta, Value qubit) { auto rxOp = create(loc, qubit, theta); const auto& qubitOut = rxOp.getQubitOut(); - - // Update tracking updateQubitTracking(qubit, qubitOut); - return qubitOut; } @@ -237,15 +297,14 @@ FluxProgramBuilder::mcrx(const std::variant& theta, return {controlsOut, targetsOut[0]}; } +// U2Op + Value FluxProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, Value qubit) { auto u2Op = create(loc, qubit, phi, lambda); const auto& qubitOut = u2Op.getQubitOut(); - - // Update tracking updateQubitTracking(qubit, qubitOut); - return qubitOut; } @@ -275,15 +334,14 @@ FluxProgramBuilder::mcu2(const std::variant& phi, return {controlsOut, targetsOut[0]}; } +// SWAPOp + std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { auto swapOp = create(loc, qubit0, qubit1); const auto& qubit0Out = swapOp.getQubit0Out(); const auto& qubit1Out = swapOp.getQubit1Out(); - - // Update tracking updateQubitTracking(qubit0, qubit0Out); updateQubitTracking(qubit1, qubit1Out); - return {qubit0Out, qubit1Out}; } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index ea590a50b3..73a5c147c8 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -130,6 +130,16 @@ LogicalResult MeasureOp::verify() { DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } +// SOp + +DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } + +// SdgOp + +DenseElementsAttr SdgOp::tryGetStaticMatrix() { + return getMatrixSdg(getContext()); +} + // RXOp DenseElementsAttr RXOp::tryGetStaticMatrix() { @@ -444,6 +454,50 @@ struct RemoveSubsequentX final : OpRewritePattern { } }; +/** + * @brief Remove S operations that immediately follow Sdg operations. + */ +struct RemoveSAfterSdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SOp sOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an SdgOp + auto prevOp = sOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both Sdg and S Ops + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(sOp, sOp.getQubitIn()); + + return success(); + } +}; + +/** + * @brief Remove Sdg operations that immediately follow S operations. + */ +struct RemoveSdgAfterS final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SdgOp sdgOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an SOp + auto prevOp = sdgOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both S and Sdg Ops + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(sdgOp, sdgOp.getQubitIn()); + + return success(); + } +}; + /** * @brief Merge subsequent RX operations on the same qubit by adding their * angles. @@ -533,6 +587,16 @@ void XOp::getCanonicalizationPatterns(RewritePatternSet& results, results.add(context); } +void SOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 12c55117d9..9002ecf3ef 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -235,6 +235,8 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// +// XOp + QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -273,6 +275,88 @@ QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, return *this; } +// SOp + +QIRProgramBuilder& QIRProgramBuilder::s(const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create s call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_S, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); + + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::cs(const Value control, + const Value target) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create cs call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_CS, qirSignature); + builder.create(loc, fnDecl, ValueRange{control, target}); + + return *this; +} + +// SdgOp + +QIRProgramBuilder& QIRProgramBuilder::sdg(const Value qubit) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create sdg call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_SDG, qirSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); + + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::csdg(const Value control, + const Value target) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create csdg call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, QIR_CSDG, qirSignature); + builder.create(loc, fnDecl, ValueRange{control, target}); + + return *this; +} + +// RXOp + QIRProgramBuilder& QIRProgramBuilder::rx(const std::variant& theta, const Value qubit) { @@ -351,6 +435,8 @@ QIRProgramBuilder::crx(const std::variant& theta, return *this; } +// U2Op + QIRProgramBuilder& QIRProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, @@ -454,6 +540,8 @@ QIRProgramBuilder::cu2(const std::variant& phi, return *this; } +// SWAPOp + QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, const Value qubit1) { // Save current insertion point diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index cabc77928d..84df5806ca 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -124,6 +124,8 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// +// XOp + QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { create(loc, qubit); return *this; @@ -140,6 +142,44 @@ QuartzProgramBuilder& QuartzProgramBuilder::mcx(ValueRange controls, return *this; } +// SOp + +QuartzProgramBuilder& QuartzProgramBuilder::s(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::cs(Value control, Value target) { + return mcs({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcs(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// SdgOp + +QuartzProgramBuilder& QuartzProgramBuilder::sdg(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::csdg(Value control, Value target) { + return mcsdg({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcsdg(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// RXOp + QuartzProgramBuilder& QuartzProgramBuilder::rx(const std::variant& theta, Value qubit) { @@ -161,6 +201,8 @@ QuartzProgramBuilder::mcrx(const std::variant& theta, return *this; } +// U2Op + QuartzProgramBuilder& QuartzProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, @@ -186,6 +228,8 @@ QuartzProgramBuilder::mcu2(const std::variant& phi, return *this; } +// SWAPOp + QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { create(loc, qubit0, qubit1); return *this; diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 556484426e..dc9417f2c2 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -131,6 +131,16 @@ LogicalResult MeasureOp::verify() { DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } +// SOp + +DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } + +// SdgOp + +DenseElementsAttr SdgOp::tryGetStaticMatrix() { + return getMatrixSdg(getContext()); +} + // RXOp DenseElementsAttr RXOp::tryGetStaticMatrix() { const auto& theta = getStaticParameter(getTheta()); diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 5e114869ad..4d9f64fdd1 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -265,6 +265,49 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds S operations + * + * @details + * Translates S operations from the QuantumComputation to quartz.s operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The S operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addSOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.s(target); + } else { + builder.mcs(posControls, target); + } +} + +/** + * @brief Adds Sdg operations + * + * @details + * Translates Sdg operations from the QuantumComputation to quartz.sdg + * operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The Sdg operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addSdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.sdg(target); + } else { + builder.mcsdg(posControls, target); + } +} + /** * @brief Adds RX operations * @@ -367,6 +410,12 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::X: addXOp(builder, *operation, qubits); break; + case qc::OpType::S: + addSOp(builder, *operation, qubits); + break; + case qc::OpType::Sdg: + addSdgOp(builder, *operation, qubits); + break; case qc::OpType::RX: addRXOp(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 1e8249c3e3..019cc7c7fe 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1380,6 +1380,168 @@ TEST_F(CompilerPipelineTest, CX3) { }); } +TEST_F(CompilerPipelineTest, S) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.s(0); + qc.s(0); + qc.sdg(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.s(q); + b.s(q); + b.sdg(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.s(q0); + const auto q2 = b.s(q1); + b.sdg(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.s(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.s(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.s(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CS) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.cs(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cs(reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cs(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cs(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, Sdg) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.sdg(0); + qc.sdg(0); + qc.s(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sdg(q); + b.sdg(q); + b.s(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.sdg(q0); + const auto q2 = b.sdg(q1); + b.s(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.sdg(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sdg(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.sdg(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, CSdg) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.csdg(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.csdg(reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.csdg(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.csdg(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, RX) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From e606f3fd5c0b30245dcdae47f7ab84440773f456 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:30:48 +0100 Subject: [PATCH 212/419] Streamline conversions by defining template functions --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 68 ++++---- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 112 +++++------- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 161 +++++++----------- 3 files changed, 145 insertions(+), 196 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 8965482ecb..dd7f6e971d 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -25,6 +25,8 @@ #include #include +namespace {} + namespace mlir { using namespace flux; using namespace quartz; @@ -32,6 +34,39 @@ using namespace quartz; #define GEN_PASS_DEF_FLUXTOQUARTZ #include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" +namespace { + +/** + * @brief Converts one-target, zero-parameter Flux operations to Quartz + * operations + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit); + + // Replace the output qubit with the same Quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); +} + +} // namespace + /** * @brief Type converter for Flux-to-Quartz conversion * @@ -251,16 +286,7 @@ struct ConvertFluxXOp final : OpConversionPattern { LogicalResult matchAndRewrite(flux::XOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); - - // Create quartz.x (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit); - - // Replace the output qubit with the same quartz reference - rewriter.replaceOp(op, quartzQubit); - - return success(); + return convertOneTargetZeroParameter(op, adaptor, rewriter); } }; @@ -282,16 +308,7 @@ struct ConvertFluxSOp final : OpConversionPattern { LogicalResult matchAndRewrite(flux::SOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); - - // Create quartz.s (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit); - - // Replace the output qubit with the same quartz reference - rewriter.replaceOp(op, quartzQubit); - - return success(); + return convertOneTargetZeroParameter(op, adaptor, rewriter); } }; @@ -313,16 +330,7 @@ struct ConvertFluxSdgOp final : OpConversionPattern { LogicalResult matchAndRewrite(flux::SdgOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); - - // Create quartz.sdg (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit); - - // Replace the output qubit with the same quartz reference - rewriter.replaceOp(op, quartzQubit); - - return success(); + return convertOneTargetZeroParameter(op, adaptor, rewriter); } }; diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index f7cee2a0a1..5f3353469a 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -104,6 +104,46 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +/** + * @brief Converts one-target, zero-parameter Quartz operations to Flux + * operations + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& [qubitMap] = state; + const auto inRegion = llvm::isa(op->getParentOp()); + + // Get the atest Flux qubit + const auto quartzQubit = op->getOperand(0); + Value fluxQubit = nullptr; + if (inRegion) { + fluxQubit = rewriter.getRemappedValue(quartzQubit); + } else { + fluxQubit = qubitMap[quartzQubit]; + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + + // Update the state map + if (!inRegion) { + qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } + + rewriter.eraseOp(op); + + return success(); +} + } // namespace /** @@ -371,29 +411,7 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - const auto inRegion = llvm::isa(op->getParentOp()); - - // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; - if (inRegion) { - fluxQubit = rewriter.getRemappedValue(quartzQubit); - } else { - fluxQubit = qubitMap[quartzQubit]; - } - - // Create flux.x (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); - - // Update state map - if (!inRegion) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); - } - - rewriter.eraseOp(op); - - return success(); + return convertOneTargetZeroParameter(op, rewriter, getState()); } }; @@ -415,29 +433,7 @@ struct ConvertQuartzSOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - const auto inRegion = llvm::isa(op->getParentOp()); - - // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; - if (inRegion) { - fluxQubit = rewriter.getRemappedValue(quartzQubit); - } else { - fluxQubit = qubitMap[quartzQubit]; - } - - // Create flux.s (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); - - // Update state map - if (!inRegion) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); - } - - rewriter.eraseOp(op); - - return success(); + return convertOneTargetZeroParameter(op, rewriter, getState()); } }; @@ -459,29 +455,7 @@ struct ConvertQuartzSdgOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SdgOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - const auto inRegion = llvm::isa(op->getParentOp()); - - // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; - if (inRegion) { - fluxQubit = rewriter.getRemappedValue(quartzQubit); - } else { - fluxQubit = qubitMap[quartzQubit]; - } - - // Create flux.sdg (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); - - // Update state map - if (!inRegion) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); - } - - rewriter.eraseOp(op); - - return success(); + return convertOneTargetZeroParameter(op, rewriter, getState()); } }; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 96d40d1928..04734a0f91 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -81,7 +81,7 @@ struct LoweringState : QIRMetadata { /// This allows caching result pointers for measurements with register info DenseMap, Value> registerResultMap; - /// Test + /// Modifier information bool inCtrlOp = false; SmallVector posCtrls; }; @@ -112,6 +112,63 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +/** + * @brief Converts one-target, zero-parameter Quartz operations to QIR calls + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 1); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 1); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp) { + state.inCtrlOp = false; + state.posCtrls.clear(); + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + } // namespace /** @@ -438,7 +495,6 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(XOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); auto& state = getState(); // Query state for modifier information @@ -462,37 +518,8 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { fnName = QIR_X; } - // Define function argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 1); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 1); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); } }; @@ -514,7 +541,6 @@ struct ConvertQuartzSQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(SOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); auto& state = getState(); // Query state for modifier information @@ -538,37 +564,8 @@ struct ConvertQuartzSQIR final : StatefulOpConversionPattern { fnName = QIR_S; } - // Define function argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 1); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 1); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); } }; @@ -590,7 +587,6 @@ struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(SdgOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); auto& state = getState(); // Query state for modifier information @@ -614,37 +610,8 @@ struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { fnName = QIR_SDG; } - // Define function argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 1); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 1); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); } }; From 4ebb7f24d9da4dcf180203f6f2593a7ad6ed123c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:02:32 +0100 Subject: [PATCH 213/419] Streamline builders by defining template functions --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 36 ++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 20 ++++ .../Flux/Builder/FluxProgramBuilder.cpp | 87 ++++++++---------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 92 +++++-------------- 4 files changed, 119 insertions(+), 116 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 6b257e2571..b74e343e37 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -705,6 +705,42 @@ class FluxProgramBuilder final : public OpBuilder { Location loc; ModuleOp module; + /** + * @brief Helper to create a one-target, zero-parameter Flux operation + * + * @tparam OpType The operation type of the Flux operation + * @param qubit Input qubit + * @return Output qubit + */ + template Value createOneTargetZeroParameter(Value qubit); + + /** + * @brief Helper to create a controlled one-target, zero-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param control Input control qubit + * @param target Input target qubit + * @return Pair of (output_control_qubit, output_target_qubit) + */ + template + std::pair createControlledOneTargetZeroParameter(Value control, + Value target); + + /** + * @brief Helper to create a multi-controlled one-target, zero-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param controls Input control qubits + * @param target Input target qubit + * @return Pair of (output_control_qubits, output_target_qubit) + */ + template + std::pair + createMultiControlledOneTargetZeroParameter(ValueRange controls, + Value target); + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index fbb683ec46..8f24375d81 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -529,6 +529,26 @@ class QIRProgramBuilder { /// Track qubit and result counts for QIR metadata QIRMetadata metadata_; + /** + * @brief Helper to create a one-target, zero-parameter QIR operation + * + * @param qubit Input qubit + * @param fnName Name of the QIR function to call + */ + void createOneTargetZeroParameter(const Value qubit, StringRef fnName); + + /** + * @brief Helper to create a controlled one-target, zero-parameter QIR + * operation + * + * @param control Input control qubit + * @param target Input target qubit + * @param fnName Name of the QIR function to call + */ + void createControlledOneTargetZeroParameter(const Value control, + const Value target, + StringRef fnName); + /** * @brief Generate array-based output recording in the output block * diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0e2d47a30e..dae4eab70a 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -170,97 +170,88 @@ Value FluxProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// -// XOp +// Helper methods -Value FluxProgramBuilder::x(Value qubit) { - auto xOp = create(loc, qubit); - const auto& qubitOut = xOp.getQubitOut(); +template +Value FluxProgramBuilder::createOneTargetZeroParameter(Value qubit) { + auto op = create(loc, qubit); + const auto& qubitOut = op.getQubitOut(); updateQubitTracking(qubit, qubitOut); return qubitOut; } -std::pair FluxProgramBuilder::cx(const Value control, - const Value target) { +template +std::pair +FluxProgramBuilder::createControlledOneTargetZeroParameter(Value control, + Value target) { const auto [controlsOut, targetsOut] = ctrl(control, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto x = b.create(loc, targets[0]); - return x->getResults(); + const auto op = b.create(loc, targets[0]); + return op->getResults(); }); return {controlsOut[0], targetsOut[0]}; } -std::pair FluxProgramBuilder::mcx(const ValueRange controls, - const Value target) { +template +std::pair +FluxProgramBuilder::createMultiControlledOneTargetZeroParameter( + ValueRange controls, Value target) { const auto [controlsOut, targetsOut] = ctrl(controls, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto x = b.create(loc, targets[0]); - return x->getResults(); + const auto op = b.create(loc, targets[0]); + return op->getResults(); }); return {controlsOut, targetsOut[0]}; } +// XOp + +Value FluxProgramBuilder::x(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::cx(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mcx(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + // SOp Value FluxProgramBuilder::s(Value qubit) { - auto sOp = create(loc, qubit); - const auto& qubitOut = sOp.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; + return createOneTargetZeroParameter(qubit); } std::pair FluxProgramBuilder::cs(const Value control, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto s = b.create(loc, targets[0]); - return s->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; + return createControlledOneTargetZeroParameter(control, target); } std::pair FluxProgramBuilder::mcs(const ValueRange controls, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto s = b.create(loc, targets[0]); - return s->getResults(); - }); - return {controlsOut, targetsOut[0]}; + return createMultiControlledOneTargetZeroParameter(controls, target); } // SdgOp Value FluxProgramBuilder::sdg(Value qubit) { - auto sdgOp = create(loc, qubit); - const auto& qubitOut = sdgOp.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; + return createOneTargetZeroParameter(qubit); } std::pair FluxProgramBuilder::csdg(const Value control, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto sdg = b.create(loc, targets[0]); - return sdg->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; + return createControlledOneTargetZeroParameter(control, target); } std::pair FluxProgramBuilder::mcsdg(const ValueRange controls, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto sdg = b.create(loc, targets[0]); - return sdg->getResults(); - }); - return {controlsOut, targetsOut[0]}; + return createMultiControlledOneTargetZeroParameter(controls, target); } // RXOp diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 9002ecf3ef..a1834a55dd 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -235,123 +235,79 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// -// XOp +// Helper methods -QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { +void QIRProgramBuilder::createOneTargetZeroParameter(const Value qubit, + StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create x call + // Create call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())); auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_X, qirSignature); + getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); builder.create(loc, fnDecl, ValueRange{qubit}); - - return *this; } -QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, - const Value target) { +void QIRProgramBuilder::createControlledOneTargetZeroParameter( + const Value control, const Value target, StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create cx call + // Create call const auto qirSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {LLVM::LLVMPointerType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())}); auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_CX, qirSignature); + getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); builder.create(loc, fnDecl, ValueRange{control, target}); +} + +// XOp +QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_X); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CX); return *this; } // SOp QIRProgramBuilder& QIRProgramBuilder::s(const Value qubit) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create s call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_S, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit}); - + createOneTargetZeroParameter(qubit, QIR_S); return *this; } QIRProgramBuilder& QIRProgramBuilder::cs(const Value control, const Value target) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create cs call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_CS, qirSignature); - builder.create(loc, fnDecl, ValueRange{control, target}); - + createControlledOneTargetZeroParameter(control, target, QIR_CS); return *this; } // SdgOp QIRProgramBuilder& QIRProgramBuilder::sdg(const Value qubit) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create sdg call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_SDG, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit}); - + createOneTargetZeroParameter(qubit, QIR_SDG); return *this; } QIRProgramBuilder& QIRProgramBuilder::csdg(const Value control, const Value target) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create csdg call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_CSDG, qirSignature); - builder.create(loc, fnDecl, ValueRange{control, target}); - + createControlledOneTargetZeroParameter(control, target, QIR_CSDG); return *this; } From 242830441408d6637b9079beb604567cdd34e1e5 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:07:23 +0100 Subject: [PATCH 214/419] Add support for Id --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 61 +++++++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 23 +++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 33 ++++++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 ++ .../Quartz/Builder/QuartzProgramBuilder.h | 54 ++++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 20 ++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 8 +++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 33 ++++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 35 ++++++++--- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 48 +++++++++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 16 +++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 49 ++++++++++++++- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 13 ++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 18 ++++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 6 ++ .../TranslateQuantumComputationToQuartz.cpp | 36 +++++++++-- .../pipeline/test_compiler_pipeline.cpp | 56 +++++++++++++++++ 17 files changed, 493 insertions(+), 20 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index b74e343e37..2ce880e809 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -240,6 +240,67 @@ class FluxProgramBuilder final : public OpBuilder { // Unitary Operations //===--------------------------------------------------------------------===// + /** + * @brief Apply an Id gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.id(q_in); + * ``` + * ```mlir + * %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value id(Value qubit); + + /** + * @brief Apply a controlled Id gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cid(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.id %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cid(Value control, Value target); + + /** + * @brief Apply a multi-controlled Id gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcid({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.id %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcid(ValueRange controls, Value target); + /** * @brief Apply an X gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 8d05172611..23631f2135 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -255,6 +255,29 @@ def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; // Unitary Operations //===----------------------------------------------------------------------===// +def IdOp : FluxOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Id gate to a qubit"; + let description = [{ + Applies an Id gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "id"; } + }]; + + let hasCanonicalizer = 1; +} + def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 8f24375d81..6988e4d294 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -263,6 +263,39 @@ class QIRProgramBuilder { // Unitary Operations //===--------------------------------------------------------------------===// + /** + * @brief Apply an Id gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.id(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__i__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& id(Value qubit); + + /** + * @brief Apply a controlled Id gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cid(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ci__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& cid(Value control, Value target); + /** * @brief Apply an X gate to a qubit * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 00ee0c2978..c0a0d22454 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -24,6 +24,10 @@ static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; static constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; +static constexpr auto QIR_ID = "__quantum__qis__i__body"; +static constexpr auto QIR_CID = "__quantum__qis__ci__body"; +static constexpr auto QIR_CCID = "__quantum__qis__cci__body"; +static constexpr auto QIR_CCCID = "__quantum__qis__ccci__body"; static constexpr auto QIR_X = "__quantum__qis__x__body"; static constexpr auto QIR_CX = "__quantum__qis__cx__body"; static constexpr auto QIR_CCX = "__quantum__qis__ccx__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 3ee308e710..ea24531fad 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -228,6 +228,60 @@ class QuartzProgramBuilder final : public OpBuilder { // Unitary Operations //===--------------------------------------------------------------------===// + /** + * @brief Apply an Id gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.id(q); + * ``` + * ```mlir + * quartz.id %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& id(Value qubit); + + /** + * @brief Apply a controlled Id gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cid(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.id %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cid(Value control, Value target); + + /** + * @brief Apply a multi-controlled Id gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcid({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.id %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcid(ValueRange controls, Value target); + /** * @brief Apply an X gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index eb0e147b9f..fb2dd29e9b 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -243,6 +243,26 @@ def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; // Unitary Operations //===----------------------------------------------------------------------===// +def IdOp : QuartzOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an Id gate to a qubit"; + let description = [{ + Applies an Id gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.id %q : !quartz.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "id"; } + }]; +} + def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 4732902fb5..11317a829e 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -17,6 +17,14 @@ using namespace std::complex_literals; namespace mlir::utils { +inline DenseElementsAttr getMatrixId(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, 1.0 + 0i}; // row 1 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixX(MLIRContext* ctx) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index dd7f6e971d..efb6b4a7f1 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -268,6 +268,28 @@ struct ConvertFluxResetOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.id to quartz.id + * + * @par Example: + * ```mlir + * %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.id %q : !quartz.qubit + * ``` + */ +struct ConvertFluxIdOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::IdOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + /** * @brief Converts flux.x to quartz.x * @@ -538,12 +560,11 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns - .add( - typeConverter, context); + patterns.add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 5f3353469a..828939789e 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -393,6 +393,28 @@ struct ConvertQuartzResetOp final } }; +/** + * @brief Converts quartz.id to flux.id + * + * @par Example: + * ```mlir + * quartz.id %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzIdOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::IdOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + /** * @brief Converts quartz.x to flux.x * @@ -735,13 +757,12 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns - .add( - typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 04734a0f91..331df2c01a 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -477,6 +477,53 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } }; +/** + * @brief Converts quartz.id operation to QIR i + * + * @par Example: + * ```mlir + * quartz.id %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__i__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzIdQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(IdOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CID; + ; + } else if (numCtrls == 2) { + fnName = QIR_CCID; + } else if (numCtrls == 3) { + fnName = QIR_CCCID; + } else { + return failure(); + } + } else { + fnName = QIR_ID; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + /** * @brief Converts quartz.x operation to QIR x * @@ -1217,6 +1264,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index dae4eab70a..f64e41c2f5 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -206,6 +206,22 @@ FluxProgramBuilder::createMultiControlledOneTargetZeroParameter( return {controlsOut, targetsOut[0]}; } +// IdOp + +Value FluxProgramBuilder::id(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::cid(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mcid(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + // XOp Value FluxProgramBuilder::x(Value qubit) { diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 73a5c147c8..380211634a 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -126,6 +126,12 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// +// IdOp + +DenseElementsAttr IdOp::tryGetStaticMatrix() { + return getMatrixId(getContext()); +} + // XOp DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } @@ -432,6 +438,19 @@ struct RemoveResetAfterAlloc final : OpRewritePattern { } }; +/** + * @brief Remove identity operations. + */ +struct RemoveId final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(IdOp idOp, + PatternRewriter& rewriter) const override { + rewriter.replaceOp(idOp, idOp.getQubitIn()); + return success(); + } +}; + /** * @brief Remove subsequent X operations on the same qubit. */ @@ -570,6 +589,29 @@ struct RemoveTrivialCtrl final : OpRewritePattern { } }; +struct CtrlInlineId final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + if (!llvm::isa(ctrlOp.getBodyUnitary().getOperation())) { + return failure(); + } + + auto idOp = + rewriter.create(ctrlOp.getLoc(), ctrlOp.getTargetsIn().front()); + + SmallVector newOperands; + newOperands.reserve(ctrlOp.getNumControls() + 1); + newOperands.append(ctrlOp.getControlsIn().begin(), + ctrlOp.getControlsIn().end()); + newOperands.push_back(idOp.getQubitOut()); + rewriter.replaceOp(ctrlOp, newOperands); + + return success(); + } +}; + } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -582,6 +624,11 @@ void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, results.add(context); } +void IdOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + void XOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); @@ -604,5 +651,5 @@ void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index a1834a55dd..5c4cabb74d 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -272,6 +272,19 @@ void QIRProgramBuilder::createControlledOneTargetZeroParameter( builder.create(loc, fnDecl, ValueRange{control, target}); } +// IdOp + +QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_ID); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::cid(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CID); + return *this; +} + // XOp QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 84df5806ca..7497b63912 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -124,6 +124,24 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// +// IdOp + +QuartzProgramBuilder& QuartzProgramBuilder::id(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::cid(Value control, Value target) { + return mcid({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcid(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + // XOp QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index dc9417f2c2..996749be8e 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -127,6 +127,12 @@ LogicalResult MeasureOp::verify() { // Unitary Operations //===----------------------------------------------------------------------===// +// IdOp + +DenseElementsAttr IdOp::tryGetStaticMatrix() { + return getMatrixId(getContext()); +} + // XOp DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 4d9f64fdd1..b3a863cd31 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -245,7 +245,28 @@ getPosControls(const qc::Operation& operation, } /** - * @brief Adds X operations + * @brief Adds an Id operation + * + * @details + * Translates Id operations from the QuantumComputation to quartz.id operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The Id operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addIdOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.id(target); + } else { + builder.mcid(posControls, target); + } +} + +/** + * @brief Adds an X operation * * @details * Translates X operations from the QuantumComputation to quartz.x operations. @@ -266,7 +287,7 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } /** - * @brief Adds S operations + * @brief Adds an S operation * * @details * Translates S operations from the QuantumComputation to quartz.s operations. @@ -287,7 +308,7 @@ void addSOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } /** - * @brief Adds Sdg operations + * @brief Adds an Sdg operation * * @details * Translates Sdg operations from the QuantumComputation to quartz.sdg @@ -309,7 +330,7 @@ void addSdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } /** - * @brief Adds RX operations + * @brief Adds an RX operation * * @details * Translates RX operations from the QuantumComputation to quartz.rx operations. @@ -331,7 +352,7 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } /** - * @brief Adds U2 operations + * @brief Adds a U2 operation * * @details * Translates U2 operations from the QuantumComputation to quartz.u2 operations. @@ -354,7 +375,7 @@ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, } /** - * @brief Adds SWAP operations + * @brief Adds a SWAP operation * * @details * Translates SWAP operations from the QuantumComputation to quartz.swap @@ -407,6 +428,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::Reset: addResetOp(builder, *operation, qubits); break; + case qc::OpType::I: + addIdOp(builder, *operation, qubits); + break; case qc::OpType::X: addXOp(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 019cc7c7fe..b3829b71ec 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1254,6 +1254,62 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { // # Temporary Unitary Operation Tests // ################################################## +TEST_F(CompilerPipelineTest, Id) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.i(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.id(q); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.id(q); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + }); +} + +TEST_F(CompilerPipelineTest, CId) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.ci(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cid(reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cid(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = emptyFlux.get(), + .quartzConversion = emptyQuartz.get(), + .qirConversion = emptyQIR.get(), + }); +} + TEST_F(CompilerPipelineTest, X) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From 6412088edfc929a8ba5f0b1be5bf05379ebefc2a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:35:20 +0100 Subject: [PATCH 215/419] Add support for Y, Z, H, T, Tdg, SX, and SXdg --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 427 ++++++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 161 +++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 233 ++++++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 28 ++ .../Quartz/Builder/QuartzProgramBuilder.h | 378 ++++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 140 ++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 58 +++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 158 ++++++- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 164 ++++++- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 339 +++++++++++++- .../Flux/Builder/FluxProgramBuilder.cpp | 112 +++++ mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 223 +++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 91 ++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 126 ++++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 34 ++ .../TranslateQuantumComputationToQuartz.cpp | 170 +++++++ .../pipeline/test_compiler_pipeline.cpp | 382 ++++++++++++++-- 17 files changed, 3168 insertions(+), 56 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 2ce880e809..82b2456d6b 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -362,6 +362,189 @@ class FluxProgramBuilder final : public OpBuilder { */ std::pair mcx(ValueRange controls, Value target); + /** + * @brief Apply a Y gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.y(q_in); + * ``` + * ```mlir + * %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value y(Value qubit); + + /** + * @brief Apply a controlled Y gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cy(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.y %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cy(Value control, Value target); + + /** + * @brief Apply a multi-controlled Y gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcy({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.y %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcy(ValueRange controls, Value target); + + /** + * @brief Apply a Z gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.z(q_in); + * ``` + * ```mlir + * %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value z(Value qubit); + + /** + * @brief Apply a controlled Z gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cz(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.z %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cz(Value control, Value target); + + /** + * @brief Apply a multi-controlled Z gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcz({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.z %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcz(ValueRange controls, Value target); + + /** + * @brief Apply an H gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.h(q_in); + * ``` + * ```mlir + * %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value h(Value qubit); + + /** + * @brief Apply a controlled H gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.ch(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.h %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair ch(Value control, Value target); + + /** + * @brief Apply a multi-controlled H gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mch({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.h %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mch(ValueRange controls, Value target); + /** * @brief Apply an S gate to a qubit * @@ -423,6 +606,250 @@ class FluxProgramBuilder final : public OpBuilder { */ std::pair mcs(ValueRange controls, Value target); + /** + * @brief Apply a T gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.t(q_in); + * ``` + * ```mlir + * %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value t(Value qubit); + + /** + * @brief Apply a controlled T gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.ct(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.t %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair ct(Value control, Value target); + + /** + * @brief Apply a multi-controlled T gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mct({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.t %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mct(ValueRange controls, Value target); + + /** + * @brief Apply a Tdg gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.tdg(q_in); + * ``` + * ```mlir + * %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value tdg(Value qubit); + + /** + * @brief Apply a controlled Tdg gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.ctdg(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.tdg %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair ctdg(Value control, Value target); + + /** + * @brief Apply a multi-controlled Tdg gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mctdg({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.tdg %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mctdg(ValueRange controls, Value target); + + /** + * @brief Apply an SX gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.sx(q_in); + * ``` + * ```mlir + * %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value sx(Value qubit); + + /** + * @brief Apply a controlled SX gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.csx(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.sx %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair csx(Value control, Value target); + + /** + * @brief Apply a multi-controlled SX gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcsx({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.sx %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcsx(ValueRange controls, Value target); + + /** + * @brief Apply an SXdg gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.sxdg(q_in); + * ``` + * ```mlir + * %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value sxdg(Value qubit); + + /** + * @brief Apply a controlled SXdg gate + * + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.csxdg(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.sxdg %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair csxdg(Value control, Value target); + + /** + * @brief Apply a multi-controlled SXdg gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcsxdg({q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.sxdg %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcsxdg(ValueRange controls, Value target); + /** * @brief Apply an Sdg gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 23631f2135..c2cf3fee34 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -301,6 +301,75 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } +def YOp : FluxOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Y gate to a qubit"; + let description = [{ + Applies a Y gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "y"; } + }]; + + let hasCanonicalizer = 1; +} + +def ZOp : FluxOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Z gate to a qubit"; + let description = [{ + Applies a Z gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "z"; } + }]; + + let hasCanonicalizer = 1; +} + +def HOp : FluxOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a H gate to a qubit"; + let description = [{ + Applies a H gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "h"; } + }]; + + let hasCanonicalizer = 1; +} + def SOp : FluxOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an S gate to a qubit"; let description = [{ @@ -347,6 +416,98 @@ def SdgOp : FluxOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let hasCanonicalizer = 1; } +def TOp : FluxOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a T gate to a qubit"; + let description = [{ + Applies a T gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "t"; } + }]; + + let hasCanonicalizer = 1; +} + +def TdgOp : FluxOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Tdg gate to a qubit"; + let description = [{ + Applies a Tdg gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "tdg"; } + }]; + + let hasCanonicalizer = 1; +} + +def SXOp : FluxOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SX gate to a qubit"; + let description = [{ + Applies an SX gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "sx"; } + }]; + + let hasCanonicalizer = 1; +} + +def SXdgOp : FluxOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SXdg gate to a qubit"; + let description = [{ + Applies an SXdg gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "sxdg"; } + }]; + + let hasCanonicalizer = 1; +} + def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 6988e4d294..94a743937d 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -329,6 +329,105 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cx(Value control, Value target); + /** + * @brief Apply a Y gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.y(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__y__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& y(Value qubit); + + /** + * @brief Apply a controlled Y gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cy(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cy__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& cy(Value control, Value target); + + /** + * @brief Apply a Z gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.z(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__z__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& z(Value qubit); + + /** + * @brief Apply a controlled Z gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cz(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cz__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& cz(Value control, Value target); + + /** + * @brief Apply an H gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.h(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__h__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& h(Value qubit); + + /** + * @brief Apply a controlled H gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ch(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ch__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& ch(Value control, Value target); + /** * @brief Apply an S gate to a qubit * @@ -396,6 +495,140 @@ class QIRProgramBuilder { */ QIRProgramBuilder& csdg(Value control, Value target); + /** + * @brief Apply a T gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.t(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__t__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& t(Value qubit); + + /** + * @brief Apply a controlled T gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ct(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ct__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& ct(Value control, Value target); + + /** + * @brief Apply a Tdg gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.tdg(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__tdg__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& tdg(Value qubit); + + /** + * @brief Apply a controlled Tdg gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ctdg(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ctdg__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> + * () + * ``` + */ + QIRProgramBuilder& ctdg(Value control, Value target); + + /** + * @brief Apply an SX gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.sx(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__sx__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& sx(Value qubit); + + /** + * @brief Apply a controlled SX gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.csx(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__csx__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& csx(Value control, Value target); + + /** + * @brief Apply an SXdg gate to a qubit + * + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.sxdg(qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__sxdg__body(%q) : (!llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& sxdg(Value qubit); + + /** + * @brief Apply a controlled SXdg gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.csxdg(control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__csxdg__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> + * () + * ``` + */ + QIRProgramBuilder& csxdg(Value control, Value target); + /** * @brief Apply an RX gate to a qubit * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index c0a0d22454..affd40b017 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -32,6 +32,18 @@ static constexpr auto QIR_X = "__quantum__qis__x__body"; static constexpr auto QIR_CX = "__quantum__qis__cx__body"; static constexpr auto QIR_CCX = "__quantum__qis__ccx__body"; static constexpr auto QIR_CCCX = "__quantum__qis__cccx__body"; +static constexpr auto QIR_Y = "__quantum__qis__y__body"; +static constexpr auto QIR_CY = "__quantum__qis__cy__body"; +static constexpr auto QIR_CCY = "__quantum__qis__ccy__body"; +static constexpr auto QIR_CCCY = "__quantum__qis__cccy__body"; +static constexpr auto QIR_Z = "__quantum__qis__z__body"; +static constexpr auto QIR_CZ = "__quantum__qis__cz__body"; +static constexpr auto QIR_CCZ = "__quantum__qis__ccz__body"; +static constexpr auto QIR_CCCZ = "__quantum__qis__cccz__body"; +static constexpr auto QIR_H = "__quantum__qis__h__body"; +static constexpr auto QIR_CH = "__quantum__qis__ch__body"; +static constexpr auto QIR_CCH = "__quantum__qis__cch__body"; +static constexpr auto QIR_CCCH = "__quantum__qis__ccch__body"; static constexpr auto QIR_S = "__quantum__qis__s__body"; static constexpr auto QIR_CS = "__quantum__qis__cs__body"; static constexpr auto QIR_CCS = "__quantum__qis__ccs__body"; @@ -40,6 +52,22 @@ static constexpr auto QIR_SDG = "__quantum__qis__sdg__body"; static constexpr auto QIR_CSDG = "__quantum__qis__csdg__body"; static constexpr auto QIR_CCSDG = "__quantum__qis__ccsdg__body"; static constexpr auto QIR_CCCSDG = "__quantum__qis__cccsdg__body"; +static constexpr auto QIR_T = "__quantum__qis__t__body"; +static constexpr auto QIR_CT = "__quantum__qis__ct__body"; +static constexpr auto QIR_CCT = "__quantum__qis__cct__body"; +static constexpr auto QIR_CCCT = "__quantum__qis__ccct__body"; +static constexpr auto QIR_TDG = "__quantum__qis__tdg__body"; +static constexpr auto QIR_CTDG = "__quantum__qis__ctdg__body"; +static constexpr auto QIR_CCTDG = "__quantum__qis__cctdg__body"; +static constexpr auto QIR_CCCTDG = "__quantum__qis__ccctdg__body"; +static constexpr auto QIR_SX = "__quantum__qis__sx__body"; +static constexpr auto QIR_CSX = "__quantum__qis__csx__body"; +static constexpr auto QIR_CCSX = "__quantum__qis__ccsx__body"; +static constexpr auto QIR_CCCSX = "__quantum__qis__cccsx__body"; +static constexpr auto QIR_SXDG = "__quantum__qis__sxdg__body"; +static constexpr auto QIR_CSXDG = "__quantum__qis__csxdg__body"; +static constexpr auto QIR_CCSXDG = "__quantum__qis__ccsxdg__body"; +static constexpr auto QIR_CCCSXDG = "__quantum__qis__cccsxdg__body"; static constexpr auto QIR_RX = "__quantum__qis__rx__body"; static constexpr auto QIR_CRX = "__quantum__qis__crx__body"; static constexpr auto QIR_CCRX = "__quantum__qis__ccrx__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index ea24531fad..c2c3d028ed 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -336,6 +336,168 @@ class QuartzProgramBuilder final : public OpBuilder { */ QuartzProgramBuilder& mcx(ValueRange controls, Value target); + /** + * @brief Apply a Y gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.y(q); + * ``` + * ```mlir + * quartz.y %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& y(Value qubit); + + /** + * @brief Apply a controlled Y gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cy(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.y %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cy(Value control, Value target); + + /** + * @brief Apply a multi-controlled Y gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcy({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.y %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcy(ValueRange controls, Value target); + + /** + * @brief Apply a Z gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.z(q); + * ``` + * ```mlir + * quartz.z %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& z(Value qubit); + + /** + * @brief Apply a controlled Z gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cz(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.z %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cz(Value control, Value target); + + /** + * @brief Apply a multi-controlled Z gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcz({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.z %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcz(ValueRange controls, Value target); + + /** + * @brief Apply an H gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.h(q); + * ``` + * ```mlir + * quartz.h %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& h(Value qubit); + + /** + * @brief Apply a controlled H gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ch(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.h %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& ch(Value control, Value target); + + /** + * @brief Apply a multi-controlled H gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mch({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.h %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mch(ValueRange controls, Value target); + /** * @brief Apply an S gate to a qubit * @@ -444,6 +606,222 @@ class QuartzProgramBuilder final : public OpBuilder { */ QuartzProgramBuilder& mcsdg(ValueRange controls, Value target); + /** + * @brief Apply a T gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.t(q); + * ``` + * ```mlir + * quartz.t %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& t(Value qubit); + + /** + * @brief Apply a controlled T gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ct(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.t %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& ct(Value control, Value target); + + /** + * @brief Apply a multi-controlled T gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mct({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.t %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mct(ValueRange controls, Value target); + + /** + * @brief Apply a Tdg gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.tdg(q); + * ``` + * ```mlir + * quartz.tdg %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& tdg(Value qubit); + + /** + * @brief Apply a controlled Tdg gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ctdg(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.tdg %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& ctdg(Value control, Value target); + + /** + * @brief Apply a multi-controlled Tdg gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mctdg({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.tdg %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mctdg(ValueRange controls, Value target); + + /** + * @brief Apply an SX gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.sx(q); + * ``` + * ```mlir + * quartz.sx %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& sx(Value qubit); + + /** + * @brief Apply a controlled SX gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.csx(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.sx %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& csx(Value control, Value target); + + /** + * @brief Apply a multi-controlled SX gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcsx({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.sx %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcsx(ValueRange controls, Value target); + + /** + * @brief Apply an SXdg gate to a qubit + * + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.sxdg(q); + * ``` + * ```mlir + * quartz.sxdg %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& sxdg(Value qubit); + + /** + * @brief Apply a controlled SXdg gate + * + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.csxdg(q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.sxdg %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& csxdg(Value control, Value target); + + /** + * @brief Apply a multi-controlled SXdg gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcsxdg({q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.sxdg %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcsxdg(ValueRange controls, Value target); + /** * @brief Apply an RX gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index fb2dd29e9b..cc4b5c30ba 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -283,6 +283,66 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } +def YOp : QuartzOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Y gate to a qubit"; + let description = [{ + Applies a Y gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.y %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "y"; } + }]; +} + +def ZOp : QuartzOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Z gate to a qubit"; + let description = [{ + Applies a Z gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.z %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "z"; } + }]; +} + +def HOp : QuartzOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an H gate to a qubit"; + let description = [{ + Applies an H gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.h %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "h"; } + }]; +} + def SOp : QuartzOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an S gate to a qubit"; let description = [{ @@ -323,6 +383,86 @@ def SdgOp : QuartzOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter }]; } +def TOp : QuartzOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a T gate to a qubit"; + let description = [{ + Applies a T gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.t %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "t"; } + }]; +} + +def TdgOp : QuartzOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply a Tdg gate to a qubit"; + let description = [{ + Applies a Tdg gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.tdg %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "tdg"; } + }]; +} + +def SXOp : QuartzOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SX gate to a qubit"; + let description = [{ + Applies an SX gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.sx %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "sx"; } + }]; +} + +def SXdgOp : QuartzOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { + let summary = "Apply an SXdg gate to a qubit"; + let description = [{ + Applies an SXdg gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.sxdg %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in); + let assemblyFormat = "$qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "sxdg"; } + }]; +} + def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 11317a829e..947c3be271 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -33,6 +33,30 @@ inline DenseElementsAttr getMatrixX(MLIRContext* ctx) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixY(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const auto& matrix = {0.0 + 0i, 0.0 - 1i, // row 0 + 0.0 + 1i, 0.0 + 0i}; // row 1 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixZ(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, -1.0 + 0i}; // row 1 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixH(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = 1.0 / std::sqrt(2) + 0i; + const std::complex m11 = -1.0 / std::sqrt(2) + 0i; + return DenseElementsAttr::get(type, {m00, m00, m00, m11}); +} + inline DenseElementsAttr getMatrixS(MLIRContext* ctx) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); @@ -49,6 +73,40 @@ inline DenseElementsAttr getMatrixSdg(MLIRContext* ctx) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixT(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = 1.0 + 0i; + const std::complex m01 = 0.0 + 0i; + const std::complex m11 = std::exp(1i * M_PI / 4.0); + return DenseElementsAttr::get(type, {m00, m01, m01, m11}); +} + +inline DenseElementsAttr getMatrixTdg(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = 1.0 + 0i; + const std::complex m01 = 0.0 + 0i; + const std::complex m11 = std::exp(-1i * M_PI / 4.0); + return DenseElementsAttr::get(type, {m00, m01, m01, m11}); +} + +inline DenseElementsAttr getMatrixSX(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = (1.0 + 1i) / 2.0; + const std::complex m01 = (1.0 - 1i) / 2.0; + return DenseElementsAttr::get(type, {m00, m01, m01, m00}); +} + +inline DenseElementsAttr getMatrixSXdg(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = (1.0 - 1i) / 2.0; + const std::complex m01 = (1.0 + 1i) / 2.0; + return DenseElementsAttr::get(type, {m00, m01, m01, m00}); +} + inline DenseElementsAttr getMatrixRX(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index efb6b4a7f1..271c253ae6 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -312,6 +312,72 @@ struct ConvertFluxXOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.y to quartz.y + * + * @par Example: + * ```mlir + * %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.y %q : !quartz.qubit + * ``` + */ +struct ConvertFluxYOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::YOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + +/** + * @brief Converts flux.z to quartz.z + * + * @par Example: + * ```mlir + * %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.z %q : !quartz.qubit + * ``` + */ +struct ConvertFluxZOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::ZOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + +/** + * @brief Converts flux.h to quartz.h + * + * @par Example: + * ```mlir + * %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.h %q : !quartz.qubit + * ``` + */ +struct ConvertFluxHOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::HOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + /** * @brief Converts flux.s to quartz.s * @@ -356,6 +422,94 @@ struct ConvertFluxSdgOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.t to quartz.t + * + * @par Example: + * ```mlir + * %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.t %q : !quartz.qubit + * ``` + */ +struct ConvertFluxTOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::TOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + +/** + * @brief Converts flux.tdg to quartz.tdg + * + * @par Example: + * ```mlir + * %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.tdg %q : !quartz.qubit + * ``` + */ +struct ConvertFluxTdgOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::TdgOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + +/** + * @brief Converts flux.sx to quartz.sx + * + * @par Example: + * ```mlir + * %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.sx %q : !quartz.qubit + * ``` + */ +struct ConvertFluxSXOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::SXOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + +/** + * @brief Converts flux.sxdg to quartz.sxdg + * + * @par Example: + * ```mlir + * %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.sxdg %q : !quartz.qubit + * ``` + */ +struct ConvertFluxSXdgOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::SXdgOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, adaptor, rewriter); + } +}; + /** * @brief Converts flux.rx to quartz.rx * @@ -562,7 +716,9 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Note: No state tracking needed - OpAdaptors handle type conversion patterns.add(typeConverter, context); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 828939789e..aefd82cc6d 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -437,6 +437,72 @@ struct ConvertQuartzXOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.y to flux.y + * + * @par Example: + * ```mlir + * quartz.y %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzYOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::YOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + +/** + * @brief Converts quartz.z to flux.z + * + * @par Example: + * ```mlir + * quartz.z %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzZOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::ZOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + +/** + * @brief Converts quartz.h to flux.h + * + * @par Example: + * ```mlir + * quartz.h %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzHOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::HOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + /** * @brief Converts quartz.s to flux.s * @@ -481,6 +547,95 @@ struct ConvertQuartzSdgOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.t to flux.t + * + * @par Example: + * ```mlir + * quartz.t %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzTOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::TOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + +/** + * @brief Converts quartz.tdg to flux.tdg + * + * @par Example: + * ```mlir + * quartz.tdg %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzTdgOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::TdgOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + +/** + * @brief Converts quartz.sx to flux.sx + * + * @par Example: + * ```mlir + * quartz.sx %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzSXOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::SXOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, getState()); + } +}; + +/** + * @brief Converts quartz.sxdg to flux.sxdg + * + * @par Example: + * ```mlir + * quartz.sxdg %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzSXdgOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::SXdgOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetZeroParameter(op, rewriter, + getState()); + } +}; + /** * @brief Converts quartz.rx to flux.rx * @@ -760,9 +915,12 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { patterns.add(typeConverter, context, &state); + ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, + ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, + ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, + ConvertQuartzRXOp, ConvertQuartzU2Op, ConvertQuartzSWAPOp, + ConvertQuartzCtrlOp, ConvertQuartzYieldOp>(typeConverter, + context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 331df2c01a..20f53e4595 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -571,7 +571,145 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.s operation to QIR S + * @brief Converts quartz.y operation to QIR y + * + * @par Example: + * ```mlir + * quartz.y %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__y__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzYQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(YOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CY; + } else if (numCtrls == 2) { + fnName = QIR_CCY; + } else if (numCtrls == 3) { + fnName = QIR_CCCY; + } else { + return failure(); + } + } else { + fnName = QIR_Y; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.z operation to QIR z + * + * @par Example: + * ```mlir + * quartz.z %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__z__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzZQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(ZOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CZ; + } else if (numCtrls == 2) { + fnName = QIR_CCZ; + } else if (numCtrls == 3) { + fnName = QIR_CCCZ; + } else { + return failure(); + } + } else { + fnName = QIR_Z; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.h operation to QIR h + * + * @par Example: + * ```mlir + * quartz.h %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__h__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzHQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(HOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CH; + } else if (numCtrls == 2) { + fnName = QIR_CCH; + } else if (numCtrls == 3) { + fnName = QIR_CCCH; + } else { + return failure(); + } + } else { + fnName = QIR_H; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.s operation to QIR s * * @par Example: * ```mlir @@ -617,7 +755,7 @@ struct ConvertQuartzSQIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.sdg operation to QIR Sdg + * @brief Converts quartz.sdg operation to QIR sdg * * @par Example: * ```mlir @@ -663,7 +801,191 @@ struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.rx operation to QIR RX + * @brief Converts quartz.t operation to QIR t + * + * @par Example: + * ```mlir + * quartz.t %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__t__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzTQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(TOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CT; + } else if (numCtrls == 2) { + fnName = QIR_CCT; + } else if (numCtrls == 3) { + fnName = QIR_CCCT; + } else { + return failure(); + } + } else { + fnName = QIR_T; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.tdg operation to QIR tdg + * + * @par Example: + * ```mlir + * quartz.tdg %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__tdg__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzTdgQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(TdgOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CTDG; + } else if (numCtrls == 2) { + fnName = QIR_CCTDG; + } else if (numCtrls == 3) { + fnName = QIR_CCCTDG; + } else { + return failure(); + } + } else { + fnName = QIR_TDG; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.sx operation to QIR sx + * + * @par Example: + * ```mlir + * quartz.sx %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__sx__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzSXQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(SXOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CSX; + } else if (numCtrls == 2) { + fnName = QIR_CCSX; + } else if (numCtrls == 3) { + fnName = QIR_CCCSX; + } else { + return failure(); + } + } else { + fnName = QIR_SX; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.sxdg operation to QIR sxdg + * + * @par Example: + * ```mlir + * quartz.sxdg %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__sxdg__body(%q) : (!llvm.ptr) -> () + * ``` + */ +struct ConvertQuartzSXdgQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(SXdgOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const auto& posCtrls = state.posCtrls; + const auto numCtrls = posCtrls.size(); + + // Define function name + StringRef fnName; + if (inCtrlOp) { + if (numCtrls == 1) { + fnName = QIR_CSXDG; + } else if (numCtrls == 2) { + fnName = QIR_CCSXDG; + } else if (numCtrls == 3) { + fnName = QIR_CCCSXDG; + } else { + return failure(); + } + } else { + fnName = QIR_SXDG; + } + + return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.rx operation to QIR rx * * @par Example: * ```mlir @@ -742,7 +1064,7 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.u2 operation to QIR U2 + * @brief Converts quartz.u2 operation to QIR u2 * * @par Example: * ```mlir @@ -824,7 +1146,7 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.swap operation to QIR SWAP + * @brief Converts quartz.swap operation to QIR swap * * @par Example: * ```mlir @@ -1266,8 +1588,15 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index f64e41c2f5..1fd37037c0 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -238,6 +238,54 @@ std::pair FluxProgramBuilder::mcx(const ValueRange controls, return createMultiControlledOneTargetZeroParameter(controls, target); } +// YOp + +Value FluxProgramBuilder::y(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::cy(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mcy(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + +// ZOp + +Value FluxProgramBuilder::z(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::cz(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mcz(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + +// HOp + +Value FluxProgramBuilder::h(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::ch(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mch(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + // SOp Value FluxProgramBuilder::s(Value qubit) { @@ -270,6 +318,70 @@ FluxProgramBuilder::mcsdg(const ValueRange controls, const Value target) { return createMultiControlledOneTargetZeroParameter(controls, target); } +// TOp + +Value FluxProgramBuilder::t(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::ct(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mct(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + +// TdgOp + +Value FluxProgramBuilder::tdg(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::ctdg(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair +FluxProgramBuilder::mctdg(const ValueRange controls, const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + +// SXOp + +Value FluxProgramBuilder::sx(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::csx(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair FluxProgramBuilder::mcsx(const ValueRange controls, + const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + +// SXdgOp + +Value FluxProgramBuilder::sxdg(Value qubit) { + return createOneTargetZeroParameter(qubit); +} + +std::pair FluxProgramBuilder::csxdg(const Value control, + const Value target) { + return createControlledOneTargetZeroParameter(control, target); +} + +std::pair +FluxProgramBuilder::mcsxdg(const ValueRange controls, const Value target) { + return createMultiControlledOneTargetZeroParameter(controls, target); +} + // RXOp Value FluxProgramBuilder::rx(const std::variant& theta, diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 380211634a..5cd4861ef9 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -136,6 +136,18 @@ DenseElementsAttr IdOp::tryGetStaticMatrix() { DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } +// YOp + +DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } + +// ZOp + +DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } + +// HOp + +DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } + // SOp DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } @@ -146,6 +158,28 @@ DenseElementsAttr SdgOp::tryGetStaticMatrix() { return getMatrixSdg(getContext()); } +// TOp + +DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } + +// TdgOp + +DenseElementsAttr TdgOp::tryGetStaticMatrix() { + return getMatrixTdg(getContext()); +} + +// SXOp + +DenseElementsAttr SXOp::tryGetStaticMatrix() { + return getMatrixSX(getContext()); +} + +// SXdgOp + +DenseElementsAttr SXdgOp::tryGetStaticMatrix() { + return getMatrixSXdg(getContext()); +} + // RXOp DenseElementsAttr RXOp::tryGetStaticMatrix() { @@ -473,6 +507,72 @@ struct RemoveSubsequentX final : OpRewritePattern { } }; +/** + * @brief Remove subsequent Y operations on the same qubit. + */ +struct RemoveSubsequentY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(YOp yOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is a YOp + auto prevOp = yOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both YOps + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(yOp, yOp.getQubitIn()); + + return success(); + } +}; + +/** + * @brief Remove subsequent Z operations on the same qubit. + */ +struct RemoveSubsequentZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ZOp zOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is a ZOp + auto prevOp = zOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both ZOps + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(zOp, zOp.getQubitIn()); + + return success(); + } +}; + +/** + * @brief Remove subsequent H operations on the same qubit. + */ +struct RemoveSubsequentH final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(HOp hOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an HOp + auto prevOp = hOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both HOps + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(hOp, hOp.getQubitIn()); + + return success(); + } +}; + /** * @brief Remove S operations that immediately follow Sdg operations. */ @@ -517,6 +617,94 @@ struct RemoveSdgAfterS final : OpRewritePattern { } }; +/** + * @brief Remove T operations that immediately follow Tdg operations. + */ +struct RemoveTAfterTdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TOp tOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is a TdgOp + auto prevOp = tOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both Tdg and T Ops + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(tOp, tOp.getQubitIn()); + + return success(); + } +}; + +/** + * @brief Remove Tdg operations that immediately follow T operations. + */ +struct RemoveTdgAfterT final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TdgOp tdgOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is a TOp + auto prevOp = tdgOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both T and Tdg Ops + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(tdgOp, tdgOp.getQubitIn()); + + return success(); + } +}; + +/** + * @brief Remove SX operations that immediately follow SXdg operations. + */ +struct RemoveSXAfterSXdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXOp sxOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an SXdgOp + auto prevOp = sxOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both SXdg and SX Ops + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(sxOp, sxOp.getQubitIn()); + + return success(); + } +}; + +/** + * @brief Remove SXdg operations that immediately follow SX operations. + */ +struct RemoveSXdgAfterSX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXdgOp sxdgOp, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an SXOp + auto prevOp = sxdgOp.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both SX and SXdg Ops + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(sxdgOp, sxdgOp.getQubitIn()); + + return success(); + } +}; + /** * @brief Merge subsequent RX operations on the same qubit by adding their * angles. @@ -634,6 +822,21 @@ void XOp::getCanonicalizationPatterns(RewritePatternSet& results, results.add(context); } +void YOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void ZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void HOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + void SOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); @@ -644,6 +847,26 @@ void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, results.add(context); } +void TOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void TdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void SXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +void SXdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 5c4cabb74d..2acc3638b5 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -298,6 +298,45 @@ QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, return *this; } +// YOp + +QIRProgramBuilder& QIRProgramBuilder::y(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_Y); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::cy(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CY); + return *this; +} + +// ZOp + +QIRProgramBuilder& QIRProgramBuilder::z(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_Z); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::cz(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CZ); + return *this; +} + +// HOp + +QIRProgramBuilder& QIRProgramBuilder::h(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_H); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::ch(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CH); + return *this; +} + // SOp QIRProgramBuilder& QIRProgramBuilder::s(const Value qubit) { @@ -324,6 +363,58 @@ QIRProgramBuilder& QIRProgramBuilder::csdg(const Value control, return *this; } +// TOp + +QIRProgramBuilder& QIRProgramBuilder::t(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_T); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::ct(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CT); + return *this; +} + +// TdgOp + +QIRProgramBuilder& QIRProgramBuilder::tdg(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_TDG); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::ctdg(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CTDG); + return *this; +} + +// SXOp + +QIRProgramBuilder& QIRProgramBuilder::sx(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_SX); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::csx(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CSX); + return *this; +} + +// SXdgOp + +QIRProgramBuilder& QIRProgramBuilder::sxdg(const Value qubit) { + createOneTargetZeroParameter(qubit, QIR_SXDG); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::csxdg(const Value control, + const Value target) { + createControlledOneTargetZeroParameter(control, target, QIR_CSXDG); + return *this; +} + // RXOp QIRProgramBuilder& diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 7497b63912..f501d9c915 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -160,6 +160,60 @@ QuartzProgramBuilder& QuartzProgramBuilder::mcx(ValueRange controls, return *this; } +// YOp + +QuartzProgramBuilder& QuartzProgramBuilder::y(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::cy(Value control, Value target) { + return mcy({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcy(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// ZOp + +QuartzProgramBuilder& QuartzProgramBuilder::z(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::cz(Value control, Value target) { + return mcz({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcz(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// HOp + +QuartzProgramBuilder& QuartzProgramBuilder::h(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::ch(Value control, Value target) { + return mch({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mch(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + // SOp QuartzProgramBuilder& QuartzProgramBuilder::s(Value qubit) { @@ -196,6 +250,78 @@ QuartzProgramBuilder& QuartzProgramBuilder::mcsdg(ValueRange controls, return *this; } +// TOp + +QuartzProgramBuilder& QuartzProgramBuilder::t(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::ct(Value control, Value target) { + return mct({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mct(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// TdgOp + +QuartzProgramBuilder& QuartzProgramBuilder::tdg(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::ctdg(Value control, Value target) { + return mctdg({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mctdg(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// SXOp + +QuartzProgramBuilder& QuartzProgramBuilder::sx(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::csx(Value control, Value target) { + return mcsx({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcsx(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + +// SXdgOp + +QuartzProgramBuilder& QuartzProgramBuilder::sxdg(Value qubit) { + create(loc, qubit); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::csxdg(Value control, Value target) { + return mcsxdg({control}, target); +} + +QuartzProgramBuilder& QuartzProgramBuilder::mcsxdg(ValueRange controls, + Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target); }); + return *this; +} + // RXOp QuartzProgramBuilder& diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 996749be8e..f472155668 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -137,6 +137,18 @@ DenseElementsAttr IdOp::tryGetStaticMatrix() { DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } +// YOp + +DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } + +// ZOp + +DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } + +// HOp + +DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } + // SOp DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } @@ -147,6 +159,28 @@ DenseElementsAttr SdgOp::tryGetStaticMatrix() { return getMatrixSdg(getContext()); } +// TOp + +DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } + +// TdgOp + +DenseElementsAttr TdgOp::tryGetStaticMatrix() { + return getMatrixTdg(getContext()); +} + +// SXOp + +DenseElementsAttr SXOp::tryGetStaticMatrix() { + return getMatrixSX(getContext()); +} + +// SXdgOp + +DenseElementsAttr SXdgOp::tryGetStaticMatrix() { + return getMatrixSXdg(getContext()); +} + // RXOp DenseElementsAttr RXOp::tryGetStaticMatrix() { const auto& theta = getStaticParameter(getTheta()); diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index b3a863cd31..0b35bf15bf 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -286,6 +286,69 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds a Y operation + * + * @details + * Translates Y operations from the QuantumComputation to quartz.y operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The Y operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.y(target); + } else { + builder.mcy(posControls, target); + } +} + +/** + * @brief Adds a Z operation + * + * @details + * Translates Z operations from the QuantumComputation to quartz.z operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The Z operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.z(target); + } else { + builder.mcz(posControls, target); + } +} + +/** + * @brief Adds an H operation + * + * @details + * Translates H operations from the QuantumComputation to quartz.h operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The H operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addHOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.h(target); + } else { + builder.mch(posControls, target); + } +} + /** * @brief Adds an S operation * @@ -329,6 +392,92 @@ void addSdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds a T operation + * + * @details + * Translates T operations from the QuantumComputation to quartz.t operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The T operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addTOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.t(target); + } else { + builder.mct(posControls, target); + } +} + +/** + * @brief Adds a Tdg operation + * + * @details + * Translates Tdg operations from the QuantumComputation to quartz.tdg + * operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The Tdg operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addTdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.tdg(target); + } else { + builder.mctdg(posControls, target); + } +} + +/** + * @brief Adds an SX operation + * + * @details + * Translates SX operations from the QuantumComputation to quartz.sx operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The SX operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addSXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.sx(target); + } else { + builder.mcsx(posControls, target); + } +} + +/** + * @brief Adds an SXdg operation + * + * @details + * Translates SXdg operations from the QuantumComputation to quartz.sxdg + * operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The SXdg operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addSXdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.sxdg(target); + } else { + builder.mcsxdg(posControls, target); + } +} + /** * @brief Adds an RX operation * @@ -434,12 +583,33 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::X: addXOp(builder, *operation, qubits); break; + case qc::OpType::Y: + addYOp(builder, *operation, qubits); + break; + case qc::OpType::Z: + addZOp(builder, *operation, qubits); + break; + case qc::OpType::H: + addHOp(builder, *operation, qubits); + break; case qc::OpType::S: addSOp(builder, *operation, qubits); break; case qc::OpType::Sdg: addSdgOp(builder, *operation, qubits); break; + case qc::OpType::T: + addTOp(builder, *operation, qubits); + break; + case qc::OpType::Tdg: + addTdgOp(builder, *operation, qubits); + break; + case qc::OpType::SX: + addSXOp(builder, *operation, qubits); + break; + case qc::OpType::SXdg: + addSXdgOp(builder, *operation, qubits); + break; case qc::OpType::RX: addRXOp(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index b3829b71ec..a41179b75e 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1436,12 +1436,12 @@ TEST_F(CompilerPipelineTest, CX3) { }); } -TEST_F(CompilerPipelineTest, S) { +TEST_F(CompilerPipelineTest, Y) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); - qc.s(0); - qc.s(0); - qc.sdg(0); + qc.y(0); + qc.y(0); + qc.y(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1450,31 +1450,31 @@ TEST_F(CompilerPipelineTest, S) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; - b.s(q); - b.s(q); - b.sdg(q); + b.y(q); + b.y(q); + b.y(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q0 = reg[0]; - const auto q1 = b.s(q0); - const auto q2 = b.s(q1); - b.sdg(q2); + const auto q1 = b.y(q0); + const auto q2 = b.y(q1); + b.y(q2); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q0 = reg[0]; - b.s(q0); + b.y(q0); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; - b.s(q); + b.y(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; - b.s(q); + b.y(q); }); verifyAllStages({ @@ -1486,34 +1486,153 @@ TEST_F(CompilerPipelineTest, S) { }); } -TEST_F(CompilerPipelineTest, CS) { +TEST_F(CompilerPipelineTest, Z) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.cs(0, 1); + qc.addQubitRegister(1, "q"); + qc.z(0); + qc.z(0); + qc.z(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.cs(reg[0], reg[1]); + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.z(q); + b.z(q); + b.z(q); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.cs(reg[0], reg[1]); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.z(q0); + const auto q2 = b.z(q1); + b.z(q2); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); - b.cs(reg[0], reg[1]); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.z(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.z(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.z(q); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, H) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.h(0); + qc.h(0); + qc.h(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.h(q); + b.h(q); + b.h(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.h(q0); + const auto q2 = b.h(q1); + b.h(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.h(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.h(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.h(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, S) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.s(0); + qc.s(0); + qc.sdg(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.s(q); + b.s(q); + b.sdg(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.s(q0); + const auto q2 = b.s(q1); + b.sdg(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.s(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.s(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.s(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -1567,34 +1686,203 @@ TEST_F(CompilerPipelineTest, Sdg) { }); } -TEST_F(CompilerPipelineTest, CSdg) { +TEST_F(CompilerPipelineTest, T) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.csdg(0, 1); + qc.addQubitRegister(1, "q"); + qc.t(0); + qc.t(0); + qc.tdg(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.csdg(reg[0], reg[1]); + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.t(q); + b.t(q); + b.tdg(q); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.csdg(reg[0], reg[1]); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.t(q0); + const auto q2 = b.t(q1); + b.tdg(q2); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); - b.csdg(reg[0], reg[1]); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.t(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.t(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.t(q); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, Tdg) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.tdg(0); + qc.tdg(0); + qc.t(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.tdg(q); + b.tdg(q); + b.t(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.tdg(q0); + const auto q2 = b.tdg(q1); + b.t(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.tdg(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.tdg(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.tdg(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, SX) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.sx(0); + qc.sx(0); + qc.sxdg(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sx(q); + b.sx(q); + b.sxdg(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.sx(q0); + const auto q2 = b.sx(q1); + b.sxdg(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.sx(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sx(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.sx(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, SXdg) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.sxdg(0); + qc.sxdg(0); + qc.sx(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sxdg(q); + b.sxdg(q); + b.sx(q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + const auto q1 = b.sxdg(q0); + const auto q2 = b.sxdg(q1); + b.sx(q2); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q0 = reg[0]; + b.sxdg(q0); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.sxdg(q); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.sxdg(q); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } From 8130308a7309d4643e177d861aaffe3d5ff83efa Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:53:32 +0100 Subject: [PATCH 216/419] Use std::numbers::pi --- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 947c3be271..66621f7797 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -12,11 +12,12 @@ #include #include - -using namespace std::complex_literals; +#include namespace mlir::utils { +using namespace std::complex_literals; + inline DenseElementsAttr getMatrixId(MLIRContext* ctx) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); @@ -78,7 +79,7 @@ inline DenseElementsAttr getMatrixT(MLIRContext* ctx) { const auto& type = RankedTensorType::get({2, 2}, complexType); const std::complex m00 = 1.0 + 0i; const std::complex m01 = 0.0 + 0i; - const std::complex m11 = std::exp(1i * M_PI / 4.0); + const std::complex m11 = std::exp(1i * std::numbers::pi / 4.0); return DenseElementsAttr::get(type, {m00, m01, m01, m11}); } @@ -87,7 +88,7 @@ inline DenseElementsAttr getMatrixTdg(MLIRContext* ctx) { const auto& type = RankedTensorType::get({2, 2}, complexType); const std::complex m00 = 1.0 + 0i; const std::complex m01 = 0.0 + 0i; - const std::complex m11 = std::exp(-1i * M_PI / 4.0); + const std::complex m11 = std::exp(-1i * std::numbers::pi / 4.0); return DenseElementsAttr::get(type, {m00, m01, m01, m11}); } From b7ee9b91b256f79b217ffa153104d267daa2a836 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 22 Nov 2025 17:35:46 +0100 Subject: [PATCH 217/419] Improve support for nested regions --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 130 +++++++----- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 188 +++++++++--------- 2 files changed, 178 insertions(+), 140 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index aefd82cc6d..3a27142ddb 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -71,6 +70,11 @@ namespace { struct LoweringState { /// Map from original Quartz qubit references to their latest Flux SSA values llvm::DenseMap qubitMap; + + /// Modifier information + int64_t inCtrlOp = 0; + DenseMap> targetsIn; + DenseMap> targetsOut; }; /** @@ -119,24 +123,28 @@ template LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { - auto& [qubitMap] = state; - const auto inRegion = llvm::isa(op->getParentOp()); + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; // Get the atest Flux qubit const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; - if (inRegion) { - fluxQubit = rewriter.getRemappedValue(quartzQubit); - } else { + if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; + } else { + fluxQubit = state.targetsIn[inCtrlOp].front(); } // Create the Flux operation (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); // Update the state map - if (!inRegion) { + if (inCtrlOp == 0) { qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + SmallVector targetsOut({fluxOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); } rewriter.eraseOp(op); @@ -195,7 +203,7 @@ struct ConvertQuartzAllocOp final LogicalResult matchAndRewrite(quartz::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); + auto& qubitMap = getState().qubitMap; const auto& quartzQubit = op.getResult(); // Create the flux.alloc operation with preserved register metadata @@ -235,7 +243,7 @@ struct ConvertQuartzDeallocOp final LogicalResult matchAndRewrite(quartz::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); + auto& qubitMap = getState().qubitMap; const auto& quartzQubit = op.getQubit(); // Look up the latest Flux value for this Quartz qubit @@ -273,7 +281,7 @@ struct ConvertQuartzStaticOp final LogicalResult matchAndRewrite(quartz::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); + auto& qubitMap = getState().qubitMap; const auto& quartzQubit = op.getQubit(); // Create new flux.static operation with the same index @@ -322,7 +330,7 @@ struct ConvertQuartzMeasureOp final LogicalResult matchAndRewrite(quartz::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); + auto& qubitMap = getState().qubitMap; const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map @@ -373,8 +381,7 @@ struct ConvertQuartzResetOp final LogicalResult matchAndRewrite(quartz::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - // Track the Quartz qubit being reset + auto& qubitMap = getState().qubitMap; const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map @@ -654,16 +661,17 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - const auto inRegion = llvm::isa(op->getParentOp()); + auto& state = getState(); + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; - if (inRegion) { - fluxQubit = rewriter.getRemappedValue(quartzQubit); - } else { + if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; + } else { + fluxQubit = state.targetsIn[inCtrlOp].front(); } // Create flux.rx (consumes input, produces output) @@ -671,8 +679,12 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { rewriter.create(op.getLoc(), fluxQubit, adaptor.getTheta()); // Update state map - if (!inRegion) { + if (inCtrlOp == 0) { qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + SmallVector targetsOut({fluxOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); } rewriter.eraseOp(op); @@ -699,16 +711,17 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::U2Op op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - const auto inRegion = llvm::isa(op->getParentOp()); + auto& state = getState(); + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; - if (inRegion) { - fluxQubit = rewriter.getRemappedValue(quartzQubit); - } else { + if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; + } else { + fluxQubit = state.targetsIn[inCtrlOp].front(); } // Create flux.u2 (consumes input, produces output) @@ -716,8 +729,12 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { op.getLoc(), fluxQubit, adaptor.getPhi(), adaptor.getLambda()); // Update state map - if (!inRegion) { + if (inCtrlOp == 0) { qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + SmallVector targetsOut({fluxOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); } rewriter.eraseOp(op); @@ -745,20 +762,22 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); - const auto inRegion = llvm::isa(op->getParentOp()); + auto& state = getState(); + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit const auto quartzQubit0 = op->getOperand(0); const auto quartzQubit1 = op->getOperand(1); Value fluxQubit0 = nullptr; Value fluxQubit1 = nullptr; - if (inRegion) { - fluxQubit0 = rewriter.getRemappedValue(quartzQubit0); - fluxQubit1 = rewriter.getRemappedValue(quartzQubit1); - } else { + if (inCtrlOp == 0) { fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; + } else { + const auto& targetsIn = state.targetsIn[inCtrlOp]; + fluxQubit0 = targetsIn[0]; + fluxQubit1 = targetsIn[1]; } // Create flux.swap (consumes input, produces output) @@ -766,9 +785,14 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); // Update state map - if (!inRegion) { + if (inCtrlOp == 0) { qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + SmallVector targetsOut( + {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); } rewriter.eraseOp(op); @@ -801,7 +825,9 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap] = getState(); + auto& state = getState(); + auto& qubitMap = state.qubitMap; + // Get Flux controls from state map const auto& quartzControls = op.getControls(); SmallVector fluxControls; @@ -810,8 +836,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { fluxControls.push_back(qubitMap[quartzControl]); } - // Get Flux targets from state map and build mapping for region - IRMapping regionMap; + // Get Flux targets from state map const auto numTargets = op.getNumTargets(); SmallVector fluxTargets; fluxTargets.reserve(numTargets); @@ -819,7 +844,6 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { const auto& quartzTarget = op.getTarget(i); const auto& fluxTarget = qubitMap[quartzTarget]; fluxTargets.push_back(fluxTarget); - regionMap.map(quartzTarget, fluxTarget); } // Create flux.ctrl @@ -827,20 +851,25 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { rewriter.create(op.getLoc(), fluxControls, fluxTargets); // Update state map - for (const auto& [quartzControl, fluxControl] : - llvm::zip(quartzControls, fluxOp.getControlsOut())) { - qubitMap[quartzControl] = fluxControl; - } - const auto targetsOut = fluxOp.getTargetsOut(); - for (size_t i = 0; i < numTargets; ++i) { - const auto& quartzTarget = op.getTarget(i); - qubitMap[quartzTarget] = targetsOut[i]; + if (state.inCtrlOp == 0) { + for (const auto& [quartzControl, fluxControl] : + llvm::zip(quartzControls, fluxOp.getControlsOut())) { + qubitMap[quartzControl] = fluxControl; + } + const auto targetsOut = fluxOp.getTargetsOut(); + for (size_t i = 0; i < numTargets; ++i) { + const auto& quartzTarget = op.getTarget(i); + qubitMap[quartzTarget] = targetsOut[i]; + } } + // Update modifier information + state.inCtrlOp++; + state.targetsIn.try_emplace(state.inCtrlOp, fluxTargets); + // Clone body region from Quartz to Flux auto& dstRegion = fluxOp.getBody(); - rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end(), - regionMap); + rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); rewriter.eraseOp(op); return success(); @@ -866,10 +895,11 @@ struct ConvertQuartzYieldOp final LogicalResult matchAndRewrite(quartz::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto ctrlOp = dyn_cast(op->getParentOp()); - assert(ctrlOp != nullptr); - rewriter.replaceOpWithNewOp( - op, ctrlOp.getBodyUnitary()->getResults()); + auto& state = getState(); + const auto& targets = state.targetsOut[state.inCtrlOp]; + rewriter.replaceOpWithNewOp(op, targets); + state.targetsOut.erase(state.inCtrlOp); + state.inCtrlOp--; return success(); } }; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 20f53e4595..ab4e1b4c3d 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -82,8 +82,8 @@ struct LoweringState : QIRMetadata { DenseMap, Value> registerResultMap; /// Modifier information - bool inCtrlOp = false; - SmallVector posCtrls; + int64_t inCtrlOp = 0; + DenseMap> posCtrls; }; /** @@ -133,8 +133,9 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, StringRef fnName) { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const int64_t numCtrls = posCtrls.size(); // Define function argument types SmallVector argumentTypes; @@ -159,9 +160,9 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; } // Replace operation with CallOp @@ -504,7 +505,9 @@ struct ConvertQuartzIdQIR final : StatefulOpConversionPattern { // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_ID; + } else { if (numCtrls == 1) { fnName = QIR_CID; ; @@ -515,8 +518,6 @@ struct ConvertQuartzIdQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_ID; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -546,12 +547,14 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_X; + } else { if (numCtrls == 1) { fnName = QIR_CX; } else if (numCtrls == 2) { @@ -561,8 +564,6 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_X; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -592,12 +593,14 @@ struct ConvertQuartzYQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_Y; + } else { if (numCtrls == 1) { fnName = QIR_CY; } else if (numCtrls == 2) { @@ -607,8 +610,6 @@ struct ConvertQuartzYQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_Y; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -638,12 +639,14 @@ struct ConvertQuartzZQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_Z; + } else { if (numCtrls == 1) { fnName = QIR_CZ; } else if (numCtrls == 2) { @@ -653,8 +656,6 @@ struct ConvertQuartzZQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_Z; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -684,12 +685,14 @@ struct ConvertQuartzHQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_H; + } else { if (numCtrls == 1) { fnName = QIR_CH; } else if (numCtrls == 2) { @@ -699,8 +702,6 @@ struct ConvertQuartzHQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_H; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -730,12 +731,14 @@ struct ConvertQuartzSQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_S; + } else { if (numCtrls == 1) { fnName = QIR_CS; } else if (numCtrls == 2) { @@ -745,8 +748,6 @@ struct ConvertQuartzSQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_S; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -776,12 +777,14 @@ struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_SDG; + } else { if (numCtrls == 1) { fnName = QIR_CSDG; } else if (numCtrls == 2) { @@ -791,8 +794,6 @@ struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_SDG; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -822,12 +823,14 @@ struct ConvertQuartzTQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_T; + } else { if (numCtrls == 1) { fnName = QIR_CT; } else if (numCtrls == 2) { @@ -837,8 +840,6 @@ struct ConvertQuartzTQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_T; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -868,12 +869,14 @@ struct ConvertQuartzTdgQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_TDG; + } else { if (numCtrls == 1) { fnName = QIR_CTDG; } else if (numCtrls == 2) { @@ -883,8 +886,6 @@ struct ConvertQuartzTdgQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_TDG; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -914,12 +915,14 @@ struct ConvertQuartzSXQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_SX; + } else { if (numCtrls == 1) { fnName = QIR_CSX; } else if (numCtrls == 2) { @@ -929,8 +932,6 @@ struct ConvertQuartzSXQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_SX; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -960,12 +961,14 @@ struct ConvertQuartzSXdgQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); + const int64_t numCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_SXDG; + } else { if (numCtrls == 1) { fnName = QIR_CSXDG; } else if (numCtrls == 2) { @@ -975,8 +978,6 @@ struct ConvertQuartzSXdgQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_SXDG; } return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), @@ -1006,13 +1007,16 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { auto& state = getState(); // Query state for modifier information - const bool inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_RX; + } else { if (numCtrls == 1) { fnName = QIR_CRX; } else if (numCtrls == 2) { @@ -1022,8 +1026,6 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_RX; } // Define function argument types @@ -1052,9 +1054,9 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; } // Replace operation with CallOp @@ -1087,12 +1089,15 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_U2; + } else { if (numCtrls == 1) { fnName = QIR_CU2; } else if (numCtrls == 2) { @@ -1102,8 +1107,6 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_U2; } // Define function argument types @@ -1134,9 +1137,9 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; } // Replace operation with CallOp @@ -1169,12 +1172,15 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; const auto numCtrls = posCtrls.size(); // Define function name StringRef fnName; - if (inCtrlOp) { + if (inCtrlOp == 0) { + fnName = QIR_SWAP; + } else { if (numCtrls == 1) { fnName = QIR_CSWAP; } else if (numCtrls == 2) { @@ -1184,8 +1190,6 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { } else { return failure(); } - } else { - fnName = QIR_SWAP; } // Define function argument types @@ -1213,9 +1217,9 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); // Clean up modifier information - if (inCtrlOp) { - state.inCtrlOp = false; - state.posCtrls.clear(); + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; } // Replace operation with CallOp @@ -1235,9 +1239,10 @@ struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { // Update modifier information auto& state = getState(); - state.inCtrlOp = true; - state.posCtrls.append(adaptor.getControls().begin(), - adaptor.getControls().end()); + state.inCtrlOp++; + const SmallVector posCtrls(adaptor.getControls().begin(), + adaptor.getControls().end()); + state.posCtrls[state.inCtrlOp] = posCtrls; // Inline region and remove operation rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), @@ -1273,7 +1278,8 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { * * Conversion stages: * 1. Convert func dialect to LLVM - * 2. Ensure proper block structure for QIR base profile and add initialization + * 2. Ensure proper block structure for QIR base profile and add + * initialization * 3. Convert Quartz operations to QIR calls * 4. Set QIR metadata attributes * 5. Convert arith and cf dialects to LLVM @@ -1448,7 +1454,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { OpBuilder builder(ctx); const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Find the output block (4th block: entry, body, measurements, output, end) + // Find the output block (4th block: entry, body, measurements, output, + // end) auto& outputBlock = main.getBlocks().back(); // Insert before the branch to end block @@ -1532,15 +1539,16 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * measure, reset) and add output recording to the output block. * * **Stage 4: QIR attributes** - * Add QIR base profile metadata to the main function, including qubit/result - * counts and version information. + * Add QIR base profile metadata to the main function, including + * qubit/result counts and version information. * * **Stage 5: Standard dialects to LLVM** * Convert arith and control flow dialects to LLVM (for index arithmetic and * function control flow). * * **Stage 6: Reconcile casts** - * Clean up any unrealized cast operations introduced during type conversion. + * Clean up any unrealized cast operations introduced during type + * conversion. */ void runOnOperation() override { MLIRContext* ctx = &getContext(); From 2aca4ab163e22e1849d0b5a1d6a34b16f0a25b2d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:15:15 +0100 Subject: [PATCH 218/419] Streamline canonicalizations by defining template functions --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 223 +++++++++------------------ 1 file changed, 73 insertions(+), 150 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 5cd4861ef9..e2099ec55e 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -437,16 +437,16 @@ namespace { struct RemoveAllocDeallocPair final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(DeallocOp deallocOp, + LogicalResult matchAndRewrite(DeallocOp op, PatternRewriter& rewriter) const override { // Check if the predecessor is an AllocOp - auto allocOp = deallocOp.getQubit().getDefiningOp(); + auto allocOp = op.getQubit().getDefiningOp(); if (!allocOp) { return failure(); } // Remove the AllocOp and the DeallocOp - rewriter.eraseOp(deallocOp); + rewriter.eraseOp(op); rewriter.eraseOp(allocOp); return success(); } @@ -458,16 +458,15 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { struct RemoveResetAfterAlloc final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(ResetOp resetOp, + LogicalResult matchAndRewrite(ResetOp op, PatternRewriter& rewriter) const override { // Check if the predecessor is an AllocOp - if (auto allocOp = resetOp.getQubitIn().getDefiningOp(); - !allocOp) { + if (auto allocOp = op.getQubitIn().getDefiningOp(); !allocOp) { return failure(); } // Remove the ResetOp - rewriter.replaceOp(resetOp, resetOp.getQubitIn()); + rewriter.replaceOp(op, op.getQubitIn()); return success(); } }; @@ -478,32 +477,46 @@ struct RemoveResetAfterAlloc final : OpRewritePattern { struct RemoveId final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(IdOp idOp, + LogicalResult matchAndRewrite(IdOp op, PatternRewriter& rewriter) const override { - rewriter.replaceOp(idOp, idOp.getQubitIn()); + rewriter.replaceOp(op, op.getQubitIn()); return success(); } }; +/** + * @brief Remove a pair of inverse operations. + * + * @tparam InverseOpType The type of the inverse operation. + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance to be checked. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +LogicalResult removeInversePair(OpType op, PatternRewriter& rewriter) { + // Check if the predecessor is the inverse operation + auto prevOp = op.getQubitIn().template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both XOps + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(op, op.getQubitIn()); + + return success(); +} + /** * @brief Remove subsequent X operations on the same qubit. */ struct RemoveSubsequentX final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(XOp xOp, + LogicalResult matchAndRewrite(XOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an XOp - auto prevOp = xOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both XOps - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(xOp, xOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -513,19 +526,9 @@ struct RemoveSubsequentX final : OpRewritePattern { struct RemoveSubsequentY final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(YOp yOp, + LogicalResult matchAndRewrite(YOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is a YOp - auto prevOp = yOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both YOps - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(yOp, yOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -535,19 +538,9 @@ struct RemoveSubsequentY final : OpRewritePattern { struct RemoveSubsequentZ final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(ZOp zOp, + LogicalResult matchAndRewrite(ZOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is a ZOp - auto prevOp = zOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both ZOps - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(zOp, zOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -557,19 +550,9 @@ struct RemoveSubsequentZ final : OpRewritePattern { struct RemoveSubsequentH final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(HOp hOp, + LogicalResult matchAndRewrite(HOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an HOp - auto prevOp = hOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both HOps - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(hOp, hOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -579,19 +562,9 @@ struct RemoveSubsequentH final : OpRewritePattern { struct RemoveSAfterSdg final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(SOp sOp, + LogicalResult matchAndRewrite(SOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an SdgOp - auto prevOp = sOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both Sdg and S Ops - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(sOp, sOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -601,19 +574,9 @@ struct RemoveSAfterSdg final : OpRewritePattern { struct RemoveSdgAfterS final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(SdgOp sdgOp, + LogicalResult matchAndRewrite(SdgOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an SOp - auto prevOp = sdgOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both S and Sdg Ops - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(sdgOp, sdgOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -623,19 +586,9 @@ struct RemoveSdgAfterS final : OpRewritePattern { struct RemoveTAfterTdg final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(TOp tOp, + LogicalResult matchAndRewrite(TOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is a TdgOp - auto prevOp = tOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both Tdg and T Ops - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(tOp, tOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -645,19 +598,9 @@ struct RemoveTAfterTdg final : OpRewritePattern { struct RemoveTdgAfterT final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(TdgOp tdgOp, + LogicalResult matchAndRewrite(TdgOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is a TOp - auto prevOp = tdgOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both T and Tdg Ops - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(tdgOp, tdgOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -667,19 +610,9 @@ struct RemoveTdgAfterT final : OpRewritePattern { struct RemoveSXAfterSXdg final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(SXOp sxOp, + LogicalResult matchAndRewrite(SXOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an SXdgOp - auto prevOp = sxOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both SXdg and SX Ops - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(sxOp, sxOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -689,19 +622,9 @@ struct RemoveSXAfterSXdg final : OpRewritePattern { struct RemoveSXdgAfterSX final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(SXdgOp sxdgOp, + LogicalResult matchAndRewrite(SXdgOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an SXOp - auto prevOp = sxdgOp.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both SX and SXdg Ops - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(sxdgOp, sxdgOp.getQubitIn()); - - return success(); + return removeInversePair(op, rewriter); } }; @@ -712,18 +635,18 @@ struct RemoveSXdgAfterSX final : OpRewritePattern { struct MergeSubsequentRX final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(RXOp rxOp, + LogicalResult matchAndRewrite(RXOp op, PatternRewriter& rewriter) const override { // Check if the predecessor is an RXOp - auto prevOp = rxOp.getQubitIn().getDefiningOp(); + auto prevOp = op.getQubitIn().getDefiningOp(); if (!prevOp) { return failure(); } // Compute and set new theta - auto newTheta = rewriter.create( - rxOp.getLoc(), rxOp.getTheta(), prevOp.getTheta()); - rxOp->setOperand(1, newTheta.getResult()); + auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), + prevOp.getTheta()); + op->setOperand(1, newTheta.getResult()); // Trivialize previous RXOp rewriter.replaceOp(prevOp, prevOp.getQubitIn()); @@ -738,22 +661,21 @@ struct MergeSubsequentRX final : OpRewritePattern { struct MergeNestedCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, + LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto bodyUnitary = ctrlOp.getBodyUnitary(); + auto bodyUnitary = op.getBodyUnitary(); auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); if (!bodyCtrlOp) { return failure(); } // Merge controls - SmallVector newControls(ctrlOp.getControlsIn()); + SmallVector newControls(op.getControlsIn()); for (const auto control : bodyCtrlOp.getControlsIn()) { newControls.push_back(control); } - rewriter.replaceOpWithNewOp(ctrlOp, newControls, - ctrlOp.getTargetsIn(), + rewriter.replaceOpWithNewOp(op, newControls, op.getTargetsIn(), bodyCtrlOp.getBodyUnitary()); rewriter.eraseOp(bodyCtrlOp); @@ -767,34 +689,35 @@ struct MergeNestedCtrl final : OpRewritePattern { struct RemoveTrivialCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, + LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - if (ctrlOp.getNumControls() > 0) { + if (op.getNumControls() > 0) { return failure(); } - rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); + rewriter.replaceOp(op, op.getBodyUnitary()); return success(); } }; +/** + * @brief Inline controlled identity operations. + */ struct CtrlInlineId final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, + LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - if (!llvm::isa(ctrlOp.getBodyUnitary().getOperation())) { + if (!llvm::isa(op.getBodyUnitary().getOperation())) { return failure(); } - auto idOp = - rewriter.create(ctrlOp.getLoc(), ctrlOp.getTargetsIn().front()); + auto idOp = rewriter.create(op.getLoc(), op.getTargetsIn().front()); SmallVector newOperands; - newOperands.reserve(ctrlOp.getNumControls() + 1); - newOperands.append(ctrlOp.getControlsIn().begin(), - ctrlOp.getControlsIn().end()); + newOperands.reserve(op.getNumControls() + 1); + newOperands.append(op.getControlsIn().begin(), op.getControlsIn().end()); newOperands.push_back(idOp.getQubitOut()); - rewriter.replaceOp(ctrlOp, newOperands); + rewriter.replaceOp(op, newOperands); return success(); } From 8798f342be03f04e333f09124f9475a38a28faed Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:47:25 +0100 Subject: [PATCH 219/419] Fix linter errors --- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 2 +- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 10 +++--- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 32 +++++++------------ .../Flux/Builder/FluxProgramBuilder.cpp | 1 + 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 66621f7797..de19952ab7 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -138,7 +138,7 @@ inline DenseElementsAttr getMatrixSWAP(MLIRContext* ctx) { } inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, - int64_t numControls, + size_t numControls, mlir::DenseElementsAttr target) { // Get dimensions of target matrix const auto& targetType = llvm::dyn_cast(target.getType()); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 3a27142ddb..882d92f29e 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -15,9 +15,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -143,7 +143,7 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, qubitMap[quartzQubit] = fluxOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({fluxOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -683,7 +683,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { qubitMap[quartzQubit] = fluxOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({fluxOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -733,7 +733,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { qubitMap[quartzQubit] = fluxOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({fluxOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -790,7 +790,7 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); } else { state.targetsIn.erase(inCtrlOp); - SmallVector targetsOut( + const SmallVector targetsOut( {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index ab4e1b4c3d..863455ef2e 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -135,7 +135,7 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, const auto inCtrlOp = state.inCtrlOp; const SmallVector posCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const int64_t numCtrls = posCtrls.size(); + const size_t numCtrls = posCtrls.size(); // Define function argument types SmallVector argumentTypes; @@ -547,8 +547,7 @@ struct ConvertQuartzXQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -593,8 +592,7 @@ struct ConvertQuartzYQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -639,8 +637,7 @@ struct ConvertQuartzZQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -685,8 +682,7 @@ struct ConvertQuartzHQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -731,8 +727,7 @@ struct ConvertQuartzSQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -777,8 +772,7 @@ struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -823,8 +817,7 @@ struct ConvertQuartzTQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -869,8 +862,7 @@ struct ConvertQuartzTdgQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -915,8 +907,7 @@ struct ConvertQuartzSXQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -961,8 +952,7 @@ struct ConvertQuartzSXdgQIR final : StatefulOpConversionPattern { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const int64_t numCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 1fd37037c0..6c19f92f98 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include From f55ebba0db8c47c7bee5dcb781077ab777359b7f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 22 Nov 2025 22:48:53 +0100 Subject: [PATCH 220/419] Ignore this one linter error --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index a41179b75e..b6716a48db 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -330,6 +330,7 @@ bool areIndependentGroupsEquivalent(ArrayRef lhsOps, return false; } + // NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order) for (const auto& [lhsOp, lhsCount] : lhsFrequencyMap) { auto it = rhsFrequencyMap.find(lhsOp); if (it == rhsFrequencyMap.end() || it->second != lhsCount) { From 80715e7090923637be8a53c0289aa19e84df8073 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:15:00 +0100 Subject: [PATCH 221/419] Fix remaining linter errors --- .../TranslateQuantumComputationToQuartz.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 0b35bf15bf..fd4a0e86a9 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -49,7 +49,7 @@ struct QregInfo { }; using BitMemInfo = std::pair; // (register ref, localIdx) + size_t>; // (register ref, localIdx) using BitIndexVec = llvm::SmallVector; /** @@ -90,8 +90,8 @@ allocateQregs(QuartzProgramBuilder& builder, // Allocate quantum registers using the builder llvm::SmallVector qregs; for (const auto* qregPtr : qregPtrs) { - auto qubits = - builder.allocQubitRegister(qregPtr->getSize(), qregPtr->getName()); + auto qubits = builder.allocQubitRegister( + static_cast(qregPtr->getSize()), qregPtr->getName()); qregs.emplace_back(qregPtr, std::move(qubits)); } @@ -118,7 +118,7 @@ buildQubitMap(const qc::QuantumComputation& quantumComputation, flatQubits.resize(static_cast(maxPhys) + 1); for (const auto& qreg : qregs) { - for (std::size_t i = 0; i < qreg.qregPtr->getSize(); ++i) { + for (size_t i = 0; i < qreg.qregPtr->getSize(); ++i) { const auto globalIdx = qreg.qregPtr->getStartIndex() + i; flatQubits[globalIdx] = qreg.qubits[i]; } @@ -160,10 +160,10 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, BitIndexVec bitMap; bitMap.resize(quantumComputation.getNcbits()); for (const auto* reg : cregPtrs) { - auto& mem = - builder.allocClassicalBitRegister(reg->getSize(), reg->getName()); - for (std::size_t i = 0; i < reg->getSize(); ++i) { - const auto globalIdx = static_cast(reg->getStartIndex() + i); + auto& mem = builder.allocClassicalBitRegister( + static_cast(reg->getSize()), reg->getName()); + for (size_t i = 0; i < reg->getSize(); ++i) { + const auto globalIdx = static_cast(reg->getStartIndex() + i); bitMap[globalIdx] = {&mem, i}; } } @@ -191,13 +191,13 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const auto& targets = measureOp.getTargets(); const auto& classics = measureOp.getClassics(); - for (std::size_t i = 0; i < targets.size(); ++i) { + for (size_t i = 0; i < targets.size(); ++i) { const auto& qubit = qubits[targets[i]]; - const auto bitIdx = static_cast(classics[i]); + const auto bitIdx = static_cast(classics[i]); const auto& [mem, localIdx] = bitMap[bitIdx]; // Use builder's measure method which keeps output record - builder.measure(qubit, (*mem)[localIdx]); + builder.measure(qubit, (*mem)[static_cast(localIdx)]); } } From cfe157634a15c882c069b3eeeaa3010edb5dc403 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:36:35 +0100 Subject: [PATCH 222/419] Include missing header --- .../Quartz/Translation/TranslateQuantumComputationToQuartz.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index fd4a0e86a9..166763ce66 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include From d6ced4d536f145667d4a513c4f19ece61f3ce75a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 01:26:19 +0100 Subject: [PATCH 223/419] Modularize standard gates --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 40 ++ mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 3 + mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 369 ------------------ .../lib/Dialect/Flux/IR/StandardGates/HOp.cpp | 44 +++ .../Dialect/Flux/IR/StandardGates/IdOp.cpp | 46 +++ .../Dialect/Flux/IR/StandardGates/RXOp.cpp | 78 ++++ .../lib/Dialect/Flux/IR/StandardGates/SOp.cpp | 44 +++ .../Dialect/Flux/IR/StandardGates/SWAPOp.cpp | 20 + .../Dialect/Flux/IR/StandardGates/SXOp.cpp | 46 +++ .../Dialect/Flux/IR/StandardGates/SXdgOp.cpp | 46 +++ .../Dialect/Flux/IR/StandardGates/SdgOp.cpp | 46 +++ .../lib/Dialect/Flux/IR/StandardGates/TOp.cpp | 44 +++ .../Dialect/Flux/IR/StandardGates/TdgOp.cpp | 46 +++ .../Dialect/Flux/IR/StandardGates/U2Op.cpp | 51 +++ .../lib/Dialect/Flux/IR/StandardGates/XOp.cpp | 44 +++ .../lib/Dialect/Flux/IR/StandardGates/YOp.cpp | 44 +++ .../lib/Dialect/Flux/IR/StandardGates/ZOp.cpp | 44 +++ mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 3 + mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 124 ------ .../Dialect/Quartz/IR/StandardGates/HOp.cpp | 18 + .../Dialect/Quartz/IR/StandardGates/IdOp.cpp | 20 + .../Dialect/Quartz/IR/StandardGates/RXOp.cpp | 38 ++ .../Dialect/Quartz/IR/StandardGates/SOp.cpp | 18 + .../Quartz/IR/StandardGates/SWAPOp.cpp | 20 + .../Dialect/Quartz/IR/StandardGates/SXOp.cpp | 20 + .../Quartz/IR/StandardGates/SXdgOp.cpp | 20 + .../Dialect/Quartz/IR/StandardGates/SdgOp.cpp | 20 + .../Dialect/Quartz/IR/StandardGates/TOp.cpp | 18 + .../Dialect/Quartz/IR/StandardGates/TdgOp.cpp | 20 + .../Dialect/Quartz/IR/StandardGates/U2Op.cpp | 51 +++ .../Dialect/Quartz/IR/StandardGates/XOp.cpp | 18 + .../Dialect/Quartz/IR/StandardGates/YOp.cpp | 18 + .../Dialect/Quartz/IR/StandardGates/ZOp.cpp | 18 + 33 files changed, 1006 insertions(+), 493 deletions(-) create mode 100644 mlir/include/mlir/Dialect/Flux/FluxUtils.h create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h new file mode 100644 index 0000000000..2d8b6b946e --- /dev/null +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include + +namespace mlir::flux { + +/** + * @brief Remove a pair of inverse operations. + * + * @tparam InverseOpType The type of the inverse operation. + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance to be checked. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult removeInversePair(OpType op, + mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the inverse operation + auto prevOp = op.getQubitIn().template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Remove both operations + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(op, op.getQubitIn()); + + return success(); +} + +} // namespace mlir::flux diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt index 7babc99a63..bc226f052e 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -6,9 +6,12 @@ # # Licensed under the MIT License +file(GLOB_RECURSE STANDARD_GATES "${CMAKE_CURRENT_SOURCE_DIR}/StandardGates/*.cpp") + add_mlir_dialect_library( MLIRFluxDialect FluxOps.cpp + ${STANDARD_GATES} ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux DEPENDS diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index e2099ec55e..e4b3e6575b 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -122,131 +122,6 @@ LogicalResult MeasureOp::verify() { return success(); } -//===----------------------------------------------------------------------===// -// Unitary Operations -//===----------------------------------------------------------------------===// - -// IdOp - -DenseElementsAttr IdOp::tryGetStaticMatrix() { - return getMatrixId(getContext()); -} - -// XOp - -DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } - -// YOp - -DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } - -// ZOp - -DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } - -// HOp - -DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } - -// SOp - -DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } - -// SdgOp - -DenseElementsAttr SdgOp::tryGetStaticMatrix() { - return getMatrixSdg(getContext()); -} - -// TOp - -DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } - -// TdgOp - -DenseElementsAttr TdgOp::tryGetStaticMatrix() { - return getMatrixTdg(getContext()); -} - -// SXOp - -DenseElementsAttr SXOp::tryGetStaticMatrix() { - return getMatrixSX(getContext()); -} - -// SXdgOp - -DenseElementsAttr SXdgOp::tryGetStaticMatrix() { - return getMatrixSXdg(getContext()); -} - -// RXOp - -DenseElementsAttr RXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRX(getContext(), thetaValue); -} - -void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) - const std::variant& theta) { - Value thetaOperand = nullptr; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - build(odsBuilder, odsState, qubit_in, thetaOperand); -} - -// U2Op - -DenseElementsAttr U2Op::tryGetStaticMatrix() { - const auto phi = getStaticParameter(getPhi()); - const auto lambda = getStaticParameter(getLambda()); - if (!phi || !lambda) { - return nullptr; - } - const auto phiValue = phi.getValueAsDouble(); - const auto lambdaValue = lambda.getValueAsDouble(); - return getMatrixU2(getContext(), phiValue, lambdaValue); -} - -void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) - const std::variant& phi, - const std::variant& lambda) { - Value phiOperand = nullptr; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand = nullptr; - if (std::holds_alternative(lambda)) { - lambdaOperand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(lambda))); - } else { - lambdaOperand = std::get(lambda); - } - - build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); -} - -// SWAPOp - -DenseElementsAttr SWAPOp::tryGetStaticMatrix() { - return getMatrixSWAP(getContext()); -} - //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// @@ -471,190 +346,6 @@ struct RemoveResetAfterAlloc final : OpRewritePattern { } }; -/** - * @brief Remove identity operations. - */ -struct RemoveId final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(IdOp op, - PatternRewriter& rewriter) const override { - rewriter.replaceOp(op, op.getQubitIn()); - return success(); - } -}; - -/** - * @brief Remove a pair of inverse operations. - * - * @tparam InverseOpType The type of the inverse operation. - * @tparam OpType The type of the operation to be checked. - * @param op The operation instance to be checked. - * @param rewriter The pattern rewriter. - * @return LogicalResult Success or failure of the removal. - */ -template -LogicalResult removeInversePair(OpType op, PatternRewriter& rewriter) { - // Check if the predecessor is the inverse operation - auto prevOp = op.getQubitIn().template getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Remove both XOps - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(op, op.getQubitIn()); - - return success(); -} - -/** - * @brief Remove subsequent X operations on the same qubit. - */ -struct RemoveSubsequentX final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(XOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove subsequent Y operations on the same qubit. - */ -struct RemoveSubsequentY final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(YOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove subsequent Z operations on the same qubit. - */ -struct RemoveSubsequentZ final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(ZOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove subsequent H operations on the same qubit. - */ -struct RemoveSubsequentH final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(HOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove S operations that immediately follow Sdg operations. - */ -struct RemoveSAfterSdg final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(SOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove Sdg operations that immediately follow S operations. - */ -struct RemoveSdgAfterS final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(SdgOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove T operations that immediately follow Tdg operations. - */ -struct RemoveTAfterTdg final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(TOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove Tdg operations that immediately follow T operations. - */ -struct RemoveTdgAfterT final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(TdgOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove SX operations that immediately follow SXdg operations. - */ -struct RemoveSXAfterSXdg final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(SXOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Remove SXdg operations that immediately follow SX operations. - */ -struct RemoveSXdgAfterSX final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(SXdgOp op, - PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); - } -}; - -/** - * @brief Merge subsequent RX operations on the same qubit by adding their - * angles. - */ -struct MergeSubsequentRX final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(RXOp op, - PatternRewriter& rewriter) const override { - // Check if the predecessor is an RXOp - auto prevOp = op.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Compute and set new theta - auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), - prevOp.getTheta()); - op->setOperand(1, newTheta.getResult()); - - // Trivialize previous RXOp - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - - return success(); - } -}; - /** * @brief Merge nested control modifiers into a single one. */ @@ -735,66 +426,6 @@ void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, results.add(context); } -void IdOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void XOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void YOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void ZOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void HOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void SOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void TOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void TdgOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void SXOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void SXdgOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp new file mode 100644 index 0000000000..7d1fc15f3d --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove subsequent H operations on the same qubit. + */ +struct RemoveSubsequentH final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(HOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } + +void HOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp new file mode 100644 index 0000000000..f651272398 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove identity operations. + */ +struct RemoveId final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(IdOp op, + PatternRewriter& rewriter) const override { + rewriter.replaceOp(op, op.getQubitIn()); + return success(); + } +}; + +} // namespace + +DenseElementsAttr IdOp::tryGetStaticMatrix() { + return getMatrixId(getContext()); +} + +void IdOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp new file mode 100644 index 0000000000..cebf4008d4 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RX operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an RXOp + auto prevOp = op.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Compute and set new theta + auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), + prevOp.getTheta()); + op->setOperand(1, newTheta.getResult()); + + // Trivialize previous RXOp + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + + return success(); + } +}; + +} // namespace + +DenseElementsAttr RXOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRX(getContext(), thetaValue); +} + +void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit_in, thetaOperand); +} + +void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp new file mode 100644 index 0000000000..03fed9f9b4 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove S operations that immediately follow Sdg operations. + */ +struct RemoveSAfterSdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } + +void SOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp new file mode 100644 index 0000000000..3253ee6eb8 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr SWAPOp::tryGetStaticMatrix() { + return getMatrixSWAP(getContext()); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp new file mode 100644 index 0000000000..37068b5382 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove SX operations that immediately follow SXdg operations. + */ +struct RemoveSXAfterSXdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr SXOp::tryGetStaticMatrix() { + return getMatrixSX(getContext()); +} + +void SXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp new file mode 100644 index 0000000000..0096a2bca0 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove SXdg operations that immediately follow SX operations. + */ +struct RemoveSXdgAfterSX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXdgOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr SXdgOp::tryGetStaticMatrix() { + return getMatrixSXdg(getContext()); +} + +void SXdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp new file mode 100644 index 0000000000..f12b653766 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove Sdg operations that immediately follow S operations. + */ +struct RemoveSdgAfterS final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SdgOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr SdgOp::tryGetStaticMatrix() { + return getMatrixSdg(getContext()); +} + +void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp new file mode 100644 index 0000000000..379b598776 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove T operations that immediately follow Tdg operations. + */ +struct RemoveTAfterTdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } + +void TOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp new file mode 100644 index 0000000000..9b976e43b9 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove Tdg operations that immediately follow T operations. + */ +struct RemoveTdgAfterT final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TdgOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr TdgOp::tryGetStaticMatrix() { + return getMatrixTdg(getContext()); +} + +void TdgOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp new file mode 100644 index 0000000000..f82255645c --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr U2Op::tryGetStaticMatrix() { + const auto phi = getStaticParameter(getPhi()); + const auto lambda = getStaticParameter(getLambda()); + if (!phi || !lambda) { + return nullptr; + } + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + return getMatrixU2(getContext(), phiValue, lambdaValue); +} + +void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& phi, + const std::variant& lambda) { + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); + } else { + phiOperand = std::get(phi); + } + + Value lambdaOperand = nullptr; + if (std::holds_alternative(lambda)) { + lambdaOperand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(lambda))); + } else { + lambdaOperand = std::get(lambda); + } + + build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp new file mode 100644 index 0000000000..49d9557d76 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove subsequent X operations on the same qubit. + */ +struct RemoveSubsequentX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } + +void XOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp new file mode 100644 index 0000000000..1e241e86a8 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove subsequent Y operations on the same qubit. + */ +struct RemoveSubsequentY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(YOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } + +void YOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp new file mode 100644 index 0000000000..67ef6345e4 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove subsequent Z operations on the same qubit. + */ +struct RemoveSubsequentZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ZOp op, + PatternRewriter& rewriter) const override { + return removeInversePair(op, rewriter); + } +}; + +} // namespace + +DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } + +void ZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt index 0c0e3aa280..282a4962c1 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -6,9 +6,12 @@ # # Licensed under the MIT License +file(GLOB_RECURSE STANDARD_GATES "${CMAKE_CURRENT_SOURCE_DIR}/StandardGates/*.cpp") + add_mlir_dialect_library( MLIRQuartzDialect QuartzOps.cpp + ${STANDARD_GATES} ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz DEPENDS diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index f472155668..28b040e0eb 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -123,130 +123,6 @@ LogicalResult MeasureOp::verify() { return success(); } -//===----------------------------------------------------------------------===// -// Unitary Operations -//===----------------------------------------------------------------------===// - -// IdOp - -DenseElementsAttr IdOp::tryGetStaticMatrix() { - return getMatrixId(getContext()); -} - -// XOp - -DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } - -// YOp - -DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } - -// ZOp - -DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } - -// HOp - -DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } - -// SOp - -DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } - -// SdgOp - -DenseElementsAttr SdgOp::tryGetStaticMatrix() { - return getMatrixSdg(getContext()); -} - -// TOp - -DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } - -// TdgOp - -DenseElementsAttr TdgOp::tryGetStaticMatrix() { - return getMatrixTdg(getContext()); -} - -// SXOp - -DenseElementsAttr SXOp::tryGetStaticMatrix() { - return getMatrixSX(getContext()); -} - -// SXdgOp - -DenseElementsAttr SXdgOp::tryGetStaticMatrix() { - return getMatrixSXdg(getContext()); -} - -// RXOp -DenseElementsAttr RXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRX(getContext(), thetaValue); -} - -void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) - const std::variant& theta) { - Value thetaOperand = nullptr; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - build(odsBuilder, odsState, qubit_in, thetaOperand); -} - -// U2Op - -DenseElementsAttr U2Op::tryGetStaticMatrix() { - const auto phi = getStaticParameter(getPhi()); - const auto lambda = getStaticParameter(getLambda()); - if (!phi || !lambda) { - return nullptr; - } - const auto phiValue = phi.getValueAsDouble(); - const auto lambdaValue = lambda.getValueAsDouble(); - return getMatrixU2(getContext(), phiValue, lambdaValue); -} - -void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) - const std::variant& phi, - const std::variant& lambda) { - Value phiOperand = nullptr; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand = nullptr; - if (std::holds_alternative(lambda)) { - lambdaOperand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(lambda))); - } else { - lambdaOperand = std::get(lambda); - } - - build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); -} - -// SWAPOp - -DenseElementsAttr SWAPOp::tryGetStaticMatrix() { - return getMatrixSWAP(getContext()); -} - //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp new file mode 100644 index 0000000000..3eab468335 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp new file mode 100644 index 0000000000..776a9144d4 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr IdOp::tryGetStaticMatrix() { + return getMatrixId(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp new file mode 100644 index 0000000000..12ff530478 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RXOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRX(getContext(), thetaValue); +} + +void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit_in, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp new file mode 100644 index 0000000000..dea06b8f7f --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp new file mode 100644 index 0000000000..008521a49f --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr SWAPOp::tryGetStaticMatrix() { + return getMatrixSWAP(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp new file mode 100644 index 0000000000..3bc2d38a35 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr SXOp::tryGetStaticMatrix() { + return getMatrixSX(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp new file mode 100644 index 0000000000..44aed3ba16 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr SXdgOp::tryGetStaticMatrix() { + return getMatrixSXdg(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp new file mode 100644 index 0000000000..3d2dff68f2 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr SdgOp::tryGetStaticMatrix() { + return getMatrixSdg(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp new file mode 100644 index 0000000000..9e48d36bba --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp new file mode 100644 index 0000000000..c5a3d9cbaf --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr TdgOp::tryGetStaticMatrix() { + return getMatrixTdg(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp new file mode 100644 index 0000000000..cf37d59190 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr U2Op::tryGetStaticMatrix() { + const auto phi = getStaticParameter(getPhi()); + const auto lambda = getStaticParameter(getLambda()); + if (!phi || !lambda) { + return nullptr; + } + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + return getMatrixU2(getContext(), phiValue, lambdaValue); +} + +void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit_in, // NOLINT(*-identifier-naming) + const std::variant& phi, + const std::variant& lambda) { + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); + } else { + phiOperand = std::get(phi); + } + + Value lambdaOperand = nullptr; + if (std::holds_alternative(lambda)) { + lambdaOperand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(lambda))); + } else { + lambdaOperand = std::get(lambda); + } + + build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp new file mode 100644 index 0000000000..14c147ae0f --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp new file mode 100644 index 0000000000..0b59d99b54 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp new file mode 100644 index 0000000000..dd08e0152a --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } From 7eb161a96346bc03291495f2ab7567e2c0714978 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 01:53:17 +0100 Subject: [PATCH 224/419] Fix linter errors --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 2 -- mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp | 5 +++++ mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp | 6 ++++++ mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp | 2 ++ mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 2 -- mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp | 6 ++++++ mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp | 6 ++++++ mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp | 2 ++ mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp | 2 ++ 30 files changed, 71 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index e4b3e6575b..eee067de2b 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -25,13 +25,11 @@ #include #include #include -#include #include #include #include #include #include -#include using namespace mlir; using namespace mlir::flux; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp index 7d1fc15f3d..fceb29854f 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp index f651272398..0d16126ecf 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp index cebf4008d4..46902c7e1b 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp @@ -11,9 +11,14 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include +#include +#include #include #include #include +#include using namespace mlir; using namespace mlir::flux; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp index 03fed9f9b4..f63785bba3 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp index 3253ee6eb8..81d949c078 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp index 37068b5382..b409dd44b2 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp index 0096a2bca0..873ce16a4d 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp index f12b653766..4d13a9b9f0 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp index 379b598776..de1a169b62 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp index 9b976e43b9..ea864798da 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp index f82255645c..bb673995cb 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp @@ -11,6 +11,12 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include +#include +#include +#include + using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp index 49d9557d76..ed65e904ef 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp index 1e241e86a8..60c6157d42 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp index 67ef6345e4..782a66b4e5 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 28b040e0eb..26210d5a4e 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -24,13 +24,11 @@ #include #include #include -#include #include #include #include #include #include -#include using namespace mlir; using namespace mlir::quartz; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp index 3eab468335..791cb37feb 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp index 776a9144d4..2b01b03cc6 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp index 12ff530478..fadd30c2a3 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp @@ -11,6 +11,12 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include +#include +#include +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp index dea06b8f7f..a0eba979f0 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp index 008521a49f..c3f2b01ec4 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp index 3bc2d38a35..38a07bf840 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp index 44aed3ba16..ae535a853f 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp index 3d2dff68f2..dcf0f1f2a8 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp index 9e48d36bba..58ff397214 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp index c5a3d9cbaf..bed0eae7c6 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp index cf37d59190..5c61f6dbbf 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp @@ -11,6 +11,12 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include +#include +#include +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp index 14c147ae0f..efba2f635f 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp index 0b59d99b54..adf92e63ad 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp index dd08e0152a..71eee1c654 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include + using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; From 92019ce3a170e0b84ee5d3730f6434882b5f65f3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:07:30 +0100 Subject: [PATCH 225/419] Add support for RY --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 133 +++++++++++++-- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 28 ++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 60 +++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 + .../Quartz/Builder/QuartzProgramBuilder.h | 73 ++++++++- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 25 +++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 14 +- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 65 ++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 113 ++++++++----- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 137 ++++++++++++---- .../Flux/Builder/FluxProgramBuilder.cpp | 111 +++++++++---- .../Dialect/Flux/IR/StandardGates/RXOp.cpp | 4 +- .../Dialect/Flux/IR/StandardGates/RYOp.cpp | 83 ++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 153 ++++++++++-------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 23 +++ .../Dialect/Quartz/IR/StandardGates/RXOp.cpp | 4 +- .../Dialect/Quartz/IR/StandardGates/RYOp.cpp | 44 +++++ .../TranslateQuantumComputationToQuartz.cpp | 25 +++ .../pipeline/test_compiler_pipeline.cpp | 44 +++++ 19 files changed, 939 insertions(+), 204 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RYOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 82b2456d6b..0c836c3134 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -927,7 +927,7 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.rx(1.0, q_in); * ``` * ```mlir - * %q_out = flux.rx(1.0) %q_in : !flux.qubit -> !flux.qubit + * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit * ``` */ Value rx(const std::variant& theta, Value qubit); @@ -946,7 +946,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` * ```mlir * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.rx(1.0) %q1_in : !flux.qubit -> !flux.qubit + * %q1_res = flux.rx(%theta) %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` @@ -968,7 +968,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` * ```mlir * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.rx(1.0) %q2_in : !flux.qubit -> !flux.qubit + * %q2_res = flux.rx(%theta) %q2_in : !flux.qubit -> !flux.qubit * flux.yield %q2_res * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, * !flux.qubit}, {!flux.qubit}) @@ -977,6 +977,72 @@ class FluxProgramBuilder final : public OpBuilder { std::pair mcrx(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an RY gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param theta Rotation angle in radians + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.ry(1.0, q_in); + * ``` + * ```mlir + * %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value ry(const std::variant& theta, Value qubit); + + /** + * @brief Apply a controlled RY gate + * + * @param theta Rotation angle in radians + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cry(1.0, q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.ry(%theta) %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cry(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled RY gate + * + * @param theta Rotation angle in radians + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcry(1.0, {q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.ry(%theta) %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcry(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -994,7 +1060,7 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.u2(1.0, 0.5, q_in); * ``` * ```mlir - * %q_out = flux.u2(1.0, 0.5) %q_in : !flux.qubit -> !flux.qubit + * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit * ``` */ Value u2(const std::variant& phi, @@ -1015,7 +1081,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` * ```mlir * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.u2(1.0, 0.5) %q1_in : !flux.qubit -> !flux.qubit + * %q1_res = flux.u2(%phi, %lambda) %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` @@ -1039,7 +1105,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` * ```mlir * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.u2(1.0, 0.5) %q2_in : !flux.qubit -> !flux.qubit + * %q2_res = flux.u2(%phi, %lambda) %q2_in : !flux.qubit -> !flux.qubit * flux.yield %q2_res * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, * !flux.qubit}, {!flux.qubit}) @@ -1200,7 +1266,8 @@ class FluxProgramBuilder final : public OpBuilder { * @param qubit Input qubit * @return Output qubit */ - template Value createOneTargetZeroParameter(Value qubit); + template + Value createOneTargetZeroParameter(const Value qubit); /** * @brief Helper to create a controlled one-target, zero-parameter Flux @@ -1212,8 +1279,9 @@ class FluxProgramBuilder final : public OpBuilder { * @return Pair of (output_control_qubit, output_target_qubit) */ template - std::pair createControlledOneTargetZeroParameter(Value control, - Value target); + std::pair + createControlledOneTargetZeroParameter(const Value control, + const Value target); /** * @brief Helper to create a multi-controlled one-target, zero-parameter Flux @@ -1226,8 +1294,51 @@ class FluxProgramBuilder final : public OpBuilder { */ template std::pair - createMultiControlledOneTargetZeroParameter(ValueRange controls, - Value target); + createMultiControlledOneTargetZeroParameter(const ValueRange controls, + const Value target); + + /** + * @brief Helper to create a one-target, one-parameter Flux operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter Operation parameter + * @param qubit Input qubit + * @return Output qubit + */ + template + Value + createOneTargetOneParameter(const std::variant& parameter, + const Value qubit); + + /** + * @brief Helper to create a controlled one-target, one-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter Operation parameter + * @param control Input control qubit + * @param target Input target qubit + * @return Pair of (output_control_qubit, output_target_qubit) + */ + template + std::pair createControlledOneTargetOneParameter( + const std::variant& parameter, const Value control, + const Value target); + + /** + * @brief Helper to create a multi-controlled one-target, one-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter Operation parameter + * @param controls Input control qubits + * @param target Input target qubit + * @return Pair of (output_control_qubits, output_target_qubit) + */ + template + std::pair createMultiControlledOneTargetOneParameter( + const std::variant& parameter, const ValueRange controls, + const Value target); //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index c2cf3fee34..6e43a97be2 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -536,6 +536,34 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } +def RYOp : FluxOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RY gate to a qubit"; + let description = [{ + Applies an RY gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ry"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 94a743937d..320541db56 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -666,6 +666,43 @@ class QIRProgramBuilder { QIRProgramBuilder& crx(const std::variant& theta, Value control, Value target); + /** + * @brief Apply a RY gate to a qubit + * + * @param theta Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ry(theta, qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ry__body(%q, %theta) : (!llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& ry(const std::variant& theta, Value qubit); + + /** + * @brief Apply a controlled RY gate + * + * @param theta Rotation angle + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cry(theta, control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cry__body(%c, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& cry(const std::variant& theta, + Value control, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -815,6 +852,29 @@ class QIRProgramBuilder { const Value target, StringRef fnName); + /** + * @brief Helper to create a one-target, one-parameter QIR operation + * + * @param parameter Operation parameter + * @param qubit Input qubit + * @param fnName Name of the QIR function to call + */ + void createOneTargetOneParameter(const std::variant& parameter, + const Value qubit, StringRef fnName); + + /** + * @brief Helper to create a controlled one-target, one-parameter QIR + * operation + * + * @param parameter Operation parameter + * @param control Input control qubit + * @param target Input target qubit + * @param fnName Name of the QIR function to call + */ + void createControlledOneTargetOneParameter( + const std::variant& parameter, const Value control, + const Value target, StringRef fnName); + /** * @brief Generate array-based output recording in the output block * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index affd40b017..3070a64a9e 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -72,6 +72,10 @@ static constexpr auto QIR_RX = "__quantum__qis__rx__body"; static constexpr auto QIR_CRX = "__quantum__qis__crx__body"; static constexpr auto QIR_CCRX = "__quantum__qis__ccrx__body"; static constexpr auto QIR_CCCRX = "__quantum__qis__cccrx__body"; +static constexpr auto QIR_RY = "__quantum__qis__ry__body"; +static constexpr auto QIR_CRY = "__quantum__qis__cry__body"; +static constexpr auto QIR_CCRY = "__quantum__qis__ccry__body"; +static constexpr auto QIR_CCCRY = "__quantum__qis__cccry__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; static constexpr auto QIR_CU2 = "__quantum__qis__cu2__body"; static constexpr auto QIR_CCU2 = "__quantum__qis__ccu2__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index c2c3d028ed..37faa3c450 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -834,7 +834,7 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.rx(1.0, q); * ``` * ```mlir - * quartz.rx(1.0) %q : !quartz.qubit + * quartz.rx(%theta) %q : !quartz.qubit * ``` */ QuartzProgramBuilder& rx(const std::variant& theta, @@ -847,13 +847,14 @@ class QuartzProgramBuilder final : public OpBuilder { * @param control Control qubit * @param target Target qubit * @return Reference to this builder for method chaining + * * @par Example: * ```c++ * builder.crx(1.0, q0, q1); * ``` * ```mlir * quartz.ctrl(%q0) { - * quartz.rx(1.0) %q1 : !quartz.qubit + * quartz.rx(%theta) %q1 : !quartz.qubit * } * ``` */ @@ -873,13 +874,73 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` * ```mlir * quartz.ctrl(%q0, %q1) { - * quartz.rx(1.0) %q2 : !quartz.qubit + * quartz.rx(%theta) %q2 : !quartz.qubit * } * ``` */ QuartzProgramBuilder& mcrx(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an RY gate to a qubit + * + * @param theta Rotation angle in radians + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ry(1.0, q); + * ``` + * ```mlir + * quartz.ry(%theta) %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& ry(const std::variant& theta, + Value qubit); + + /** + * @brief Apply a CRY gate + * + * @param theta Rotation angle in radians + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cry(1.0, q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.ry(%theta) %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cry(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled RY gate + * + * @param theta Rotation angle in radians + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcry(1.0, {q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.ry(%theta) %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcry(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -893,7 +954,7 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.u2(1.0, 0.5, q); * ``` * ```mlir - * quartz.u2(1.0, 0.5) %q : !quartz.qubit + * quartz.u2(%phi, %lambda) %q : !quartz.qubit * ``` */ QuartzProgramBuilder& u2(const std::variant& phi, @@ -914,7 +975,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` * ```mlir * quartz.ctrl(%q0) { - * quartz.u2(1.0, 0.5) %q1 : !quartz.qubit + * quartz.u2(%phi, %lambda) %q1 : !quartz.qubit * } * ``` */ @@ -936,7 +997,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` * ```mlir * quartz.ctrl(%q0, %q1) { - * quartz.u2(1.0, 0.5) %q2 : !quartz.qubit + * quartz.u2(%phi, %lambda) %q2 : !quartz.qubit * } * ``` */ diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index cc4b5c30ba..d87bc79da8 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -488,6 +488,31 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> ]; } +def RYOp : QuartzOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RY gate to a qubit"; + let description = [{ + Applies an RY gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.ry(%theta) %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ry"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index de19952ab7..b40fa6b19d 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -111,9 +111,17 @@ inline DenseElementsAttr getMatrixSXdg(MLIRContext* ctx) { inline DenseElementsAttr getMatrixRX(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m0 = std::cos(theta / 2) + 0i; - const std::complex m1 = -1i * std::sin(theta / 2); - return DenseElementsAttr::get(type, {m0, m1, m1, m0}); + const std::complex m00 = std::cos(theta / 2) + 0i; + const std::complex m01 = -1i * std::sin(theta / 2); + return DenseElementsAttr::get(type, {m00, m01, m01, m00}); +} + +inline DenseElementsAttr getMatrixRY(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = std::cos(theta / 2) + 0i; + const std::complex m01 = -std::sin(theta / 2) + 0i; + return DenseElementsAttr::get(type, {m00, m01, -m01, m00}); } inline DenseElementsAttr getMatrixU2(MLIRContext* ctx, double phi, diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 271c253ae6..396dabc6b0 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -37,8 +37,7 @@ using namespace quartz; namespace { /** - * @brief Converts one-target, zero-parameter Flux operations to Quartz - * operations + * @brief Converts a one-target, zero-parameter Flux operation to Quartz * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation @@ -65,6 +64,34 @@ convertOneTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a one-target, one-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit, op.getOperand(1)); + + // Replace the output qubit with the same Quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); +} + } // namespace /** @@ -528,16 +555,29 @@ struct ConvertFluxRXOp final : OpConversionPattern { LogicalResult matchAndRewrite(flux::RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); - - // Create quartz.rx (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, adaptor.getTheta()); + return convertOneTargetOneParameter(op, adaptor, rewriter); + } +}; - // Replace the output qubit with the same quartz reference - rewriter.replaceOp(op, quartzQubit); +/** + * @brief Converts flux.ry to quartz.ry + * + * @par Example: + * ```mlir + * %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.ry(%theta) %q : !quartz.qubit + * ``` + */ +struct ConvertFluxRYOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; - return success(); + LogicalResult + matchAndRewrite(flux::RYOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetOneParameter(op, adaptor, rewriter); } }; @@ -719,8 +759,9 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { ConvertFluxXOp, ConvertFluxYOp, ConvertFluxZOp, ConvertFluxHOp, ConvertFluxSOp, ConvertFluxSdgOp, ConvertFluxTOp, ConvertFluxTdgOp, ConvertFluxSXOp, ConvertFluxSXdgOp, - ConvertFluxRXOp, ConvertFluxU2Op, ConvertFluxSWAPOp, - ConvertFluxCtrlOp, ConvertFluxYieldOp>(typeConverter, context); + ConvertFluxRXOp, ConvertFluxRYOp, ConvertFluxU2Op, + ConvertFluxSWAPOp, ConvertFluxCtrlOp, ConvertFluxYieldOp>( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 882d92f29e..19675948d9 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -109,8 +109,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { }; /** - * @brief Converts one-target, zero-parameter Quartz operations to Flux - * operations + * @brief Converts a one-target, zero-parameter Quartz operation to Flux * * @tparam FluxOpType The operation type of the Flux operation * @tparam QuartzOpType The operation type of the Quartz operation @@ -152,6 +151,50 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, return success(); } +/** + * @brief Converts a one-target, one-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertOneTargetOneParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest Flux qubit + const auto quartzQubit = op->getOperand(0); + Value fluxQubit = nullptr; + if (inCtrlOp == 0) { + fluxQubit = qubitMap[quartzQubit]; + } else { + fluxQubit = state.targetsIn[inCtrlOp].front(); + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit, op.getOperand(1)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({fluxOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + } // namespace /** @@ -661,35 +704,29 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - auto& qubitMap = state.qubitMap; - const auto inCtrlOp = state.inCtrlOp; - - // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; - if (inCtrlOp == 0) { - fluxQubit = qubitMap[quartzQubit]; - } else { - fluxQubit = state.targetsIn[inCtrlOp].front(); - } - - // Create flux.rx (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, adaptor.getTheta()); - - // Update state map - if (inCtrlOp == 0) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); - } else { - state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({fluxOp.getQubitOut()}); - state.targetsOut.try_emplace(inCtrlOp, targetsOut); - } + return convertOneTargetOneParameter(op, rewriter, getState()); + } +}; - rewriter.eraseOp(op); +/** + * @brief Converts quartz.ry to flux.ry + * + * @par Example: + * ```mlir + * quartz.ry(%theta) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzRYOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; - return success(); + LogicalResult + matchAndRewrite(quartz::RYOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetOneParameter(op, rewriter, getState()); } }; @@ -942,15 +979,15 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns.add(typeConverter, - context, &state); + patterns.add< + ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, + ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, + ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, + ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, + ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, + ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzU2Op, + ConvertQuartzSWAPOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( + typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 863455ef2e..a61473721c 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -113,7 +113,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { }; /** - * @brief Converts one-target, zero-parameter Quartz operations to QIR calls + * @brief Converts a one-target, zero-parameter Quartz operation to QIR * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation @@ -170,6 +170,66 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a one-target, one-parameter Quartz operation to QIR + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define function argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 2); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + // Add parameter type + argumentTypes.push_back(Float64Type::get(ctx)); + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 2); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + } // namespace /** @@ -993,14 +1053,11 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(RXOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); auto& state = getState(); // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const auto numCtrls = posCtrls.size(); + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -1018,40 +1075,53 @@ struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { } } - // Define function argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 2); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add theta - argumentTypes.push_back(Float64Type::get(ctx)); + return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), argumentTypes); +/** + * @brief Converts quartz.ry operation to QIR ry + * + * @par Example: + * ```mlir + * quartz.ry(%theta) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__ry__body(%q, %theta) : (!llvm.ptr, f64) -> () + * ``` + */ +struct ConvertQuartzRYQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + LogicalResult + matchAndRewrite(RYOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); - SmallVector operands; - operands.reserve(numCtrls + 2); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; + // Define function name + StringRef fnName; + if (inCtrlOp == 0) { + fnName = QIR_RY; + } else { + if (numCtrls == 1) { + fnName = QIR_CRY; + } else if (numCtrls == 2) { + fnName = QIR_CCRY; + } else if (numCtrls == 3) { + fnName = QIR_CCCRY; + } else { + return failure(); + } } - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); + return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), + state, fnName); } }; @@ -1596,6 +1666,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 6c19f92f98..9a0f2eb457 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -174,7 +174,7 @@ Value FluxProgramBuilder::reset(Value qubit) { // Helper methods template -Value FluxProgramBuilder::createOneTargetZeroParameter(Value qubit) { +Value FluxProgramBuilder::createOneTargetZeroParameter(const Value qubit) { auto op = create(loc, qubit); const auto& qubitOut = op.getQubitOut(); updateQubitTracking(qubit, qubitOut); @@ -183,8 +183,8 @@ Value FluxProgramBuilder::createOneTargetZeroParameter(Value qubit) { template std::pair -FluxProgramBuilder::createControlledOneTargetZeroParameter(Value control, - Value target) { +FluxProgramBuilder::createControlledOneTargetZeroParameter(const Value control, + const Value target) { const auto [controlsOut, targetsOut] = ctrl(control, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { @@ -197,7 +197,7 @@ FluxProgramBuilder::createControlledOneTargetZeroParameter(Value control, template std::pair FluxProgramBuilder::createMultiControlledOneTargetZeroParameter( - ValueRange controls, Value target) { + const ValueRange controls, const Value target) { const auto [controlsOut, targetsOut] = ctrl(controls, target, [&](OpBuilder& b, const ValueRange targets) -> ValueRange { @@ -207,9 +207,46 @@ FluxProgramBuilder::createMultiControlledOneTargetZeroParameter( return {controlsOut, targetsOut[0]}; } +template +Value FluxProgramBuilder::createOneTargetOneParameter( + const std::variant& parameter, const Value qubit) { + auto op = create(loc, qubit, parameter); + const auto& qubitOut = op.getQubitOut(); + updateQubitTracking(qubit, qubitOut); + return qubitOut; +} + +template +std::pair +FluxProgramBuilder::createControlledOneTargetOneParameter( + const std::variant& parameter, const Value control, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(control, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], parameter); + return op->getResults(); + }); + return {controlsOut[0], targetsOut[0]}; +} + +template +std::pair +FluxProgramBuilder::createMultiControlledOneTargetOneParameter( + const std::variant& parameter, const ValueRange controls, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], parameter); + return op->getResults(); + }); + return {controlsOut, targetsOut[0]}; +} + // IdOp -Value FluxProgramBuilder::id(Value qubit) { +Value FluxProgramBuilder::id(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -225,7 +262,7 @@ std::pair FluxProgramBuilder::mcid(const ValueRange controls, // XOp -Value FluxProgramBuilder::x(Value qubit) { +Value FluxProgramBuilder::x(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -241,7 +278,7 @@ std::pair FluxProgramBuilder::mcx(const ValueRange controls, // YOp -Value FluxProgramBuilder::y(Value qubit) { +Value FluxProgramBuilder::y(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -257,7 +294,7 @@ std::pair FluxProgramBuilder::mcy(const ValueRange controls, // ZOp -Value FluxProgramBuilder::z(Value qubit) { +Value FluxProgramBuilder::z(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -273,7 +310,7 @@ std::pair FluxProgramBuilder::mcz(const ValueRange controls, // HOp -Value FluxProgramBuilder::h(Value qubit) { +Value FluxProgramBuilder::h(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -289,7 +326,7 @@ std::pair FluxProgramBuilder::mch(const ValueRange controls, // SOp -Value FluxProgramBuilder::s(Value qubit) { +Value FluxProgramBuilder::s(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -305,7 +342,7 @@ std::pair FluxProgramBuilder::mcs(const ValueRange controls, // SdgOp -Value FluxProgramBuilder::sdg(Value qubit) { +Value FluxProgramBuilder::sdg(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -321,7 +358,7 @@ FluxProgramBuilder::mcsdg(const ValueRange controls, const Value target) { // TOp -Value FluxProgramBuilder::t(Value qubit) { +Value FluxProgramBuilder::t(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -337,7 +374,7 @@ std::pair FluxProgramBuilder::mct(const ValueRange controls, // TdgOp -Value FluxProgramBuilder::tdg(Value qubit) { +Value FluxProgramBuilder::tdg(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -353,7 +390,7 @@ FluxProgramBuilder::mctdg(const ValueRange controls, const Value target) { // SXOp -Value FluxProgramBuilder::sx(Value qubit) { +Value FluxProgramBuilder::sx(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -369,7 +406,7 @@ std::pair FluxProgramBuilder::mcsx(const ValueRange controls, // SXdgOp -Value FluxProgramBuilder::sxdg(Value qubit) { +Value FluxProgramBuilder::sxdg(const Value qubit) { return createOneTargetZeroParameter(qubit); } @@ -386,35 +423,41 @@ FluxProgramBuilder::mcsxdg(const ValueRange controls, const Value target) { // RXOp Value FluxProgramBuilder::rx(const std::variant& theta, - Value qubit) { - auto rxOp = create(loc, qubit, theta); - const auto& qubitOut = rxOp.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; + const Value qubit) { + return createOneTargetOneParameter(theta, qubit); } std::pair FluxProgramBuilder::crx(const std::variant& theta, const Value control, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto rx = b.create(loc, targets[0], theta); - return rx->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; + return createControlledOneTargetOneParameter(theta, control, target); } std::pair FluxProgramBuilder::mcrx(const std::variant& theta, const ValueRange controls, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto rx = b.create(loc, targets[0], theta); - return rx->getResults(); - }); - return {controlsOut, targetsOut[0]}; + return createMultiControlledOneTargetOneParameter(theta, controls, + target); +} + +// RYOp + +Value FluxProgramBuilder::ry(const std::variant& theta, + const Value qubit) { + return createOneTargetOneParameter(theta, qubit); +} + +std::pair +FluxProgramBuilder::cry(const std::variant& theta, + const Value control, const Value target) { + return createControlledOneTargetOneParameter(theta, control, target); +} + +std::pair +FluxProgramBuilder::mcry(const std::variant& theta, + const ValueRange controls, const Value target) { + return createMultiControlledOneTargetOneParameter(theta, controls, + target); } // U2Op diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp index 46902c7e1b..9324838a12 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp @@ -65,7 +65,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) + const Value qubitIn, const std::variant& theta) { Value thetaOperand = nullptr; if (std::holds_alternative(theta)) { @@ -74,7 +74,7 @@ void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, } else { thetaOperand = std::get(theta); } - build(odsBuilder, odsState, qubit_in, thetaOperand); + build(odsBuilder, odsState, qubitIn, thetaOperand); } void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp new file mode 100644 index 0000000000..a7b05de77e --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RY operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an RYOp + auto prevOp = op.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Compute and set new theta + auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), + prevOp.getTheta()); + op->setOperand(1, newTheta.getResult()); + + // Trivialize previous RYOp + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + + return success(); + } +}; + +} // namespace + +DenseElementsAttr RYOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRY(getContext(), thetaValue); +} + +void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubitIn, thetaOperand); +} + +void RYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 2acc3638b5..c430495a31 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -272,6 +272,81 @@ void QIRProgramBuilder::createControlledOneTargetZeroParameter( builder.create(loc, fnDecl, ValueRange{control, target}); } +void QIRProgramBuilder::createOneTargetOneParameter( + const std::variant& parameter, const Value qubit, + StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value parameterOperand; + if (std::holds_alternative(parameter)) { + parameterOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter))) + .getResult(); + } else { + parameterOperand = std::get(parameter); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + Float64Type::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); + builder.create(loc, fnDecl, + ValueRange{qubit, parameterOperand}); +} + +void QIRProgramBuilder::createControlledOneTargetOneParameter( + const std::variant& parameter, const Value control, + const Value target, StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value parameterOperand; + if (std::holds_alternative(parameter)) { + parameterOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter))) + .getResult(); + } else { + parameterOperand = std::get(parameter); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Create call + const auto qirSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), + {LLVM::LLVMPointerType::get(builder.getContext()), + LLVM::LLVMPointerType::get(builder.getContext()), + Float64Type::get(builder.getContext())}); + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); + builder.create(loc, fnDecl, + ValueRange{control, target, parameterOperand}); +} + // IdOp QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { @@ -420,78 +495,30 @@ QIRProgramBuilder& QIRProgramBuilder::csxdg(const Value control, QIRProgramBuilder& QIRProgramBuilder::rx(const std::variant& theta, const Value qubit) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(theta))) - .getResult(); - } else { - thetaOperand = std::get(theta); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create rx call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - Float64Type::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_RX, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit, thetaOperand}); - + createOneTargetOneParameter(theta, qubit, QIR_RX); return *this; } QIRProgramBuilder& QIRProgramBuilder::crx(const std::variant& theta, const Value control, const Value target) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(theta))) - .getResult(); - } else { - thetaOperand = std::get(theta); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); + createControlledOneTargetOneParameter(theta, control, target, QIR_CRX); + return *this; +} - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); +// RYOp - // Create crx call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext()), - Float64Type::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_CRX, qirSignature); - builder.create(loc, fnDecl, - ValueRange{control, target, thetaOperand}); +QIRProgramBuilder& +QIRProgramBuilder::ry(const std::variant& theta, + const Value qubit) { + createOneTargetOneParameter(theta, qubit, QIR_RY); + return *this; +} +QIRProgramBuilder& +QIRProgramBuilder::cry(const std::variant& theta, + const Value control, const Value target) { + createControlledOneTargetOneParameter(theta, control, target, QIR_CRY); return *this; } diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index f501d9c915..9497e0b203 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -345,6 +345,29 @@ QuartzProgramBuilder::mcrx(const std::variant& theta, return *this; } +// RYOp + +QuartzProgramBuilder& +QuartzProgramBuilder::ry(const std::variant& theta, + Value qubit) { + create(loc, qubit, theta); + return *this; +} + +QuartzProgramBuilder& +QuartzProgramBuilder::cry(const std::variant& theta, + Value control, const Value target) { + return mcry(theta, {control}, target); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mcry(const std::variant& theta, + ValueRange controls, Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target, theta); }); + return *this; +} + // U2Op QuartzProgramBuilder& diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp index fadd30c2a3..b1dbbfa9f6 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp @@ -31,7 +31,7 @@ DenseElementsAttr RXOp::tryGetStaticMatrix() { } void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) + const Value qubitIn, const std::variant& theta) { Value thetaOperand = nullptr; if (std::holds_alternative(theta)) { @@ -40,5 +40,5 @@ void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, } else { thetaOperand = std::get(theta); } - build(odsBuilder, odsState, qubit_in, thetaOperand); + build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RYOp.cpp new file mode 100644 index 0000000000..ee71374783 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RYOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RYOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRY(getContext(), thetaValue); +} + +void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 166763ce66..39a46c90e2 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -501,6 +501,28 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an RY operation + * + * @details + * Translates RY operations from the QuantumComputation to quartz.ry operations. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RY operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addRYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.ry(theta, target); + } else { + builder.mcry(theta, posControls, target); + } +} + /** * @brief Adds a U2 operation * @@ -614,6 +636,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::RX: addRXOp(builder, *operation, qubits); break; + case qc::OpType::RY: + addRYOp(builder, *operation, qubits); + break; case qc::OpType::U2: addU2Op(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index b6716a48db..cbb62f576c 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1962,6 +1962,50 @@ TEST_F(CompilerPipelineTest, CRX) { }); } +TEST_F(CompilerPipelineTest, RY) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.ry(1.0, 0); + qc.ry(0.5, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.ry(1.0, q); + b.ry(0.5, q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.ry(1.0, q); + b.ry(0.5, q); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.5, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(1.5, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, U2) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From 4fe37392ca592d33d0ba61bea83d4b1323ae58ba Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:42:23 +0100 Subject: [PATCH 226/419] Add support for RZ and P --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 132 ++++++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 56 ++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 75 +++++++++- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 8 ++ .../Quartz/Builder/QuartzProgramBuilder.h | 120 ++++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 50 +++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 26 +++- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 61 ++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 63 +++++++-- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 92 ++++++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 40 ++++++ .../lib/Dialect/Flux/IR/StandardGates/POp.cpp | 82 +++++++++++ .../Dialect/Flux/IR/StandardGates/RZOp.cpp | 83 +++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 32 +++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 45 ++++++ .../Quartz/IR/StandardGates/PhaseOp.cpp | 43 ++++++ .../Dialect/Quartz/IR/StandardGates/RZOp.cpp | 44 ++++++ .../TranslateQuantumComputationToQuartz.cpp | 79 ++++++++--- .../pipeline/test_compiler_pipeline.cpp | 88 ++++++++++++ 19 files changed, 1181 insertions(+), 38 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/PhaseOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RZOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 0c836c3134..a334f47d97 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -1043,6 +1043,138 @@ class FluxProgramBuilder final : public OpBuilder { std::pair mcry(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an RZ gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param theta Rotation angle in radians + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.rz(1.0, q_in); + * ``` + * ```mlir + * %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value rz(const std::variant& theta, Value qubit); + + /** + * @brief Apply a controlled RZ gate + * + * @param theta Rotation angle in radians + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.crz(1.0, q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.rz(%theta) %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair crz(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled RZ gate + * + * @param theta Rotation angle in radians + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcrz(1.0, {q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.rz(%theta) %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcrz(const std::variant& theta, + ValueRange controls, Value target); + + /** + * @brief Apply a P gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param theta Rotation angle in radians + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.p(1.0, q_in); + * ``` + * ```mlir + * %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value p(const std::variant& theta, Value qubit); + + /** + * @brief Apply a controlled P gate + * + * @param theta Rotation angle in radians + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cp(1.0, q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.p(%theta) %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cp(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled P gate + * + * @param theta Rotation angle in radians + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcp(1.0, {q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.p(%theta) %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcp(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 6e43a97be2..7461eac4c6 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -564,6 +564,62 @@ def RYOp : FluxOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } +def RZOp : FluxOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RZ gate to a qubit"; + let description = [{ + Applies an RZ gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + +def POp : FluxOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply a P gate to a qubit"; + let description = [{ + Applies a P gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "p"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 320541db56..62e9e122fb 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -667,7 +667,7 @@ class QIRProgramBuilder { Value control, Value target); /** - * @brief Apply a RY gate to a qubit + * @brief Apply an RY gate to a qubit * * @param theta Rotation angle * @param qubit Input qubit @@ -703,6 +703,79 @@ class QIRProgramBuilder { QIRProgramBuilder& cry(const std::variant& theta, Value control, Value target); + /** + * @brief Apply an RZ gate to a qubit + * + * @param theta Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.rz(theta, qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__rz__body(%q, %theta) : (!llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& rz(const std::variant& theta, Value qubit); + + /** + * @brief Apply a controlled RZ gate + * + * @param theta Rotation angle + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.crz(theta, control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__crz__body(%c, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& crz(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a P gate to a qubit + * + * @param theta Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.p(theta, qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__p__body(%q, %theta) : (!llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& p(const std::variant& theta, Value qubit); + + /** + * @brief Apply a controlled P gate + * + * @param theta Rotation angle + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cp(theta, control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cp__body(%c, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& cp(const std::variant& theta, Value control, + Value target); /** * @brief Apply a U2 gate to a qubit * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 3070a64a9e..759dc3394c 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -76,6 +76,14 @@ static constexpr auto QIR_RY = "__quantum__qis__ry__body"; static constexpr auto QIR_CRY = "__quantum__qis__cry__body"; static constexpr auto QIR_CCRY = "__quantum__qis__ccry__body"; static constexpr auto QIR_CCCRY = "__quantum__qis__cccry__body"; +static constexpr auto QIR_RZ = "__quantum__qis__rz__body"; +static constexpr auto QIR_CRZ = "__quantum__qis__crz__body"; +static constexpr auto QIR_CCRZ = "__quantum__qis__ccrz__body"; +static constexpr auto QIR_CCCRZ = "__quantum__qis__cccrz__body"; +static constexpr auto QIR_P = "__quantum__qis__p__body"; +static constexpr auto QIR_CP = "__quantum__qis__cp__body"; +static constexpr auto QIR_CCP = "__quantum__qis__ccp__body"; +static constexpr auto QIR_CCCP = "__quantum__qis__cccp__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; static constexpr auto QIR_CU2 = "__quantum__qis__cu2__body"; static constexpr auto QIR_CCU2 = "__quantum__qis__ccu2__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 37faa3c450..acb9d95eeb 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -941,6 +941,126 @@ class QuartzProgramBuilder final : public OpBuilder { QuartzProgramBuilder& mcry(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an RZ gate to a qubit + * + * @param theta Rotation angle in radians + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.rz(1.0, q); + * ``` + * ```mlir + * quartz.rz(%theta) %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& rz(const std::variant& theta, + Value qubit); + + /** + * @brief Apply a CRZ gate + * + * @param theta Rotation angle in radians + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.crz(1.0, q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.rz(%theta) %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& crz(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled RZ gate + * + * @param theta Rotation angle in radians + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcrz(1.0, {q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.rz(%theta) %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcrz(const std::variant& theta, + ValueRange controls, Value target); + + /** + * @brief Apply a P gate to a qubit + * + * @param theta Rotation angle in radians + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.p(1.0, q); + * ``` + * ```mlir + * quartz.p(%theta) %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& p(const std::variant& theta, + Value qubit); + + /** + * @brief Apply a controlled P gate + * + * @param theta Rotation angle in radians + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cp(1.0, q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.p(%theta) %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cp(const std::variant& theta, + Value control, Value target); + + /** + * @brief Apply a multi-controlled P gate + * + * @param theta Rotation angle in radians + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcp(1.0, {q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.p(%theta) %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcp(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index d87bc79da8..a460148836 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -513,6 +513,56 @@ def RYOp : QuartzOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> ]; } +def RZOp : QuartzOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply an RZ gate to a qubit"; + let description = [{ + Applies an RZ gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.rz(%theta) %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + +def POp : QuartzOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { + let summary = "Apply a P gate to a qubit"; + let description = [{ + Applies a P gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.p(%theta) %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "p"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta)> + ]; +} + def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index b40fa6b19d..16f831192b 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -111,19 +111,37 @@ inline DenseElementsAttr getMatrixSXdg(MLIRContext* ctx) { inline DenseElementsAttr getMatrixRX(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::cos(theta / 2) + 0i; - const std::complex m01 = -1i * std::sin(theta / 2); + const std::complex m00 = std::cos(theta / 2.0) + 0i; + const std::complex m01 = -1i * std::sin(theta / 2.0); return DenseElementsAttr::get(type, {m00, m01, m01, m00}); } inline DenseElementsAttr getMatrixRY(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::cos(theta / 2) + 0i; - const std::complex m01 = -std::sin(theta / 2) + 0i; + const std::complex m00 = std::cos(theta / 2.0) + 0i; + const std::complex m01 = -std::sin(theta / 2.0) + 0i; return DenseElementsAttr::get(type, {m00, m01, -m01, m00}); } +inline DenseElementsAttr getMatrixRZ(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = std::exp(-1i * theta / 2.0); + const std::complex m01 = 0.0 + 0i; + const std::complex m11 = std::exp(1i * theta / 2.0); + return DenseElementsAttr::get(type, {m00, m01, m01, m11}); +} + +inline DenseElementsAttr getMatrixP(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = 1.0 + 0i; + const std::complex m01 = 0.0 + 0i; + const std::complex m11 = std::exp(1i * theta); + return DenseElementsAttr::get(type, {m00, m01, m01, m11}); +} + inline DenseElementsAttr getMatrixU2(MLIRContext* ctx, double phi, double lambda) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 396dabc6b0..20c42963d1 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -581,6 +581,50 @@ struct ConvertFluxRYOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.rz to quartz.rz + * + * @par Example: + * ```mlir + * %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.rz(%theta) %q : !quartz.qubit + * ``` + */ +struct ConvertFluxRZOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::RZOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetOneParameter(op, adaptor, rewriter); + } +}; + +/** + * @brief Converts flux.p to quartz.p + * + * @par Example: + * ```mlir + * %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.p(%theta) %q : !quartz.qubit + * ``` + */ +struct ConvertFluxPOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::POp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetOneParameter(op, adaptor, rewriter); + } +}; + /** * @brief Converts flux.u2 to quartz.u2 * @@ -754,14 +798,15 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add( - typeConverter, context); + patterns + .add( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 19675948d9..0eaeebf202 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -730,6 +730,50 @@ struct ConvertQuartzRYOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.rz to flux.rz + * + * @par Example: + * ```mlir + * quartz.rz(%theta) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzRZOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::RZOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetOneParameter(op, rewriter, getState()); + } +}; + +/** + * @brief Converts quartz.p to flux.p + * + * @par Example: + * ```mlir + * quartz.p(%theta) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzPOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::POp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetOneParameter(op, rewriter, getState()); + } +}; + /** * @brief Converts quartz.u2 to flux.u2 * @@ -979,15 +1023,16 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns.add< - ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, - ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, - ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, - ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, - ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, - ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzU2Op, - ConvertQuartzSWAPOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( - typeConverter, context, &state); + patterns.add(typeConverter, + context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index a61473721c..461987d3bc 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -1125,6 +1125,96 @@ struct ConvertQuartzRYQIR final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.rz operation to QIR rz + * + * @par Example: + * ```mlir + * quartz.rz(%theta) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__rz__body(%q, %theta) : (!llvm.ptr, f64) -> () + * ``` + */ +struct ConvertQuartzRZQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(RZOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + + // Define function name + StringRef fnName; + if (inCtrlOp == 0) { + fnName = QIR_RZ; + } else { + if (numCtrls == 1) { + fnName = QIR_CRZ; + } else if (numCtrls == 2) { + fnName = QIR_CCRZ; + } else if (numCtrls == 3) { + fnName = QIR_CCCRZ; + } else { + return failure(); + } + } + + return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + +/** + * @brief Converts quartz.p operation to QIR p + * + * @par Example: + * ```mlir + * quartz.p(%lambda) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__p__body(%q, %lambda) : (!llvm.ptr, f64) -> () + * ``` + */ +struct ConvertQuartzPQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(POp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + + // Define function name + StringRef fnName; + if (inCtrlOp == 0) { + fnName = QIR_P; + } else { + if (numCtrls == 1) { + fnName = QIR_CP; + } else if (numCtrls == 2) { + fnName = QIR_CCP; + } else if (numCtrls == 3) { + fnName = QIR_CCCP; + } else { + return failure(); + } + } + + return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + /** * @brief Converts quartz.u2 operation to QIR u2 * @@ -1667,6 +1757,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 9a0f2eb457..0d4386d592 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -460,6 +460,46 @@ FluxProgramBuilder::mcry(const std::variant& theta, target); } +// RZOp + +Value FluxProgramBuilder::rz(const std::variant& theta, + const Value qubit) { + return createOneTargetOneParameter(theta, qubit); +} + +std::pair +FluxProgramBuilder::crz(const std::variant& theta, + const Value control, const Value target) { + return createControlledOneTargetOneParameter(theta, control, target); +} + +std::pair +FluxProgramBuilder::mcrz(const std::variant& theta, + const ValueRange controls, const Value target) { + return createMultiControlledOneTargetOneParameter(theta, controls, + target); +} + +// POp + +Value FluxProgramBuilder::p(const std::variant& theta, + const Value qubit) { + return createOneTargetOneParameter(theta, qubit); +} + +std::pair +FluxProgramBuilder::cp(const std::variant& theta, + const Value control, const Value target) { + return createControlledOneTargetOneParameter(theta, control, target); +} + +std::pair +FluxProgramBuilder::mcp(const std::variant& theta, + const ValueRange controls, const Value target) { + return createMultiControlledOneTargetOneParameter(theta, controls, + target); +} + // U2Op Value FluxProgramBuilder::u2(const std::variant& phi, diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp new file mode 100644 index 0000000000..7530c29057 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent P operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(POp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an POp + auto prevOp = op.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Compute and set new theta + auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), + prevOp.getTheta()); + op->setOperand(1, newTheta.getResult()); + + // Trivialize previous POp + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + + return success(); + } +}; + +} // namespace + +DenseElementsAttr POp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixP(getContext(), thetaValue); +} + +void POp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubitIn, thetaOperand); +} + +void POp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp new file mode 100644 index 0000000000..bc105cde60 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge subsequent RZ operations on the same qubit by adding their + * angles. + */ +struct MergeSubsequentRZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an RZOp + auto prevOp = op.getQubitIn().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Compute and set new theta + auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), + prevOp.getTheta()); + op->setOperand(1, newTheta.getResult()); + + // Trivialize previous RZOp + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + + return success(); + } +}; + +} // namespace + +DenseElementsAttr RZOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRZ(getContext(), thetaValue); +} + +void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubitIn, thetaOperand); +} + +void RZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index c430495a31..a4d10dc575 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -522,6 +522,38 @@ QIRProgramBuilder::cry(const std::variant& theta, return *this; } +// RZOp + +QIRProgramBuilder& +QIRProgramBuilder::rz(const std::variant& theta, + const Value qubit) { + createOneTargetOneParameter(theta, qubit, QIR_RZ); + return *this; +} + +QIRProgramBuilder& +QIRProgramBuilder::crz(const std::variant& theta, + const Value control, const Value target) { + createControlledOneTargetOneParameter(theta, control, target, QIR_CRZ); + return *this; +} + +// POp + +QIRProgramBuilder& +QIRProgramBuilder::p(const std::variant& theta, + const Value qubit) { + createOneTargetOneParameter(theta, qubit, QIR_P); + return *this; +} + +QIRProgramBuilder& +QIRProgramBuilder::cp(const std::variant& theta, + const Value control, const Value target) { + createControlledOneTargetOneParameter(theta, control, target, QIR_CP); + return *this; +} + // U2Op QIRProgramBuilder& diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 9497e0b203..fe622643d5 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -368,6 +368,51 @@ QuartzProgramBuilder::mcry(const std::variant& theta, return *this; } +// RZOp + +QuartzProgramBuilder& +QuartzProgramBuilder::rz(const std::variant& theta, + Value qubit) { + create(loc, qubit, theta); + return *this; +} + +QuartzProgramBuilder& +QuartzProgramBuilder::crz(const std::variant& theta, + Value control, const Value target) { + return mcrz(theta, {control}, target); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mcrz(const std::variant& theta, + ValueRange controls, Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target, theta); }); + return *this; +} + +// POp + +QuartzProgramBuilder& +QuartzProgramBuilder::p(const std::variant& theta, Value qubit) { + create(loc, qubit, theta); + return *this; +} + +QuartzProgramBuilder& +QuartzProgramBuilder::cp(const std::variant& theta, + Value control, Value target) { + return mcp(theta, {control}, target); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mcp(const std::variant& theta, + ValueRange controls, Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target, theta); }); + return *this; +} + // U2Op QuartzProgramBuilder& diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/PhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/PhaseOp.cpp new file mode 100644 index 0000000000..506a5861c6 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/PhaseOp.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr POp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixP(getContext(), thetaValue); +} + +void POp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RZOp.cpp new file mode 100644 index 0000000000..b1586e3dbb --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RZOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RZOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRZ(getContext(), thetaValue); +} + +void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubitIn, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 39a46c90e2..1d78ef5a8e 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -249,7 +249,7 @@ getPosControls(const qc::Operation& operation, * @brief Adds an Id operation * * @details - * Translates Id operations from the QuantumComputation to quartz.id operations. + * Translates an Id operation from the QuantumComputation to quartz.id. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The Id operation to translate @@ -270,7 +270,7 @@ void addIdOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an X operation * * @details - * Translates X operations from the QuantumComputation to quartz.x operations. + * Translates an X operation from the QuantumComputation to quartz.x. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The X operation to translate @@ -291,7 +291,7 @@ void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds a Y operation * * @details - * Translates Y operations from the QuantumComputation to quartz.y operations. + * Translates a Y operation from the QuantumComputation to quartz.y. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The Y operation to translate @@ -312,7 +312,7 @@ void addYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds a Z operation * * @details - * Translates Z operations from the QuantumComputation to quartz.z operations. + * Translates a Z operation from the QuantumComputation to quartz.z. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The Z operation to translate @@ -333,7 +333,7 @@ void addZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an H operation * * @details - * Translates H operations from the QuantumComputation to quartz.h operations. + * Translates an H operation from the QuantumComputation to quartz.h. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The H operation to translate @@ -354,7 +354,7 @@ void addHOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an S operation * * @details - * Translates S operations from the QuantumComputation to quartz.s operations. + * Translates an S operation from the QuantumComputation to quartz.s. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The S operation to translate @@ -375,8 +375,7 @@ void addSOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an Sdg operation * * @details - * Translates Sdg operations from the QuantumComputation to quartz.sdg - * operations. + * Translates an Sdg operation from the QuantumComputation to quartz.sdg. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The Sdg operation to translate @@ -397,7 +396,7 @@ void addSdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds a T operation * * @details - * Translates T operations from the QuantumComputation to quartz.t operations. + * Translates a T operation from the QuantumComputation to quartz.t. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The T operation to translate @@ -418,8 +417,7 @@ void addTOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds a Tdg operation * * @details - * Translates Tdg operations from the QuantumComputation to quartz.tdg - * operations. + * Translates a Tdg operation from the QuantumComputation to quartz.tdg. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The Tdg operation to translate @@ -440,7 +438,7 @@ void addTdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an SX operation * * @details - * Translates SX operations from the QuantumComputation to quartz.sx operations. + * Translates an SX operation from the QuantumComputation to quartz.sx. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The SX operation to translate @@ -461,8 +459,7 @@ void addSXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an SXdg operation * * @details - * Translates SXdg operations from the QuantumComputation to quartz.sxdg - * operations. + * Translates an SXdg operation from the QuantumComputation to quartz.sxdg. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The SXdg operation to translate @@ -483,7 +480,7 @@ void addSXdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an RX operation * * @details - * Translates RX operations from the QuantumComputation to quartz.rx operations. + * Translates an RX operation from the QuantumComputation to quartz.rx. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The RX operation to translate @@ -505,7 +502,7 @@ void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds an RY operation * * @details - * Translates RY operations from the QuantumComputation to quartz.ry operations. + * Translates an RY operation from the QuantumComputation to quartz.ry. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The RY operation to translate @@ -523,6 +520,50 @@ void addRYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an RZ operation + * + * @details + * Translates an RZ operation from the QuantumComputation to quartz.rz. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RZ operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addRZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.rz(theta, target); + } else { + builder.mcrz(theta, posControls, target); + } +} + +/** + * @brief Adds a P operation + * + * @details + * Translates a P operation from the QuantumComputation to quartz.p. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The P operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.p(theta, target); + } else { + builder.mcp(theta, posControls, target); + } +} + /** * @brief Adds a U2 operation * @@ -639,6 +680,12 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::RY: addRYOp(builder, *operation, qubits); break; + case qc::OpType::RZ: + addRZOp(builder, *operation, qubits); + break; + case qc::OpType::P: + addPOp(builder, *operation, qubits); + break; case qc::OpType::U2: addU2Op(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index cbb62f576c..3a73f2e575 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2006,6 +2006,94 @@ TEST_F(CompilerPipelineTest, RY) { }); } +TEST_F(CompilerPipelineTest, RZ) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.rz(1.0, 0); + qc.rz(0.5, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.rz(1.0, q); + b.rz(0.5, q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.rz(1.0, q); + b.rz(0.5, q); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rz(1.5, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rz(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rz(1.5, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, P) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.p(1.0, 0); + qc.p(0.5, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + const auto q = reg[0]; + b.p(1.0, q); + b.p(0.5, q); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto q = reg[0]; + q = b.p(1.0, q); + b.p(0.5, q); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.5, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.5, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.p(1.5, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, U2) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From 85a3956dd775552a203a565b486213cc52434595 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:04:21 +0100 Subject: [PATCH 227/419] Fix linter errors --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 0eaeebf202..f00d2f4b6d 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -702,7 +702,7 @@ struct ConvertQuartzRXOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::RXOp op, OpAdaptor adaptor, + matchAndRewrite(quartz::RXOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { return convertOneTargetOneParameter(op, rewriter, getState()); } @@ -724,7 +724,7 @@ struct ConvertQuartzRYOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::RYOp op, OpAdaptor adaptor, + matchAndRewrite(quartz::RYOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { return convertOneTargetOneParameter(op, rewriter, getState()); } @@ -746,7 +746,7 @@ struct ConvertQuartzRZOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::RZOp op, OpAdaptor adaptor, + matchAndRewrite(quartz::RZOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { return convertOneTargetOneParameter(op, rewriter, getState()); } @@ -768,7 +768,7 @@ struct ConvertQuartzPOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::POp op, OpAdaptor adaptor, + matchAndRewrite(quartz::POp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { return convertOneTargetOneParameter(op, rewriter, getState()); } From b71e6fc2aebdd8da6302f4f7baef110c5385553d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:07:21 +0100 Subject: [PATCH 228/419] Rename removeInversePair() --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 7 ++++--- mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp | 2 +- 11 files changed, 14 insertions(+), 13 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index 2d8b6b946e..b1cff2ddaf 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -13,7 +13,7 @@ namespace mlir::flux { /** - * @brief Remove a pair of inverse operations. + * @brief Remove a pair of inverse one-target, zero-parameter operations * * @tparam InverseOpType The type of the inverse operation. * @tparam OpType The type of the operation to be checked. @@ -22,8 +22,9 @@ namespace mlir::flux { * @return LogicalResult Success or failure of the removal. */ template -inline mlir::LogicalResult removeInversePair(OpType op, - mlir::PatternRewriter& rewriter) { +inline mlir::LogicalResult +removeInversePairOneTargetZeroParameter(OpType op, + mlir::PatternRewriter& rewriter) { // Check if the predecessor is the inverse operation auto prevOp = op.getQubitIn().template getDefiningOp(); if (!prevOp) { diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp index fceb29854f..0c72063a39 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp @@ -32,7 +32,7 @@ struct RemoveSubsequentH final : OpRewritePattern { LogicalResult matchAndRewrite(HOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp index f63785bba3..ae4f8aaf99 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp @@ -32,7 +32,7 @@ struct RemoveSAfterSdg final : OpRewritePattern { LogicalResult matchAndRewrite(SOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp index b409dd44b2..d47b9a2595 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp @@ -32,7 +32,7 @@ struct RemoveSXAfterSXdg final : OpRewritePattern { LogicalResult matchAndRewrite(SXOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp index 873ce16a4d..aaca5d8ba1 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp @@ -32,7 +32,7 @@ struct RemoveSXdgAfterSX final : OpRewritePattern { LogicalResult matchAndRewrite(SXdgOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp index 4d13a9b9f0..f96fed9a6e 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp @@ -32,7 +32,7 @@ struct RemoveSdgAfterS final : OpRewritePattern { LogicalResult matchAndRewrite(SdgOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp index de1a169b62..c266858e7a 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp @@ -32,7 +32,7 @@ struct RemoveTAfterTdg final : OpRewritePattern { LogicalResult matchAndRewrite(TOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp index ea864798da..0453fbecd1 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp @@ -32,7 +32,7 @@ struct RemoveTdgAfterT final : OpRewritePattern { LogicalResult matchAndRewrite(TdgOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp index ed65e904ef..46bb025dbc 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp @@ -32,7 +32,7 @@ struct RemoveSubsequentX final : OpRewritePattern { LogicalResult matchAndRewrite(XOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp index 60c6157d42..d13fa625bc 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp @@ -32,7 +32,7 @@ struct RemoveSubsequentY final : OpRewritePattern { LogicalResult matchAndRewrite(YOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp index 782a66b4e5..05b4213d44 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp @@ -32,7 +32,7 @@ struct RemoveSubsequentZ final : OpRewritePattern { LogicalResult matchAndRewrite(ZOp op, PatternRewriter& rewriter) const override { - return removeInversePair(op, rewriter); + return removeInversePairOneTargetZeroParameter(op, rewriter); } }; From a487837c2306690d6105f3830b5c5ee252c20c36 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:14:09 +0100 Subject: [PATCH 229/419] Streamline canonicalizations by defining template functions --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 31 ++++++++++++++++++- .../lib/Dialect/Flux/IR/StandardGates/POp.cpp | 17 ++-------- .../Dialect/Flux/IR/StandardGates/RXOp.cpp | 17 ++-------- .../Dialect/Flux/IR/StandardGates/RYOp.cpp | 17 ++-------- .../Dialect/Flux/IR/StandardGates/RZOp.cpp | 17 ++-------- 5 files changed, 38 insertions(+), 61 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index b1cff2ddaf..18285d87b5 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include #include namespace mlir::flux { @@ -17,7 +18,7 @@ namespace mlir::flux { * * @tparam InverseOpType The type of the inverse operation. * @tparam OpType The type of the operation to be checked. - * @param op The operation instance to be checked. + * @param op The operation instance. * @param rewriter The pattern rewriter. * @return LogicalResult Success or failure of the removal. */ @@ -38,4 +39,32 @@ removeInversePairOneTargetZeroParameter(OpType op, return success(); } +/** + * @brief Merge two compatible one-target, one-parameter operations + * + * @tparam OpType The type of the operation to be merged. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the merge. + */ +template +inline mlir::LogicalResult +mergeOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the same operation + auto prevOp = op.getQubitIn().template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Compute and set new theta + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(1), prevOp.getOperand(1)); + op->setOperand(1, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + + return success(); +} + } // namespace mlir::flux diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp index 7530c29057..a859f65150 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" @@ -35,21 +36,7 @@ struct MergeSubsequentP final : OpRewritePattern { LogicalResult matchAndRewrite(POp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an POp - auto prevOp = op.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Compute and set new theta - auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), - prevOp.getTheta()); - op->setOperand(1, newTheta.getResult()); - - // Trivialize previous POp - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - - return success(); + return mergeOneTargetOneParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp index 9324838a12..52ffc626cb 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" @@ -35,21 +36,7 @@ struct MergeSubsequentRX final : OpRewritePattern { LogicalResult matchAndRewrite(RXOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an RXOp - auto prevOp = op.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Compute and set new theta - auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), - prevOp.getTheta()); - op->setOperand(1, newTheta.getResult()); - - // Trivialize previous RXOp - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - - return success(); + return mergeOneTargetOneParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp index a7b05de77e..0ba46ddb4c 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" @@ -35,21 +36,7 @@ struct MergeSubsequentRY final : OpRewritePattern { LogicalResult matchAndRewrite(RYOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an RYOp - auto prevOp = op.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Compute and set new theta - auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), - prevOp.getTheta()); - op->setOperand(1, newTheta.getResult()); - - // Trivialize previous RYOp - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - - return success(); + return mergeOneTargetOneParameter(op, rewriter); } }; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp index bc105cde60..96fc1da591 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" @@ -35,21 +36,7 @@ struct MergeSubsequentRZ final : OpRewritePattern { LogicalResult matchAndRewrite(RZOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an RZOp - auto prevOp = op.getQubitIn().getDefiningOp(); - if (!prevOp) { - return failure(); - } - - // Compute and set new theta - auto newTheta = rewriter.create(op.getLoc(), op.getTheta(), - prevOp.getTheta()); - op->setOperand(1, newTheta.getResult()); - - // Trivialize previous RZOp - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - - return success(); + return mergeOneTargetOneParameter(op, rewriter); } }; From dff917d006fec66b9757cb0eff90fdaf1fa62f64 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:25:40 +0100 Subject: [PATCH 230/419] Modularize modifiers --- mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 2 + mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 250 ---------------- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 273 ++++++++++++++++++ mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 2 + .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 178 ++++++++++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 156 ---------- 6 files changed, 455 insertions(+), 406 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt index bc226f052e..5772e45b3d 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -6,11 +6,13 @@ # # Licensed under the MIT License +file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") file(GLOB_RECURSE STANDARD_GATES "${CMAKE_CURRENT_SOURCE_DIR}/StandardGates/*.cpp") add_mlir_dialect_library( MLIRFluxDialect FluxOps.cpp + ${MODIFIERS} ${STANDARD_GATES} ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index eee067de2b..6acafcda8e 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -120,183 +120,6 @@ LogicalResult MeasureOp::verify() { return success(); } -//===----------------------------------------------------------------------===// -// Modifiers -//===----------------------------------------------------------------------===// - -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const ValueRange controls, const ValueRange targets, - UnitaryOpInterface bodyUnitary) { - build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); - - // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); - odsBuilder.create(odsState.location, op->getResults()); -} - -void CtrlOp::build( - OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, - const ValueRange targets, - const std::function& bodyBuilder) { - build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); - - // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - auto targetsOut = bodyBuilder(odsBuilder, targets); - odsBuilder.create(odsState.location, targetsOut); -} - -UnitaryOpInterface CtrlOp::getBodyUnitary() { - return llvm::dyn_cast(&getBody().front().front()); -} - -size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } - -size_t CtrlOp::getNumTargets() { return getTargetsIn().size(); } - -size_t CtrlOp::getNumControls() { - return getNumPosControls() + getNumNegControls(); -} - -size_t CtrlOp::getNumPosControls() { return getControlsIn().size(); } - -size_t CtrlOp::getNumNegControls() { - return getBodyUnitary().getNumNegControls(); -} - -Value CtrlOp::getInputQubit(const size_t i) { - const auto numPosControls = getNumPosControls(); - if (i < numPosControls) { - return getControlsIn()[i]; - } - if (numPosControls <= i && i < getNumQubits()) { - return getBodyUnitary().getInputQubit(i - numPosControls); - } - llvm::report_fatal_error("Invalid qubit index"); -} - -Value CtrlOp::getOutputQubit(const size_t i) { - const auto numPosControls = getNumPosControls(); - if (i < numPosControls) { - return getControlsOut()[i]; - } - if (numPosControls <= i && i < getNumQubits()) { - return getBodyUnitary().getOutputQubit(i - numPosControls); - } - llvm::report_fatal_error("Invalid qubit index"); -} - -Value CtrlOp::getInputTarget(const size_t i) { return getTargetsIn()[i]; } - -Value CtrlOp::getOutputTarget(const size_t i) { return getTargetsOut()[i]; } - -Value CtrlOp::getInputPosControl(const size_t i) { return getControlsIn()[i]; } - -Value CtrlOp::getOutputPosControl(const size_t i) { - return getControlsOut()[i]; -} - -Value CtrlOp::getInputNegControl(const size_t i) { - return getBodyUnitary().getInputNegControl(i); -} - -Value CtrlOp::getOutputNegControl(const size_t i) { - return getBodyUnitary().getOutputNegControl(i); -} - -Value CtrlOp::getInputForOutput(const Value output) { - for (size_t i = 0; i < getNumPosControls(); ++i) { - if (output == getControlsOut()[i]) { - return getControlsIn()[i]; - } - } - for (size_t i = 0; i < getNumTargets(); ++i) { - if (output == getTargetsOut()[i]) { - return getTargetsIn()[i]; - } - } - llvm::report_fatal_error("Given qubit is not an output of the operation"); -} - -Value CtrlOp::getOutputForInput(const Value input) { - for (size_t i = 0; i < getNumPosControls(); ++i) { - if (input == getControlsIn()[i]) { - return getControlsOut()[i]; - } - } - for (size_t i = 0; i < getNumTargets(); ++i) { - if (input == getTargetsIn()[i]) { - return getTargetsOut()[i]; - } - } - llvm::report_fatal_error("Given qubit is not an input of the operation"); -} - -size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } - -bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } - -Value CtrlOp::getParameter(const size_t i) { - return getBodyUnitary().getParameter(i); -} - -DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - return getMatrixCtrl(getContext(), getNumPosControls(), - getBodyUnitary().tryGetStaticMatrix()); -} - -LogicalResult CtrlOp::verify() { - auto& block = getBody().front(); - if (block.getOperations().size() != 2) { - return emitOpError("body region must have exactly two operations"); - } - if (!llvm::isa(block.front())) { - return emitOpError( - "first operation in body region must be a unitary operation"); - } - if (!llvm::isa(block.back())) { - return emitOpError( - "second operation in body region must be a yield operation"); - } - // The yield operation must yield as many values as there are targets - if (block.back().getNumOperands() != getNumTargets()) { - return emitOpError("yield operation must yield ") - << getNumTargets() << " values, but found " - << block.back().getNumOperands(); - } - - SmallPtrSet uniqueQubitsIn; - for (const auto& control : getControlsIn()) { - if (!uniqueQubitsIn.insert(control).second) { - return emitOpError("duplicate control qubit found"); - } - } - auto bodyUnitary = getBodyUnitary(); - const auto numQubits = bodyUnitary.getNumQubits(); - for (size_t i = 0; i < numQubits; i++) { - if (!uniqueQubitsIn.insert(bodyUnitary.getInputQubit(i)).second) { - return emitOpError("duplicate qubit found"); - } - } - SmallPtrSet uniqueQubitsOut; - for (const auto& control : getControlsOut()) { - if (!uniqueQubitsOut.insert(control).second) { - return emitOpError("duplicate control qubit found"); - } - } - for (size_t i = 0; i < numQubits; i++) { - if (!uniqueQubitsOut.insert(bodyUnitary.getOutputQubit(i)).second) { - return emitOpError("duplicate qubit found"); - } - } - return success(); -} - //===----------------------------------------------------------------------===// // Canonicalization Patterns //===----------------------------------------------------------------------===// @@ -344,74 +167,6 @@ struct RemoveResetAfterAlloc final : OpRewritePattern { } }; -/** - * @brief Merge nested control modifiers into a single one. - */ -struct MergeNestedCtrl final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(CtrlOp op, - PatternRewriter& rewriter) const override { - auto bodyUnitary = op.getBodyUnitary(); - auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); - if (!bodyCtrlOp) { - return failure(); - } - - // Merge controls - SmallVector newControls(op.getControlsIn()); - for (const auto control : bodyCtrlOp.getControlsIn()) { - newControls.push_back(control); - } - - rewriter.replaceOpWithNewOp(op, newControls, op.getTargetsIn(), - bodyCtrlOp.getBodyUnitary()); - rewriter.eraseOp(bodyCtrlOp); - - return success(); - } -}; - -/** - * @brief Remove control modifiers without controls. - */ -struct RemoveTrivialCtrl final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(CtrlOp op, - PatternRewriter& rewriter) const override { - if (op.getNumControls() > 0) { - return failure(); - } - rewriter.replaceOp(op, op.getBodyUnitary()); - return success(); - } -}; - -/** - * @brief Inline controlled identity operations. - */ -struct CtrlInlineId final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(CtrlOp op, - PatternRewriter& rewriter) const override { - if (!llvm::isa(op.getBodyUnitary().getOperation())) { - return failure(); - } - - auto idOp = rewriter.create(op.getLoc(), op.getTargetsIn().front()); - - SmallVector newOperands; - newOperands.reserve(op.getNumControls() + 1); - newOperands.append(op.getControlsIn().begin(), op.getControlsIn().end()); - newOperands.push_back(idOp.getQubitOut()); - rewriter.replaceOp(op, newOperands); - - return success(); - } -}; - } // namespace void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, @@ -423,8 +178,3 @@ void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); } - -void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp new file mode 100644 index 0000000000..6f5d900287 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge nested control modifiers into a single one. + */ +struct MergeNestedCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + auto bodyUnitary = op.getBodyUnitary(); + auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); + if (!bodyCtrlOp) { + return failure(); + } + + // Merge controls + SmallVector newControls(op.getControlsIn()); + for (const auto control : bodyCtrlOp.getControlsIn()) { + newControls.push_back(control); + } + + rewriter.replaceOpWithNewOp(op, newControls, op.getTargetsIn(), + bodyCtrlOp.getBodyUnitary()); + rewriter.eraseOp(bodyCtrlOp); + + return success(); + } +}; + +/** + * @brief Remove control modifiers without controls. + */ +struct RemoveTrivialCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + if (op.getNumControls() > 0) { + return failure(); + } + rewriter.replaceOp(op, op.getBodyUnitary()); + return success(); + } +}; + +/** + * @brief Inline controlled identity operations. + */ +struct CtrlInlineId final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + if (!llvm::isa(op.getBodyUnitary().getOperation())) { + return failure(); + } + + auto idOp = rewriter.create(op.getLoc(), op.getTargetsIn().front()); + + SmallVector newOperands; + newOperands.reserve(op.getNumControls() + 1); + newOperands.append(op.getControlsIn().begin(), op.getControlsIn().end()); + newOperands.push_back(idOp.getQubitOut()); + rewriter.replaceOp(op, newOperands); + + return success(); + } +}; + +} // namespace + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, const ValueRange targets, + UnitaryOpInterface bodyUnitary) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location, op->getResults()); +} + +void CtrlOp::build( + OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, + const ValueRange targets, + const std::function& bodyBuilder) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto targetsOut = bodyBuilder(odsBuilder, targets); + odsBuilder.create(odsState.location, targetsOut); +} + +UnitaryOpInterface CtrlOp::getBodyUnitary() { + return llvm::dyn_cast(&getBody().front().front()); +} + +size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } + +size_t CtrlOp::getNumTargets() { return getTargetsIn().size(); } + +size_t CtrlOp::getNumControls() { + return getNumPosControls() + getNumNegControls(); +} + +size_t CtrlOp::getNumPosControls() { return getControlsIn().size(); } + +size_t CtrlOp::getNumNegControls() { + return getBodyUnitary().getNumNegControls(); +} + +Value CtrlOp::getInputQubit(const size_t i) { + const auto numPosControls = getNumPosControls(); + if (i < numPosControls) { + return getControlsIn()[i]; + } + if (numPosControls <= i && i < getNumQubits()) { + return getBodyUnitary().getInputQubit(i - numPosControls); + } + llvm::report_fatal_error("Invalid qubit index"); +} + +Value CtrlOp::getOutputQubit(const size_t i) { + const auto numPosControls = getNumPosControls(); + if (i < numPosControls) { + return getControlsOut()[i]; + } + if (numPosControls <= i && i < getNumQubits()) { + return getBodyUnitary().getOutputQubit(i - numPosControls); + } + llvm::report_fatal_error("Invalid qubit index"); +} + +Value CtrlOp::getInputTarget(const size_t i) { return getTargetsIn()[i]; } + +Value CtrlOp::getOutputTarget(const size_t i) { return getTargetsOut()[i]; } + +Value CtrlOp::getInputPosControl(const size_t i) { return getControlsIn()[i]; } + +Value CtrlOp::getOutputPosControl(const size_t i) { + return getControlsOut()[i]; +} + +Value CtrlOp::getInputNegControl(const size_t i) { + return getBodyUnitary().getInputNegControl(i); +} + +Value CtrlOp::getOutputNegControl(const size_t i) { + return getBodyUnitary().getOutputNegControl(i); +} + +Value CtrlOp::getInputForOutput(const Value output) { + for (size_t i = 0; i < getNumPosControls(); ++i) { + if (output == getControlsOut()[i]) { + return getControlsIn()[i]; + } + } + for (size_t i = 0; i < getNumTargets(); ++i) { + if (output == getTargetsOut()[i]) { + return getTargetsIn()[i]; + } + } + llvm::report_fatal_error("Given qubit is not an output of the operation"); +} + +Value CtrlOp::getOutputForInput(const Value input) { + for (size_t i = 0; i < getNumPosControls(); ++i) { + if (input == getControlsIn()[i]) { + return getControlsOut()[i]; + } + } + for (size_t i = 0; i < getNumTargets(); ++i) { + if (input == getTargetsIn()[i]) { + return getTargetsOut()[i]; + } + } + llvm::report_fatal_error("Given qubit is not an input of the operation"); +} + +size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } + +bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } + +Value CtrlOp::getParameter(const size_t i) { + return getBodyUnitary().getParameter(i); +} + +DenseElementsAttr CtrlOp::tryGetStaticMatrix() { + return getMatrixCtrl(getContext(), getNumPosControls(), + getBodyUnitary().tryGetStaticMatrix()); +} + +LogicalResult CtrlOp::verify() { + auto& block = getBody().front(); + if (block.getOperations().size() != 2) { + return emitOpError("body region must have exactly two operations"); + } + if (!llvm::isa(block.front())) { + return emitOpError( + "first operation in body region must be a unitary operation"); + } + if (!llvm::isa(block.back())) { + return emitOpError( + "second operation in body region must be a yield operation"); + } + // The yield operation must yield as many values as there are targets + if (block.back().getNumOperands() != getNumTargets()) { + return emitOpError("yield operation must yield ") + << getNumTargets() << " values, but found " + << block.back().getNumOperands(); + } + + SmallPtrSet uniqueQubitsIn; + for (const auto& control : getControlsIn()) { + if (!uniqueQubitsIn.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + auto bodyUnitary = getBodyUnitary(); + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubitsIn.insert(bodyUnitary.getInputQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + SmallPtrSet uniqueQubitsOut; + for (const auto& control : getControlsOut()) { + if (!uniqueQubitsOut.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubitsOut.insert(bodyUnitary.getOutputQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + return success(); +} + +void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt index 282a4962c1..69555a228d 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -6,11 +6,13 @@ # # Licensed under the MIT License +file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") file(GLOB_RECURSE STANDARD_GATES "${CMAKE_CURRENT_SOURCE_DIR}/StandardGates/*.cpp") add_mlir_dialect_library( MLIRQuartzDialect QuartzOps.cpp + ${MODIFIERS} ${STANDARD_GATES} ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp new file mode 100644 index 0000000000..921906cbd0 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +namespace { + +/** + * @brief Merge nested control modifiers into a single one. + */ +struct MergeNestedCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + auto bodyUnitary = ctrlOp.getBodyUnitary(); + auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); + if (!bodyCtrlOp) { + return failure(); + } + + llvm::SmallVector newControls(ctrlOp.getControls()); + for (const auto control : bodyCtrlOp.getControls()) { + newControls.push_back(control); + } + + rewriter.replaceOpWithNewOp(ctrlOp, newControls, + bodyCtrlOp.getBodyUnitary()); + rewriter.eraseOp(bodyCtrlOp); + + return success(); + } +}; + +/** + * @brief Remove control modifiers without controls. + */ +struct RemoveTrivialCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp ctrlOp, + PatternRewriter& rewriter) const override { + if (ctrlOp.getNumControls() > 0) { + return failure(); + } + rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); + return success(); + } +}; + +} // namespace + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, UnitaryOpInterface bodyUnitary) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + // Move the unitary op into the block + odsBuilder.setInsertionPointToStart(&block); + odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location); +} + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, + const std::function& bodyBuilder) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(odsBuilder); + odsBuilder.create(odsState.location); +} + +UnitaryOpInterface CtrlOp::getBodyUnitary() { + return llvm::dyn_cast(&getBody().front().front()); +} + +size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } + +size_t CtrlOp::getNumTargets() { return getBodyUnitary().getNumTargets(); } + +size_t CtrlOp::getNumControls() { + return getNumPosControls() + getNumNegControls(); +} + +size_t CtrlOp::getNumPosControls() { return getControls().size(); } + +size_t CtrlOp::getNumNegControls() { + return getBodyUnitary().getNumNegControls(); +} + +Value CtrlOp::getQubit(const size_t i) { + const auto numPosControls = getNumPosControls(); + if (i < numPosControls) { + return getControls()[i]; + } + if (numPosControls <= i && i < getNumQubits()) { + return getBodyUnitary().getQubit(i - numPosControls); + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value CtrlOp::getTarget(const size_t i) { + return getBodyUnitary().getTarget(i); +} + +Value CtrlOp::getPosControl(const size_t i) { return getControls()[i]; } + +Value CtrlOp::getNegControl(const size_t i) { + return getBodyUnitary().getNegControl(i); +} + +size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } + +bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } + +Value CtrlOp::getParameter(const size_t i) { + return getBodyUnitary().getParameter(i); +} + +DenseElementsAttr CtrlOp::tryGetStaticMatrix() { + return getMatrixCtrl(getContext(), getNumPosControls(), + getBodyUnitary().tryGetStaticMatrix()); +} + +LogicalResult CtrlOp::verify() { + auto& block = getBody().front(); + if (block.getOperations().size() != 2) { + return emitOpError("body region must have exactly two operations"); + } + if (!llvm::isa(block.front())) { + return emitOpError( + "first operation in body region must be a unitary operation"); + } + if (!llvm::isa(block.back())) { + return emitOpError( + "second operation in body region must be a yield operation"); + } + llvm::SmallPtrSet uniqueQubits; + for (const auto& control : getControls()) { + if (!uniqueQubits.insert(control).second) { + return emitOpError("duplicate control qubit found"); + } + } + auto bodyUnitary = getBodyUnitary(); + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (!uniqueQubits.insert(bodyUnitary.getQubit(i)).second) { + return emitOpError("duplicate qubit found"); + } + } + return success(); +} + +void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 26210d5a4e..fea13c06d8 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -120,159 +120,3 @@ LogicalResult MeasureOp::verify() { } return success(); } - -//===----------------------------------------------------------------------===// -// Modifiers -//===----------------------------------------------------------------------===// - -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const ValueRange controls, UnitaryOpInterface bodyUnitary) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); - - // Move the unitary op into the block - odsBuilder.setInsertionPointToStart(&block); - odsBuilder.clone(*bodyUnitary.getOperation()); - odsBuilder.create(odsState.location); -} - -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const ValueRange controls, - const std::function& bodyBuilder) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); - odsBuilder.setInsertionPointToStart(&block); - bodyBuilder(odsBuilder); - odsBuilder.create(odsState.location); -} - -UnitaryOpInterface CtrlOp::getBodyUnitary() { - return llvm::dyn_cast(&getBody().front().front()); -} - -size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } - -size_t CtrlOp::getNumTargets() { return getBodyUnitary().getNumTargets(); } - -size_t CtrlOp::getNumControls() { - return getNumPosControls() + getNumNegControls(); -} - -size_t CtrlOp::getNumPosControls() { return getControls().size(); } - -size_t CtrlOp::getNumNegControls() { - return getBodyUnitary().getNumNegControls(); -} - -Value CtrlOp::getQubit(const size_t i) { - const auto numPosControls = getNumPosControls(); - if (i < numPosControls) { - return getControls()[i]; - } - if (numPosControls <= i && i < getNumQubits()) { - return getBodyUnitary().getQubit(i - numPosControls); - } - llvm::reportFatalUsageError("Invalid qubit index"); -} - -Value CtrlOp::getTarget(const size_t i) { - return getBodyUnitary().getTarget(i); -} - -Value CtrlOp::getPosControl(const size_t i) { return getControls()[i]; } - -Value CtrlOp::getNegControl(const size_t i) { - return getBodyUnitary().getNegControl(i); -} - -size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } - -bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } - -Value CtrlOp::getParameter(const size_t i) { - return getBodyUnitary().getParameter(i); -} - -DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - return getMatrixCtrl(getContext(), getNumPosControls(), - getBodyUnitary().tryGetStaticMatrix()); -} - -LogicalResult CtrlOp::verify() { - auto& block = getBody().front(); - if (block.getOperations().size() != 2) { - return emitOpError("body region must have exactly two operations"); - } - if (!llvm::isa(block.front())) { - return emitOpError( - "first operation in body region must be a unitary operation"); - } - if (!llvm::isa(block.back())) { - return emitOpError( - "second operation in body region must be a yield operation"); - } - llvm::SmallPtrSet uniqueQubits; - for (const auto& control : getControls()) { - if (!uniqueQubits.insert(control).second) { - return emitOpError("duplicate control qubit found"); - } - } - auto bodyUnitary = getBodyUnitary(); - const auto numQubits = bodyUnitary.getNumQubits(); - for (size_t i = 0; i < numQubits; i++) { - if (!uniqueQubits.insert(bodyUnitary.getQubit(i)).second) { - return emitOpError("duplicate qubit found"); - } - } - return success(); -} - -/** - * @brief A rewrite pattern for merging nested control modifiers. - */ -struct MergeNestedCtrl final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, - PatternRewriter& rewriter) const override { - auto bodyUnitary = ctrlOp.getBodyUnitary(); - auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); - if (!bodyCtrlOp) { - return failure(); - } - - llvm::SmallVector newControls(ctrlOp.getControls()); - for (const auto control : bodyCtrlOp.getControls()) { - newControls.push_back(control); - } - - rewriter.replaceOpWithNewOp(ctrlOp, newControls, - bodyCtrlOp.getBodyUnitary()); - rewriter.eraseOp(bodyCtrlOp); - - return success(); - } -}; - -/** - * @brief A rewrite pattern for removing control modifiers without controls. - */ -struct RemoveTrivialCtrl final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, - PatternRewriter& rewriter) const override { - if (ctrlOp.getNumControls() > 0) { - return failure(); - } - rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); - return success(); - } -}; - -void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} From 530e8476f00b81abfa63acaa7df13c799cd708cb Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 16:49:07 +0100 Subject: [PATCH 231/419] Support multi-controlled operations in QIR --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 337 ++++++++++++++- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 8 +- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 391 ++++++++++++++++-- .../pipeline/test_compiler_pipeline.cpp | 62 +++ 4 files changed, 748 insertions(+), 50 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 62e9e122fb..901bbb696c 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -296,6 +296,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cid(Value control, Value target); + /** + * @brief Apply a multi-controlled Id gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcid({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cci__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcid(ValueRange controls, Value target); + /** * @brief Apply an X gate to a qubit * @@ -329,6 +347,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cx(Value control, Value target); + /** + * @brief Apply a multi-controlled X gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcx({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccx__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcx(ValueRange controls, Value target); + /** * @brief Apply a Y gate to a qubit * @@ -362,6 +398,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cy(Value control, Value target); + /** + * @brief Apply a multi-controlled Y gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcy({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccy__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcy(ValueRange controls, Value target); + /** * @brief Apply a Z gate to a qubit * @@ -395,6 +449,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cz(Value control, Value target); + /** + * @brief Apply a multi-controlled Z gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcz({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccz__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcz(ValueRange controls, Value target); + /** * @brief Apply an H gate to a qubit * @@ -428,6 +500,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& ch(Value control, Value target); + /** + * @brief Apply a multi-controlled H gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mch({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cch__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mch(ValueRange controls, Value target); + /** * @brief Apply an S gate to a qubit * @@ -461,6 +551,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cs(Value control, Value target); + /** + * @brief Apply a multi-controlled S gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcs({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccs__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcs(ValueRange controls, Value target); + /** * @brief Apply an Sdg gate to a qubit * @@ -495,6 +603,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& csdg(Value control, Value target); + /** + * @brief Apply a multi-controlled Sdg gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcsdg({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccsdg__body(%c1, %c2, %t) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcsdg(ValueRange controls, Value target); + /** * @brief Apply a T gate to a qubit * @@ -528,6 +654,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& ct(Value control, Value target); + /** + * @brief Apply a multi-controlled T gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mct({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cct__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mct(ValueRange controls, Value target); + /** * @brief Apply a Tdg gate to a qubit * @@ -562,6 +706,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& ctdg(Value control, Value target); + /** + * @brief Apply a multi-controlled Tdg gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mctdg({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cctdg__body(%c1, %c2, %t) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mctdg(ValueRange controls, Value target); + /** * @brief Apply an SX gate to a qubit * @@ -595,6 +757,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& csx(Value control, Value target); + /** + * @brief Apply a multi-controlled SX gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcs({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccs__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, + * !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcsx(ValueRange controls, Value target); + /** * @brief Apply an SXdg gate to a qubit * @@ -629,6 +809,24 @@ class QIRProgramBuilder { */ QIRProgramBuilder& csxdg(Value control, Value target); + /** + * @brief Apply a multi-controlled SXdg gate + * + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcsxdg({control1, control2}, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ccsxdg__body(%c1, %c2, %t) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcsxdg(ValueRange controls, Value target); + /** * @brief Apply an RX gate to a qubit * @@ -666,6 +864,26 @@ class QIRProgramBuilder { QIRProgramBuilder& crx(const std::variant& theta, Value control, Value target); + /** + * @brief Apply a multi-controlled RX gate + * + * @param theta Rotation angle + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcrx(theta, controls, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mcrx__body(%c1, %c2, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& mcrx(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply an RY gate to a qubit * @@ -703,6 +921,26 @@ class QIRProgramBuilder { QIRProgramBuilder& cry(const std::variant& theta, Value control, Value target); + /** + * @brief Apply a multi-controlled RY gate + * + * @param theta Rotation angle + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcry(theta, controls, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mcry__body(%c1, %c2, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& mcry(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply an RZ gate to a qubit * @@ -740,6 +978,26 @@ class QIRProgramBuilder { QIRProgramBuilder& crz(const std::variant& theta, Value control, Value target); + /** + * @brief Apply a multi-controlled RZ gate + * + * @param theta Rotation angle + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcrz(theta, controls, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mcrz__body(%c1, %c2, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& mcrz(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a P gate to a qubit * @@ -776,6 +1034,27 @@ class QIRProgramBuilder { */ QIRProgramBuilder& cp(const std::variant& theta, Value control, Value target); + + /** + * @brief Apply a multi-controlled P gate + * + * @param theta Rotation angle + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcp(theta, controls, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mcp__body(%c1, %c2, %t, %theta) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr, f64) -> () + * ``` + */ + QIRProgramBuilder& mcp(const std::variant& theta, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -818,6 +1097,28 @@ class QIRProgramBuilder { const std::variant& lambda, Value control, Value target); + /** + * @brief Apply a multi-controlled U2 gate + * + * @param phi Rotation angle + * @param lambda Rotation angle + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mu2(phi, lambda, controls, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mu2__body(%c1, %c2, %t, %phi, %lambda) : + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () + * ``` + */ + QIRProgramBuilder& mcu2(const std::variant& phi, + const std::variant& lambda, + ValueRange controls, Value target); + /** * @brief Apply a SWAP gate to two qubits * @@ -850,12 +1151,30 @@ class QIRProgramBuilder { * ``` * ```mlir * llvm.call @__quantum__qis__cswap__body(%c, %t0, %t1) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr) -> - * () + * !llvm.ptr, !llvm.ptr) -> () * ``` */ QIRProgramBuilder& cswap(Value control, Value target0, Value target1); + /** + * @brief Apply a multi-controlled SWAP gate + * + * @param controls Control qubits + * @param target1 Target qubit + * @param target2 Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcswap({control1, control2}, target1, target2); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mcswap__body(%c1, %c2, %t0, %t1) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mcswap(ValueRange controls, Value target0, Value target1); + //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// @@ -908,7 +1227,7 @@ class QIRProgramBuilder { /** * @brief Helper to create a one-target, zero-parameter QIR operation * - * @param qubit Input qubit + * @param qubit Target qubit * @param fnName Name of the QIR function to call */ void createOneTargetZeroParameter(const Value qubit, StringRef fnName); @@ -917,11 +1236,11 @@ class QIRProgramBuilder { * @brief Helper to create a controlled one-target, zero-parameter QIR * operation * - * @param control Input control qubit - * @param target Input target qubit + * @param controls Control qubits + * @param target Target qubit * @param fnName Name of the QIR function to call */ - void createControlledOneTargetZeroParameter(const Value control, + void createControlledOneTargetZeroParameter(const ValueRange controls, const Value target, StringRef fnName); @@ -940,12 +1259,12 @@ class QIRProgramBuilder { * operation * * @param parameter Operation parameter - * @param control Input control qubit - * @param target Input target qubit + * @param controls Control qubits + * @param target Target qubit * @param fnName Name of the QIR function to call */ void createControlledOneTargetOneParameter( - const std::variant& parameter, const Value control, + const std::variant& parameter, const ValueRange controls, const Value target, StringRef fnName); /** diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 461987d3bc..edc3ac828b 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -137,7 +137,7 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; const size_t numCtrls = posCtrls.size(); - // Define function argument types + // Define argument types SmallVector argumentTypes; argumentTypes.reserve(numCtrls + 1); const auto ptrType = LLVM::LLVMPointerType::get(ctx); @@ -147,6 +147,8 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, } // Add target pointer argumentTypes.push_back(ptrType); + + // Define function signature const auto fnSignature = LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); @@ -195,7 +197,7 @@ convertOneTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; const size_t numCtrls = posCtrls.size(); - // Define function argument types + // Define argument types SmallVector argumentTypes; argumentTypes.reserve(numCtrls + 2); const auto ptrType = LLVM::LLVMPointerType::get(ctx); @@ -207,6 +209,8 @@ convertOneTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, argumentTypes.push_back(ptrType); // Add parameter type argumentTypes.push_back(Float64Type::get(ctx)); + + // Define function signature const auto fnSignature = LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index a4d10dc575..5bccb0c5d5 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -245,31 +245,51 @@ void QIRProgramBuilder::createOneTargetZeroParameter(const Value qubit, // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create call - const auto qirSignature = LLVM::LLVMFunctionType::get( + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), LLVM::LLVMPointerType::get(builder.getContext())); + + // Declare QIR function auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + builder.create(loc, fnDecl, ValueRange{qubit}); } void QIRProgramBuilder::createControlledOneTargetZeroParameter( - const Value control, const Value target, StringRef fnName) { + const ValueRange controls, const Value target, StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); - builder.create(loc, fnDecl, ValueRange{control, target}); + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 1); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 1); + operands.append(controls.begin(), controls.end()); + operands.push_back(target); + + builder.create(loc, fnDecl, operands); } void QIRProgramBuilder::createOneTargetOneParameter( @@ -298,19 +318,22 @@ void QIRProgramBuilder::createOneTargetOneParameter( // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create call - const auto qirSignature = LLVM::LLVMFunctionType::get( + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(builder.getContext()), {LLVM::LLVMPointerType::get(builder.getContext()), Float64Type::get(builder.getContext())}); + + // Declare QIR function auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + builder.create(loc, fnDecl, ValueRange{qubit, parameterOperand}); } void QIRProgramBuilder::createControlledOneTargetOneParameter( - const std::variant& parameter, const Value control, + const std::variant& parameter, const ValueRange controls, const Value target, StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard entryGuard(builder); @@ -335,16 +358,34 @@ void QIRProgramBuilder::createControlledOneTargetOneParameter( // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Create call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext()), - Float64Type::get(builder.getContext())}); + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 2); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + // Add parameter type + argumentTypes.push_back(Float64Type::get(builder.getContext())); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, qirSignature); - builder.create(loc, fnDecl, - ValueRange{control, target, parameterOperand}); + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 2); + operands.append(controls.begin(), controls.end()); + operands.push_back(target); + operands.push_back(parameterOperand); + + builder.create(loc, fnDecl, operands); } // IdOp @@ -356,7 +397,24 @@ QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::cid(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CID); + createControlledOneTargetZeroParameter({control}, target, QIR_CID); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcid(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CID; + } else if (controls.size() == 2) { + fnName = QIR_CCID; + } else if (controls.size() == 3) { + fnName = QIR_CCCID; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -369,7 +427,24 @@ QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CX); + createControlledOneTargetZeroParameter({control}, target, QIR_CX); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcx(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CX; + } else if (controls.size() == 2) { + fnName = QIR_CCX; + } else if (controls.size() == 3) { + fnName = QIR_CCCX; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -382,7 +457,24 @@ QIRProgramBuilder& QIRProgramBuilder::y(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::cy(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CY); + createControlledOneTargetZeroParameter({control}, target, QIR_CY); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcy(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CY; + } else if (controls.size() == 2) { + fnName = QIR_CCY; + } else if (controls.size() == 3) { + fnName = QIR_CCCY; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -395,7 +487,24 @@ QIRProgramBuilder& QIRProgramBuilder::z(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::cz(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CZ); + createControlledOneTargetZeroParameter({control}, target, QIR_CZ); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcz(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CZ; + } else if (controls.size() == 2) { + fnName = QIR_CCZ; + } else if (controls.size() == 3) { + fnName = QIR_CCCZ; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -408,7 +517,24 @@ QIRProgramBuilder& QIRProgramBuilder::h(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::ch(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CH); + createControlledOneTargetZeroParameter({control}, target, QIR_CH); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mch(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CH; + } else if (controls.size() == 2) { + fnName = QIR_CCH; + } else if (controls.size() == 3) { + fnName = QIR_CCCH; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -421,7 +547,24 @@ QIRProgramBuilder& QIRProgramBuilder::s(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::cs(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CS); + createControlledOneTargetZeroParameter({control}, target, QIR_CS); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcs(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CS; + } else if (controls.size() == 2) { + fnName = QIR_CCS; + } else if (controls.size() == 3) { + fnName = QIR_CCCS; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -434,7 +577,24 @@ QIRProgramBuilder& QIRProgramBuilder::sdg(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::csdg(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CSDG); + createControlledOneTargetZeroParameter({control}, target, QIR_CSDG); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcsdg(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CSDG; + } else if (controls.size() == 2) { + fnName = QIR_CCSDG; + } else if (controls.size() == 3) { + fnName = QIR_CCCSDG; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -447,7 +607,24 @@ QIRProgramBuilder& QIRProgramBuilder::t(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::ct(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CT); + createControlledOneTargetZeroParameter({control}, target, QIR_CT); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mct(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CT; + } else if (controls.size() == 2) { + fnName = QIR_CCT; + } else if (controls.size() == 3) { + fnName = QIR_CCCT; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -464,6 +641,23 @@ QIRProgramBuilder& QIRProgramBuilder::ctdg(const Value control, return *this; } +QIRProgramBuilder& QIRProgramBuilder::mctdg(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CTDG; + } else if (controls.size() == 2) { + fnName = QIR_CCTDG; + } else if (controls.size() == 3) { + fnName = QIR_CCCTDG; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); + return *this; +} + // SXOp QIRProgramBuilder& QIRProgramBuilder::sx(const Value qubit) { @@ -473,7 +667,24 @@ QIRProgramBuilder& QIRProgramBuilder::sx(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::csx(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CSX); + createControlledOneTargetZeroParameter({control}, target, QIR_CSX); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcsx(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CSX; + } else if (controls.size() == 2) { + fnName = QIR_CCSX; + } else if (controls.size() == 3) { + fnName = QIR_CCCSX; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -486,7 +697,24 @@ QIRProgramBuilder& QIRProgramBuilder::sxdg(const Value qubit) { QIRProgramBuilder& QIRProgramBuilder::csxdg(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CSXDG); + createControlledOneTargetZeroParameter({control}, target, QIR_CSXDG); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mcsxdg(const ValueRange controls, + const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CSXDG; + } else if (controls.size() == 2) { + fnName = QIR_CCSXDG; + } else if (controls.size() == 3) { + fnName = QIR_CCCSXDG; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -502,7 +730,25 @@ QIRProgramBuilder::rx(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::crx(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, control, target, QIR_CRX); + createControlledOneTargetOneParameter(theta, {control}, target, QIR_CRX); + return *this; +} + +QIRProgramBuilder& +QIRProgramBuilder::mcrx(const std::variant& theta, + const ValueRange controls, const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CRX; + } else if (controls.size() == 2) { + fnName = QIR_CCRX; + } else if (controls.size() == 3) { + fnName = QIR_CCCRX; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -518,7 +764,25 @@ QIRProgramBuilder::ry(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::cry(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, control, target, QIR_CRY); + createControlledOneTargetOneParameter(theta, {control}, target, QIR_CRY); + return *this; +} + +QIRProgramBuilder& +QIRProgramBuilder::mcry(const std::variant& theta, + const ValueRange controls, const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CRY; + } else if (controls.size() == 2) { + fnName = QIR_CCRY; + } else if (controls.size() == 3) { + fnName = QIR_CCCRY; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -534,7 +798,25 @@ QIRProgramBuilder::rz(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::crz(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, control, target, QIR_CRZ); + createControlledOneTargetOneParameter(theta, {control}, target, QIR_CRZ); + return *this; +} + +QIRProgramBuilder& +QIRProgramBuilder::mcrz(const std::variant& theta, + const ValueRange controls, const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CRZ; + } else if (controls.size() == 2) { + fnName = QIR_CCRZ; + } else if (controls.size() == 3) { + fnName = QIR_CCCRZ; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -550,7 +832,25 @@ QIRProgramBuilder::p(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::cp(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, control, target, QIR_CP); + createControlledOneTargetOneParameter(theta, {control}, target, QIR_CP); + return *this; +} + +QIRProgramBuilder& +QIRProgramBuilder::mcp(const std::variant& theta, + const ValueRange controls, const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CP; + } else if (controls.size() == 2) { + fnName = QIR_CCP; + } else if (controls.size() == 3) { + fnName = QIR_CCCP; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createControlledOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -659,6 +959,13 @@ QIRProgramBuilder::cu2(const std::variant& phi, return *this; } +QIRProgramBuilder& +QIRProgramBuilder::mcu2(const std::variant& phi, + const std::variant& lambda, + const ValueRange controls, const Value target) { + llvm::report_fatal_error("Not implemented yet"); +} + // SWAPOp QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, @@ -704,6 +1011,12 @@ QIRProgramBuilder& QIRProgramBuilder::cswap(const Value control, return *this; } +QIRProgramBuilder& QIRProgramBuilder::mcswap(const ValueRange controls, + const Value target0, + const Value target1) { + llvm::report_fatal_error("Not implemented yet"); +} + //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 3a73f2e575..82741d6151 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1437,6 +1437,37 @@ TEST_F(CompilerPipelineTest, CX3) { }); } +TEST_F(CompilerPipelineTest, MCX) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.mcx({0, 1}, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, Y) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); @@ -1962,6 +1993,37 @@ TEST_F(CompilerPipelineTest, CRX) { }); } +TEST_F(CompilerPipelineTest, MCRX) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.mcrx(1.0, {0, 1}, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, RY) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From dd599ca702e0b802da75c71e64fb55135432992e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:22:24 +0100 Subject: [PATCH 232/419] Simplify template functions of QIR builders --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 32 +--- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 153 ++++++------------ 2 files changed, 52 insertions(+), 133 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 901bbb696c..abd89498aa 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -1227,45 +1227,23 @@ class QIRProgramBuilder { /** * @brief Helper to create a one-target, zero-parameter QIR operation * - * @param qubit Target qubit - * @param fnName Name of the QIR function to call - */ - void createOneTargetZeroParameter(const Value qubit, StringRef fnName); - - /** - * @brief Helper to create a controlled one-target, zero-parameter QIR - * operation - * * @param controls Control qubits * @param target Target qubit * @param fnName Name of the QIR function to call */ - void createControlledOneTargetZeroParameter(const ValueRange controls, - const Value target, - StringRef fnName); - + void createOneTargetZeroParameter(const ValueRange controls, + const Value target, StringRef fnName); /** * @brief Helper to create a one-target, one-parameter QIR operation * * @param parameter Operation parameter - * @param qubit Input qubit - * @param fnName Name of the QIR function to call - */ - void createOneTargetOneParameter(const std::variant& parameter, - const Value qubit, StringRef fnName); - - /** - * @brief Helper to create a controlled one-target, one-parameter QIR - * operation - * - * @param parameter Operation parameter * @param controls Control qubits * @param target Target qubit * @param fnName Name of the QIR function to call */ - void createControlledOneTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value target, StringRef fnName); + void createOneTargetOneParameter(const std::variant& parameter, + const ValueRange controls, + const Value target, StringRef fnName); /** * @brief Generate array-based output recording in the output block diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 5bccb0c5d5..483e6b82c5 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -237,7 +237,8 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { // Helper methods -void QIRProgramBuilder::createOneTargetZeroParameter(const Value qubit, +void QIRProgramBuilder::createOneTargetZeroParameter(const ValueRange controls, + const Value target, StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); @@ -245,26 +246,6 @@ void QIRProgramBuilder::createOneTargetZeroParameter(const Value qubit, // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())); - - // Declare QIR function - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - builder.create(loc, fnDecl, ValueRange{qubit}); -} - -void QIRProgramBuilder::createControlledOneTargetZeroParameter( - const ValueRange controls, const Value target, StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - // Define argument types SmallVector argumentTypes; argumentTypes.reserve(controls.size() + 1); @@ -293,46 +274,6 @@ void QIRProgramBuilder::createControlledOneTargetZeroParameter( } void QIRProgramBuilder::createOneTargetOneParameter( - const std::variant& parameter, const Value qubit, - StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value parameterOperand; - if (std::holds_alternative(parameter)) { - parameterOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter))) - .getResult(); - } else { - parameterOperand = std::get(parameter); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - Float64Type::get(builder.getContext())}); - - // Declare QIR function - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - builder.create(loc, fnDecl, - ValueRange{qubit, parameterOperand}); -} - -void QIRProgramBuilder::createControlledOneTargetOneParameter( const std::variant& parameter, const ValueRange controls, const Value target, StringRef fnName) { // Save current insertion point @@ -391,13 +332,13 @@ void QIRProgramBuilder::createControlledOneTargetOneParameter( // IdOp QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_ID); + createOneTargetZeroParameter({}, qubit, QIR_ID); return *this; } QIRProgramBuilder& QIRProgramBuilder::cid(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CID); + createOneTargetZeroParameter({control}, target, QIR_CID); return *this; } @@ -414,20 +355,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcid(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // XOp QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_X); + createOneTargetZeroParameter({}, qubit, QIR_CX); return *this; } QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CX); + createOneTargetZeroParameter({control}, target, QIR_CX); return *this; } @@ -444,20 +385,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcx(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // YOp QIRProgramBuilder& QIRProgramBuilder::y(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_Y); + createOneTargetZeroParameter({}, qubit, QIR_Y); return *this; } QIRProgramBuilder& QIRProgramBuilder::cy(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CY); + createOneTargetZeroParameter({control}, target, QIR_CY); return *this; } @@ -474,20 +415,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcy(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // ZOp QIRProgramBuilder& QIRProgramBuilder::z(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_Z); + createOneTargetZeroParameter({}, qubit, QIR_Z); return *this; } QIRProgramBuilder& QIRProgramBuilder::cz(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CZ); + createOneTargetZeroParameter({control}, target, QIR_CZ); return *this; } @@ -504,20 +445,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcz(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // HOp QIRProgramBuilder& QIRProgramBuilder::h(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_H); + createOneTargetZeroParameter({}, qubit, QIR_H); return *this; } QIRProgramBuilder& QIRProgramBuilder::ch(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CH); + createOneTargetZeroParameter({control}, target, QIR_CH); return *this; } @@ -534,20 +475,20 @@ QIRProgramBuilder& QIRProgramBuilder::mch(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // SOp QIRProgramBuilder& QIRProgramBuilder::s(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_S); + createOneTargetZeroParameter({}, qubit, QIR_S); return *this; } QIRProgramBuilder& QIRProgramBuilder::cs(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CS); + createOneTargetZeroParameter({control}, target, QIR_CS); return *this; } @@ -564,20 +505,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcs(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // SdgOp QIRProgramBuilder& QIRProgramBuilder::sdg(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_SDG); + createOneTargetZeroParameter({}, qubit, QIR_SDG); return *this; } QIRProgramBuilder& QIRProgramBuilder::csdg(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CSDG); + createOneTargetZeroParameter({control}, target, QIR_CSDG); return *this; } @@ -594,20 +535,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcsdg(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // TOp QIRProgramBuilder& QIRProgramBuilder::t(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_T); + createOneTargetZeroParameter({}, qubit, QIR_T); return *this; } QIRProgramBuilder& QIRProgramBuilder::ct(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CT); + createOneTargetZeroParameter({control}, target, QIR_CT); return *this; } @@ -624,20 +565,20 @@ QIRProgramBuilder& QIRProgramBuilder::mct(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // TdgOp QIRProgramBuilder& QIRProgramBuilder::tdg(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_TDG); + createOneTargetZeroParameter({}, qubit, QIR_TDG); return *this; } QIRProgramBuilder& QIRProgramBuilder::ctdg(const Value control, const Value target) { - createControlledOneTargetZeroParameter(control, target, QIR_CTDG); + createOneTargetZeroParameter(control, target, QIR_CTDG); return *this; } @@ -654,20 +595,20 @@ QIRProgramBuilder& QIRProgramBuilder::mctdg(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // SXOp QIRProgramBuilder& QIRProgramBuilder::sx(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_SX); + createOneTargetZeroParameter({}, qubit, QIR_SX); return *this; } QIRProgramBuilder& QIRProgramBuilder::csx(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CSX); + createOneTargetZeroParameter({control}, target, QIR_CSX); return *this; } @@ -684,20 +625,20 @@ QIRProgramBuilder& QIRProgramBuilder::mcsx(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } // SXdgOp QIRProgramBuilder& QIRProgramBuilder::sxdg(const Value qubit) { - createOneTargetZeroParameter(qubit, QIR_SXDG); + createOneTargetZeroParameter({}, qubit, QIR_SXDG); return *this; } QIRProgramBuilder& QIRProgramBuilder::csxdg(const Value control, const Value target) { - createControlledOneTargetZeroParameter({control}, target, QIR_CSXDG); + createOneTargetZeroParameter({control}, target, QIR_CSXDG); return *this; } @@ -714,7 +655,7 @@ QIRProgramBuilder& QIRProgramBuilder::mcsxdg(const ValueRange controls, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetZeroParameter(controls, target, fnName); + createOneTargetZeroParameter(controls, target, fnName); return *this; } @@ -723,14 +664,14 @@ QIRProgramBuilder& QIRProgramBuilder::mcsxdg(const ValueRange controls, QIRProgramBuilder& QIRProgramBuilder::rx(const std::variant& theta, const Value qubit) { - createOneTargetOneParameter(theta, qubit, QIR_RX); + createOneTargetOneParameter(theta, {}, qubit, QIR_RX); return *this; } QIRProgramBuilder& QIRProgramBuilder::crx(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, {control}, target, QIR_CRX); + createOneTargetOneParameter(theta, {control}, target, QIR_CRX); return *this; } @@ -748,7 +689,7 @@ QIRProgramBuilder::mcrx(const std::variant& theta, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetOneParameter(theta, controls, target, fnName); + createOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -757,14 +698,14 @@ QIRProgramBuilder::mcrx(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::ry(const std::variant& theta, const Value qubit) { - createOneTargetOneParameter(theta, qubit, QIR_RY); + createOneTargetOneParameter(theta, {}, qubit, QIR_RY); return *this; } QIRProgramBuilder& QIRProgramBuilder::cry(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, {control}, target, QIR_CRY); + createOneTargetOneParameter(theta, {control}, target, QIR_CRY); return *this; } @@ -782,7 +723,7 @@ QIRProgramBuilder::mcry(const std::variant& theta, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetOneParameter(theta, controls, target, fnName); + createOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -791,14 +732,14 @@ QIRProgramBuilder::mcry(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::rz(const std::variant& theta, const Value qubit) { - createOneTargetOneParameter(theta, qubit, QIR_RZ); + createOneTargetOneParameter(theta, {}, qubit, QIR_RZ); return *this; } QIRProgramBuilder& QIRProgramBuilder::crz(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, {control}, target, QIR_CRZ); + createOneTargetOneParameter(theta, {control}, target, QIR_CRZ); return *this; } @@ -816,7 +757,7 @@ QIRProgramBuilder::mcrz(const std::variant& theta, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetOneParameter(theta, controls, target, fnName); + createOneTargetOneParameter(theta, controls, target, fnName); return *this; } @@ -825,14 +766,14 @@ QIRProgramBuilder::mcrz(const std::variant& theta, QIRProgramBuilder& QIRProgramBuilder::p(const std::variant& theta, const Value qubit) { - createOneTargetOneParameter(theta, qubit, QIR_P); + createOneTargetOneParameter(theta, {}, qubit, QIR_P); return *this; } QIRProgramBuilder& QIRProgramBuilder::cp(const std::variant& theta, const Value control, const Value target) { - createControlledOneTargetOneParameter(theta, {control}, target, QIR_CP); + createOneTargetOneParameter(theta, {control}, target, QIR_CP); return *this; } @@ -850,7 +791,7 @@ QIRProgramBuilder::mcp(const std::variant& theta, llvm::report_fatal_error("Multi-controlled with more than 3 controls are " "currently not supported"); } - createControlledOneTargetOneParameter(theta, controls, target, fnName); + createOneTargetOneParameter(theta, controls, target, fnName); return *this; } From 639afe8dc57ed182652549a1d50779d006cb9a54 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:12:02 +0100 Subject: [PATCH 233/419] Add support for R --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 121 ++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 27 +++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 79 +++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 + .../Quartz/Builder/QuartzProgramBuilder.h | 65 ++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 25 +++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 13 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 69 +++++- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 116 ++++++---- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 153 +++++++++---- .../Flux/Builder/FluxProgramBuilder.cpp | 89 ++++++-- .../lib/Dialect/Flux/IR/StandardGates/ROp.cpp | 55 +++++ .../Dialect/Flux/IR/StandardGates/U2Op.cpp | 5 +- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 208 ++++++++++-------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 25 +++ .../Dialect/Quartz/IR/StandardGates/ROp.cpp | 55 +++++ .../Dialect/Quartz/IR/StandardGates/U2Op.cpp | 5 +- .../TranslateQuantumComputationToQuartz.cpp | 26 +++ .../pipeline/test_compiler_pipeline.cpp | 83 ++++++- 19 files changed, 1013 insertions(+), 210 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/ROp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/ROp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index a334f47d97..6ef5ceefcf 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -1175,6 +1175,78 @@ class FluxProgramBuilder final : public OpBuilder { std::pair mcp(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an R gate to a qubit + * + * @details + * Consumes the input qubit and produces a new output qubit SSA value. + * The input is validated and the tracking is updated. + * + * @param theta Rotation angle in radians + * @param phi Rotation angle in radians + * @param qubit Input qubit (must be valid/unconsumed) + * @return Output qubit + * + * @par Example: + * ```c++ + * q_out = builder.r(1.0, 0.5, q_in); + * ``` + * ```mlir + * %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ + Value r(const std::variant& theta, + const std::variant& phi, Value qubit); + + /** + * @brief Apply a controlled R gate + * + * @param theta Rotation angle in radians + * @param phi Rotation angle in radians + * @param control Input control qubit (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, output_target_qubit) + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.cr(1.0, 0.5, q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { + * %q1_res = flux.r(%theta, %phi) %q1_in : !flux.qubit -> !flux.qubit + * flux.yield %q1_res + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair cr(const std::variant& theta, + const std::variant& phi, + Value control, Value target); + + /** + * @brief Apply a multi-controlled R gate + * + * @param theta Rotation angle in radians + * @param phi Rotation angle in radians + * @param controls Input control qubits (must be valid/unconsumed) + * @param target Input target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, output_target_qubit) + * + * @par Example: + * ```c++ + * {controls_out, target_out} = builder.mcr(1.0, 0.5, {q0_in, q1_in}, q2_in); + * ``` + * ```mlir + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { + * %q2_res = flux.r(%theta, %phi) %q2_in : !flux.qubit -> !flux.qubit + * flux.yield %q2_res + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, + * !flux.qubit}, {!flux.qubit}) + * ``` + */ + std::pair mcr(const std::variant& theta, + const std::variant& phi, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -1472,6 +1544,55 @@ class FluxProgramBuilder final : public OpBuilder { const std::variant& parameter, const ValueRange controls, const Value target); + /** + * @brief Helper to create a one-target, two-parameter Flux operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param qubit Input qubit + * @return Output qubit + */ + template + Value + createOneTargetTwoParameter(const std::variant& parameter1, + const std::variant& parameter2, + const Value qubit); + + /** + * @brief Helper to create a controlled one-target, two-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param control Input control qubit + * @param target Input target qubit + * @return Pair of (output_control_qubit, output_target_qubit) + */ + template + std::pair createControlledOneTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const Value control, + const Value target); + + /** + * @brief Helper to create a multi-controlled one-target, two-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param controls Input control qubits + * @param target Input target qubit + * @return Pair of (output_control_qubits, output_target_qubit) + */ + template + std::pair createMultiControlledOneTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const ValueRange controls, + const Value target); + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 7461eac4c6..f324bab3bb 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -620,6 +620,33 @@ def POp : FluxOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } +def ROp : FluxOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { + let summary = "Apply an R gate to a qubit"; + let description = [{ + Applies an R gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "r"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi)> + ]; +} + def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index abd89498aa..0f9413d733 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -1055,6 +1055,70 @@ class QIRProgramBuilder { QIRProgramBuilder& mcp(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an R gate to a qubit + * + * @param theta Rotation angle + * @param phi Rotation angle + * @param qubit Input qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.r(theta, phi, qubit); + * ``` + * ```mlir + * llvm.call @__quantum__qis__r__body(%q, %theta, %phi) : (!llvm.ptr, f64, + * f64) -> () + * ``` + */ + QIRProgramBuilder& r(const std::variant& theta, + const std::variant& phi, Value qubit); + + /** + * @brief Apply a controlled R gate + * + * @param theta Rotation angle + * @param phi Rotation angle + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cr(theta, phi, control, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__cr__body(%c, %t, %theta, %phi) : (!llvm.ptr, + * !llvm.ptr, f64, f64) -> () + * ``` + */ + QIRProgramBuilder& cr(const std::variant& theta, + const std::variant& phi, Value control, + Value target); + + /** + * @brief Apply a multi-controlled R gate + * + * @param theta Rotation angle + * @param phi Rotation angle + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcr(theta, phi, controls, target); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mcr__body(%c1, %c2, %t, %theta, %phi) : + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () + * ``` + */ + QIRProgramBuilder& mcr(const std::variant& theta, + const std::variant& phi, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * @@ -1245,6 +1309,21 @@ class QIRProgramBuilder { const ValueRange controls, const Value target, StringRef fnName); + /** + * @brief Helper to create a one-target, two-parameter QIR operation + * + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param controls Control qubits + * @param target Target qubit + * @param fnName Name of the QIR function to call + */ + void + createOneTargetTwoParameter(const std::variant& parameter1, + const std::variant& parameter2, + const ValueRange controls, const Value target, + StringRef fnName); + /** * @brief Generate array-based output recording in the output block * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 759dc3394c..e1e62e8875 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -84,6 +84,10 @@ static constexpr auto QIR_P = "__quantum__qis__p__body"; static constexpr auto QIR_CP = "__quantum__qis__cp__body"; static constexpr auto QIR_CCP = "__quantum__qis__ccp__body"; static constexpr auto QIR_CCCP = "__quantum__qis__cccp__body"; +static constexpr auto QIR_R = "__quantum__qis__r__body"; +static constexpr auto QIR_CR = "__quantum__qis__cr__body"; +static constexpr auto QIR_CCR = "__quantum__qis__ccr__body"; +static constexpr auto QIR_CCCR = "__quantum__qis__cccr__body"; static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; static constexpr auto QIR_CU2 = "__quantum__qis__cu2__body"; static constexpr auto QIR_CCU2 = "__quantum__qis__ccu2__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index acb9d95eeb..f55545ac50 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -1061,6 +1061,71 @@ class QuartzProgramBuilder final : public OpBuilder { QuartzProgramBuilder& mcp(const std::variant& theta, ValueRange controls, Value target); + /** + * @brief Apply an R gate to a qubit + * + * @param theta Rotation angle in radians + * @param phi Rotation angle in radians + * @param qubit Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.r(1.0, 0.5, q); + * ``` + * ```mlir + * quartz.r(%theta, %phi) %q : !quartz.qubit + * ``` + */ + QuartzProgramBuilder& r(const std::variant& theta, + const std::variant& phi, Value qubit); + + /** + * @brief Apply a controlled R gate + * + * @param theta Rotation angle in radians + * @param phi Rotation angle in radians + * @param control Control qubit + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.cr(1.0, 0.5, q0, q1); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.r(%theta, %phi) %q1 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& cr(const std::variant& theta, + const std::variant& phi, + Value control, Value target); + + /** + * @brief Apply a multi-controlled R gate + * + * @param theta Rotation angle in radians + * @param phi Rotation angle in radians + * @param controls Control qubits + * @param target Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mcr(1.0, 0.5, {q0, q1}, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.r(%theta, %phi) %q2 : !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mcr(const std::variant& theta, + const std::variant& phi, + ValueRange controls, Value target); + /** * @brief Apply a U2 gate to a qubit * diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index a460148836..5839b9e549 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -563,6 +563,31 @@ def POp : QuartzOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { ]; } +def ROp : QuartzOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { + let summary = "Apply an R gate to a qubit"; + let description = [{ + Applies an R gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.r(%theta, %phi) %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi); + let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict"; + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "r"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi)> + ]; +} + def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 16f831192b..42dc4007eb 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -153,6 +153,19 @@ inline DenseElementsAttr getMatrixU2(MLIRContext* ctx, double phi, return DenseElementsAttr::get(type, {m00, m01, m10, m11}); } +inline DenseElementsAttr getMatrixR(MLIRContext* ctx, double theta, + double phi) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = std::cos(theta / 2.0) + 0i; + const std::complex m01 = + -1i * std::exp(-1i * phi) * std::sin(theta / 2.0); + const std::complex m10 = + -1i * std::exp(1i * phi) * std::sin(theta / 2.0); + const std::complex m11 = std::cos(theta / 2.0) + 0i; + return DenseElementsAttr::get(type, {m00, m01, m10, m11}); +} + inline DenseElementsAttr getMatrixSWAP(MLIRContext* ctx) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({4, 4}, complexType); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 20c42963d1..3ba570a176 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -92,6 +92,35 @@ convertOneTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a one-target, two-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit, op.getOperand(1), + op.getOperand(2)); + + // Replace the output qubit with the same Quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); +} + } // namespace /** @@ -625,6 +654,28 @@ struct ConvertFluxPOp final : OpConversionPattern { } }; +/** + * @brief Converts flux.r to quartz.r + * + * @par Example: + * ```mlir + * %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.r(%theta, %phi) %q : !quartz.qubit + * ``` + */ +struct ConvertFluxROp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::ROp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetTwoParameter(op, adaptor, rewriter); + } +}; + /** * @brief Converts flux.u2 to quartz.u2 * @@ -798,15 +849,15 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns - .add( - typeConverter, context); + patterns.add( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index f00d2f4b6d..870c668bb3 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -195,6 +195,50 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, return success(); } +/** + * @brief Converts a one-target, two-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest Flux qubit + const auto quartzQubit = op->getOperand(0); + Value fluxQubit = nullptr; + if (inCtrlOp == 0) { + fluxQubit = qubitMap[quartzQubit]; + } else { + fluxQubit = state.targetsIn[inCtrlOp].front(); + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, + op.getOperand(1), op.getOperand(2)); + + // Update the state map + if (inCtrlOp == 0) { + qubitMap[quartzQubit] = fluxOp.getQubitOut(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({fluxOp.getQubitOut()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + } // namespace /** @@ -774,6 +818,28 @@ struct ConvertQuartzPOp final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.r to flux.r + * + * @par Example: + * ```mlir + * quartz.r(%theta, %phi) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit + * ``` + */ +struct ConvertQuartzROp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::ROp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertOneTargetTwoParameter(op, rewriter, getState()); + } +}; + /** * @brief Converts quartz.u2 to flux.u2 * @@ -792,35 +858,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::U2Op op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - auto& qubitMap = state.qubitMap; - const auto inCtrlOp = state.inCtrlOp; - - // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; - if (inCtrlOp == 0) { - fluxQubit = qubitMap[quartzQubit]; - } else { - fluxQubit = state.targetsIn[inCtrlOp].front(); - } - - // Create flux.u2 (consumes input, produces output) - auto fluxOp = rewriter.create( - op.getLoc(), fluxQubit, adaptor.getPhi(), adaptor.getLambda()); - - // Update state map - if (inCtrlOp == 0) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); - } else { - state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({fluxOp.getQubitOut()}); - state.targetsOut.try_emplace(inCtrlOp, targetsOut); - } - - rewriter.eraseOp(op); - - return success(); + return convertOneTargetTwoParameter(op, rewriter, getState()); } }; @@ -1023,16 +1061,16 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns.add(typeConverter, - context, &state); + patterns.add< + ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, + ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, + ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, + ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, + ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, + ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, + ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, + ConvertQuartzSWAPOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( + typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index edc3ac828b..94f9f59e0a 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -234,6 +234,69 @@ convertOneTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a one-target, two-parameter Quartz operation to QIR + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 3); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + // Add parameter types + argumentTypes.push_back(Float64Type::get(ctx)); + argumentTypes.push_back(Float64Type::get(ctx)); + + // Define function signature + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 3); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + } // namespace /** @@ -1219,6 +1282,52 @@ struct ConvertQuartzPQIR final : StatefulOpConversionPattern { } }; +/** + * @brief Converts quartz.r operation to QIR r + * + * @par Example: + * ```mlir + * quartz.r(%theta, %phi) %q : !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__r__body(%q, %theta, %phi) : (!llvm.ptr, f64, f64) + * -> () + * ``` + */ +struct ConvertQuartzRQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(ROp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; + + // Define function name + StringRef fnName; + if (inCtrlOp == 0) { + fnName = QIR_R; + } else { + if (numCtrls == 1) { + fnName = QIR_CR; + } else if (numCtrls == 2) { + fnName = QIR_CCR; + } else if (numCtrls == 3) { + fnName = QIR_CCCR; + } else { + return failure(); + } + } + + return convertOneTargetTwoParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; + /** * @brief Converts quartz.u2 operation to QIR u2 * @@ -1238,14 +1347,11 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(U2Op op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); auto& state = getState(); // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const auto numCtrls = posCtrls.size(); + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -1263,42 +1369,8 @@ struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { } } - // Define function argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 3); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add phi - argumentTypes.push_back(Float64Type::get(ctx)); - // Add lambda - argumentTypes.push_back(Float64Type::get(ctx)); - - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 3); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); + return convertOneTargetTwoParameter(op, adaptor, rewriter, getContext(), + state, fnName); } }; @@ -1763,6 +1835,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0d4386d592..0398b74f00 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -244,6 +244,48 @@ FluxProgramBuilder::createMultiControlledOneTargetOneParameter( return {controlsOut, targetsOut[0]}; } +template +Value FluxProgramBuilder::createOneTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const Value qubit) { + auto op = create(loc, qubit, parameter1, parameter2); + const auto& qubitOut = op.getQubitOut(); + updateQubitTracking(qubit, qubitOut); + return qubitOut; +} + +template +std::pair +FluxProgramBuilder::createControlledOneTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const Value control, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(control, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = + b.create(loc, targets[0], parameter1, parameter2); + return op->getResults(); + }); + return {controlsOut[0], targetsOut[0]}; +} + +template +std::pair +FluxProgramBuilder::createMultiControlledOneTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const ValueRange controls, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = + b.create(loc, targets[0], parameter1, parameter2); + return op->getResults(); + }); + return {controlsOut, targetsOut[0]}; +} + // IdOp Value FluxProgramBuilder::id(const Value qubit) { @@ -500,41 +542,52 @@ FluxProgramBuilder::mcp(const std::variant& theta, target); } +// ROp + +Value FluxProgramBuilder::r(const std::variant& theta, + const std::variant& phi, + const Value qubit) { + return createOneTargetTwoParameter(theta, phi, qubit); +} + +std::pair +FluxProgramBuilder::cr(const std::variant& theta, + const std::variant& phi, + const Value control, const Value target) { + return createControlledOneTargetTwoParameter(theta, phi, control, + target); +} + +std::pair +FluxProgramBuilder::mcr(const std::variant& theta, + const std::variant& phi, + const ValueRange controls, const Value target) { + return createMultiControlledOneTargetTwoParameter(theta, phi, controls, + target); +} + // U2Op Value FluxProgramBuilder::u2(const std::variant& phi, const std::variant& lambda, Value qubit) { - auto u2Op = create(loc, qubit, phi, lambda); - const auto& qubitOut = u2Op.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; + return createOneTargetTwoParameter(phi, lambda, qubit); } std::pair FluxProgramBuilder::cu2(const std::variant& phi, const std::variant& lambda, const Value control, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto u2 = b.create(loc, targets[0], phi, lambda); - return u2->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; + return createControlledOneTargetTwoParameter(phi, lambda, control, + target); } std::pair FluxProgramBuilder::mcu2(const std::variant& phi, const std::variant& lambda, const ValueRange controls, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto u2 = b.create(loc, targets[0], phi, lambda); - return u2->getResults(); - }); - return {controlsOut, targetsOut[0]}; + return createMultiControlledOneTargetTwoParameter(phi, lambda, controls, + target); } // SWAPOp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/ROp.cpp new file mode 100644 index 0000000000..53bccb2d3b --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/ROp.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr ROp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto phi = getStaticParameter(getPhi()); + if (!theta || !phi) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto phiValue = phi.getValueAsDouble(); + return getMatrixR(getContext(), thetaValue, phiValue); +} + +void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, const std::variant& theta, + const std::variant& phi) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); + } else { + phiOperand = std::get(phi); + } + + build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp index bb673995cb..36008194f1 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp @@ -33,8 +33,7 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) - const std::variant& phi, + const Value qubitIn, const std::variant& phi, const std::variant& lambda) { Value phiOperand = nullptr; if (std::holds_alternative(phi)) { @@ -53,5 +52,5 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, lambdaOperand = std::get(lambda); } - build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); + build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 483e6b82c5..757d4ea554 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -329,6 +329,76 @@ void QIRProgramBuilder::createOneTargetOneParameter( builder.create(loc, fnDecl, operands); } +void QIRProgramBuilder::createOneTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const ValueRange controls, + const Value target, StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value parameter1Operand; + if (std::holds_alternative(parameter1)) { + parameter1Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter1))) + .getResult(); + } else { + parameter1Operand = std::get(parameter1); + } + + Value parameter2Operand; + if (std::holds_alternative(parameter2)) { + parameter2Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter2))) + .getResult(); + } else { + parameter2Operand = std::get(parameter2); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 3); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + // Add parameter types + argumentTypes.push_back(Float64Type::get(builder.getContext())); + argumentTypes.push_back(Float64Type::get(builder.getContext())); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 3); + operands.append(controls.begin(), controls.end()); + operands.push_back(target); + operands.push_back(parameter1Operand); + operands.push_back(parameter2Operand); + + builder.create(loc, fnDecl, operands); +} + // IdOp QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { @@ -795,56 +865,50 @@ QIRProgramBuilder::mcp(const std::variant& theta, return *this; } -// U2Op +// ROp QIRProgramBuilder& -QIRProgramBuilder::u2(const std::variant& phi, - const std::variant& lambda, - const Value qubit) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); +QIRProgramBuilder::r(const std::variant& theta, + const std::variant& phi, + const Value qubit) { + createOneTargetTwoParameter(theta, phi, {}, qubit, QIR_R); + return *this; +} - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = builder - .create( - loc, builder.getF64FloatAttr(std::get(phi))) - .getResult(); - } else { - phiOperand = std::get(phi); - } +QIRProgramBuilder& +QIRProgramBuilder::cr(const std::variant& theta, + const std::variant& phi, + const Value control, const Value target) { + createOneTargetTwoParameter(theta, phi, {control}, target, QIR_CR); + return *this; +} - Value lambdaOperand; - if (std::holds_alternative(lambda)) { - lambdaOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(lambda))) - .getResult(); +QIRProgramBuilder& +QIRProgramBuilder::mcr(const std::variant& theta, + const std::variant& phi, + const ValueRange controls, const Value target) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CR; + } else if (controls.size() == 2) { + fnName = QIR_CCR; + } else if (controls.size() == 3) { + fnName = QIR_CCCR; } else { - lambdaOperand = std::get(lambda); + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); } + createOneTargetTwoParameter(theta, phi, controls, target, fnName); + return *this; +} - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create u2 call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - Float64Type::get(builder.getContext()), - Float64Type::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_U2, qirSignature); - builder.create(loc, fnDecl, - ValueRange{qubit, phiOperand, lambdaOperand}); +// U2Op +QIRProgramBuilder& +QIRProgramBuilder::u2(const std::variant& phi, + const std::variant& lambda, + const Value qubit) { + createOneTargetTwoParameter(phi, lambda, {}, qubit, QIR_U2); return *this; } @@ -852,51 +916,7 @@ QIRProgramBuilder& QIRProgramBuilder::cu2(const std::variant& phi, const std::variant& lambda, const Value control, const Value target) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = builder - .create( - loc, builder.getF64FloatAttr(std::get(phi))) - .getResult(); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand; - if (std::holds_alternative(lambda)) { - lambdaOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(lambda))) - .getResult(); - } else { - lambdaOperand = std::get(lambda); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create cu2 call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext()), - Float64Type::get(builder.getContext()), - Float64Type::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_CU2, qirSignature); - builder.create( - loc, fnDecl, ValueRange{control, target, phiOperand, lambdaOperand}); - + createOneTargetTwoParameter(phi, lambda, {control}, target, QIR_CU2); return *this; } @@ -904,7 +924,19 @@ QIRProgramBuilder& QIRProgramBuilder::mcu2(const std::variant& phi, const std::variant& lambda, const ValueRange controls, const Value target) { - llvm::report_fatal_error("Not implemented yet"); + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CU2; + } else if (controls.size() == 2) { + fnName = QIR_CCU2; + } else if (controls.size() == 3) { + fnName = QIR_CCCU2; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createOneTargetTwoParameter(phi, lambda, controls, target, fnName); + return *this; } // SWAPOp diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index fe622643d5..78861905bd 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -413,6 +413,31 @@ QuartzProgramBuilder::mcp(const std::variant& theta, return *this; } +// ROp + +QuartzProgramBuilder& +QuartzProgramBuilder::r(const std::variant& theta, + const std::variant& phi, Value qubit) { + create(loc, qubit, theta, phi); + return *this; +} + +QuartzProgramBuilder& +QuartzProgramBuilder::cr(const std::variant& theta, + const std::variant& phi, Value control, + Value target) { + return mcr(theta, phi, {control}, target); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mcr(const std::variant& theta, + const std::variant& phi, + ValueRange controls, Value target) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, target, theta, phi); }); + return *this; +} + // U2Op QuartzProgramBuilder& diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/ROp.cpp new file mode 100644 index 0000000000..8fee48d788 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/ROp.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr ROp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto phi = getStaticParameter(getPhi()); + if (!theta || !phi) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto phiValue = phi.getValueAsDouble(); + return getMatrixR(getContext(), thetaValue, phiValue); +} + +void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, const std::variant& theta, + const std::variant& phi) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); + } else { + phiOperand = std::get(phi); + } + + build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp index 5c61f6dbbf..043aace08e 100644 --- a/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp @@ -33,8 +33,7 @@ DenseElementsAttr U2Op::tryGetStaticMatrix() { } void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubit_in, // NOLINT(*-identifier-naming) - const std::variant& phi, + const Value qubitIn, const std::variant& phi, const std::variant& lambda) { Value phiOperand = nullptr; if (std::holds_alternative(phi)) { @@ -53,5 +52,5 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, lambdaOperand = std::get(lambda); } - build(odsBuilder, odsState, qubit_in, phiOperand, lambdaOperand); + build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 1d78ef5a8e..d16eb4706e 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -564,6 +564,29 @@ void addPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an R operation + * + * @details + * Translates an R operation from the QuantumComputation to quartz.r. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The R operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addROp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& phi = operation.getParameter()[1]; + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.r(theta, phi, target); + } else { + builder.mcr(theta, phi, posControls, target); + } +} + /** * @brief Adds a U2 operation * @@ -686,6 +709,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::P: addPOp(builder, *operation, qubits); break; + case qc::OpType::R: + addROp(builder, *operation, qubits); + break; case qc::OpType::U2: addU2Op(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 82741d6151..066da5b12d 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2156,10 +2156,10 @@ TEST_F(CompilerPipelineTest, P) { }); } -TEST_F(CompilerPipelineTest, U2) { +TEST_F(CompilerPipelineTest, R) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); - qc.u2(1.0, 0.5, 0); + qc.r(1.0, 0.5, 0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -2167,16 +2167,16 @@ TEST_F(CompilerPipelineTest, U2) { const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - b.u2(1.0, 0.5, reg[0]); + b.r(1.0, 0.5, reg[0]); }); const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - b.u2(1.0, 0.5, reg[0]); + b.r(1.0, 0.5, reg[0]); }); const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; - b.u2(1.0, 0.5, q); + b.r(1.0, 0.5, q); }); verifyAllStages({ @@ -2188,10 +2188,10 @@ TEST_F(CompilerPipelineTest, U2) { }); } -TEST_F(CompilerPipelineTest, CU2) { +TEST_F(CompilerPipelineTest, CR) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); - qc.cu2(1.0, 0.5, 0, 1); + qc.cr(1.0, 0.5, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -2199,15 +2199,78 @@ TEST_F(CompilerPipelineTest, CU2) { const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.cu2(1.0, 0.5, reg[0], reg[1]); + b.cr(1.0, 0.5, reg[0], reg[1]); }); const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.cu2(1.0, 0.5, reg[0], reg[1]); + b.cr(1.0, 0.5, reg[0], reg[1]); }); const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.cu2(1.0, 0.5, reg[0], reg[1]); + b.cr(1.0, 0.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCR) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.mcr(1.0, 0.5, {0, 1}, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, U2) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.u2(1.0, 0.5, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, reg[0]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(1.0, 0.5, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + const auto q = reg[0]; + b.u2(1.0, 0.5, q); }); verifyAllStages({ From 466eb0a4d15e6e2541fc734033cadfc1c42524f7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:58:08 +0100 Subject: [PATCH 234/419] Add support for iSWAP --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 123 +++++++++++++++- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 22 +++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 72 +++++++++- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 + .../Quartz/Builder/QuartzProgramBuilder.h | 67 ++++++++- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 21 +++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 10 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 68 ++++++--- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 136 ++++++++++++------ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 136 ++++++++++++++---- .../Flux/Builder/FluxProgramBuilder.cpp | 91 +++++++++--- .../Dialect/Flux/IR/StandardGates/iSWAPOp.cpp | 22 +++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 116 +++++++++++---- .../Quartz/Builder/QuartzProgramBuilder.cpp | 20 +++ .../Quartz/IR/StandardGates/iSWAPOp.cpp | 22 +++ .../TranslateQuantumComputationToQuartz.cpp | 30 +++- .../pipeline/test_compiler_pipeline.cpp | 62 ++++++++ 17 files changed, 857 insertions(+), 165 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/iSWAPOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/iSWAPOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 6ef5ceefcf..7817a4304d 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -1326,8 +1326,8 @@ class FluxProgramBuilder final : public OpBuilder { * Consumes the input qubits and produces new output qubit SSA values. * The inputs are validated and the tracking is updated. * - * @param qubit0 First input qubit (must be valid/unconsumed) - * @param qubit1 Second input qubit (must be valid/unconsumed) + * @param qubit0 Input qubit (must be valid/unconsumed) + * @param qubit1 Input qubit (must be valid/unconsumed) * @return Output qubits * * @par Example: @@ -1344,8 +1344,8 @@ class FluxProgramBuilder final : public OpBuilder { /** * @brief Apply a controlled SWAP gate * @param control Input control qubit (must be valid/unconsumed) - * @param qubit0 First target qubit (must be valid/unconsumed) - * @param qubit1 Second target qubit (must be valid/unconsumed) + * @param qubit0 Target qubit (must be valid/unconsumed) + * @param qubit1 Target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) * * @par Example: @@ -1367,8 +1367,8 @@ class FluxProgramBuilder final : public OpBuilder { /** * @brief Apply a multi-controlled SWAP gate * @param controls Input control qubits (must be valid/unconsumed) - * @param qubit0 First target qubit (must be valid/unconsumed) - * @param qubit1 Second target qubit (must be valid/unconsumed) + * @param qubit0 Target qubit (must be valid/unconsumed) + * @param qubit1 Target qubit (must be valid/unconsumed) * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) * * @par Example: @@ -1387,6 +1387,75 @@ class FluxProgramBuilder final : public OpBuilder { std::pair> mcswap(ValueRange controls, Value qubit0, Value qubit1); + /** + * @brief Apply an iSWAP gate to two qubits + * + * @details + * Consumes the input qubits and produces new output qubit SSA values. + * The inputs are validated and the tracking is updated. + * + * @param qubit0 Input qubit (must be valid/unconsumed) + * @param qubit1 Input qubit (must be valid/unconsumed) + * @return Output qubits + * + * @par Example: + * ```c++ + * {q0_out, q1_out} = builder.iswap(q0_in, q1_in); + * ``` + * ```mlir + * %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit + * -> !flux.qubit, !flux.qubit + * ``` + */ + std::pair iswap(Value qubit0, Value qubit1); + + /** + * @brief Apply a controlled iSWAP gate + * @param control Input control qubit (must be valid/unconsumed) + * @param qubit0 Target qubit (must be valid/unconsumed) + * @param qubit1 Target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) + * + * @par Example: + * ```c++ + * {q0_out, {q1_out, q2_out}} = builder.ciswap(q0_in, q1_in, q2_in); + * ``` + * ```mlir + * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { + * %q1_res, %q2_res = flux.iswap %q1_in, %q2_in : !flux.qubit, + * !flux.qubit -> !flux.qubit, !flux.qubit + * flux.yield %q1_res, %q2_res + * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, + * !flux.qubit + * ``` + */ + std::pair> ciswap(Value control, Value qubit0, + Value qubit1); + + /** + * @brief Apply a multi-controlled iSWAP gate + * + * @param controls Input control qubits (must be valid/unconsumed) + * @param qubit0 Target qubit (must be valid/unconsumed) + * @param qubit1 Target qubit (must be valid/unconsumed) + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) + * + * @par Example: + * ```c++ + * {controls_out, {q1_out, q2_out}} = builder.mciswap({q0_in, q1_in}, q2_in, + * q3_in); + * ``` + * ```mlir + * %controls_out, %q2_out, %q3_out = flux.ctrl(%q0_in, %q1_in) %q2_in, %q3_in + * { %q2_res, %q3_res = flux.iswap %q2_in, %q3_in : !flux.qubit, !flux.qubit + * -> !flux.qubit, !flux.qubit flux.yield %q2_res, %q3_res } : ({!flux.qubit, + * !flux.qubit}, {!flux.qubit, !flux.qubit}) -> ({!flux.qubit, !flux.qubit}, + * {!flux.qubit, !flux.qubit}) + * ``` + */ + std::pair> + mciswap(ValueRange controls, Value qubit0, Value qubit1); + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// @@ -1593,6 +1662,48 @@ class FluxProgramBuilder final : public OpBuilder { const std::variant& parameter2, const ValueRange controls, const Value target); + /** + * @brief Helper to create a two-target, zero-parameter Flux operation + * @tparam OpType The operation type of the Flux operation + * @param qubit0 Input qubit + * @param qubit1 Input qubit + * @return Pair of (output_qubit0, output_qubit1) + */ + template + std::pair createTwoTargetZeroParameter(const Value qubit0, + const Value qubit1); + + /** + * @brief Helper to create a controlled two-target, zero-parameter Flux + * operation + * @tparam OpType The operation type of the Flux operation + * @param control Input control qubit + * @param qubit0 Input target qubit + * @param qubit1 Input target qubit + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) + */ + template + std::pair> + createControlledTwoTargetZeroParameter(const Value control, + const Value qubit0, + const Value qubit1); + + /** + * @brief Helper to create a multi-controlled two-target, zero-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param controls Input control qubits + * @param qubit0 Input target qubit + * @param qubit1 Input target qubit + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) + */ + template + std::pair> + createMultiControlledTwoTargetZeroParameter(const ValueRange controls, + const Value qubit0, + const Value qubit1); + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index f324bab3bb..7fc91eefee 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -696,6 +696,28 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter }]; } +def iSWAPOp : FluxOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a iSWAP gate to two qubits"; + let description = [{ + Applies a iSWAP gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "iswap"; } + }]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 0f9413d733..2acaf131a1 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -1186,8 +1186,8 @@ class QIRProgramBuilder { /** * @brief Apply a SWAP gate to two qubits * - * @param qubit0 Input qubit - * @param qubit1 Input qubit + * @param qubit0 Target qubit + * @param qubit1 Target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -1239,6 +1239,62 @@ class QIRProgramBuilder { */ QIRProgramBuilder& mcswap(ValueRange controls, Value target0, Value target1); + /** + * @brief Apply an iSWAP gate to two qubits + * + * @param qubit0 Target qubit + * @param qubit1 Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.iswap(qubit0, qubit1); + * ``` + * ```mlir + * llvm.call @__quantum__qis__iswap__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) + * -> () + * ``` + */ + QIRProgramBuilder& iswap(Value qubit0, Value qubit1); + + /** + * @brief Apply a controlled iSWAP gate + * + * @param control Control qubit + * @param target1 Target qubit + * @param target2 Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.ciswap(control, target1, target2); + * ``` + * ```mlir + * llvm.call @__quantum__qis__ciswap__body(%c, %t0, %t1) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& ciswap(Value control, Value target0, Value target1); + + /** + * @brief Apply a multi-controlled iSWAP gate + * + * @param controls Control qubits + * @param target1 Target qubit + * @param target2 Target qubit + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.mciswap({control1, control2}, target1, target2); + * ``` + * ```mlir + * llvm.call @__quantum__qis__mciswap__body(%c1, %c2, %t0, %t1) : (!llvm.ptr, + * !llvm.ptr, !llvm.ptr, !llvm.ptr) -> () + * ``` + */ + QIRProgramBuilder& mciswap(ValueRange controls, Value target0, Value target1); + //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// @@ -1324,6 +1380,18 @@ class QIRProgramBuilder { const ValueRange controls, const Value target, StringRef fnName); + /** + * @brief Helper to create a two-target, zero-parameter QIR operation + * + * @param controls Control qubits + * @param target0 Target qubit + * @param target1 Target qubit + * @param fnName Name of the QIR function to call + */ + void createTwoTargetZeroParameter(const ValueRange controls, + const Value target0, const Value target1, + StringRef fnName); + /** * @brief Generate array-based output recording in the output block * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index e1e62e8875..b125b2de1f 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -96,6 +96,10 @@ static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; static constexpr auto QIR_CSWAP = "__quantum__qis__cswap__body"; static constexpr auto QIR_CCSWAP = "__quantum__qis__ccswap__body"; static constexpr auto QIR_CCCSWAP = "__quantum__qis__cccswap__body"; +static constexpr auto QIR_ISWAP = "__quantum__qis__iswap__body"; +static constexpr auto QIR_CISWAP = "__quantum__qis__ciswap__body"; +static constexpr auto QIR_CCISWAP = "__quantum__qis__cciswap__body"; +static constexpr auto QIR_CCCISWAP = "__quantum__qis__ccciswap__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index f55545ac50..3bc0223019 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -1193,8 +1193,8 @@ class QuartzProgramBuilder final : public OpBuilder { /** * @brief Apply a SWAP gate to two qubits * - * @param qubit0 First target qubit - * @param qubit1 Second target qubit + * @param qubit0 Target qubit + * @param qubit1 Target qubit * @return Reference to this builder for method chaining * * @par Example: @@ -1211,8 +1211,8 @@ class QuartzProgramBuilder final : public OpBuilder { * @brief Apply a controlled SWAP gate * * @param control Control qubit - * @param qubit0 First target qubit - * @param qubit1 Second target qubit + * @param qubit0 Target qubit + * @param qubit1 Target qubit * @return Reference to this builder for method chaining * @par Example: * ```c++ @@ -1230,8 +1230,8 @@ class QuartzProgramBuilder final : public OpBuilder { * @brief Apply a multi-controlled SWAP gate * * @param controls Control qubits - * @param qubit0 First target qubit - * @param qubit1 Second target qubit + * @param qubit0 Target qubit + * @param qubit1 Target qubit * @return Reference to this builder for method chaining * @par Example: * ```c++ @@ -1245,6 +1245,61 @@ class QuartzProgramBuilder final : public OpBuilder { */ QuartzProgramBuilder& mcswap(ValueRange controls, Value qubit0, Value qubit1); + /** + * @brief Apply an iSWAP gate to two qubits + * + * @param qubit0 Target qubit + * @param qubit1 Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.iswap(q0, q1); + * ``` + * ```mlir + * quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + */ + QuartzProgramBuilder& iswap(Value qubit0, Value qubit1); + + /** + * @brief Apply a controlled iSWAP gate + * + * @param control Control qubit + * @param qubit0 Target qubit + * @param qubit1 Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.ciswap(q0, q1, q2); + * ``` + * ```mlir + * quartz.ctrl(%q0) { + * quartz.iswap %q1, %q2 : !quartz.qubit, !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& ciswap(Value control, Value qubit0, Value qubit1); + + /** + * @brief Apply a multi-controlled iSWAP gate + * + * @param controls Control qubits + * @param qubit0 Target qubit + * @param qubit1 Target qubit + * @return Reference to this builder for method chaining + * @par Example: + * ```c++ + * builder.mciswap({q0, q1}, q2, q3); + * ``` + * ```mlir + * quartz.ctrl(%q0, %q1) { + * quartz.iswap %q2, %q3 : !quartz.qubit, !quartz.qubit + * } + * ``` + */ + QuartzProgramBuilder& mciswap(ValueRange controls, Value qubit0, + Value qubit1); + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 5839b9e549..b832c7fd34 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -634,6 +634,27 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParamet }]; } +def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a iSWAP gate to two qubits"; + let description = [{ + Applies a iSWAP gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.iswap %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0, + Arg:$qubit1); + let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "iswap"; } + }]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 42dc4007eb..ac8bce90fc 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -176,6 +176,16 @@ inline DenseElementsAttr getMatrixSWAP(MLIRContext* ctx) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixiSWAP(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const auto matrix = {1.0 + 0i, 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, 0.0 + 0i, 0.0 + 1i, 0.0 + 0i, // row 1 + 0.0 + 0i, 0.0 + 1i, 0.0 + 0i, 0.0 + 0i, // row 2 + 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, 1.0 + 0i}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, size_t numControls, mlir::DenseElementsAttr target) { diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 3ba570a176..c19f9efb65 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -121,6 +121,24 @@ convertOneTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, return success(); } +template +LogicalResult +convertTwoTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubits + const auto& quartzQubit0 = adaptor.getQubit0In(); + const auto& quartzQubit1 = adaptor.getQubit1In(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1); + + // Replace the output qubits with the same Quartz references + rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + + return success(); +} + } // namespace /** @@ -727,17 +745,31 @@ struct ConvertFluxSWAPOp final : OpConversionPattern { LogicalResult matchAndRewrite(flux::SWAPOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted input qubits - const auto& quartzQubit0 = adaptor.getQubit0In(); - const auto& quartzQubit1 = adaptor.getQubit1In(); - - // Create quartz.swap (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1); + return convertTwoTargetZeroParameter(op, adaptor, rewriter); + } +}; - // Replace the output qubit with the same quartz references - rewriter.replaceOp(op, adaptor.getOperands()); +/** + * @brief Converts flux.iswap to quartz.iswap + * + * @par Example: + * ```mlir + * %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + */ +struct ConvertFluxiSWAPOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; - return success(); + LogicalResult + matchAndRewrite(flux::iSWAPOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertTwoTargetZeroParameter(op, adaptor, + rewriter); } }; @@ -849,15 +881,15 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add( - typeConverter, context); + patterns + .add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 870c668bb3..10b366856f 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -179,7 +179,7 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, // Create the Flux operation (consumes input, produces output) auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, op.getOperand(1)); + rewriter.create(op.getLoc(), fluxQubit, op->getOperand(1)); // Update the state map if (inCtrlOp == 0) { @@ -195,6 +195,57 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, return success(); } +/** + * @brief Converts a two-target, zero-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest Flux qubits + const auto quartzQubit0 = op->getOperand(0); + const auto quartzQubit1 = op->getOperand(1); + Value fluxQubit0 = nullptr; + Value fluxQubit1 = nullptr; + if (inCtrlOp == 0) { + fluxQubit0 = qubitMap[quartzQubit0]; + fluxQubit1 = qubitMap[quartzQubit1]; + } else { + const auto& targetsIn = state.targetsIn[inCtrlOp]; + fluxQubit0 = targetsIn[0]; + fluxQubit1 = targetsIn[1]; + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); + + // Update state map + if (inCtrlOp == 0) { + qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + /** * @brief Converts a one-target, two-parameter Quartz operation to Flux * @@ -222,8 +273,8 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, } // Create the Flux operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit, - op.getOperand(1), op.getOperand(2)); + auto fluxOp = rewriter.create( + op.getLoc(), fluxQubit, op->getOperand(1), op->getOperand(2)); // Update the state map if (inCtrlOp == 0) { @@ -881,42 +932,33 @@ struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - auto& qubitMap = state.qubitMap; - const auto inCtrlOp = state.inCtrlOp; - - // Get the latest Flux qubit - const auto quartzQubit0 = op->getOperand(0); - const auto quartzQubit1 = op->getOperand(1); - Value fluxQubit0 = nullptr; - Value fluxQubit1 = nullptr; - if (inCtrlOp == 0) { - fluxQubit0 = qubitMap[quartzQubit0]; - fluxQubit1 = qubitMap[quartzQubit1]; - } else { - const auto& targetsIn = state.targetsIn[inCtrlOp]; - fluxQubit0 = targetsIn[0]; - fluxQubit1 = targetsIn[1]; - } - - // Create flux.swap (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); - - // Update state map - if (inCtrlOp == 0) { - qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); - } else { - state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut( - {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); - state.targetsOut.try_emplace(inCtrlOp, targetsOut); - } + return convertTwoTargetZeroParameter(op, rewriter, + getState()); + } +}; - rewriter.eraseOp(op); +/** + * @brief Converts quartz.iswap to flux.iswap + * + * @par Example: + * ```mlir + * quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit + * ``` + */ +struct ConvertQuartziSWAPOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; - return success(); + LogicalResult + matchAndRewrite(quartz::iSWAPOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + return convertTwoTargetZeroParameter(op, rewriter, + getState()); } }; @@ -1061,16 +1103,16 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addLegalDialect(); // Register operation conversion patterns with state tracking - patterns.add< - ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, - ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, - ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, - ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, - ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, - ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, - ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, - ConvertQuartzSWAPOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( - typeConverter, context, &state); + patterns.add(typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 94f9f59e0a..ae69d61d13 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -297,6 +297,67 @@ convertOneTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a two-target, zero-parameter Quartz operation to QIR + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertTwoTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 2); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); + + // Define function signature + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 2); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + } // namespace /** @@ -1393,14 +1454,11 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(SWAPOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto* ctx = getContext(); auto& state = getState(); // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const auto numCtrls = posCtrls.size(); + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; // Define function name StringRef fnName; @@ -1418,39 +1476,54 @@ struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { } } - // Define function argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 2); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); + return convertTwoTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); + } +}; - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(ctx), argumentTypes); +/** + * @brief Converts quartz.iswap operation to QIR iswap + * + * @par Example: + * ```mlir + * quartz.iswap %q1, %q2 : !quartz.qubit, !quartz.qubit + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__iswap__body(%q1, %q2) : (!llvm.ptr, !llvm.ptr) + * -> () + * ``` + */ +struct ConvertQuartziSWAPQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + LogicalResult + matchAndRewrite(iSWAPOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); - SmallVector operands; - operands.reserve(numCtrls + 2); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; + // Define function name + StringRef fnName; + if (inCtrlOp == 0) { + fnName = QIR_ISWAP; + } else { + if (numCtrls == 1) { + fnName = QIR_CISWAP; + } else if (numCtrls == 2) { + fnName = QIR_CCISWAP; + } else if (numCtrls == 3) { + fnName = QIR_CCCISWAP; + } else { + return failure(); + } } - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); + return convertTwoTargetZeroParameter(op, adaptor, rewriter, getContext(), + state, fnName); } }; @@ -1838,6 +1911,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0398b74f00..0f5dec4474 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -171,7 +171,7 @@ Value FluxProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// -// Helper methods +// OneTargetZeroParameter helpers template Value FluxProgramBuilder::createOneTargetZeroParameter(const Value qubit) { @@ -207,6 +207,8 @@ FluxProgramBuilder::createMultiControlledOneTargetZeroParameter( return {controlsOut, targetsOut[0]}; } +// OneTargetOneParameter helpers + template Value FluxProgramBuilder::createOneTargetOneParameter( const std::variant& parameter, const Value qubit) { @@ -244,6 +246,8 @@ FluxProgramBuilder::createMultiControlledOneTargetOneParameter( return {controlsOut, targetsOut[0]}; } +// OneTargetTwoParameter helpers + template Value FluxProgramBuilder::createOneTargetTwoParameter( const std::variant& parameter1, @@ -286,6 +290,47 @@ FluxProgramBuilder::createMultiControlledOneTargetTwoParameter( return {controlsOut, targetsOut[0]}; } +// TwoTargetZeroParameter helpers + +template +std::pair +FluxProgramBuilder::createTwoTargetZeroParameter(const Value qubit0, + const Value qubit1) { + auto op = create(loc, qubit0, qubit1); + const auto& qubit0Out = op.getQubit0Out(); + const auto& qubit1Out = op.getQubit1Out(); + updateQubitTracking(qubit0, qubit0Out); + updateQubitTracking(qubit1, qubit1Out); + return {qubit0Out, qubit1Out}; +} + +template +std::pair> +FluxProgramBuilder::createControlledTwoTargetZeroParameter(const Value control, + const Value qubit0, + const Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(control, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], targets[1]); + return op->getResults(); + }); + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; +} + +template +std::pair> +FluxProgramBuilder::createMultiControlledTwoTargetZeroParameter( + const ValueRange controls, const Value qubit0, const Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], targets[1]); + return op->getResults(); + }); + return {controlsOut, {targetsOut[0], targetsOut[1]}}; +} + // IdOp Value FluxProgramBuilder::id(const Value qubit) { @@ -593,35 +638,39 @@ FluxProgramBuilder::mcu2(const std::variant& phi, // SWAPOp std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { - auto swapOp = create(loc, qubit0, qubit1); - const auto& qubit0Out = swapOp.getQubit0Out(); - const auto& qubit1Out = swapOp.getQubit1Out(); - updateQubitTracking(qubit0, qubit0Out); - updateQubitTracking(qubit1, qubit1Out); - return {qubit0Out, qubit1Out}; + return createTwoTargetZeroParameter(qubit0, qubit1); } std::pair> FluxProgramBuilder::cswap(const Value control, Value qubit0, Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(control, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto swap = b.create(loc, targets[0], targets[1]); - return swap->getResults(); - }); - return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; + return createControlledTwoTargetZeroParameter(control, qubit0, + qubit1); } std::pair> FluxProgramBuilder::mcswap(const ValueRange controls, Value qubit0, Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(controls, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto swap = b.create(loc, targets[0], targets[1]); - return swap->getResults(); - }); - return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; + return createMultiControlledTwoTargetZeroParameter(controls, qubit0, + qubit1); +} + +// iSWAPOp + +std::pair FluxProgramBuilder::iswap(Value qubit0, Value qubit1) { + return createTwoTargetZeroParameter(qubit0, qubit1); +} + +std::pair> +FluxProgramBuilder::ciswap(const Value control, Value qubit0, Value qubit1) { + return createControlledTwoTargetZeroParameter(control, qubit0, + qubit1); +} + +std::pair> +FluxProgramBuilder::mciswap(const ValueRange controls, Value qubit0, + Value qubit1) { + return createMultiControlledTwoTargetZeroParameter(controls, qubit0, + qubit1); } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/iSWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/iSWAPOp.cpp new file mode 100644 index 0000000000..0de914665e --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/iSWAPOp.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr iSWAPOp::tryGetStaticMatrix() { + return getMatrixiSWAP(getContext()); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 757d4ea554..2823781a08 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -399,6 +399,45 @@ void QIRProgramBuilder::createOneTargetTwoParameter( builder.create(loc, fnDecl, operands); } +void QIRProgramBuilder::createTwoTargetZeroParameter(const ValueRange controls, + const Value target0, + const Value target1, + StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard insertGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 2); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 2); + operands.append(controls.begin(), controls.end()); + operands.push_back(target0); + operands.push_back(target1); + + builder.create(loc, fnDecl, operands); +} + // IdOp QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { @@ -943,51 +982,66 @@ QIRProgramBuilder::mcu2(const std::variant& phi, QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, const Value qubit1) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Create swap call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_SWAP, qirSignature); - builder.create(loc, fnDecl, ValueRange{qubit0, qubit1}); - + createTwoTargetZeroParameter({}, qubit0, qubit1, QIR_SWAP); return *this; } QIRProgramBuilder& QIRProgramBuilder::cswap(const Value control, const Value target0, const Value target1) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + createTwoTargetZeroParameter({control}, target0, target1, QIR_CSWAP); + return *this; +} - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); +QIRProgramBuilder& QIRProgramBuilder::mcswap(const ValueRange controls, + const Value target0, + const Value target1) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CSWAP; + } else if (controls.size() == 2) { + fnName = QIR_CCSWAP; + } else if (controls.size() == 3) { + fnName = QIR_CCCSWAP; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createTwoTargetZeroParameter(controls, target0, target1, fnName); + return *this; +} - // Create cswap call - const auto qirSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), - {LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext()), - LLVM::LLVMPointerType::get(builder.getContext())}); - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, QIR_CSWAP, qirSignature); - builder.create(loc, fnDecl, - ValueRange{control, target0, target1}); +// iSWAPOp +QIRProgramBuilder& QIRProgramBuilder::iswap(const Value qubit0, + const Value qubit1) { + createTwoTargetZeroParameter({}, qubit0, qubit1, QIR_ISWAP); return *this; } -QIRProgramBuilder& QIRProgramBuilder::mcswap(const ValueRange controls, +QIRProgramBuilder& QIRProgramBuilder::ciswap(const Value control, const Value target0, const Value target1) { - llvm::report_fatal_error("Not implemented yet"); + createTwoTargetZeroParameter({control}, target0, target1, QIR_CISWAP); + return *this; +} + +QIRProgramBuilder& QIRProgramBuilder::mciswap(const ValueRange controls, + const Value target0, + const Value target1) { + StringRef fnName; + if (controls.size() == 1) { + fnName = QIR_CISWAP; + } else if (controls.size() == 2) { + fnName = QIR_CCISWAP; + } else if (controls.size() == 3) { + fnName = QIR_CCCISWAP; + } else { + llvm::report_fatal_error("Multi-controlled with more than 3 controls are " + "currently not supported"); + } + createTwoTargetZeroParameter(controls, target0, target1, fnName); + return *this; } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 78861905bd..0b68ac4b8c 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -485,6 +485,26 @@ QuartzProgramBuilder& QuartzProgramBuilder::mcswap(ValueRange controls, return *this; } +// iSWAPOp + +QuartzProgramBuilder& QuartzProgramBuilder::iswap(Value qubit0, Value qubit1) { + create(loc, qubit0, qubit1); + return *this; +} + +QuartzProgramBuilder& QuartzProgramBuilder::ciswap(Value control, + const Value qubit0, + const Value qubit1) { + return mciswap({control}, qubit0, qubit1); +} + +QuartzProgramBuilder& +QuartzProgramBuilder::mciswap(ValueRange controls, Value qubit0, Value qubit1) { + create(loc, controls, + [&](OpBuilder& b) { b.create(loc, qubit0, qubit1); }); + return *this; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/iSWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/iSWAPOp.cpp new file mode 100644 index 0000000000..13ad45d1e0 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/iSWAPOp.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr iSWAPOp::tryGetStaticMatrix() { + return getMatrixiSWAP(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index d16eb4706e..6fb2b33833 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -591,7 +591,7 @@ void addROp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds a U2 operation * * @details - * Translates U2 operations from the QuantumComputation to quartz.u2 operations. + * Translate a U2 operation from the QuantumComputation to quartz.u2. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The U2 operation to translate @@ -614,8 +614,7 @@ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, * @brief Adds a SWAP operation * * @details - * Translates SWAP operations from the QuantumComputation to quartz.swap - * operations. + * Translate a SWAP operation from the QuantumComputation to quartz.swap. * * @param builder The QuartzProgramBuilder used to create operations * @param operation The SWAP operation to translate @@ -633,6 +632,28 @@ void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an iSWAP operation + * + * @details + * Translate an iSWAP operation from the QuantumComputation to quartz.iswap. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The iSWAP operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addiSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.iswap(target0, target1); + } else { + builder.mciswap(posControls, target0, target1); + } +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -718,6 +739,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::SWAP: addSWAPOp(builder, *operation, qubits); break; + case qc::OpType::iSWAP: + addiSWAPOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 066da5b12d..731b1534dc 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2344,4 +2344,66 @@ TEST_F(CompilerPipelineTest, CSWAP) { }); } +TEST_F(CompilerPipelineTest, MCSWAP) { + qc::QuantumComputation qc; + qc.addQubitRegister(4, "q"); + qc.mcswap({0, 1}, 2, 3); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(4); + b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, iSWAP) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.iswap(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.iswap(reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.iswap(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.iswap(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From 05db54f15581fbe19e1afcbeaea48b5067a55c5f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:48:05 +0100 Subject: [PATCH 235/419] Fix linter errors --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 ++-- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 8 -------- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 6 ++++++ mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 1 + mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 6 ++++++ mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 9 --------- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 10b366856f..50335c2962 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -885,7 +885,7 @@ struct ConvertQuartzROp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::ROp op, OpAdaptor adaptor, + matchAndRewrite(quartz::ROp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { return convertOneTargetTwoParameter(op, rewriter, getState()); } @@ -907,7 +907,7 @@ struct ConvertQuartzU2Op final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::U2Op op, OpAdaptor adaptor, + matchAndRewrite(quartz::U2Op op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { return convertOneTargetTwoParameter(op, rewriter, getState()); } diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 6acafcda8e..fd00fc0560 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" // IWYU pragma: associated -#include "mlir/Dialect/Utils/MatrixUtils.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -19,12 +18,6 @@ #include // IWYU pragma: end_keep -#include -#include -#include -#include -#include -#include #include #include #include @@ -33,7 +26,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; //===----------------------------------------------------------------------===// // Dialect diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 6f5d900287..ddaf7bf34e 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -12,10 +12,16 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include +#include +#include +#include #include #include #include #include +#include #include using namespace mlir; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 2823781a08..b9ad85aed1 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 921906cbd0..ea07ad6f04 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -11,10 +11,16 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" +#include +#include +#include +#include +#include #include #include #include #include +#include #include using namespace mlir; diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index fea13c06d8..bb2e01bb06 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -18,16 +18,7 @@ #include // IWYU pragma: end_keep -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include using namespace mlir; From bbd10c360137e5fad17461df20b8187b17a1a96a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:06:49 +0100 Subject: [PATCH 236/419] Add canonicalizer to SWAP --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 ++ .../Dialect/Flux/IR/StandardGates/SWAPOp.cpp | 34 +++++++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 34 ++++++++++++++----- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 7fc91eefee..fe29c16799 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -694,6 +694,8 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; + + let hasCanonicalizer = 1; } def iSWAPOp : FluxOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp index 81d949c078..842aef7c53 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp @@ -12,11 +12,45 @@ #include "mlir/Dialect/Utils/MatrixUtils.h" #include +#include +#include +#include +#include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +/** + * @brief Remove subsequent SWAP operations on the same qubit. + */ +struct RemoveSubsequentSWAP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SWAPOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is a SWAP operation + auto prevOp = op.getQubit0In().getDefiningOp(); + if (!prevOp) { + return failure(); + } + if (op.getQubit1In() != prevOp.getQubit1Out()) { + return failure(); + } + + // Remove both SWAP operations + rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + rewriter.replaceOp(op, {op.getQubit0In(), op.getQubit1In()}); + + return success(); + } +}; + DenseElementsAttr SWAPOp::tryGetStaticMatrix() { return getMatrixSWAP(getContext()); } + +void SWAPOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 731b1534dc..d68b566608 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2286,30 +2286,48 @@ TEST_F(CompilerPipelineTest, SWAP) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.swap(0, 1); + qc.swap(0, 1); + qc.swap(0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.swap(q0, q1); + b.swap(q0, q1); + b.swap(q0, q1); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.swap(q0, q1); + std::tie(q0, q1) = b.swap(q0, q1); + b.swap(q0, q1); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.swap(reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.swap(reg[0], reg[1]); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.swap(reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } From 866c2c3ad83cd82448c31cc2a675b021f11d92f1 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:29:12 +0100 Subject: [PATCH 237/419] Add untested canonicalizer to U2 --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 + .../Dialect/Flux/IR/StandardGates/U2Op.cpp | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index fe29c16799..db268fe8f5 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -672,6 +672,8 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let builders = [ OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$phi, "const std::variant&":$lambda)> ]; + + let hasCanonicalizer = 1; } def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp index 36008194f1..adaac21a33 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp @@ -14,13 +14,49 @@ #include #include #include +#include #include +#include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Remove trivial U2 operations. + */ +struct RemoveSubsequentU2 final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(U2Op op, + PatternRewriter& rewriter) const override { + const auto phi = op.getStaticParameter(op.getPhi()); + const auto lambda = op.getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (phiValue != 0.0 || lambdaValue != 0.0) { + return failure(); + } + + auto rxOp = rewriter.create(op.getLoc(), op.getQubitIn(), + std::numbers::pi / 2.0); + rewriter.replaceOp(op, rxOp.getResult()); + + return success(); + } +}; + +} // namespace + DenseElementsAttr U2Op::tryGetStaticMatrix() { const auto phi = getStaticParameter(getPhi()); const auto lambda = getStaticParameter(getLambda()); @@ -54,3 +90,8 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); } + +void U2Op::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} From 65eeab9d1eb8a1da22ac743f697a27d48bc125bd Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:31:03 +0100 Subject: [PATCH 238/419] Fix linter errors --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 1 + mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp | 10 +++++----- mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 1 + mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 2 -- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index ddaf7bf34e..73e111bfb7 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp index adaac21a33..4d016aae49 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp @@ -28,15 +28,15 @@ using namespace mlir::utils; namespace { /** - * @brief Remove trivial U2 operations. + * @brief Replace trivial U2 operations with RY operations. */ -struct RemoveSubsequentU2 final : OpRewritePattern { +struct ReplaceTrivialU2 final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(U2Op op, PatternRewriter& rewriter) const override { - const auto phi = op.getStaticParameter(op.getPhi()); - const auto lambda = op.getStaticParameter(op.getLambda()); + const auto phi = U2Op::getStaticParameter(op.getPhi()); + const auto lambda = U2Op::getStaticParameter(op.getLambda()); if (!phi || !lambda) { return failure(); } @@ -93,5 +93,5 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, void U2Op::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index ea07ad6f04..255a27c3fa 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index bb2e01bb06..7519ff0c75 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" // IWYU pragma: associated -#include "mlir/Dialect/Utils/MatrixUtils.h" // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; //===----------------------------------------------------------------------===// // Dialect From f3f3ebeaee16fa0dcbd9dd9d27ea61441d1c82f2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:12:24 +0100 Subject: [PATCH 239/419] Use macros to define Quartz builder methods --- .../Quartz/Builder/QuartzProgramBuilder.cpp | 484 ++++-------------- 1 file changed, 109 insertions(+), 375 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 0b68ac4b8c..2b056102d6 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -124,386 +124,120 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// -// IdOp - -QuartzProgramBuilder& QuartzProgramBuilder::id(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::cid(Value control, Value target) { - return mcid({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcid(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// XOp - -QuartzProgramBuilder& QuartzProgramBuilder::x(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::cx(Value control, Value target) { - return mcx({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcx(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// YOp - -QuartzProgramBuilder& QuartzProgramBuilder::y(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::cy(Value control, Value target) { - return mcy({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcy(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// ZOp - -QuartzProgramBuilder& QuartzProgramBuilder::z(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::cz(Value control, Value target) { - return mcz({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcz(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// HOp - -QuartzProgramBuilder& QuartzProgramBuilder::h(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::ch(Value control, Value target) { - return mch({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mch(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// SOp - -QuartzProgramBuilder& QuartzProgramBuilder::s(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::cs(Value control, Value target) { - return mcs({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcs(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// SdgOp - -QuartzProgramBuilder& QuartzProgramBuilder::sdg(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::csdg(Value control, Value target) { - return mcsdg({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcsdg(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// TOp - -QuartzProgramBuilder& QuartzProgramBuilder::t(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::ct(Value control, Value target) { - return mct({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mct(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// TdgOp - -QuartzProgramBuilder& QuartzProgramBuilder::tdg(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::ctdg(Value control, Value target) { - return mctdg({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mctdg(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// SXOp - -QuartzProgramBuilder& QuartzProgramBuilder::sx(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::csx(Value control, Value target) { - return mcsx({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcsx(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// SXdgOp - -QuartzProgramBuilder& QuartzProgramBuilder::sxdg(Value qubit) { - create(loc, qubit); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::csxdg(Value control, Value target) { - return mcsxdg({control}, target); -} - -QuartzProgramBuilder& QuartzProgramBuilder::mcsxdg(ValueRange controls, - Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target); }); - return *this; -} - -// RXOp - -QuartzProgramBuilder& -QuartzProgramBuilder::rx(const std::variant& theta, - Value qubit) { - create(loc, qubit, theta); - return *this; -} - -QuartzProgramBuilder& -QuartzProgramBuilder::crx(const std::variant& theta, - Value control, const Value target) { - return mcrx(theta, {control}, target); -} - -QuartzProgramBuilder& -QuartzProgramBuilder::mcrx(const std::variant& theta, - ValueRange controls, Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target, theta); }); - return *this; -} - -// RYOp - -QuartzProgramBuilder& -QuartzProgramBuilder::ry(const std::variant& theta, - Value qubit) { - create(loc, qubit, theta); - return *this; -} - -QuartzProgramBuilder& -QuartzProgramBuilder::cry(const std::variant& theta, - Value control, const Value target) { - return mcry(theta, {control}, target); -} - -QuartzProgramBuilder& -QuartzProgramBuilder::mcry(const std::variant& theta, - ValueRange controls, Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target, theta); }); - return *this; -} - -// RZOp - -QuartzProgramBuilder& -QuartzProgramBuilder::rz(const std::variant& theta, - Value qubit) { - create(loc, qubit, theta); - return *this; -} - -QuartzProgramBuilder& -QuartzProgramBuilder::crz(const std::variant& theta, - Value control, const Value target) { - return mcrz(theta, {control}, target); -} - -QuartzProgramBuilder& -QuartzProgramBuilder::mcrz(const std::variant& theta, - ValueRange controls, Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target, theta); }); - return *this; -} - -// POp - -QuartzProgramBuilder& -QuartzProgramBuilder::p(const std::variant& theta, Value qubit) { - create(loc, qubit, theta); - return *this; -} - -QuartzProgramBuilder& -QuartzProgramBuilder::cp(const std::variant& theta, - Value control, Value target) { - return mcp(theta, {control}, target); -} - -QuartzProgramBuilder& -QuartzProgramBuilder::mcp(const std::variant& theta, - ValueRange controls, Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target, theta); }); - return *this; -} - -// ROp - -QuartzProgramBuilder& -QuartzProgramBuilder::r(const std::variant& theta, - const std::variant& phi, Value qubit) { - create(loc, qubit, theta, phi); - return *this; -} - -QuartzProgramBuilder& -QuartzProgramBuilder::cr(const std::variant& theta, - const std::variant& phi, Value control, - Value target) { - return mcr(theta, phi, {control}, target); -} - -QuartzProgramBuilder& -QuartzProgramBuilder::mcr(const std::variant& theta, - const std::variant& phi, - ValueRange controls, Value target) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, target, theta, phi); }); - return *this; -} - -// U2Op - -QuartzProgramBuilder& -QuartzProgramBuilder::u2(const std::variant& phi, - const std::variant& lambda, - Value qubit) { - create(loc, qubit, phi, lambda); - return *this; -} - -QuartzProgramBuilder& -QuartzProgramBuilder::cu2(const std::variant& phi, - const std::variant& lambda, - Value control, const Value target) { - return mcu2(phi, lambda, {control}, target); -} - -QuartzProgramBuilder& -QuartzProgramBuilder::mcu2(const std::variant& phi, - const std::variant& lambda, - ValueRange controls, Value target) { - create(loc, controls, [&](OpBuilder& b) { - b.create(loc, target, phi, lambda); - }); - return *this; -} - -// SWAPOp - -QuartzProgramBuilder& QuartzProgramBuilder::swap(Value qubit0, Value qubit1) { - create(loc, qubit0, qubit1); - return *this; -} - -QuartzProgramBuilder& QuartzProgramBuilder::cswap(Value control, - const Value qubit0, - const Value qubit1) { - return mcswap({control}, qubit0, qubit1); -} +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit) { \ + create(loc, qubit); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME(Value control, \ + Value target) { \ + return mc##OP_NAME({control}, target); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME(ValueRange controls, \ + Value target) { \ + create(loc, controls, \ + [&](OpBuilder& b) { b.create(loc, target); }); \ + return *this; \ + } -QuartzProgramBuilder& QuartzProgramBuilder::mcswap(ValueRange controls, - Value qubit0, Value qubit1) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, qubit0, qubit1); }); - return *this; -} +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + const std::variant& PARAM, Value qubit) { \ + create(loc, qubit, PARAM); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + const std::variant& PARAM, Value control, Value target) { \ + return mc##OP_NAME(PARAM, {control}, target); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + const std::variant& PARAM, ValueRange controls, \ + Value target) { \ + create(loc, controls, [&](OpBuilder& b) { \ + b.create(loc, target, PARAM); \ + }); \ + return *this; \ + } -// iSWAPOp +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + const std::variant& PARAM1, \ + const std::variant& PARAM2, Value qubit) { \ + create(loc, qubit, PARAM1, PARAM2); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + const std::variant& PARAM1, \ + const std::variant& PARAM2, Value control, \ + Value target) { \ + return mc##OP_NAME(PARAM1, PARAM2, {control}, target); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + const std::variant& PARAM1, \ + const std::variant& PARAM2, ValueRange controls, \ + Value target) { \ + create(loc, controls, [&](OpBuilder& b) { \ + b.create(loc, target, PARAM1, PARAM2); \ + }); \ + return *this; \ + } -QuartzProgramBuilder& QuartzProgramBuilder::iswap(Value qubit0, Value qubit1) { - create(loc, qubit0, qubit1); - return *this; -} +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit0, \ + Value qubit1) { \ + create(loc, qubit0, qubit1); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + Value control, const Value qubit0, const Value qubit1) { \ + return mc##OP_NAME({control}, qubit0, qubit1); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + ValueRange controls, Value qubit0, Value qubit1) { \ + create(loc, controls, [&](OpBuilder& b) { \ + b.create(loc, qubit0, qubit1); \ + }); \ + return *this; \ + } -QuartzProgramBuilder& QuartzProgramBuilder::ciswap(Value control, - const Value qubit0, - const Value qubit1) { - return mciswap({control}, qubit0, qubit1); -} +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) -QuartzProgramBuilder& -QuartzProgramBuilder::mciswap(ValueRange controls, Value qubit0, Value qubit1) { - create(loc, controls, - [&](OpBuilder& b) { b.create(loc, qubit0, qubit1); }); - return *this; -} +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER //===----------------------------------------------------------------------===// // Modifiers From fbbe245998aebf449c5a67f9853fd791d53c190e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:51:11 +0100 Subject: [PATCH 240/419] Use macros also for Flux and QIR builders --- .../Flux/Builder/FluxProgramBuilder.cpp | 439 +++------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 776 ++++-------------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 19 +- 3 files changed, 284 insertions(+), 950 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0f5dec4474..2de0ffe53c 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -331,347 +331,114 @@ FluxProgramBuilder::createMultiControlledTwoTargetZeroParameter( return {controlsOut, {targetsOut[0], targetsOut[1]}}; } -// IdOp - -Value FluxProgramBuilder::id(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::cid(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mcid(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// XOp - -Value FluxProgramBuilder::x(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::cx(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mcx(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// YOp - -Value FluxProgramBuilder::y(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::cy(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mcy(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// ZOp - -Value FluxProgramBuilder::z(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::cz(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mcz(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// HOp - -Value FluxProgramBuilder::h(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::ch(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mch(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// SOp - -Value FluxProgramBuilder::s(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::cs(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mcs(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// SdgOp - -Value FluxProgramBuilder::sdg(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::csdg(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair -FluxProgramBuilder::mcsdg(const ValueRange controls, const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// TOp - -Value FluxProgramBuilder::t(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::ct(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mct(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// TdgOp - -Value FluxProgramBuilder::tdg(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::ctdg(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair -FluxProgramBuilder::mctdg(const ValueRange controls, const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// SXOp - -Value FluxProgramBuilder::sx(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::csx(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair FluxProgramBuilder::mcsx(const ValueRange controls, - const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// SXdgOp - -Value FluxProgramBuilder::sxdg(const Value qubit) { - return createOneTargetZeroParameter(qubit); -} - -std::pair FluxProgramBuilder::csxdg(const Value control, - const Value target) { - return createControlledOneTargetZeroParameter(control, target); -} - -std::pair -FluxProgramBuilder::mcsxdg(const ValueRange controls, const Value target) { - return createMultiControlledOneTargetZeroParameter(controls, target); -} - -// RXOp - -Value FluxProgramBuilder::rx(const std::variant& theta, - const Value qubit) { - return createOneTargetOneParameter(theta, qubit); -} - -std::pair -FluxProgramBuilder::crx(const std::variant& theta, - const Value control, const Value target) { - return createControlledOneTargetOneParameter(theta, control, target); -} - -std::pair -FluxProgramBuilder::mcrx(const std::variant& theta, - const ValueRange controls, const Value target) { - return createMultiControlledOneTargetOneParameter(theta, controls, - target); -} - -// RYOp - -Value FluxProgramBuilder::ry(const std::variant& theta, - const Value qubit) { - return createOneTargetOneParameter(theta, qubit); -} - -std::pair -FluxProgramBuilder::cry(const std::variant& theta, - const Value control, const Value target) { - return createControlledOneTargetOneParameter(theta, control, target); -} - -std::pair -FluxProgramBuilder::mcry(const std::variant& theta, - const ValueRange controls, const Value target) { - return createMultiControlledOneTargetOneParameter(theta, controls, - target); -} - -// RZOp - -Value FluxProgramBuilder::rz(const std::variant& theta, - const Value qubit) { - return createOneTargetOneParameter(theta, qubit); -} - -std::pair -FluxProgramBuilder::crz(const std::variant& theta, - const Value control, const Value target) { - return createControlledOneTargetOneParameter(theta, control, target); -} - -std::pair -FluxProgramBuilder::mcrz(const std::variant& theta, - const ValueRange controls, const Value target) { - return createMultiControlledOneTargetOneParameter(theta, controls, - target); -} - -// POp - -Value FluxProgramBuilder::p(const std::variant& theta, - const Value qubit) { - return createOneTargetOneParameter(theta, qubit); -} - -std::pair -FluxProgramBuilder::cp(const std::variant& theta, - const Value control, const Value target) { - return createControlledOneTargetOneParameter(theta, control, target); -} - -std::pair -FluxProgramBuilder::mcp(const std::variant& theta, - const ValueRange controls, const Value target) { - return createMultiControlledOneTargetOneParameter(theta, controls, - target); -} - -// ROp - -Value FluxProgramBuilder::r(const std::variant& theta, - const std::variant& phi, - const Value qubit) { - return createOneTargetTwoParameter(theta, phi, qubit); -} - -std::pair -FluxProgramBuilder::cr(const std::variant& theta, - const std::variant& phi, - const Value control, const Value target) { - return createControlledOneTargetTwoParameter(theta, phi, control, - target); -} - -std::pair -FluxProgramBuilder::mcr(const std::variant& theta, - const std::variant& phi, - const ValueRange controls, const Value target) { - return createMultiControlledOneTargetTwoParameter(theta, phi, controls, - target); -} - -// U2Op - -Value FluxProgramBuilder::u2(const std::variant& phi, - const std::variant& lambda, - Value qubit) { - return createOneTargetTwoParameter(phi, lambda, qubit); -} - -std::pair -FluxProgramBuilder::cu2(const std::variant& phi, - const std::variant& lambda, - const Value control, const Value target) { - return createControlledOneTargetTwoParameter(phi, lambda, control, - target); -} - -std::pair -FluxProgramBuilder::mcu2(const std::variant& phi, - const std::variant& lambda, - const ValueRange controls, const Value target) { - return createMultiControlledOneTargetTwoParameter(phi, lambda, controls, - target); -} - -// SWAPOp - -std::pair FluxProgramBuilder::swap(Value qubit0, Value qubit1) { - return createTwoTargetZeroParameter(qubit0, qubit1); -} - -std::pair> -FluxProgramBuilder::cswap(const Value control, Value qubit0, Value qubit1) { - return createControlledTwoTargetZeroParameter(control, qubit0, - qubit1); -} +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + Value FluxProgramBuilder::OP_NAME(const Value qubit) { \ + return createOneTargetZeroParameter(qubit); \ + } \ + std::pair FluxProgramBuilder::c##OP_NAME(const Value control, \ + const Value target) { \ + return createControlledOneTargetZeroParameter(control, target); \ + } \ + std::pair FluxProgramBuilder::mc##OP_NAME( \ + const ValueRange controls, const Value target) { \ + return createMultiControlledOneTargetZeroParameter(controls, \ + target); \ + } -std::pair> -FluxProgramBuilder::mcswap(const ValueRange controls, Value qubit0, - Value qubit1) { - return createMultiControlledTwoTargetZeroParameter(controls, qubit0, - qubit1); -} +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + Value FluxProgramBuilder::OP_NAME(const std::variant&(PARAM), \ + const Value qubit) { \ + return createOneTargetOneParameter(PARAM, qubit); \ + } \ + std::pair FluxProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), const Value control, \ + const Value target) { \ + return createControlledOneTargetOneParameter(PARAM, control, \ + target); \ + } \ + std::pair FluxProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), const ValueRange controls, \ + const Value target) { \ + return createMultiControlledOneTargetOneParameter( \ + PARAM, controls, target); \ + } -// iSWAPOp +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + Value FluxProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value qubit) { \ + return createOneTargetTwoParameter(PARAM1, PARAM2, qubit); \ + } \ + std::pair FluxProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value control, \ + const Value target) { \ + return createControlledOneTargetTwoParameter(PARAM1, PARAM2, \ + control, target); \ + } \ + std::pair FluxProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const ValueRange controls, \ + const Value target) { \ + return createMultiControlledOneTargetTwoParameter( \ + PARAM1, PARAM2, controls, target); \ + } -std::pair FluxProgramBuilder::iswap(Value qubit0, Value qubit1) { - return createTwoTargetZeroParameter(qubit0, qubit1); -} +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + std::pair FluxProgramBuilder::OP_NAME(Value qubit0, \ + Value qubit1) { \ + return createTwoTargetZeroParameter(qubit0, qubit1); \ + } \ + std::pair> FluxProgramBuilder::c##OP_NAME( \ + const Value control, Value qubit0, Value qubit1) { \ + return createControlledTwoTargetZeroParameter(control, qubit0, \ + qubit1); \ + } \ + std::pair> \ + FluxProgramBuilder::mc##OP_NAME(const ValueRange controls, Value qubit0, \ + Value qubit1) { \ + return createMultiControlledTwoTargetZeroParameter( \ + controls, qubit0, qubit1); \ + } -std::pair> -FluxProgramBuilder::ciswap(const Value control, Value qubit0, Value qubit1) { - return createControlledTwoTargetZeroParameter(control, qubit0, - qubit1); -} +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) -std::pair> -FluxProgramBuilder::mciswap(const ValueRange controls, Value qubit0, - Value qubit1) { - return createMultiControlledTwoTargetZeroParameter(controls, qubit0, - qubit1); -} +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER //===----------------------------------------------------------------------===// // Modifiers diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index b9ad85aed1..b44f06c94f 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -439,611 +439,177 @@ void QIRProgramBuilder::createTwoTargetZeroParameter(const ValueRange controls, builder.create(loc, fnDecl, operands); } -// IdOp - -QIRProgramBuilder& QIRProgramBuilder::id(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_ID); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::cid(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CID); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcid(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CID; - } else if (controls.size() == 2) { - fnName = QIR_CCID; - } else if (controls.size() == 3) { - fnName = QIR_CCCID; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// XOp - -QIRProgramBuilder& QIRProgramBuilder::x(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_CX); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::cx(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CX); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcx(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CX; - } else if (controls.size() == 2) { - fnName = QIR_CCX; - } else if (controls.size() == 3) { - fnName = QIR_CCCX; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// YOp - -QIRProgramBuilder& QIRProgramBuilder::y(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_Y); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::cy(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CY); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcy(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CY; - } else if (controls.size() == 2) { - fnName = QIR_CCY; - } else if (controls.size() == 3) { - fnName = QIR_CCCY; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// ZOp - -QIRProgramBuilder& QIRProgramBuilder::z(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_Z); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::cz(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CZ); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcz(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CZ; - } else if (controls.size() == 2) { - fnName = QIR_CCZ; - } else if (controls.size() == 3) { - fnName = QIR_CCCZ; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// HOp - -QIRProgramBuilder& QIRProgramBuilder::h(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_H); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::ch(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CH); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mch(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CH; - } else if (controls.size() == 2) { - fnName = QIR_CCH; - } else if (controls.size() == 3) { - fnName = QIR_CCCH; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// SOp - -QIRProgramBuilder& QIRProgramBuilder::s(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_S); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::cs(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CS); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcs(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CS; - } else if (controls.size() == 2) { - fnName = QIR_CCS; - } else if (controls.size() == 3) { - fnName = QIR_CCCS; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// SdgOp - -QIRProgramBuilder& QIRProgramBuilder::sdg(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_SDG); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::csdg(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CSDG); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcsdg(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CSDG; - } else if (controls.size() == 2) { - fnName = QIR_CCSDG; - } else if (controls.size() == 3) { - fnName = QIR_CCCSDG; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// TOp - -QIRProgramBuilder& QIRProgramBuilder::t(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_T); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::ct(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CT); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mct(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CT; - } else if (controls.size() == 2) { - fnName = QIR_CCT; - } else if (controls.size() == 3) { - fnName = QIR_CCCT; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// TdgOp - -QIRProgramBuilder& QIRProgramBuilder::tdg(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_TDG); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::ctdg(const Value control, - const Value target) { - createOneTargetZeroParameter(control, target, QIR_CTDG); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mctdg(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CTDG; - } else if (controls.size() == 2) { - fnName = QIR_CCTDG; - } else if (controls.size() == 3) { - fnName = QIR_CCCTDG; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// SXOp - -QIRProgramBuilder& QIRProgramBuilder::sx(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_SX); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::csx(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CSX); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcsx(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CSX; - } else if (controls.size() == 2) { - fnName = QIR_CCSX; - } else if (controls.size() == 3) { - fnName = QIR_CCCSX; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// SXdgOp - -QIRProgramBuilder& QIRProgramBuilder::sxdg(const Value qubit) { - createOneTargetZeroParameter({}, qubit, QIR_SXDG); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::csxdg(const Value control, - const Value target) { - createOneTargetZeroParameter({control}, target, QIR_CSXDG); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcsxdg(const ValueRange controls, - const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CSXDG; - } else if (controls.size() == 2) { - fnName = QIR_CCSXDG; - } else if (controls.size() == 3) { - fnName = QIR_CCCSXDG; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetZeroParameter(controls, target, fnName); - return *this; -} - -// RXOp - -QIRProgramBuilder& -QIRProgramBuilder::rx(const std::variant& theta, - const Value qubit) { - createOneTargetOneParameter(theta, {}, qubit, QIR_RX); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::crx(const std::variant& theta, - const Value control, const Value target) { - createOneTargetOneParameter(theta, {control}, target, QIR_CRX); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::mcrx(const std::variant& theta, - const ValueRange controls, const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CRX; - } else if (controls.size() == 2) { - fnName = QIR_CCRX; - } else if (controls.size() == 3) { - fnName = QIR_CCCRX; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetOneParameter(theta, controls, target, fnName); - return *this; -} - -// RYOp - -QIRProgramBuilder& -QIRProgramBuilder::ry(const std::variant& theta, - const Value qubit) { - createOneTargetOneParameter(theta, {}, qubit, QIR_RY); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::cry(const std::variant& theta, - const Value control, const Value target) { - createOneTargetOneParameter(theta, {control}, target, QIR_CRY); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::mcry(const std::variant& theta, - const ValueRange controls, const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CRY; - } else if (controls.size() == 2) { - fnName = QIR_CCRY; - } else if (controls.size() == 3) { - fnName = QIR_CCCRY; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetOneParameter(theta, controls, target, fnName); - return *this; -} - -// RZOp - -QIRProgramBuilder& -QIRProgramBuilder::rz(const std::variant& theta, - const Value qubit) { - createOneTargetOneParameter(theta, {}, qubit, QIR_RZ); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::crz(const std::variant& theta, - const Value control, const Value target) { - createOneTargetOneParameter(theta, {control}, target, QIR_CRZ); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::mcrz(const std::variant& theta, - const ValueRange controls, const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CRZ; - } else if (controls.size() == 2) { - fnName = QIR_CCRZ; - } else if (controls.size() == 3) { - fnName = QIR_CCCRZ; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetOneParameter(theta, controls, target, fnName); - return *this; -} - -// POp - -QIRProgramBuilder& -QIRProgramBuilder::p(const std::variant& theta, - const Value qubit) { - createOneTargetOneParameter(theta, {}, qubit, QIR_P); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::cp(const std::variant& theta, - const Value control, const Value target) { - createOneTargetOneParameter(theta, {control}, target, QIR_CP); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::mcp(const std::variant& theta, - const ValueRange controls, const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CP; - } else if (controls.size() == 2) { - fnName = QIR_CCP; - } else if (controls.size() == 3) { - fnName = QIR_CCCP; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetOneParameter(theta, controls, target, fnName); - return *this; -} - -// ROp - -QIRProgramBuilder& -QIRProgramBuilder::r(const std::variant& theta, - const std::variant& phi, - const Value qubit) { - createOneTargetTwoParameter(theta, phi, {}, qubit, QIR_R); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::cr(const std::variant& theta, - const std::variant& phi, - const Value control, const Value target) { - createOneTargetTwoParameter(theta, phi, {control}, target, QIR_CR); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::mcr(const std::variant& theta, - const std::variant& phi, - const ValueRange controls, const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CR; - } else if (controls.size() == 2) { - fnName = QIR_CCR; - } else if (controls.size() == 3) { - fnName = QIR_CCCR; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetTwoParameter(theta, phi, controls, target, fnName); - return *this; -} - -// U2Op - -QIRProgramBuilder& -QIRProgramBuilder::u2(const std::variant& phi, - const std::variant& lambda, - const Value qubit) { - createOneTargetTwoParameter(phi, lambda, {}, qubit, QIR_U2); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::cu2(const std::variant& phi, - const std::variant& lambda, - const Value control, const Value target) { - createOneTargetTwoParameter(phi, lambda, {control}, target, QIR_CU2); - return *this; -} - -QIRProgramBuilder& -QIRProgramBuilder::mcu2(const std::variant& phi, - const std::variant& lambda, - const ValueRange controls, const Value target) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CU2; - } else if (controls.size() == 2) { - fnName = QIR_CCU2; - } else if (controls.size() == 3) { - fnName = QIR_CCCU2; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createOneTargetTwoParameter(phi, lambda, controls, target, fnName); - return *this; -} - -// SWAPOp - -QIRProgramBuilder& QIRProgramBuilder::swap(const Value qubit0, - const Value qubit1) { - createTwoTargetZeroParameter({}, qubit0, qubit1, QIR_SWAP); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::cswap(const Value control, - const Value target0, - const Value target1) { - createTwoTargetZeroParameter({control}, target0, target1, QIR_CSWAP); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mcswap(const ValueRange controls, - const Value target0, - const Value target1) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CSWAP; - } else if (controls.size() == 2) { - fnName = QIR_CCSWAP; - } else if (controls.size() == 3) { - fnName = QIR_CCCSWAP; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createTwoTargetZeroParameter(controls, target0, target1, fnName); - return *this; -} - -// iSWAPOp - -QIRProgramBuilder& QIRProgramBuilder::iswap(const Value qubit0, - const Value qubit1) { - createTwoTargetZeroParameter({}, qubit0, qubit1, QIR_ISWAP); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::ciswap(const Value control, - const Value target0, - const Value target1) { - createTwoTargetZeroParameter({control}, target0, target1, QIR_CISWAP); - return *this; -} - -QIRProgramBuilder& QIRProgramBuilder::mciswap(const ValueRange controls, - const Value target0, - const Value target1) { - StringRef fnName; - if (controls.size() == 1) { - fnName = QIR_CISWAP; - } else if (controls.size() == 2) { - fnName = QIR_CCISWAP; - } else if (controls.size() == 3) { - fnName = QIR_CCCISWAP; - } else { - llvm::report_fatal_error("Multi-controlled with more than 3 controls are " - "currently not supported"); - } - createTwoTargetZeroParameter(controls, target0, target1, fnName); - return *this; -} +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(const Value qubit) { \ + createOneTargetZeroParameter({}, qubit, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL(const Value control, \ + const Value target) { \ + createOneTargetZeroParameter({control}, target, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const ValueRange controls, const Value target) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createOneTargetZeroParameter(controls, target, fnName); \ + return *this; \ + } + +DEFINE_ONE_TARGET_ZERO_PARAMETER(ID, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(X, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(H, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(S, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SDG, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(T, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TDG, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SX, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM), const Value qubit) { \ + createOneTargetOneParameter(PARAM, {}, qubit, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM), const Value control, \ + const Value target) { \ + createOneTargetOneParameter(PARAM, {control}, target, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM), const ValueRange controls, \ + const Value target) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createOneTargetOneParameter(PARAM, controls, target, fnName); \ + return *this; \ + } + +DEFINE_ONE_TARGET_ONE_PARAMETER(RX, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RY, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZ, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM1, \ + PARAM2) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value qubit) { \ + createOneTargetTwoParameter(PARAM1, PARAM2, {}, qubit, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value control, \ + const Value target) { \ + createOneTargetTwoParameter(PARAM1, PARAM2, {control}, target, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const ValueRange controls, \ + const Value target) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createOneTargetTwoParameter(PARAM1, PARAM2, controls, target, fnName); \ + return *this; \ + } + +DEFINE_ONE_TARGET_TWO_PARAMETER(R, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(const Value target0, \ + const Value target1) { \ + createTwoTargetZeroParameter({}, target0, target1, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const Value control, const Value target0, const Value target1) { \ + createTwoTargetZeroParameter({control}, target0, target1, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const ValueRange controls, const Value target0, const Value target1) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createTwoTargetZeroParameter(controls, target0, target1, fnName); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAP, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ISWAP, iswap) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER //===----------------------------------------------------------------------===// // Finalization diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 2b056102d6..28d3581293 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -160,16 +160,17 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ - const std::variant& PARAM, Value qubit) { \ + const std::variant&(PARAM), Value qubit) { \ create(loc, qubit, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ - const std::variant& PARAM, Value control, Value target) { \ + const std::variant&(PARAM), Value control, \ + Value target) { \ return mc##OP_NAME(PARAM, {control}, target); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ - const std::variant& PARAM, ValueRange controls, \ + const std::variant&(PARAM), ValueRange controls, \ Value target) { \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, target, PARAM); \ @@ -188,20 +189,20 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ - const std::variant& PARAM1, \ - const std::variant& PARAM2, Value qubit) { \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit) { \ create(loc, qubit, PARAM1, PARAM2); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ - const std::variant& PARAM1, \ - const std::variant& PARAM2, Value control, \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ Value target) { \ return mc##OP_NAME(PARAM1, PARAM2, {control}, target); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ - const std::variant& PARAM1, \ - const std::variant& PARAM2, ValueRange controls, \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ Value target) { \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, target, PARAM1, PARAM2); \ From af9de492c5ed15b917ccffe8432b0e481da4699d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:33:28 +0100 Subject: [PATCH 241/419] Use macros for converters --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 556 ++------ .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 589 +++------ .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 1136 ++++------------- 3 files changed, 544 insertions(+), 1737 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index c19f9efb65..a20a4edf9b 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -342,436 +342,142 @@ struct ConvertFluxResetOp final : OpConversionPattern { } }; -/** - * @brief Converts flux.id to quartz.id - * - * @par Example: - * ```mlir - * %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.id %q : !quartz.qubit - * ``` - */ -struct ConvertFluxIdOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::IdOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.x to quartz.x - * - * @par Example: - * ```mlir - * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.x %q : !quartz.qubit - * ``` - */ -struct ConvertFluxXOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::XOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.y to quartz.y - * - * @par Example: - * ```mlir - * %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.y %q : !quartz.qubit - * ``` - */ -struct ConvertFluxYOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::YOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.z to quartz.z - * - * @par Example: - * ```mlir - * %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.z %q : !quartz.qubit - * ``` - */ -struct ConvertFluxZOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::ZOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.h to quartz.h - * - * @par Example: - * ```mlir - * %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.h %q : !quartz.qubit - * ``` - */ -struct ConvertFluxHOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::HOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.s to quartz.s - * - * @par Example: - * ```mlir - * %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.s %q : !quartz.qubit - * ``` - */ -struct ConvertFluxSOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::SOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.sdg to quartz.sdg - * - * @par Example: - * ```mlir - * %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.sdg %q : !quartz.qubit - * ``` - */ -struct ConvertFluxSdgOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::SdgOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.t to quartz.t - * - * @par Example: - * ```mlir - * %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.t %q : !quartz.qubit - * ``` - */ -struct ConvertFluxTOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::TOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.tdg to quartz.tdg - * - * @par Example: - * ```mlir - * %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.tdg %q : !quartz.qubit - * ``` - */ -struct ConvertFluxTdgOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::TdgOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.sx to quartz.sx - * - * @par Example: - * ```mlir - * %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.sx %q : !quartz.qubit - * ``` - */ -struct ConvertFluxSXOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::SXOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.sxdg to quartz.sxdg - * - * @par Example: - * ```mlir - * %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.sxdg %q : !quartz.qubit - * ``` - */ -struct ConvertFluxSXdgOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::SXdgOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.rx to quartz.rx - * - * @par Example: - * ```mlir - * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.rx(%theta) %q : !quartz.qubit - * ``` - */ -struct ConvertFluxRXOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::RXOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.ry to quartz.ry - * - * @par Example: - * ```mlir - * %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.ry(%theta) %q : !quartz.qubit - * ``` - */ -struct ConvertFluxRYOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::RYOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.rz to quartz.rz - * - * @par Example: - * ```mlir - * %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.rz(%theta) %q : !quartz.qubit - * ``` - */ -struct ConvertFluxRZOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::RZOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.p to quartz.p - * - * @par Example: - * ```mlir - * %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.p(%theta) %q : !quartz.qubit - * ``` - */ -struct ConvertFluxPOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::POp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.r to quartz.r - * - * @par Example: - * ```mlir - * %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.r(%theta, %phi) %q : !quartz.qubit - * ``` - */ -struct ConvertFluxROp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::ROp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetTwoParameter(op, adaptor, rewriter); - } -}; - -/** - * @brief Converts flux.u2 to quartz.u2 - * - * @par Example: - * ```mlir - * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.u2(%phi, %lambda) %q : !quartz.qubit - * ``` - */ -struct ConvertFluxU2Op final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(flux::U2Op op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); - - // Create quartz.u2 (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit, adaptor.getPhi(), - adaptor.getLambda()); - - // Replace the output qubit with the same quartz reference - rewriter.replaceOp(op, quartzQubit); +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = flux.OP_NAME %q_in : !flux.qubit -> !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME %q : !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetZeroParameter(op, adaptor, \ + rewriter); \ + } \ + }; - return success(); - } -}; +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) %q : !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetOneParameter(op, adaptor, \ + rewriter); \ + } \ + }; -/** - * @brief Converts flux.swap to quartz.swap - * - * @par Example: - * ```mlir - * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit - * ``` - */ -struct ConvertFluxSWAPOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM1, %PARAM2) %q_in : !flux.qubit -> \ + * !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetTwoParameter(op, adaptor, \ + rewriter); \ + } \ + }; - LogicalResult - matchAndRewrite(flux::SWAPOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertTwoTargetZeroParameter(op, adaptor, rewriter); - } -}; +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME %q0_in, %q1_in : !flux.qubit, !flux.qubit \ + * -> !flux.qubit, !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetZeroParameter(op, adaptor, \ + rewriter); \ + } \ + }; -/** - * @brief Converts flux.iswap to quartz.iswap - * - * @par Example: - * ```mlir - * %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit - * ``` - * is converted to - * ```mlir - * quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit - * ``` - */ -struct ConvertFluxiSWAPOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) - LogicalResult - matchAndRewrite(flux::iSWAPOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - return convertTwoTargetZeroParameter(op, adaptor, - rewriter); - } -}; +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER /** * @brief Converts quartz.ctrl to flux.ctrl diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 50335c2962..610b6767dc 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -538,429 +538,146 @@ struct ConvertQuartzResetOp final } }; -/** - * @brief Converts quartz.id to flux.id - * - * @par Example: - * ```mlir - * quartz.id %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzIdOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::IdOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.x to flux.x - * - * @par Example: - * ```mlir - * quartz.x %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzXOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::XOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.y to flux.y - * - * @par Example: - * ```mlir - * quartz.y %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzYOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::YOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.z to flux.z - * - * @par Example: - * ```mlir - * quartz.z %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzZOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::ZOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.h to flux.h - * - * @par Example: - * ```mlir - * quartz.h %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzHOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::HOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.s to flux.s - * - * @par Example: - * ```mlir - * quartz.s %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzSOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::SOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.sdg to flux.sdg - * - * @par Example: - * ```mlir - * quartz.sdg %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzSdgOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::SdgOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.t to flux.t - * - * @par Example: - * ```mlir - * quartz.t %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzTOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::TOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.tdg to flux.tdg - * - * @par Example: - * ```mlir - * quartz.tdg %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzTdgOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::TdgOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.sx to flux.sx - * - * @par Example: - * ```mlir - * quartz.sx %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzSXOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::SXOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.sxdg to flux.sxdg - * - * @par Example: - * ```mlir - * quartz.sxdg %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzSXdgOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::SXdgOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetZeroParameter(op, rewriter, - getState()); - } -}; - -/** - * @brief Converts quartz.rx to flux.rx - * - * @par Example: - * ```mlir - * quartz.rx(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzRXOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::RXOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.ry to flux.ry - * - * @par Example: - * ```mlir - * quartz.ry(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzRYOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::RYOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.rz to flux.rz - * - * @par Example: - * ```mlir - * quartz.rz(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzRZOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::RZOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.p to flux.p - * - * @par Example: - * ```mlir - * quartz.p(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzPOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::POp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetOneParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.r to flux.r - * - * @par Example: - * ```mlir - * quartz.r(%theta, %phi) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzROp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(quartz::ROp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetTwoParameter(op, rewriter, getState()); - } -}; - -/** - * @brief Converts quartz.u2 to flux.u2 - * - * @par Example: - * ```mlir - * quartz.u2(%phi, %lambda) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ -struct ConvertQuartzU2Op final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = flux.OP_NAME %q_in : !flux.qubit -> !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetZeroParameter(op, rewriter, \ + getState()); \ + } \ + }; - LogicalResult - matchAndRewrite(quartz::U2Op op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertOneTargetTwoParameter(op, rewriter, getState()); - } -}; +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetOneParameter(op, rewriter, \ + getState()); \ + } \ + }; -/** - * @brief Converts quartz.swap to flux.swap - * - * @par Example: - * ```mlir - * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit - * ``` - */ -struct ConvertQuartzSWAPOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM1, %PARAM2) %q_in : !flux.qubit -> \ + * !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetTwoParameter(op, rewriter, \ + getState()); \ + } \ + }; - LogicalResult - matchAndRewrite(quartz::SWAPOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertTwoTargetZeroParameter(op, rewriter, - getState()); - } -}; +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME %q0_in, %q1_in : !flux.qubit, !flux.qubit \ + * -> !flux.qubit, !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetZeroParameter(op, rewriter, \ + getState()); \ + } \ + }; -/** - * @brief Converts quartz.iswap to flux.iswap - * - * @par Example: - * ```mlir - * quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit - * ``` - * is converted to - * ```mlir - * %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit - * ``` - */ -struct ConvertQuartziSWAPOp final - : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) - LogicalResult - matchAndRewrite(quartz::iSWAPOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - return convertTwoTargetZeroParameter(op, rewriter, - getState()); - } -}; +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER /** * @brief Converts quartz.ctrl to flux.ctrl @@ -1069,20 +786,24 @@ struct ConvertQuartzYieldOp final * @brief Pass implementation for Quartz-to-Flux conversion * * @details - * This pass converts Quartz dialect operations (reference semantics) to - * Flux dialect operations (value semantics). The conversion is essential - * for enabling optimization passes that rely on SSA form and explicit - * dataflow analysis. + * This pass converts Quartz dialect operations (reference + * semantics) to Flux dialect operations (value semantics). + * The conversion is essential for enabling optimization + * passes that rely on SSA form and explicit dataflow + * analysis. * * The pass operates in several phases: * 1. Type conversion: !quartz.qubit -> !flux.qubit - * 2. Operation conversion: Each Quartz op is converted to its Flux equivalent - * 3. State tracking: A LoweringState maintains qubit value mappings - * 4. Function/control-flow adaptation: Function signatures and control flow - * are updated to use Flux types + * 2. Operation conversion: Each Quartz op is converted to + * its Flux equivalent + * 3. State tracking: A LoweringState maintains qubit value + * mappings + * 4. Function/control-flow adaptation: Function signatures + * and control flow are updated to use Flux types * - * The conversion maintains semantic equivalence while transforming the - * representation from imperative (mutation-based) to functional (SSA-based). + * The conversion maintains semantic equivalence while + * transforming the representation from imperative + * (mutation-based) to functional (SSA-based). */ struct QuartzToFlux final : impl::QuartzToFluxBase { using QuartzToFluxBase::QuartzToFluxBase; @@ -1098,11 +819,13 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { RewritePatternSet patterns(context); QuartzToFluxTypeConverter typeConverter(context); - // Configure conversion target: Quartz illegal, Flux legal + // Configure conversion target: Quartz illegal, Flux + // legal target.addIllegalDialect(); target.addLegalDialect(); - // Register operation conversion patterns with state tracking + // Register operation conversion patterns with state + // tracking patterns.add { ConvertQuartzYieldOp>(typeConverter, context, &state); // Conversion of quartz types in func.func signatures - // Note: This currently has limitations with signature changes + // Note: This currently has limitations with signature + // changes populateFunctionOpInterfaceTypeConversionPattern( patterns, typeConverter); target.addDynamicallyLegalOp([&](func::FuncOp op) { @@ -1133,7 +857,8 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { target.addDynamicallyLegalOp( [&](const func::CallOp op) { return typeConverter.isLegal(op); }); - // Conversion of quartz types in control-flow ops (e.g., cf.br, cf.cond_br) + // Conversion of quartz types in control-flow ops (e.g., + // cf.br, cf.cond_br) populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); // Apply the conversion diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index ae69d61d13..64de81ee86 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -666,866 +666,243 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } }; -/** - * @brief Converts quartz.id operation to QIR i - * - * @par Example: - * ```mlir - * quartz.id %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__i__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzIdQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(IdOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const auto& posCtrls = state.posCtrls; - const auto numCtrls = posCtrls.size(); - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_ID; - } else { - if (numCtrls == 1) { - fnName = QIR_CID; - ; - } else if (numCtrls == 2) { - fnName = QIR_CCID; - } else if (numCtrls == 3) { - fnName = QIR_CCCID; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.x operation to QIR x - * - * @par Example: - * ```mlir - * quartz.x %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzXQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(XOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_X; - } else { - if (numCtrls == 1) { - fnName = QIR_CX; - } else if (numCtrls == 2) { - fnName = QIR_CCX; - } else if (numCtrls == 3) { - fnName = QIR_CCCX; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.y operation to QIR y - * - * @par Example: - * ```mlir - * quartz.y %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__y__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzYQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(YOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_Y; - } else { - if (numCtrls == 1) { - fnName = QIR_CY; - } else if (numCtrls == 2) { - fnName = QIR_CCY; - } else if (numCtrls == 3) { - fnName = QIR_CCCY; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.z operation to QIR z - * - * @par Example: - * ```mlir - * quartz.z %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__z__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzZQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(ZOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_Z; - } else { - if (numCtrls == 1) { - fnName = QIR_CZ; - } else if (numCtrls == 2) { - fnName = QIR_CCZ; - } else if (numCtrls == 3) { - fnName = QIR_CCCZ; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.h operation to QIR h - * - * @par Example: - * ```mlir - * quartz.h %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__h__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzHQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(HOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_H; - } else { - if (numCtrls == 1) { - fnName = QIR_CH; - } else if (numCtrls == 2) { - fnName = QIR_CCH; - } else if (numCtrls == 3) { - fnName = QIR_CCCH; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.s operation to QIR s - * - * @par Example: - * ```mlir - * quartz.s %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__s__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzSQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(SOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_S; - } else { - if (numCtrls == 1) { - fnName = QIR_CS; - } else if (numCtrls == 2) { - fnName = QIR_CCS; - } else if (numCtrls == 3) { - fnName = QIR_CCCS; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.sdg operation to QIR sdg - * - * @par Example: - * ```mlir - * quartz.sdg %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__sdg__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzSdgQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(SdgOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_SDG; - } else { - if (numCtrls == 1) { - fnName = QIR_CSDG; - } else if (numCtrls == 2) { - fnName = QIR_CCSDG; - } else if (numCtrls == 3) { - fnName = QIR_CCCSDG; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.t operation to QIR t - * - * @par Example: - * ```mlir - * quartz.t %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__t__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzTQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(TOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_T; - } else { - if (numCtrls == 1) { - fnName = QIR_CT; - } else if (numCtrls == 2) { - fnName = QIR_CCT; - } else if (numCtrls == 3) { - fnName = QIR_CCCT; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.tdg operation to QIR tdg - * - * @par Example: - * ```mlir - * quartz.tdg %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__tdg__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzTdgQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(TdgOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_TDG; - } else { - if (numCtrls == 1) { - fnName = QIR_CTDG; - } else if (numCtrls == 2) { - fnName = QIR_CCTDG; - } else if (numCtrls == 3) { - fnName = QIR_CCCTDG; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.sx operation to QIR sx - * - * @par Example: - * ```mlir - * quartz.sx %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__sx__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzSXQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(SXOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_SX; - } else { - if (numCtrls == 1) { - fnName = QIR_CSX; - } else if (numCtrls == 2) { - fnName = QIR_CCSX; - } else if (numCtrls == 3) { - fnName = QIR_CCCSX; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.sxdg operation to QIR sxdg - * - * @par Example: - * ```mlir - * quartz.sxdg %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__sxdg__body(%q) : (!llvm.ptr) -> () - * ``` - */ -struct ConvertQuartzSXdgQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(SXdgOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_SXDG; - } else { - if (numCtrls == 1) { - fnName = QIR_CSXDG; - } else if (numCtrls == 2) { - fnName = QIR_CCSXDG; - } else if (numCtrls == 3) { - fnName = QIR_CCCSXDG; - } else { - return failure(); - } - } - - return convertOneTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.rx operation to QIR rx - * - * @par Example: - * ```mlir - * quartz.rx(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__rx__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ -struct ConvertQuartzRXQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(RXOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_RX; - } else { - if (numCtrls == 1) { - fnName = QIR_CRX; - } else if (numCtrls == 2) { - fnName = QIR_CCRX; - } else if (numCtrls == 3) { - fnName = QIR_CCCRX; - } else { - return failure(); - } - } - - return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.ry operation to QIR ry - * - * @par Example: - * ```mlir - * quartz.ry(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__ry__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ -struct ConvertQuartzRYQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(RYOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_RY; - } else { - if (numCtrls == 1) { - fnName = QIR_CRY; - } else if (numCtrls == 2) { - fnName = QIR_CCRY; - } else if (numCtrls == 3) { - fnName = QIR_CCCRY; - } else { - return failure(); - } - } - - return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.rz operation to QIR rz - * - * @par Example: - * ```mlir - * quartz.rz(%theta) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__rz__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ -struct ConvertQuartzRZQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(RZOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_RZ; - } else { - if (numCtrls == 1) { - fnName = QIR_CRZ; - } else if (numCtrls == 2) { - fnName = QIR_CCRZ; - } else if (numCtrls == 3) { - fnName = QIR_CCCRZ; - } else { - return failure(); - } - } - - return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.p operation to QIR p - * - * @par Example: - * ```mlir - * quartz.p(%lambda) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__p__body(%q, %lambda) : (!llvm.ptr, f64) -> () - * ``` - */ -struct ConvertQuartzPQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(POp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_P; - } else { - if (numCtrls == 1) { - fnName = QIR_CP; - } else if (numCtrls == 2) { - fnName = QIR_CCP; - } else if (numCtrls == 3) { - fnName = QIR_CCCP; - } else { - return failure(); - } - } - - return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.r operation to QIR r - * - * @par Example: - * ```mlir - * quartz.r(%theta, %phi) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__r__body(%q, %theta, %phi) : (!llvm.ptr, f64, f64) - * -> () - * ``` - */ -struct ConvertQuartzRQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(ROp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_R; - } else { - if (numCtrls == 1) { - fnName = QIR_CR; - } else if (numCtrls == 2) { - fnName = QIR_CCR; - } else if (numCtrls == 3) { - fnName = QIR_CCCR; - } else { - return failure(); - } - } - - return convertOneTargetTwoParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.u2 operation to QIR u2 - * - * @par Example: - * ```mlir - * quartz.u2(%phi, %lambda) %q : !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__u2__body(%q, %phi, %lambda) : (!llvm.ptr, f64, - * f64) -> () - * ``` - */ -struct ConvertQuartzU2QIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(U2Op op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_U2; - } else { - if (numCtrls == 1) { - fnName = QIR_CU2; - } else if (numCtrls == 2) { - fnName = QIR_CCU2; - } else if (numCtrls == 3) { - fnName = QIR_CCCU2; - } else { - return failure(); - } - } - - return convertOneTargetTwoParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.swap operation to QIR swap - * - * @par Example: - * ```mlir - * quartz.swap %q1, %q2 : !quartz.qubit, !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__swap__body(%q1, %q2) : (!llvm.ptr, !llvm.ptr) -> - * () - * ``` - */ -struct ConvertQuartzSWAPQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(SWAPOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_SWAP; - } else { - if (numCtrls == 1) { - fnName = QIR_CSWAP; - } else if (numCtrls == 2) { - fnName = QIR_CCSWAP; - } else if (numCtrls == 3) { - fnName = QIR_CCCSWAP; - } else { - return failure(); - } - } - - return convertTwoTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; - -/** - * @brief Converts quartz.iswap operation to QIR iswap - * - * @par Example: - * ```mlir - * quartz.iswap %q1, %q2 : !quartz.qubit, !quartz.qubit - * ``` - * is converted to - * ```mlir - * llvm.call @__quantum__qis__iswap__body(%q1, %q2) : (!llvm.ptr, !llvm.ptr) - * -> () - * ``` - */ -struct ConvertQuartziSWAPQIR final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(iSWAPOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; - - // Define function name - StringRef fnName; - if (inCtrlOp == 0) { - fnName = QIR_ISWAP; - } else { - if (numCtrls == 1) { - fnName = QIR_CISWAP; - } else if (numCtrls == 2) { - fnName = QIR_CCISWAP; - } else if (numCtrls == 3) { - fnName = QIR_CCCISWAP; - } else { - return failure(); - } - } - - return convertTwoTargetZeroParameter(op, adaptor, rewriter, getContext(), - state, fnName); - } -}; +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ + QIR_NAME) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q) : (!llvm.ptr) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertOneTargetZeroParameter(op, adaptor, rewriter, \ + getContext(), state, fnName); \ + } \ + }; + +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id, ID, i) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x, X, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y, Y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z, Z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h, H, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s, S, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg, SDG, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t, T, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg, TDG, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx, SX, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg, SXDG, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ + QIR_NAME, PARAM) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL(%PARAM) %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q, %PARAM) : (!llvm.ptr, f64) \ + * -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), \ + state, fnName); \ + } \ + }; + +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, RX, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, RY, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, RZ, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, P, p, theta) + +#undef DEFINE_ONE_TARGET_ONE_PARAMETER + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ + QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q, %PARAM1, %PARAM2) : \ + * (!llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertOneTargetTwoParameter(op, adaptor, rewriter, getContext(), \ + state, fnName); \ + } \ + }; + +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, R, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, U2, u2, phi, lambda) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ + QIR_NAME) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q1, %q2) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertTwoTargetZeroParameter(op, adaptor, rewriter, \ + getContext(), state, fnName); \ + } \ + }; + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap, SWAP, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap, ISWAP, iswap) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER /** * @brief Inlines quartz.ctrl region removes the operation @@ -1893,28 +1270,27 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); - // Gate operations will be added here as the dialect expands if (applyPartialConversion(moduleOp, target, std::move(quartzPatterns)) From cff1d62bb164e71674faf465cd49eb8ccf4f13ff Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 00:50:25 +0100 Subject: [PATCH 242/419] Use macros for builder header files --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 1560 ++++------------- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 1296 +++----------- .../Quartz/Builder/QuartzProgramBuilder.h | 1336 +++----------- 3 files changed, 875 insertions(+), 3317 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 7817a4304d..c4072b35e9 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -240,1221 +240,351 @@ class FluxProgramBuilder final : public OpBuilder { // Unitary Operations //===--------------------------------------------------------------------===// - /** - * @brief Apply an Id gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.id(q_in); - * ``` - * ```mlir - * %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value id(Value qubit); - - /** - * @brief Apply a controlled Id gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cid(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.id %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cid(Value control, Value target); - - /** - * @brief Apply a multi-controlled Id gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcid({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.id %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcid(ValueRange controls, Value target); - - /** - * @brief Apply an X gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.x(q_in); - * ``` - * ```mlir - * %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value x(Value qubit); - - /** - * @brief Apply a controlled X gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cx(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cx(Value control, Value target); - - /** - * @brief Apply a multi-controlled X gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcx({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.x %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcx(ValueRange controls, Value target); - - /** - * @brief Apply a Y gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.y(q_in); - * ``` - * ```mlir - * %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value y(Value qubit); - - /** - * @brief Apply a controlled Y gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cy(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.y %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cy(Value control, Value target); - - /** - * @brief Apply a multi-controlled Y gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcy({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.y %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcy(ValueRange controls, Value target); - - /** - * @brief Apply a Z gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.z(q_in); - * ``` - * ```mlir - * %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value z(Value qubit); - - /** - * @brief Apply a controlled Z gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cz(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.z %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cz(Value control, Value target); - - /** - * @brief Apply a multi-controlled Z gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcz({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.z %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcz(ValueRange controls, Value target); - - /** - * @brief Apply an H gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.h(q_in); - * ``` - * ```mlir - * %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value h(Value qubit); - - /** - * @brief Apply a controlled H gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.ch(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.h %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair ch(Value control, Value target); - - /** - * @brief Apply a multi-controlled H gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mch({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.h %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mch(ValueRange controls, Value target); - - /** - * @brief Apply an S gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.s(q_in); - * ``` - * ```mlir - * %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value s(Value qubit); - - /** - * @brief Apply a controlled S gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cs(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.s %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cs(Value control, Value target); - - /** - * @brief Apply a multi-controlled S gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcs({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.s %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcs(ValueRange controls, Value target); - - /** - * @brief Apply a T gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.t(q_in); - * ``` - * ```mlir - * %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value t(Value qubit); - - /** - * @brief Apply a controlled T gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.ct(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.t %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair ct(Value control, Value target); - - /** - * @brief Apply a multi-controlled T gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mct({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.t %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mct(ValueRange controls, Value target); - - /** - * @brief Apply a Tdg gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.tdg(q_in); - * ``` - * ```mlir - * %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value tdg(Value qubit); - - /** - * @brief Apply a controlled Tdg gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.ctdg(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.tdg %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair ctdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled Tdg gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mctdg({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.tdg %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mctdg(ValueRange controls, Value target); - - /** - * @brief Apply an SX gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.sx(q_in); - * ``` - * ```mlir - * %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value sx(Value qubit); - - /** - * @brief Apply a controlled SX gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.csx(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.sx %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair csx(Value control, Value target); - - /** - * @brief Apply a multi-controlled SX gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcsx({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.sx %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcsx(ValueRange controls, Value target); - - /** - * @brief Apply an SXdg gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.sxdg(q_in); - * ``` - * ```mlir - * %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value sxdg(Value qubit); - - /** - * @brief Apply a controlled SXdg gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.csxdg(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.sxdg %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair csxdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled SXdg gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcsxdg({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.sxdg %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcsxdg(ValueRange controls, Value target); - - /** - * @brief Apply an Sdg gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.sdg(q_in); - * ``` - * ```mlir - * %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value sdg(Value qubit); - - /** - * @brief Apply a controlled Sdg gate - * - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.csdg(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.sdg %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair csdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled Sdg gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcsdg({q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.sdg %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcsdg(ValueRange controls, Value target); - - /** - * @brief Apply an RX gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param theta Rotation angle in radians - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.rx(1.0, q_in); - * ``` - * ```mlir - * %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value rx(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled RX gate - * - * @param theta Rotation angle in radians - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.crx(1.0, q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.rx(%theta) %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair crx(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RX gate - * - * @param theta Rotation angle in radians - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcrx(1.0, {q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.rx(%theta) %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcrx(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an RY gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param theta Rotation angle in radians - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.ry(1.0, q_in); - * ``` - * ```mlir - * %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value ry(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled RY gate - * - * @param theta Rotation angle in radians - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cry(1.0, q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.ry(%theta) %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cry(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RY gate - * - * @param theta Rotation angle in radians - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcry(1.0, {q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.ry(%theta) %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcry(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an RZ gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param theta Rotation angle in radians - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.rz(1.0, q_in); - * ``` - * ```mlir - * %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value rz(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled RZ gate - * - * @param theta Rotation angle in radians - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.crz(1.0, q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.rz(%theta) %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair crz(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RZ gate - * - * @param theta Rotation angle in radians - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcrz(1.0, {q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.rz(%theta) %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcrz(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply a P gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param theta Rotation angle in radians - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.p(1.0, q_in); - * ``` - * ```mlir - * %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value p(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled P gate - * - * @param theta Rotation angle in radians - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cp(1.0, q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.p(%theta) %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cp(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled P gate - * - * @param theta Rotation angle in radians - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcp(1.0, {q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.p(%theta) %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcp(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an R gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param theta Rotation angle in radians - * @param phi Rotation angle in radians - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.r(1.0, 0.5, q_in); - * ``` - * ```mlir - * %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value r(const std::variant& theta, - const std::variant& phi, Value qubit); - - /** - * @brief Apply a controlled R gate - * - * @param theta Rotation angle in radians - * @param phi Rotation angle in radians - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cr(1.0, 0.5, q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.r(%theta, %phi) %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cr(const std::variant& theta, - const std::variant& phi, - Value control, Value target); - - /** - * @brief Apply a multi-controlled R gate - * - * @param theta Rotation angle in radians - * @param phi Rotation angle in radians - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcr(1.0, 0.5, {q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.r(%theta, %phi) %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcr(const std::variant& theta, - const std::variant& phi, - ValueRange controls, Value target); - - /** - * @brief Apply a U2 gate to a qubit - * - * @details - * Consumes the input qubit and produces a new output qubit SSA value. - * The input is validated and the tracking is updated. - * - * @param phi Rotation angle in radians - * @param lambda Rotation angle in radians - * @param qubit Input qubit (must be valid/unconsumed) - * @return Output qubit - * - * @par Example: - * ```c++ - * q_out = builder.u2(1.0, 0.5, q_in); - * ``` - * ```mlir - * %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit - * ``` - */ - Value u2(const std::variant& phi, - const std::variant& lambda, Value qubit); - - /** - * @brief Apply a controlled U2 gate - * - * @param phi Rotation angle in radians - * @param lambda Rotation angle in radians - * @param control Input control qubit (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, output_target_qubit) - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.cu2(1.0, 0.5, q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.u2(%phi, %lambda) %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair cu2(const std::variant& phi, - const std::variant& lambda, - Value control, Value target); - - /** - * @brief Apply a multi-controlled U2 gate - * - * @param phi Rotation angle in radians - * @param lambda Rotation angle in radians - * @param controls Input control qubits (must be valid/unconsumed) - * @param target Input target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, output_target_qubit) - * - * @par Example: - * ```c++ - * {controls_out, target_out} = builder.mcu2(1.0, 0.5, {q0_in, q1_in}, q2_in); - * ``` - * ```mlir - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { - * %q2_res = flux.u2(%phi, %lambda) %q2_in : !flux.qubit -> !flux.qubit - * flux.yield %q2_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, - * !flux.qubit}, {!flux.qubit}) - * ``` - */ - std::pair mcu2(const std::variant& phi, - const std::variant& lambda, - ValueRange controls, Value target); - - /** - * @brief Apply a SWAP gate to two qubits - * - * @details - * Consumes the input qubits and produces new output qubit SSA values. - * The inputs are validated and the tracking is updated. - * - * @param qubit0 Input qubit (must be valid/unconsumed) - * @param qubit1 Input qubit (must be valid/unconsumed) - * @return Output qubits - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.swap(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit - * ``` - */ - std::pair swap(Value qubit0, Value qubit1); - - /** - * @brief Apply a controlled SWAP gate - * @param control Input control qubit (must be valid/unconsumed) - * @param qubit0 Target qubit (must be valid/unconsumed) - * @param qubit1 Target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) - * - * @par Example: - * ```c++ - * {q0_out, {q1_out, q2_out}} = builder.cswap(q0_in, q1_in, q2_in); - * ``` - * ```mlir - * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { - * %q1_res, %q2_res = flux.swap %q1_in, %q2_in : !flux.qubit, - * !flux.qubit -> !flux.qubit, !flux.qubit - * flux.yield %q1_res, %q2_res - * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, - * !flux.qubit - * ``` - */ - std::pair> cswap(Value control, Value qubit0, - Value qubit1); - - /** - * @brief Apply a multi-controlled SWAP gate - * @param controls Input control qubits (must be valid/unconsumed) - * @param qubit0 Target qubit (must be valid/unconsumed) - * @param qubit1 Target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) - * - * @par Example: - * ```c++ - * {controls_out, {q1_out, q2_out}} = builder.mcswap({q0_in, q1_in}, q2_in, - * q3_in); - * ``` - * ```mlir - * %controls_out, %q2_out, %q3_out = flux.ctrl(%q0_in, %q1_in) %q2_in, %q3_in - * { %q2_res, %q3_res = flux.swap %q2_in, %q3_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit flux.yield %q2_res, %q3_res - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> - * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) - * ``` - */ - std::pair> - mcswap(ValueRange controls, Value qubit0, Value qubit1); - - /** - * @brief Apply an iSWAP gate to two qubits - * - * @details - * Consumes the input qubits and produces new output qubit SSA values. - * The inputs are validated and the tracking is updated. - * - * @param qubit0 Input qubit (must be valid/unconsumed) - * @param qubit1 Input qubit (must be valid/unconsumed) - * @return Output qubits - * - * @par Example: - * ```c++ - * {q0_out, q1_out} = builder.iswap(q0_in, q1_in); - * ``` - * ```mlir - * %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit - * -> !flux.qubit, !flux.qubit - * ``` - */ - std::pair iswap(Value qubit0, Value qubit1); - - /** - * @brief Apply a controlled iSWAP gate - * @param control Input control qubit (must be valid/unconsumed) - * @param qubit0 Target qubit (must be valid/unconsumed) - * @param qubit1 Target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) - * - * @par Example: - * ```c++ - * {q0_out, {q1_out, q2_out}} = builder.ciswap(q0_in, q1_in, q2_in); - * ``` - * ```mlir - * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { - * %q1_res, %q2_res = flux.iswap %q1_in, %q2_in : !flux.qubit, - * !flux.qubit -> !flux.qubit, !flux.qubit - * flux.yield %q1_res, %q2_res - * } : !flux.qubit, !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit, - * !flux.qubit - * ``` - */ - std::pair> ciswap(Value control, Value qubit0, - Value qubit1); - - /** - * @brief Apply a multi-controlled iSWAP gate - * - * @param controls Input control qubits (must be valid/unconsumed) - * @param qubit0 Target qubit (must be valid/unconsumed) - * @param qubit1 Target qubit (must be valid/unconsumed) - * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) - * - * @par Example: - * ```c++ - * {controls_out, {q1_out, q2_out}} = builder.mciswap({q0_in, q1_in}, q2_in, - * q3_in); - * ``` - * ```mlir - * %controls_out, %q2_out, %q3_out = flux.ctrl(%q0_in, %q1_in) %q2_in, %q3_in - * { %q2_res, %q3_res = flux.iswap %q2_in, %q3_in : !flux.qubit, !flux.qubit - * -> !flux.qubit, !flux.qubit flux.yield %q2_res, %q3_res } : ({!flux.qubit, - * !flux.qubit}, {!flux.qubit, !flux.qubit}) -> ({!flux.qubit, !flux.qubit}, - * {!flux.qubit, !flux.qubit}) - * ``` - */ - std::pair> - mciswap(ValueRange controls, Value qubit0, Value qubit1); + // OneTargetZeroParameter + +#define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(q_in); \ + * ``` \ + * ```mlir \ + * %q_out = flux.OP_NAME %q_in : !flux.qubit -> !flux.qubit \ + * ``` \ + */ \ + Value OP_NAME(Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ + * %q1_res = flux.OP_NAME %q1_in : !flux.qubit -> !flux.qubit \ + * flux.yield %q1_res \ + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME(Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME({q0_in, q1_in}, q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = flux.OP_NAME %q2_in : !flux.qubit -> !flux.qubit \ + * flux.yield %q2_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ + * !flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME(ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) + DECLARE_ONE_TARGET_ZERO_PARAMETER(XOp, x) + DECLARE_ONE_TARGET_ZERO_PARAMETER(YOp, y) + DECLARE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) + DECLARE_ONE_TARGET_ZERO_PARAMETER(HOp, h) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SOp, s) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TOp, t) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DECLARE_ONE_TARGET_ZERO_PARAMETER + + // OneTargetOneParameter + +#define DECLARE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(PARAM, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ + * ``` \ + */ \ + Value OP_NAME(const std::variant& PARAM, Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(PARAM, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ + * %q1_res = flux.OP_NAME(%PARAM) %q1_in : !flux.qubit -> !flux.qubit \ + * flux.yield %q1_res \ + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME( \ + const std::variant&(PARAM), Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}, \ + * q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = flux.OP_NAME(%PARAM) %q2_in : !flux.qubit -> !flux.qubit \ + * flux.yield %q2_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ + * !flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DECLARE_ONE_TARGET_ONE_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(PARAM1, PARAM2, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM1, %PARAM2) %q_in : !flux.qubit -> \ + * !flux.qubit \ + * ``` \ + */ \ + Value OP_NAME(const std::variant& PARAM1, \ + const std::variant& PARAM2, Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ + * %q1_res = flux.OP_NAME(%PARAM1, %PARAM2) %q1_in : !flux.qubit -> \ + * !flux.qubit \ + * flux.yield %q1_res \ + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, \ + Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME(PARAM1, PARAM2, {q0_in, \ + * q1_in}, q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = flux.OP_NAME(%PARAM1, %PARAM2) %q2_in : !flux.qubit -> \ + * !flux.qubit \ + * flux.yield %q2_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ + * !flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) + DECLARE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DECLARE_ONE_TARGET_TWO_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubits and produces new output qubit SSA values. The \ + * inputs are validated and the tracking is updated. \ + * \ + * @param qubit0 Input qubit (must be valid/unconsumed) \ + * @param qubit1 Input qubit (must be valid/unconsumed) \ + * @return Output qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.OP_NAME(q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME %q0_in, %q1_in : !flux.qubit, !flux.qubit \ + * -> !flux.qubit, !flux.qubit \ + * ``` \ + */ \ + std::pair OP_NAME(Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(q0_in, q1_in, q2_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = flux.OP_NAME %q1_in, %q2_in : !flux.qubit, \ + * !flux.qubit -> !flux.qubit, !flux.qubit \ + * flux.yield %q1_res, %q2_res \ + * } : ({!flux.qubit}, {!flux.qubit, !flux.qubit}) -> ({!flux.qubit}, \ + * {!flux.qubit, !flux.qubit}) \ + * ``` \ + */ \ + std::pair> c##OP_NAME( \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME({q0_in, q1_in}, \ + * q2_in, q3_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %q1_out, %q2_out = flux.ctrl(%q0_in, %q1_in) %q2_in, \ + * %q3_in { \ + * %q2_res, %q3_res = flux.OP_NAME %q2_in, %q3_in : !flux.qubit, \ + * !flux.qubit -> !flux.qubit, !flux.qubit \ + * flux.yield %q2_res, %q3_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ + * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * ``` \ + */ \ + std::pair> mc##OP_NAME( \ + ValueRange controls, Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) + +#undef DECLARE_TWO_TARGET_ZERO_PARAMETER //===--------------------------------------------------------------------===// // Modifiers diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 2acaf131a1..6993eaf6e8 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -263,1037 +263,271 @@ class QIRProgramBuilder { // Unitary Operations //===--------------------------------------------------------------------===// - /** - * @brief Apply an Id gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.id(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__i__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& id(Value qubit); - - /** - * @brief Apply a controlled Id gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cid(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ci__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& cid(Value control, Value target); - - /** - * @brief Apply a multi-controlled Id gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcid({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cci__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcid(ValueRange controls, Value target); - - /** - * @brief Apply an X gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.x(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__x__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& x(Value qubit); - - /** - * @brief Apply a controlled X gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cx(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cx__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& cx(Value control, Value target); - - /** - * @brief Apply a multi-controlled X gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcx({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccx__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcx(ValueRange controls, Value target); - - /** - * @brief Apply a Y gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.y(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__y__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& y(Value qubit); - - /** - * @brief Apply a controlled Y gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cy(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cy__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& cy(Value control, Value target); - - /** - * @brief Apply a multi-controlled Y gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcy({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccy__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcy(ValueRange controls, Value target); - - /** - * @brief Apply a Z gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.z(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__z__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& z(Value qubit); - - /** - * @brief Apply a controlled Z gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cz(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cz__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& cz(Value control, Value target); - - /** - * @brief Apply a multi-controlled Z gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcz({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccz__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcz(ValueRange controls, Value target); - - /** - * @brief Apply an H gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.h(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__h__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& h(Value qubit); - - /** - * @brief Apply a controlled H gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ch(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ch__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& ch(Value control, Value target); - - /** - * @brief Apply a multi-controlled H gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mch({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cch__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mch(ValueRange controls, Value target); - - /** - * @brief Apply an S gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.s(q); - * ``` - * ```mlir - * llvm.call @__quantum__qis__s__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& s(Value qubit); - - /** - * @brief Apply a controlled S gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cs(q0, q1); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cs__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& cs(Value control, Value target); - - /** - * @brief Apply a multi-controlled S gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcs({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccs__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcs(ValueRange controls, Value target); - - /** - * @brief Apply an Sdg gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.sdg(q); - * ``` - * ```mlir - * llvm.call @__quantum__qis__sdg__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& sdg(Value qubit); - - /** - * @brief Apply a controlled Sdg gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.csdg(q0, q1); - * ``` - * ```mlir - * llvm.call @__quantum__qis__csdg__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> - * () - * ``` - */ - QIRProgramBuilder& csdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled Sdg gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcsdg({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccsdg__body(%c1, %c2, %t) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcsdg(ValueRange controls, Value target); - - /** - * @brief Apply a T gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.t(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__t__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& t(Value qubit); - - /** - * @brief Apply a controlled T gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ct(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ct__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& ct(Value control, Value target); - - /** - * @brief Apply a multi-controlled T gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mct({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cct__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mct(ValueRange controls, Value target); - - /** - * @brief Apply a Tdg gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.tdg(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__tdg__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& tdg(Value qubit); - - /** - * @brief Apply a controlled Tdg gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ctdg(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ctdg__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> - * () - * ``` - */ - QIRProgramBuilder& ctdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled Tdg gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mctdg({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cctdg__body(%c1, %c2, %t) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mctdg(ValueRange controls, Value target); - - /** - * @brief Apply an SX gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.sx(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__sx__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& sx(Value qubit); - - /** - * @brief Apply a controlled SX gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.csx(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__csx__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& csx(Value control, Value target); - - /** - * @brief Apply a multi-controlled SX gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcs({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccs__body(%c1, %c2, %t) : (!llvm.ptr, !llvm.ptr, - * !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcsx(ValueRange controls, Value target); - - /** - * @brief Apply an SXdg gate to a qubit - * - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.sxdg(qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__sxdg__body(%q) : (!llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& sxdg(Value qubit); - - /** - * @brief Apply a controlled SXdg gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.csxdg(control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__csxdg__body(%c, %t) : (!llvm.ptr, !llvm.ptr) -> - * () - * ``` - */ - QIRProgramBuilder& csxdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled SXdg gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcsxdg({control1, control2}, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ccsxdg__body(%c1, %c2, %t) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcsxdg(ValueRange controls, Value target); - - /** - * @brief Apply an RX gate to a qubit - * - * @param theta Rotation angle - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.rx(theta, qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__rx__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& rx(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled RX gate - * - * @param theta Rotation angle - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.crx(theta, control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__crx__body(%c, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& crx(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RX gate - * - * @param theta Rotation angle - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcrx(theta, controls, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mcrx__body(%c1, %c2, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& mcrx(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an RY gate to a qubit - * - * @param theta Rotation angle - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ry(theta, qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ry__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& ry(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled RY gate - * - * @param theta Rotation angle - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cry(theta, control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cry__body(%c, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& cry(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RY gate - * - * @param theta Rotation angle - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcry(theta, controls, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mcry__body(%c1, %c2, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& mcry(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an RZ gate to a qubit - * - * @param theta Rotation angle - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.rz(theta, qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__rz__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& rz(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled RZ gate - * - * @param theta Rotation angle - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.crz(theta, control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__crz__body(%c, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& crz(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RZ gate - * - * @param theta Rotation angle - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcrz(theta, controls, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mcrz__body(%c1, %c2, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& mcrz(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply a P gate to a qubit - * - * @param theta Rotation angle - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.p(theta, qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__p__body(%q, %theta) : (!llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& p(const std::variant& theta, Value qubit); - - /** - * @brief Apply a controlled P gate - * - * @param theta Rotation angle - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cp(theta, control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cp__body(%c, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& cp(const std::variant& theta, Value control, - Value target); - - /** - * @brief Apply a multi-controlled P gate - * - * @param theta Rotation angle - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcp(theta, controls, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mcp__body(%c1, %c2, %t, %theta) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr, f64) -> () - * ``` - */ - QIRProgramBuilder& mcp(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an R gate to a qubit - * - * @param theta Rotation angle - * @param phi Rotation angle - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.r(theta, phi, qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__r__body(%q, %theta, %phi) : (!llvm.ptr, f64, - * f64) -> () - * ``` - */ - QIRProgramBuilder& r(const std::variant& theta, - const std::variant& phi, Value qubit); - - /** - * @brief Apply a controlled R gate - * - * @param theta Rotation angle - * @param phi Rotation angle - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cr(theta, phi, control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cr__body(%c, %t, %theta, %phi) : (!llvm.ptr, - * !llvm.ptr, f64, f64) -> () - * ``` - */ - QIRProgramBuilder& cr(const std::variant& theta, - const std::variant& phi, Value control, - Value target); - - /** - * @brief Apply a multi-controlled R gate - * - * @param theta Rotation angle - * @param phi Rotation angle - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcr(theta, phi, controls, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mcr__body(%c1, %c2, %t, %theta, %phi) : - * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () - * ``` - */ - QIRProgramBuilder& mcr(const std::variant& theta, - const std::variant& phi, - ValueRange controls, Value target); - - /** - * @brief Apply a U2 gate to a qubit - * - * @param phi Rotation angle - * @param lambda Rotation angle - * @param qubit Input qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.u2(phi, lambda, qubit); - * ``` - * ```mlir - * llvm.call @__quantum__qis__u2__body(%q, %phi, %lambda) : (!llvm.ptr, f64, - * f64) -> () - * ``` - */ - QIRProgramBuilder& u2(const std::variant& phi, - const std::variant& lambda, Value qubit); - - /** - * @brief Apply a controlled U2 gate - * - * @param phi Rotation angle - * @param lambda Rotation angle - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cu2(phi, lambda, control, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cu2__body(%c, %t, %phi, %lambda) : (!llvm.ptr, - * !llvm.ptr, f64, f64) -> () - * ``` - */ - QIRProgramBuilder& cu2(const std::variant& phi, - const std::variant& lambda, - Value control, Value target); - - /** - * @brief Apply a multi-controlled U2 gate - * - * @param phi Rotation angle - * @param lambda Rotation angle - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mu2(phi, lambda, controls, target); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mu2__body(%c1, %c2, %t, %phi, %lambda) : - * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () - * ``` - */ - QIRProgramBuilder& mcu2(const std::variant& phi, - const std::variant& lambda, - ValueRange controls, Value target); - - /** - * @brief Apply a SWAP gate to two qubits - * - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.swap(qubit0, qubit1); - * ``` - * ```mlir - * llvm.call @__quantum__qis__swap__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) -> - * () - * ``` - */ - QIRProgramBuilder& swap(Value qubit0, Value qubit1); - - /** - * @brief Apply a controlled SWAP gate - * - * @param control Control qubit - * @param target1 Target qubit - * @param target2 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cswap(control, target1, target2); - * ``` - * ```mlir - * llvm.call @__quantum__qis__cswap__body(%c, %t0, %t1) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& cswap(Value control, Value target0, Value target1); - - /** - * @brief Apply a multi-controlled SWAP gate - * - * @param controls Control qubits - * @param target1 Target qubit - * @param target2 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcswap({control1, control2}, target1, target2); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mcswap__body(%c1, %c2, %t0, %t1) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mcswap(ValueRange controls, Value target0, Value target1); - - /** - * @brief Apply an iSWAP gate to two qubits - * - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.iswap(qubit0, qubit1); - * ``` - * ```mlir - * llvm.call @__quantum__qis__iswap__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) - * -> () - * ``` - */ - QIRProgramBuilder& iswap(Value qubit0, Value qubit1); - - /** - * @brief Apply a controlled iSWAP gate - * - * @param control Control qubit - * @param target1 Target qubit - * @param target2 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ciswap(control, target1, target2); - * ``` - * ```mlir - * llvm.call @__quantum__qis__ciswap__body(%c, %t0, %t1) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& ciswap(Value control, Value target0, Value target1); - - /** - * @brief Apply a multi-controlled iSWAP gate - * - * @param controls Control qubits - * @param target1 Target qubit - * @param target2 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mciswap({control1, control2}, target1, target2); - * ``` - * ```mlir - * llvm.call @__quantum__qis__mciswap__body(%c1, %c2, %t0, %t1) : (!llvm.ptr, - * !llvm.ptr, !llvm.ptr, !llvm.ptr) -> () - * ``` - */ - QIRProgramBuilder& mciswap(ValueRange controls, Value target0, Value target1); + // OneTargetZeroParameter + +#define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_NAME, QIR_NAME) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q) : (!llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ZERO_PARAMETER(id, i) + DECLARE_ONE_TARGET_ZERO_PARAMETER(x, x) + DECLARE_ONE_TARGET_ZERO_PARAMETER(y, y) + DECLARE_ONE_TARGET_ZERO_PARAMETER(z, z) + DECLARE_ONE_TARGET_ZERO_PARAMETER(h, h) + DECLARE_ONE_TARGET_ZERO_PARAMETER(s, s) + DECLARE_ONE_TARGET_ZERO_PARAMETER(sdg, sdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(t, t) + DECLARE_ONE_TARGET_ZERO_PARAMETER(tdg, tdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(sx, sx) + DECLARE_ONE_TARGET_ZERO_PARAMETER(sxdg, sxdg) + +#undef DECLARE_ONE_TARGET_ZERO_PARAMETER + + // OneTargetOneParameter + +#define DECLARE_ONE_TARGET_ONE_PARAMETER(OP_NAME, QIR_NAME, PARAM) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q, %PARAM) : (!llvm.ptr, \ + * f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ONE_PARAMETER(rx, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(ry, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(rz, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(p, p, theta) + +#undef DECLARE_ONE_TARGET_ONE_PARAMETER + + // OneTargetTwoParameter + +#define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_NAME, QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q, %PARAM1, %PARAM2) : \ + * (!llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %PARAM1, \ + * %PARAM2) : (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %PARAM1, \ + * %PARAM2) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_TWO_PARAMETER(r, r, theta, phi) + DECLARE_ONE_TARGET_TWO_PARAMETER(u2, u2, phi, lambda) + +#undef DECLARE_ONE_TARGET_TWO_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_NAME, QIR_NAME) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q0, %q1) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1) : (!llvm.ptr, \ + * !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_ZERO_PARAMETER(swap, swap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(iswap, iswap) + +#undef DECLARE_TWO_TARGET_ZERO_PARAMETER //===--------------------------------------------------------------------===// // Finalization diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 3bc0223019..7013dfbdf1 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -228,1077 +228,271 @@ class QuartzProgramBuilder final : public OpBuilder { // Unitary Operations //===--------------------------------------------------------------------===// - /** - * @brief Apply an Id gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.id(q); - * ``` - * ```mlir - * quartz.id %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& id(Value qubit); - - /** - * @brief Apply a controlled Id gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cid(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.id %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cid(Value control, Value target); - - /** - * @brief Apply a multi-controlled Id gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcid({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.id %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcid(ValueRange controls, Value target); - - /** - * @brief Apply an X gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.x(q); - * ``` - * ```mlir - * quartz.x %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& x(Value qubit); - - /** - * @brief Apply a controlled X gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cx(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.x %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cx(Value control, Value target); - - /** - * @brief Apply a multi-controlled X gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcx({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.x %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcx(ValueRange controls, Value target); - - /** - * @brief Apply a Y gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.y(q); - * ``` - * ```mlir - * quartz.y %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& y(Value qubit); - - /** - * @brief Apply a controlled Y gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cy(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.y %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cy(Value control, Value target); - - /** - * @brief Apply a multi-controlled Y gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcy({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.y %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcy(ValueRange controls, Value target); - - /** - * @brief Apply a Z gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.z(q); - * ``` - * ```mlir - * quartz.z %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& z(Value qubit); - - /** - * @brief Apply a controlled Z gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cz(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.z %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cz(Value control, Value target); - - /** - * @brief Apply a multi-controlled Z gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcz({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.z %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcz(ValueRange controls, Value target); - - /** - * @brief Apply an H gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.h(q); - * ``` - * ```mlir - * quartz.h %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& h(Value qubit); - - /** - * @brief Apply a controlled H gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ch(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.h %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& ch(Value control, Value target); - - /** - * @brief Apply a multi-controlled H gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mch({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.h %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mch(ValueRange controls, Value target); - - /** - * @brief Apply an S gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.s(q); - * ``` - * ```mlir - * quartz.s %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& s(Value qubit); - - /** - * @brief Apply a controlled S gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cs(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.s %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cs(Value control, Value target); - - /** - * @brief Apply a multi-controlled S gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcs({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.s %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcs(ValueRange controls, Value target); - - /** - * @brief Apply an Sdg gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.sdg(q); - * ``` - * ```mlir - * quartz.sdg %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& sdg(Value qubit); - - /** - * @brief Apply a controlled Sdg gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.csdg(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.sdg %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& csdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled Sdg gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcsdg({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.sdg %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcsdg(ValueRange controls, Value target); - - /** - * @brief Apply a T gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.t(q); - * ``` - * ```mlir - * quartz.t %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& t(Value qubit); - - /** - * @brief Apply a controlled T gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ct(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.t %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& ct(Value control, Value target); - - /** - * @brief Apply a multi-controlled T gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mct({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.t %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mct(ValueRange controls, Value target); - - /** - * @brief Apply a Tdg gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.tdg(q); - * ``` - * ```mlir - * quartz.tdg %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& tdg(Value qubit); - - /** - * @brief Apply a controlled Tdg gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ctdg(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.tdg %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& ctdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled Tdg gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mctdg({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.tdg %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mctdg(ValueRange controls, Value target); - - /** - * @brief Apply an SX gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.sx(q); - * ``` - * ```mlir - * quartz.sx %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& sx(Value qubit); - - /** - * @brief Apply a controlled SX gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.csx(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.sx %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& csx(Value control, Value target); - - /** - * @brief Apply a multi-controlled SX gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcsx({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.sx %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcsx(ValueRange controls, Value target); - - /** - * @brief Apply an SXdg gate to a qubit - * - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.sxdg(q); - * ``` - * ```mlir - * quartz.sxdg %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& sxdg(Value qubit); - - /** - * @brief Apply a controlled SXdg gate - * - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.csxdg(q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.sxdg %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& csxdg(Value control, Value target); - - /** - * @brief Apply a multi-controlled SXdg gate - * - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcsxdg({q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.sxdg %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcsxdg(ValueRange controls, Value target); - - /** - * @brief Apply an RX gate to a qubit - * - * @param theta Rotation angle in radians - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.rx(1.0, q); - * ``` - * ```mlir - * quartz.rx(%theta) %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& rx(const std::variant& theta, - Value qubit); - - /** - * @brief Apply a CRX gate - * - * @param theta Rotation angle in radians - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.crx(1.0, q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.rx(%theta) %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& crx(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RX gate - * - * @param theta Rotation angle in radians - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.mcrx(1.0, {q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.rx(%theta) %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcrx(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an RY gate to a qubit - * - * @param theta Rotation angle in radians - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.ry(1.0, q); - * ``` - * ```mlir - * quartz.ry(%theta) %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& ry(const std::variant& theta, - Value qubit); - - /** - * @brief Apply a CRY gate - * - * @param theta Rotation angle in radians - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cry(1.0, q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.ry(%theta) %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cry(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RY gate - * - * @param theta Rotation angle in radians - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcry(1.0, {q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.ry(%theta) %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcry(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an RZ gate to a qubit - * - * @param theta Rotation angle in radians - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.rz(1.0, q); - * ``` - * ```mlir - * quartz.rz(%theta) %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& rz(const std::variant& theta, - Value qubit); - - /** - * @brief Apply a CRZ gate - * - * @param theta Rotation angle in radians - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.crz(1.0, q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.rz(%theta) %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& crz(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled RZ gate - * - * @param theta Rotation angle in radians - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcrz(1.0, {q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.rz(%theta) %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcrz(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply a P gate to a qubit - * - * @param theta Rotation angle in radians - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.p(1.0, q); - * ``` - * ```mlir - * quartz.p(%theta) %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& p(const std::variant& theta, - Value qubit); - - /** - * @brief Apply a controlled P gate - * - * @param theta Rotation angle in radians - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cp(1.0, q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.p(%theta) %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cp(const std::variant& theta, - Value control, Value target); - - /** - * @brief Apply a multi-controlled P gate - * - * @param theta Rotation angle in radians - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcp(1.0, {q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.p(%theta) %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcp(const std::variant& theta, - ValueRange controls, Value target); - - /** - * @brief Apply an R gate to a qubit - * - * @param theta Rotation angle in radians - * @param phi Rotation angle in radians - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.r(1.0, 0.5, q); - * ``` - * ```mlir - * quartz.r(%theta, %phi) %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& r(const std::variant& theta, - const std::variant& phi, Value qubit); - - /** - * @brief Apply a controlled R gate - * - * @param theta Rotation angle in radians - * @param phi Rotation angle in radians - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.cr(1.0, 0.5, q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.r(%theta, %phi) %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cr(const std::variant& theta, - const std::variant& phi, - Value control, Value target); - - /** - * @brief Apply a multi-controlled R gate - * - * @param theta Rotation angle in radians - * @param phi Rotation angle in radians - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.mcr(1.0, 0.5, {q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.r(%theta, %phi) %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcr(const std::variant& theta, - const std::variant& phi, - ValueRange controls, Value target); - - /** - * @brief Apply a U2 gate to a qubit - * - * @param phi Rotation angle in radians - * @param lambda Rotation angle in radians - * @param qubit Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.u2(1.0, 0.5, q); - * ``` - * ```mlir - * quartz.u2(%phi, %lambda) %q : !quartz.qubit - * ``` - */ - QuartzProgramBuilder& u2(const std::variant& phi, - const std::variant& lambda, - Value qubit); - - /** - * @brief Apply a controlled U2 gate - * - * @param phi Rotation angle in radians - * @param lambda Rotation angle in radians - * @param control Control qubit - * @param target Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.cu2(1.0, 0.5, q0, q1); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.u2(%phi, %lambda) %q1 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cu2(const std::variant& phi, - const std::variant& lambda, - Value control, Value target); - - /** - * @brief Apply a multi-controlled U2 gate - * - * @param phi Rotation angle in radians - * @param lambda Rotation angle in radians - * @param controls Control qubits - * @param target Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.mcu2(1.0, 0.5, {q0, q1}, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.u2(%phi, %lambda) %q2 : !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcu2(const std::variant& phi, - const std::variant& lambda, - ValueRange controls, Value target); - - /** - * @brief Apply a SWAP gate to two qubits - * - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.swap(q0, q1); - * ``` - * ```mlir - * quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit - * ``` - */ - QuartzProgramBuilder& swap(Value qubit0, Value qubit1); - - /** - * @brief Apply a controlled SWAP gate - * - * @param control Control qubit - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.cswap(q0, q1, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.swap %q1, %q2 : !quartz.qubit, !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& cswap(Value control, Value qubit0, Value qubit1); - - /** - * @brief Apply a multi-controlled SWAP gate - * - * @param controls Control qubits - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.mcswap({q0, q1}, q2, q3); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.swap %q2, %q3 : !quartz.qubit, !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mcswap(ValueRange controls, Value qubit0, Value qubit1); - - /** - * @brief Apply an iSWAP gate to two qubits - * - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.iswap(q0, q1); - * ``` - * ```mlir - * quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit - * ``` - */ - QuartzProgramBuilder& iswap(Value qubit0, Value qubit1); - - /** - * @brief Apply a controlled iSWAP gate - * - * @param control Control qubit - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.ciswap(q0, q1, q2); - * ``` - * ```mlir - * quartz.ctrl(%q0) { - * quartz.iswap %q1, %q2 : !quartz.qubit, !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& ciswap(Value control, Value qubit0, Value qubit1); - - /** - * @brief Apply a multi-controlled iSWAP gate - * - * @param controls Control qubits - * @param qubit0 Target qubit - * @param qubit1 Target qubit - * @return Reference to this builder for method chaining - * @par Example: - * ```c++ - * builder.mciswap({q0, q1}, q2, q3); - * ``` - * ```mlir - * quartz.ctrl(%q0, %q1) { - * quartz.iswap %q2, %q3 : !quartz.qubit, !quartz.qubit - * } - * ``` - */ - QuartzProgramBuilder& mciswap(ValueRange controls, Value qubit0, - Value qubit1); + // OneTargetZeroParameter + +#define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME %q : !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME %q1 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME %q2 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) + DECLARE_ONE_TARGET_ZERO_PARAMETER(XOp, x) + DECLARE_ONE_TARGET_ZERO_PARAMETER(YOp, y) + DECLARE_ONE_TARGET_ZERO_PARAMETER(ZOp, z) + DECLARE_ONE_TARGET_ZERO_PARAMETER(HOp, h) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SOp, s) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TOp, t) + DECLARE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx) + DECLARE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) + +#undef DECLARE_ONE_TARGET_ZERO_PARAMETER + + // OneTargetOneParameter + +#define DECLARE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) %q : !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME(%PARAM) %q1 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM) %q2 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_ONE_PARAMETER(RzOp, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RxOp, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RyOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) + +#undef DECLARE_ONE_TARGET_ONE_PARAMETER + + // OneTargetTwoParameter + +#define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q1 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q2 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) + DECLARE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) + +#undef DECLARE_ONE_TARGET_TWO_PARAMETER + + // TwoTargetZeroParameter + +#define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param control Control qubit \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(q0, q1, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param controls Control qubits \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME({q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME %q2, %q3 : !quartz.qubit, !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) + +#undef DECLARE_TWO_TARGET_ZERO_PARAMETER //===--------------------------------------------------------------------===// // Modifiers From aae623cd2b9f6057625c4299710b33e4f12d69e0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 02:21:00 +0100 Subject: [PATCH 243/419] Add support for DCX and ECR --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 2 + mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 44 +++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 2 + .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 8 +++ .../Quartz/Builder/QuartzProgramBuilder.h | 2 + .../mlir/Dialect/Quartz/IR/QuartzOps.td | 42 +++++++++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 23 +++++++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 21 ++++--- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 7 ++- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 ++ .../Flux/Builder/FluxProgramBuilder.cpp | 2 + .../Dialect/Flux/IR/StandardGates/DCXOp.cpp | 22 +++++++ .../Dialect/Flux/IR/StandardGates/ECROp.cpp | 22 +++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 + .../Quartz/Builder/QuartzProgramBuilder.cpp | 2 + .../Dialect/Quartz/IR/StandardGates/DCXOp.cpp | 22 +++++++ .../Dialect/Quartz/IR/StandardGates/ECROp.cpp | 22 +++++++ .../TranslateQuantumComputationToQuartz.cpp | 50 +++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 62 +++++++++++++++++++ 19 files changed, 350 insertions(+), 11 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/DCXOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/DCXOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/ECROp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index c4072b35e9..9368357b76 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -583,6 +583,8 @@ class FluxProgramBuilder final : public OpBuilder { DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) + DECLARE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DECLARE_TWO_TARGET_ZERO_PARAMETER diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index db268fe8f5..ad0b55e24b 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -722,6 +722,50 @@ def iSWAPOp : FluxOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParamet }]; } +def DCXOp : FluxOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a DCX gate to two qubits"; + let description = [{ + Applies a DCX gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.dcx %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "dcx"; } + }]; +} + +def ECROp : FluxOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply an ECR gate to two qubits"; + let description = [{ + Applies an ECR gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.ecr %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ecr"; } + }]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 6993eaf6e8..9369788d6e 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -526,6 +526,8 @@ class QIRProgramBuilder { DECLARE_TWO_TARGET_ZERO_PARAMETER(swap, swap) DECLARE_TWO_TARGET_ZERO_PARAMETER(iswap, iswap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(dcx, dcx) + DECLARE_TWO_TARGET_ZERO_PARAMETER(ecr, ecr) #undef DECLARE_TWO_TARGET_ZERO_PARAMETER diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index b125b2de1f..4d8366be71 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -100,6 +100,14 @@ static constexpr auto QIR_ISWAP = "__quantum__qis__iswap__body"; static constexpr auto QIR_CISWAP = "__quantum__qis__ciswap__body"; static constexpr auto QIR_CCISWAP = "__quantum__qis__cciswap__body"; static constexpr auto QIR_CCCISWAP = "__quantum__qis__ccciswap__body"; +static constexpr auto QIR_DCX = "__quantum__qis__dcx__body"; +static constexpr auto QIR_CDCX = "__quantum__qis__cdcx__body"; +static constexpr auto QIR_CCDCX = "__quantum__qis__ccdcx__body"; +static constexpr auto QIR_CCCDCX = "__quantum__qis__cccdcx__body"; +static constexpr auto QIR_ECR = "__quantum__qis__ecr__body"; +static constexpr auto QIR_CECR = "__quantum__qis__cecr__body"; +static constexpr auto QIR_CCECR = "__quantum__qis__ccecr__body"; +static constexpr auto QIR_CCCECR = "__quantum__qis__cccecr__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 7013dfbdf1..479cb8a4c7 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -491,6 +491,8 @@ class QuartzProgramBuilder final : public OpBuilder { DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) + DECLARE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) + DECLARE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DECLARE_TWO_TARGET_ZERO_PARAMETER diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index b832c7fd34..78b88d81ac 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -655,6 +655,48 @@ def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParam }]; } +def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply a DCX gate to two qubits"; + let description = [{ + Applies a DCX gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.dcx %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0, + Arg:$qubit1); + let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "dcx"; } + }]; +} + +def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { + let summary = "Apply an ECR gate to two qubits"; + let description = [{ + Applies an ECR gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.ecr %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0, + Arg:$qubit1); + let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ecr"; } + }]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index ac8bce90fc..b31cdb9cf6 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -186,6 +186,29 @@ inline DenseElementsAttr getMatrixiSWAP(MLIRContext* ctx) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixDCX(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const auto matrix = {1.0 + 0i, 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 0 + 0.0 + 0i, 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, // row 1 + 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, 1.0 + 0i, // row 2 + 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, 0.0 + 0i}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixECR(MLIRContext* ctx) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex m0 = 0.0 + 0i; + const std::complex m1 = 1.0 / std::sqrt(2) + 0i; + const std::complex mi = 0.0 + 1i / std::sqrt(2); + const auto matrix = {m0, m0, m1, mi, // row 0 + m0, m0, mi, m1, // row 1 + m1, -mi, m0, m0, // row 2 + -mi, m1, m0, m0}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, size_t numControls, mlir::DenseElementsAttr target) { diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index a20a4edf9b..70ea116d00 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -476,6 +476,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER @@ -587,15 +589,16 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns - .add(typeConverter, context); + patterns.add( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 610b6767dc..edaa10cf68 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -676,6 +676,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER @@ -834,8 +836,9 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, - ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzCtrlOp, - ConvertQuartzYieldOp>(typeConverter, context, &state); + ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, + ConvertQuartzECROp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( + typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 64de81ee86..cf5c67e409 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -901,6 +901,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, U2, u2, phi, lambda) DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap, SWAP, swap) DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap, ISWAP, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx, DCX, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr, ECR, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER @@ -1289,6 +1291,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 2de0ffe53c..5544251154 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -437,6 +437,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/DCXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/DCXOp.cpp new file mode 100644 index 0000000000..e319d95d3c --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/DCXOp.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr DCXOp::tryGetStaticMatrix() { + return getMatrixDCX(getContext()); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp new file mode 100644 index 0000000000..073723b38a --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr ECROp::tryGetStaticMatrix() { + return getMatrixECR(getContext()); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index b44f06c94f..44c99f05df 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -608,6 +608,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAP, swap) DEFINE_TWO_TARGET_ZERO_PARAMETER(ISWAP, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCX, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 28d3581293..1a88a4b3f6 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -237,6 +237,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/DCXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/DCXOp.cpp new file mode 100644 index 0000000000..48e1c0d921 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/DCXOp.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr DCXOp::tryGetStaticMatrix() { + return getMatrixDCX(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/ECROp.cpp new file mode 100644 index 0000000000..ff925c0042 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/ECROp.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr ECROp::tryGetStaticMatrix() { + return getMatrixECR(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 6fb2b33833..5b3a9fc8b6 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -654,6 +654,50 @@ void addiSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds a DCX operation + * + * @details + * Translate a DCX operation from the QuantumComputation to quartz.dcx. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The DCX operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addDCXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.dcx(target0, target1); + } else { + builder.mcdcx(posControls, target0, target1); + } +} + +/** + * @brief Adds an ECR operation + * + * @details + * Translate an ECR operation from the QuantumComputation to quartz.ecr. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The ECR operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addECROp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.ecr(target0, target1); + } else { + builder.mcecr(posControls, target0, target1); + } +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -742,6 +786,12 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::iSWAP: addiSWAPOp(builder, *operation, qubits); break; + case qc::OpType::DCX: + addDCXOp(builder, *operation, qubits); + break; + case qc::OpType::ECR: + addECROp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index d68b566608..9f58408bc6 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2424,4 +2424,66 @@ TEST_F(CompilerPipelineTest, iSWAP) { }); } +TEST_F(CompilerPipelineTest, DCX) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.dcx(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.dcx(reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.dcx(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.dcx(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, ECR) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.ecr(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ecr(reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ecr(reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.ecr(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From 7442fc575f22c6044e532afa46a84efa9168efc5 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 03:03:31 +0100 Subject: [PATCH 244/419] Add support for U --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 156 +++++++++++++++++- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 29 ++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 96 +++++++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 + .../Quartz/Builder/QuartzProgramBuilder.h | 77 +++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 27 +++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 13 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 92 +++++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 153 ++++++++++++----- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 127 +++++++++++++- .../Flux/Builder/FluxProgramBuilder.cpp | 79 +++++++++ .../lib/Dialect/Flux/IR/StandardGates/UOp.cpp | 71 ++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 139 +++++++++++++++- .../Quartz/Builder/QuartzProgramBuilder.cpp | 33 ++++ .../Dialect/Quartz/IR/StandardGates/UOp.cpp | 67 ++++++++ .../TranslateQuantumComputationToQuartz.cpp | 27 +++ .../pipeline/test_compiler_pipeline.cpp | 99 ++++++++++- 17 files changed, 1233 insertions(+), 56 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/UOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 9368357b76..67f8ad4e0a 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -408,7 +408,7 @@ class FluxProgramBuilder final : public OpBuilder { #undef DECLARE_ONE_TARGET_ONE_PARAMETER - // TwoTargetZeroParameter + // OneTargetTwoParameter #define DECLARE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ /** \ @@ -500,6 +500,105 @@ class FluxProgramBuilder final : public OpBuilder { #undef DECLARE_ONE_TARGET_TWO_PARAMETER + // OneTargetThreeParameter + +#define DECLARE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubit and produces a new output qubit SSA value. The \ + * input is validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param qubit Input qubit (must be valid/unconsumed) \ + * @return Output qubit \ + * \ + * @par Example: \ + * ```c++ \ + * q_out = builder.OP_NAME(PARAM1, PARAM2, PARAM3, q_in); \ + * ``` \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !flux.qubit -> \ + * !flux.qubit \ + * ``` \ + */ \ + Value OP_NAME(const std::variant& PARAM1, \ + const std::variant& PARAM2, \ + const std::variant& PARAM3, Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0_in, \ + * q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ + * %q1_res = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1_in : !flux.qubit \ + * -> !flux.qubit \ + * flux.yield %q1_res \ + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value control, \ + Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param target Input target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, output_target_qubit) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, target_out} = builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, \ + * {q0_in, q1_in}, q2_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2_in : !flux.qubit \ + * -> !flux.qubit \ + * flux.yield %q2_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ + * !flux.qubit}, {!flux.qubit}) \ + * ``` \ + */ \ + std::pair mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DECLARE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -794,6 +893,61 @@ class FluxProgramBuilder final : public OpBuilder { const std::variant& parameter2, const ValueRange controls, const Value target); + /** + * @brief Helper to create a one-target, three-parameter Flux operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param parameter3 Operation parameter + * @param qubit Input qubit + * @return Output qubit + */ + template + Value + createOneTargetThreeParameter(const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, + const Value qubit); + + /** + * @brief Helper to create a controlled one-target, three-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param parameter3 Operation parameter + * @param control Input control qubit + * @param target Input target qubit + * @return Pair of (output_control_qubit, output_target_qubit) + */ + template + std::pair createControlledOneTargetThreeParameter( + const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, const Value control, + const Value target); + + /** + * @brief Helper to create a multi-controlled one-target, three-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param parameter3 Operation parameter + * @param controls Input control qubits + * @param target Input target qubit + * @return Pair of (output_control_qubits, output_target_qubit) + */ + template + std::pair createMultiControlledOneTargetThreeParameter( + const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, const ValueRange controls, + const Value target); + /** * @brief Helper to create a two-target, zero-parameter Flux operation * @tparam OpType The operation type of the Flux operation diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index ad0b55e24b..95f445091c 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -249,6 +249,7 @@ class TargetAndParameterArityTrait def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; +def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; //===----------------------------------------------------------------------===// @@ -676,6 +677,34 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } +def UOp : FluxOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { + let summary = "Apply a U gate to a qubit"; + let description = [{ + Applies a U gate to a qubit and returns the transformed qubit. + + Example: + ```mlir + %q_out = flux.u(%theta, %phi, %lambda) %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi, + Arg:$lambda); + let results = (outs QubitType:$qubit_out); + let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "u"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi, "const std::variant&":$lambda)> + ]; +} + def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 9369788d6e..b92b65ff75 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -468,6 +468,85 @@ class QIRProgramBuilder { #undef DECLARE_ONE_TARGET_TWO_PARAMETER + // OneTargetThreeParameter + +#define DECLARE_ONE_TARGET_THREE_PARAMETER(OP_NAME, QIR_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, PARAM3, q); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q, %PARAM1, %PARAM2, \ + * %PARAM3) : \ + * (!llvm.ptr, f64, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %PARAM1, \ + * %PARAM2, %PARAM3) : (!llvm.ptr, !llvm.ptr, f64, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %PARAM1, \ + * %PARAM2, %PARAM3) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, f64) -> \ + * () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + ValueRange controls, Value target); + + DECLARE_ONE_TARGET_THREE_PARAMETER(u, u3, theta, phi, lambda) + +#undef DECLARE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_NAME, QIR_NAME) \ @@ -616,6 +695,23 @@ class QIRProgramBuilder { const ValueRange controls, const Value target, StringRef fnName); + /** + * @brief Helper to create a one-target, three-parameter QIR operation + * + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param parameter3 Operation parameter + * @param controls Control qubits + * @param target Target qubit + * @param fnName Name of the QIR function to call + */ + void + createOneTargetThreeParameter(const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, + const ValueRange controls, const Value target, + StringRef fnName); + /** * @brief Helper to create a two-target, zero-parameter QIR operation * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 4d8366be71..3f6ef3b8d3 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -92,6 +92,10 @@ static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; static constexpr auto QIR_CU2 = "__quantum__qis__cu2__body"; static constexpr auto QIR_CCU2 = "__quantum__qis__ccu2__body"; static constexpr auto QIR_CCCU2 = "__quantum__qis__cccu2__body"; +static constexpr auto QIR_U = "__quantum__qis__u3__body"; +static constexpr auto QIR_CU = "__quantum__qis__cu3__body"; +static constexpr auto QIR_CCU = "__quantum__qis__ccu3__body"; +static constexpr auto QIR_CCCU = "__quantum__qis__cccu3__body"; static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; static constexpr auto QIR_CSWAP = "__quantum__qis__cswap__body"; static constexpr auto QIR_CCSWAP = "__quantum__qis__ccswap__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 479cb8a4c7..45aeb35945 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -432,6 +432,83 @@ class QuartzProgramBuilder final : public OpBuilder { #undef DECLARE_ONE_TARGET_TWO_PARAMETER + // OneTargetThreeParameter + +#define DECLARE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param qubit Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, PARAM3, q); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value control, Value target); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param PARAM3 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, {q0, q1}, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target); + + DECLARE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DECLARE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DECLARE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 78b88d81ac..79a93e22e7 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -237,6 +237,7 @@ class TargetAndParameterArityTrait def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; +def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; //===----------------------------------------------------------------------===// @@ -613,6 +614,32 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> ]; } +def UOp : QuartzOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { + let summary = "Apply a U gate to a qubit"; + let description = [{ + Applies a U gate to a qubit, modifying it in place. + + Example: + ```mlir + quartz.u(%theta, %phi, %lambda) %q : !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit_in, + Arg:$theta, + Arg:$phi, + Arg:$lambda); + let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict"; + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "u"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi, "const std::variant&":$lambda)> + ]; +} + def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index b31cdb9cf6..6ead2660b8 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -153,6 +153,19 @@ inline DenseElementsAttr getMatrixU2(MLIRContext* ctx, double phi, return DenseElementsAttr::get(type, {m00, m01, m10, m11}); } +inline DenseElementsAttr getMatrixU(MLIRContext* ctx, double theta, double phi, + double lambda) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({2, 2}, complexType); + const std::complex m00 = std::cos(theta / 2.0) + 0i; + const std::complex m01 = + -std::exp(1i * lambda) * std::sin(theta / 2.0); + const std::complex m10 = std::exp(1i * phi) * std::sin(theta / 2.0); + const std::complex m11 = + std::exp(1i * (phi + lambda)) * std::cos(theta / 2.0); + return DenseElementsAttr::get(type, {m00, m01, m10, m11}); +} + inline DenseElementsAttr getMatrixR(MLIRContext* ctx, double theta, double phi) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 70ea116d00..eafb0ac245 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -121,6 +121,46 @@ convertOneTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a two-target, zero-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetThreeParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubit + const auto& quartzQubit = adaptor.getQubitIn(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit, op.getOperand(1), + op.getOperand(2), op.getOperand(3)); + + // Replace the output qubit with the same Quartz reference + rewriter.replaceOp(op, quartzQubit); + + return success(); +} + +/** + * @brief Converts a two-target, zero-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ template LogicalResult @@ -447,6 +487,38 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !flux.qubit \ + * -> !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetThreeParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -589,16 +661,16 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add( - typeConverter, context); + patterns.add< + ConvertFluxAllocOp, ConvertFluxDeallocOp, ConvertFluxStaticOp, + ConvertFluxMeasureOp, ConvertFluxResetOp, ConvertFluxIdOp, + ConvertFluxXOp, ConvertFluxYOp, ConvertFluxZOp, ConvertFluxHOp, + ConvertFluxSOp, ConvertFluxSdgOp, ConvertFluxTOp, ConvertFluxTdgOp, + ConvertFluxSXOp, ConvertFluxSXdgOp, ConvertFluxRXOp, ConvertFluxRYOp, + ConvertFluxRZOp, ConvertFluxPOp, ConvertFluxROp, ConvertFluxU2Op, + ConvertFluxUOp, ConvertFluxSWAPOp, ConvertFluxiSWAPOp, ConvertFluxDCXOp, + ConvertFluxECROp, ConvertFluxCtrlOp, ConvertFluxYieldOp>(typeConverter, + context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index edaa10cf68..a8aea2b689 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -196,7 +196,7 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, } /** - * @brief Converts a two-target, zero-parameter Quartz operation to Flux + * @brief Converts a one-target, two-parameter Quartz operation to Flux * * @tparam FluxOpType The operation type of the Flux operation * @tparam QuartzOpType The operation type of the Quartz operation @@ -206,38 +206,31 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubits - const auto quartzQubit0 = op->getOperand(0); - const auto quartzQubit1 = op->getOperand(1); - Value fluxQubit0 = nullptr; - Value fluxQubit1 = nullptr; + // Get the latest Flux qubit + const auto quartzQubit = op->getOperand(0); + Value fluxQubit = nullptr; if (inCtrlOp == 0) { - fluxQubit0 = qubitMap[quartzQubit0]; - fluxQubit1 = qubitMap[quartzQubit1]; + fluxQubit = qubitMap[quartzQubit]; } else { - const auto& targetsIn = state.targetsIn[inCtrlOp]; - fluxQubit0 = targetsIn[0]; - fluxQubit1 = targetsIn[1]; + fluxQubit = state.targetsIn[inCtrlOp].front(); } // Create the Flux operation (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); + auto fluxOp = rewriter.create( + op.getLoc(), fluxQubit, op->getOperand(1), op->getOperand(2)); - // Update state map + // Update the state map if (inCtrlOp == 0) { - qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + qubitMap[quartzQubit] = fluxOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut( - {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + const SmallVector targetsOut({fluxOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -247,7 +240,7 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, } /** - * @brief Converts a one-target, two-parameter Quartz operation to Flux + * @brief Converts a one-target, three-parameter Quartz operation to Flux * * @tparam FluxOpType The operation type of the Flux operation * @tparam QuartzOpType The operation type of the Quartz operation @@ -257,9 +250,10 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +LogicalResult +convertOneTargetThreeParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -273,8 +267,9 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, } // Create the Flux operation (consumes input, produces output) - auto fluxOp = rewriter.create( - op.getLoc(), fluxQubit, op->getOperand(1), op->getOperand(2)); + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit, op->getOperand(1), + op->getOperand(2), op->getOperand(3)); // Update the state map if (inCtrlOp == 0) { @@ -290,6 +285,57 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, return success(); } +/** + * @brief Converts a two-target, zero-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest Flux qubits + const auto quartzQubit0 = op->getOperand(0); + const auto quartzQubit1 = op->getOperand(1); + Value fluxQubit0 = nullptr; + Value fluxQubit1 = nullptr; + if (inCtrlOp == 0) { + fluxQubit0 = qubitMap[quartzQubit0]; + fluxQubit1 = qubitMap[quartzQubit1]; + } else { + const auto& targetsIn = state.targetsIn[inCtrlOp]; + fluxQubit0 = targetsIn[0]; + fluxQubit1 = targetsIn[1]; + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); + + // Update state map + if (inCtrlOp == 0) { + qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + } // namespace /** @@ -646,6 +692,39 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q_out = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !flux.qubit \ + * -> !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertOneTargetThreeParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -828,16 +907,16 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { // Register operation conversion patterns with state // tracking - patterns.add( + patterns.add< + ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, + ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, + ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, + ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, + ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, + ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, + ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, ConvertQuartzUOp, + ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, + ConvertQuartzECROp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( typeConverter, context, &state); // Conversion of quartz types in func.func signatures diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index cf5c67e409..e51a78e3cd 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -263,6 +263,7 @@ convertOneTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, SmallVector argumentTypes; argumentTypes.reserve(numCtrls + 3); const auto ptrType = LLVM::LLVMPointerType::get(ctx); + const auto floatType = Float64Type::get(ctx); // Add control pointers for (size_t i = 0; i < numCtrls; ++i) { argumentTypes.push_back(ptrType); @@ -270,8 +271,8 @@ convertOneTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, // Add target pointer argumentTypes.push_back(ptrType); // Add parameter types - argumentTypes.push_back(Float64Type::get(ctx)); - argumentTypes.push_back(Float64Type::get(ctx)); + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); // Define function signature const auto fnSignature = @@ -297,6 +298,71 @@ convertOneTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a one-target, three-parameter Quartz operation to QIR + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertOneTargetThreeParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 4); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + const auto floatType = Float64Type::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + // Add parameter types + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); + + // Define function signature + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 4); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + /** * @brief Converts a two-target, zero-parameter Quartz operation to QIR * @@ -847,6 +913,62 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, U2, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME_SMALL, \ + OP_NAME_BIG, QIR_NAME) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q, %PARAM1, %PARAM2, %PARAM3) \ + * : (!llvm.ptr, f64, f64, f64) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertOneTargetThreeParameter( \ + op, adaptor, rewriter, getContext(), state, fnName); \ + } \ + }; + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, U, u3) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ @@ -1289,6 +1411,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 5544251154..311010a634 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -290,6 +290,53 @@ FluxProgramBuilder::createMultiControlledOneTargetTwoParameter( return {controlsOut, targetsOut[0]}; } +// OneTargetThreeParameter helpers + +template +Value FluxProgramBuilder::createOneTargetThreeParameter( + const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, const Value qubit) { + auto op = create(loc, qubit, parameter1, parameter2, parameter3); + const auto& qubitOut = op.getQubitOut(); + updateQubitTracking(qubit, qubitOut); + return qubitOut; +} + +template +std::pair +FluxProgramBuilder::createControlledOneTargetThreeParameter( + const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, const Value control, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(control, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], parameter1, + parameter2, parameter3); + return op->getResults(); + }); + return {controlsOut[0], targetsOut[0]}; +} + +template +std::pair +FluxProgramBuilder::createMultiControlledOneTargetThreeParameter( + const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, const ValueRange controls, + const Value target) { + const auto [controlsOut, targetsOut] = + ctrl(controls, target, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], parameter1, + parameter2, parameter3); + return op->getResults(); + }); + return {controlsOut, targetsOut[0]}; +} + // TwoTargetZeroParameter helpers template @@ -416,6 +463,38 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + Value FluxProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), const Value qubit) { \ + return createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, \ + qubit); \ + } \ + std::pair FluxProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), const Value control, \ + const Value target) { \ + return createControlledOneTargetThreeParameter( \ + PARAM1, PARAM2, PARAM3, control, target); \ + } \ + std::pair FluxProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), const ValueRange controls, \ + const Value target) { \ + return createMultiControlledOneTargetThreeParameter( \ + PARAM1, PARAM2, PARAM3, controls, target); \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp new file mode 100644 index 0000000000..23f846e00a --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr UOp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto phi = getStaticParameter(getPhi()); + const auto lambda = getStaticParameter(getLambda()); + if (!theta || !phi || !lambda) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + return getMatrixU(getContext(), thetaValue, phiValue, lambdaValue); +} + +void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, const std::variant& theta, + const std::variant& phi, + const std::variant& lambda) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); + } else { + phiOperand = std::get(phi); + } + + Value lambdaOperand = nullptr; + if (std::holds_alternative(lambda)) { + lambdaOperand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(lambda))); + } else { + lambdaOperand = std::get(lambda); + } + + build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 44c99f05df..039485a063 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -372,6 +372,7 @@ void QIRProgramBuilder::createOneTargetTwoParameter( SmallVector argumentTypes; argumentTypes.reserve(controls.size() + 3); const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto floatType = Float64Type::get(builder.getContext()); // Add control pointers for (size_t i = 0; i < controls.size(); ++i) { argumentTypes.push_back(ptrType); @@ -379,8 +380,8 @@ void QIRProgramBuilder::createOneTargetTwoParameter( // Add target pointer argumentTypes.push_back(ptrType); // Add parameter types - argumentTypes.push_back(Float64Type::get(builder.getContext())); - argumentTypes.push_back(Float64Type::get(builder.getContext())); + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); // Define function signature const auto fnSignature = LLVM::LLVMFunctionType::get( @@ -400,6 +401,91 @@ void QIRProgramBuilder::createOneTargetTwoParameter( builder.create(loc, fnDecl, operands); } +void QIRProgramBuilder::createOneTargetThreeParameter( + const std::variant& parameter1, + const std::variant& parameter2, + const std::variant& parameter3, const ValueRange controls, + const Value target, StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value parameter1Operand; + if (std::holds_alternative(parameter1)) { + parameter1Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter1))) + .getResult(); + } else { + parameter1Operand = std::get(parameter1); + } + + Value parameter2Operand; + if (std::holds_alternative(parameter2)) { + parameter2Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter2))) + .getResult(); + } else { + parameter2Operand = std::get(parameter2); + } + + Value parameter3Operand; + if (std::holds_alternative(parameter3)) { + parameter3Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter3))) + .getResult(); + } else { + parameter3Operand = std::get(parameter3); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 4); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto floatType = Float64Type::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointer + argumentTypes.push_back(ptrType); + // Add parameter types + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 4); + operands.append(controls.begin(), controls.end()); + operands.push_back(target); + operands.push_back(parameter1Operand); + operands.push_back(parameter2Operand); + operands.push_back(parameter3Operand); + + builder.create(loc, fnDecl, operands); +} + void QIRProgramBuilder::createTwoTargetZeroParameter(const ValueRange controls, const Value target0, const Value target1, @@ -572,6 +658,55 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM1, \ + PARAM2, PARAM3) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), const Value qubit) { \ + createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, {}, qubit, \ + QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), const Value control, \ + const Value target) { \ + createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, {control}, target, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), const ValueRange controls, \ + const Value target) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, controls, target, \ + fnName); \ + return *this; \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 1a88a4b3f6..e762e8b3e1 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -215,6 +215,39 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ + PARAM3) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value qubit) { \ + create(loc, qubit, PARAM1, PARAM2, PARAM3); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value control, \ + Value target) { \ + return mc##OP_NAME(PARAM1, PARAM2, PARAM3, {control}, target); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target) { \ + create(loc, controls, [&](OpBuilder& b) { \ + b.create(loc, target, PARAM1, PARAM2, PARAM3); \ + }); \ + return *this; \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/UOp.cpp new file mode 100644 index 0000000000..bd1feed7af --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/UOp.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr UOp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto phi = getStaticParameter(getPhi()); + const auto lambda = getStaticParameter(getLambda()); + if (!theta || !phi || !lambda) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + return getMatrixU(getContext(), thetaValue, phiValue, lambdaValue); +} + +void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubitIn, const std::variant& theta, + const std::variant& phi, + const std::variant& lambda) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value phiOperand = nullptr; + if (std::holds_alternative(phi)) { + phiOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); + } else { + phiOperand = std::get(phi); + } + + Value lambdaOperand = nullptr; + if (std::holds_alternative(lambda)) { + lambdaOperand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(lambda))); + } else { + lambdaOperand = std::get(lambda); + } + + build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 5b3a9fc8b6..c7fd68be39 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -610,6 +610,30 @@ void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds a U operation + * + * @details + * Translate a U operation from the QuantumComputation to quartz.u. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The U operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addUOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& phi = operation.getParameter()[1]; + const auto& lambda = operation.getParameter()[2]; + const auto& target = qubits[operation.getTargets()[0]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.u(theta, phi, lambda, target); + } else { + builder.mcu(theta, phi, lambda, posControls, target); + } +} + /** * @brief Adds a SWAP operation * @@ -780,6 +804,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::U2: addU2Op(builder, *operation, qubits); break; + case qc::OpType::U: + addUOp(builder, *operation, qubits); + break; case qc::OpType::SWAP: addSWAPOp(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 9f58408bc6..59aecb6acd 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2175,8 +2175,7 @@ TEST_F(CompilerPipelineTest, R) { }); const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - const auto q = reg[0]; - b.r(1.0, 0.5, q); + b.r(1.0, 0.5, reg[0]); }); verifyAllStages({ @@ -2269,8 +2268,100 @@ TEST_F(CompilerPipelineTest, U2) { }); const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - const auto q = reg[0]; - b.u2(1.0, 0.5, q); + b.u2(1.0, 0.5, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, U) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.u(1.0, 0.5, 0.2, 0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.5, 0.2, reg[0]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.5, 0.2, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.u(1.0, 0.5, 0.2, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, CU) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.cu(1.0, 0.5, 0.2, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCU) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.mcu(1.0, 0.5, 0.2, {0, 1}, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); }); verifyAllStages({ From 41298c6550d2533ff288bc45307af3f18dcf1fad Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 03:34:27 +0100 Subject: [PATCH 245/419] Add support for RXX --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 141 ++++++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 28 ++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 81 ++++++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 + .../Quartz/Builder/QuartzProgramBuilder.h | 67 +++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 27 ++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 13 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 82 ++++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 106 +++++++++++-- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 120 +++++++++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 69 +++++++++ .../Dialect/Flux/IR/StandardGates/RXXOp.cpp | 48 ++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 100 +++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 26 ++++ .../Dialect/Quartz/IR/StandardGates/RXXOp.cpp | 44 ++++++ .../TranslateQuantumComputationToQuartz.cpp | 26 ++++ .../pipeline/test_compiler_pipeline.cpp | 93 ++++++++++++ 17 files changed, 1054 insertions(+), 21 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RXXOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 67f8ad4e0a..a2467cc901 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -687,6 +687,99 @@ class FluxProgramBuilder final : public OpBuilder { #undef DECLARE_TWO_TARGET_ZERO_PARAMETER + // TwoTargetOneParameter + +#define DECLARE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubits and produces new output qubit SSA values. The \ + * inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit0 Input qubit (must be valid/unconsumed) \ + * @param qubit1 Input qubit (must be valid/unconsumed) \ + * @return Output qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.OP_NAME(PARAM, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME(%PARAM) %q0_in, %q1_in : !flux.qubit, \ + * !flux.qubit \ + * -> !flux.qubit, !flux.qubit \ + * ``` \ + */ \ + std::pair OP_NAME(const std::variant& PARAM, \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(PARAM, q0_in, q1_in, \ + * q2_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = flux.OP_NAME(%PARAM) %q1_in, %q2_in : !flux.qubit, \ + * !flux.qubit -> !flux.qubit, !flux.qubit \ + * flux.yield %q1_res, %q2_res \ + * } : ({!flux.qubit}, {!flux.qubit, !flux.qubit}) -> ({!flux.qubit}, \ + * {!flux.qubit, !flux.qubit}) \ + * ``` \ + */ \ + std::pair> c##OP_NAME( \ + const std::variant& PARAM, Value control, Value qubit0, \ + Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME(PARAM, {q0_in, \ + * q1_in}, q2_in, q3_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %q1_out, %q2_out = flux.ctrl(%q0_in, %q1_in) %q2_in, \ + * %q3_in { \ + * %q2_res, %q3_res = flux.OP_NAME(%PARAM) %q2_in, %q3_in : !flux.qubit, \ + * !flux.qubit -> !flux.qubit, !flux.qubit \ + * flux.yield %q2_res, %q3_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ + * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * ``` \ + */ \ + std::pair> mc##OP_NAME( \ + const std::variant& PARAM, ValueRange controls, \ + Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + +#undef DECLARE_TWO_TARGET_ONE_PARAMETER + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// @@ -990,6 +1083,54 @@ class FluxProgramBuilder final : public OpBuilder { const Value qubit0, const Value qubit1); + /** + * @brief Helper to create a two-target, one-parameter Flux operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter Operation parameter + * @param qubit0 Input qubit + * @param qubit1 Input qubit + * @return Pair of (output_qubit0, output_qubit1) + */ + template + std::pair + createTwoTargetOneParameter(const std::variant& parameter, + const Value qubit0, const Value qubit1); + + /** + * @brief Helper to create a controlled two-target, one-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter Operation parameter + * @param control Input control qubit + * @param qubit0 Input target qubit + * @param qubit1 Input target qubit + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) + */ + template + std::pair> + createControlledTwoTargetOneParameter( + const std::variant& parameter, const Value control, + const Value qubit0, const Value qubit1); + + /** + * @brief Helper to create a multi-controlled two-target, one-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter Operation parameter + * @param controls Input control qubits + * @param qubit0 Input target qubit + * @param qubit1 Input target qubit + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) + */ + template + std::pair> + createMultiControlledTwoTargetOneParameter( + const std::variant& parameter, const ValueRange controls, + const Value qubit0, const Value qubit1); + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 95f445091c..0553d04ea7 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -251,6 +251,7 @@ def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; +def TwoTargetOneParameter : TargetAndParameterArityTrait<2, 1>; //===----------------------------------------------------------------------===// // Unitary Operations @@ -795,6 +796,33 @@ def ECROp : FluxOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> }]; } +def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RXX gate to two qubits"; + let description = [{ + Applies an RXX gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.rxx(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rxx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index b92b65ff75..51d825302b 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -610,6 +610,73 @@ class QIRProgramBuilder { #undef DECLARE_TWO_TARGET_ZERO_PARAMETER + // TwoTargetOneParameter + +#define DECLARE_TWO_TARGET_ONE_PARAMETER(OP_NAME, QIR_NAME, PARAM) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q0, %q1, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %q2, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param target Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %q3, \ + * %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_ONE_PARAMETER(rxx, rxx, theta) + +#undef DECLARE_TWO_TARGET_ONE_PARAMETER + //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// @@ -724,6 +791,20 @@ class QIRProgramBuilder { const Value target0, const Value target1, StringRef fnName); + /** + * @brief Helper to create a two-target, one-parameter QIR operation + * + * @param parameter Operation parameter + * @param controls Control qubits + * @param target0 Target qubit + * @param target1 Target qubit + * @param fnName Name of the QIR function to call + */ + void createTwoTargetOneParameter(const std::variant& parameter, + const ValueRange controls, + const Value target0, const Value target1, + StringRef fnName); + /** * @brief Generate array-based output recording in the output block * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 3f6ef3b8d3..8269824aaa 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -112,6 +112,10 @@ static constexpr auto QIR_ECR = "__quantum__qis__ecr__body"; static constexpr auto QIR_CECR = "__quantum__qis__cecr__body"; static constexpr auto QIR_CCECR = "__quantum__qis__ccecr__body"; static constexpr auto QIR_CCCECR = "__quantum__qis__cccecr__body"; +static constexpr auto QIR_RXX = "__quantum__qis__rxx__body"; +static constexpr auto QIR_CRXX = "__quantum__qis__crxx__body"; +static constexpr auto QIR_CCRXX = "__quantum__qis__ccrxx__body"; +static constexpr auto QIR_CCCRXX = "__quantum__qis__cccrxx__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 45aeb35945..be6d79933a 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -573,6 +573,73 @@ class QuartzProgramBuilder final : public OpBuilder { #undef DECLARE_TWO_TARGET_ZERO_PARAMETER + // TwoTargetOneParameter + +#define DECLARE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM, q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME(%PARAM) %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM) %q2, %q3 : !quartz.qubit, !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value qubit0, \ + Value qubit1); + + DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + +#undef DECLARE_TWO_TARGET_ONE_PARAMETER + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 79a93e22e7..5808b07115 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -239,6 +239,7 @@ def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; +def TwoTargetOneParameter : TargetAndParameterArityTrait<2, 1>; //===----------------------------------------------------------------------===// // Unitary Operations @@ -724,6 +725,32 @@ def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter }]; } +def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RXX gate to two qubits"; + let description = [{ + Applies an RXX gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.rxx(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rxx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 6ead2660b8..dfb917ec35 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -222,6 +222,19 @@ inline DenseElementsAttr getMatrixECR(MLIRContext* ctx) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixRXX(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex z = 0.0 + 0i; + const std::complex c = std::cos(theta / 2.0) + 0i; + const std::complex s = -1i * std::sin(theta / 2.0); + const auto matrix = {c, z, z, s, // row 0 + z, c, s, z, // row 1 + z, s, c, z, // row 2 + s, z, z, c}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, size_t numControls, mlir::DenseElementsAttr target) { diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index eafb0ac245..f605fb8630 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -179,6 +179,36 @@ convertTwoTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a two-target, one-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertTwoTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubits + const auto& quartzQubit0 = adaptor.getQubit0In(); + const auto& quartzQubit1 = adaptor.getQubit1In(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1, + op.getOperand(2)); + + // Replace the output qubits with the same Quartz references + rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + + return success(); +} + } // namespace /** @@ -553,6 +583,37 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME(%PARAM) %q0_in, %q1_in : !flux.qubit, \ + * !flux.qubit -> !flux.qubit, !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetOneParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + /** * @brief Converts quartz.ctrl to flux.ctrl * @@ -661,16 +722,17 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add< - ConvertFluxAllocOp, ConvertFluxDeallocOp, ConvertFluxStaticOp, - ConvertFluxMeasureOp, ConvertFluxResetOp, ConvertFluxIdOp, - ConvertFluxXOp, ConvertFluxYOp, ConvertFluxZOp, ConvertFluxHOp, - ConvertFluxSOp, ConvertFluxSdgOp, ConvertFluxTOp, ConvertFluxTdgOp, - ConvertFluxSXOp, ConvertFluxSXdgOp, ConvertFluxRXOp, ConvertFluxRYOp, - ConvertFluxRZOp, ConvertFluxPOp, ConvertFluxROp, ConvertFluxU2Op, - ConvertFluxUOp, ConvertFluxSWAPOp, ConvertFluxiSWAPOp, ConvertFluxDCXOp, - ConvertFluxECROp, ConvertFluxCtrlOp, ConvertFluxYieldOp>(typeConverter, - context); + patterns + .add( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index a8aea2b689..1dcb9d6236 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -336,6 +336,57 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, return success(); } +/** + * @brief Converts a two-target, one-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest Flux qubits + const auto quartzQubit0 = op->getOperand(0); + const auto quartzQubit1 = op->getOperand(1); + Value fluxQubit0 = nullptr; + Value fluxQubit1 = nullptr; + if (inCtrlOp == 0) { + fluxQubit0 = qubitMap[quartzQubit0]; + fluxQubit1 = qubitMap[quartzQubit1]; + } else { + const auto& targetsIn = state.targetsIn[inCtrlOp]; + fluxQubit0 = targetsIn[0]; + fluxQubit1 = targetsIn[1]; + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1, + op->getOperand(2)); + + // Update state map + if (inCtrlOp == 0) { + qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + } // namespace /** @@ -760,6 +811,38 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME(%PARAM) %q0_in, %q1_in : !flux.qubit, \ + * !flux.qubit -> !flux.qubit, !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetOneParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + /** * @brief Converts quartz.ctrl to flux.ctrl * @@ -907,17 +990,18 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { // Register operation conversion patterns with state // tracking - patterns.add< - ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, - ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, - ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, - ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, - ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, - ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, - ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, ConvertQuartzUOp, - ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, - ConvertQuartzECROp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( - typeConverter, context, &state); + patterns.add(typeConverter, + context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index e51a78e3cd..e77c539f76 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -424,6 +424,69 @@ convertTwoTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a two-target, one-parameter Quartz operation to QIR + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertTwoTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 3); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); + // Add parameter type + argumentTypes.push_back(Float64Type::get(ctx)); + + // Define function signature + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 3); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + } // namespace /** @@ -1028,6 +1091,62 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr, ECR, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ + QIR_NAME, PARAM) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL(%PARAM) %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q1, %q2, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertTwoTargetOneParameter(op, adaptor, rewriter, getContext(), \ + state, fnName); \ + } \ + }; + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, RXX, rxx, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + /** * @brief Inlines quartz.ctrl region removes the operation */ @@ -1416,6 +1535,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 311010a634..33c54268ad 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -378,6 +378,50 @@ FluxProgramBuilder::createMultiControlledTwoTargetZeroParameter( return {controlsOut, {targetsOut[0], targetsOut[1]}}; } +// TwoTargetOneParameter helpers + +template +std::pair FluxProgramBuilder::createTwoTargetOneParameter( + const std::variant& parameter, const Value qubit0, + const Value qubit1) { + auto op = create(loc, qubit0, qubit1, parameter); + const auto& qubit0Out = op.getQubit0Out(); + const auto& qubit1Out = op.getQubit1Out(); + updateQubitTracking(qubit0, qubit0Out); + updateQubitTracking(qubit1, qubit1Out); + return {qubit0Out, qubit1Out}; +} + +template +std::pair> +FluxProgramBuilder::createControlledTwoTargetOneParameter( + const std::variant& parameter, const Value control, + const Value qubit0, const Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(control, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = + b.create(loc, targets[0], targets[1], parameter); + return op->getResults(); + }); + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; +} + +template +std::pair> +FluxProgramBuilder::createMultiControlledTwoTargetOneParameter( + const std::variant& parameter, const ValueRange controls, + const Value qubit0, const Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = + b.create(loc, targets[0], targets[1], parameter); + return op->getResults(); + }); + return {controlsOut, {targetsOut[0], targetsOut[1]}}; +} + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -521,6 +565,31 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + std::pair FluxProgramBuilder::OP_NAME( \ + const std::variant&(PARAM), Value qubit0, Value qubit1) { \ + return createTwoTargetOneParameter(PARAM, qubit0, qubit1); \ + } \ + std::pair> FluxProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), const Value control, \ + Value qubit0, Value qubit1) { \ + return createControlledTwoTargetOneParameter(PARAM, control, \ + qubit0, qubit1); \ + } \ + std::pair> \ + FluxProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), \ + const ValueRange controls, Value qubit0, Value qubit1) { \ + return createMultiControlledTwoTargetOneParameter( \ + PARAM, controls, qubit0, qubit1); \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp new file mode 100644 index 0000000000..940b536a02 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr RXXOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRXX(getContext(), thetaValue); +} + +void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 039485a063..1f16dd81d3 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -525,6 +525,64 @@ void QIRProgramBuilder::createTwoTargetZeroParameter(const ValueRange controls, builder.create(loc, fnDecl, operands); } +void QIRProgramBuilder::createTwoTargetOneParameter( + const std::variant& parameter, const ValueRange controls, + const Value target0, const Value target1, StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value parameterOperand; + if (std::holds_alternative(parameter)) { + parameterOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter))) + .getResult(); + } else { + parameterOperand = std::get(parameter); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 3); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); + // Add parameter type + argumentTypes.push_back(Float64Type::get(builder.getContext())); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 3); + operands.append(controls.begin(), controls.end()); + operands.push_back(target0); + operands.push_back(target1); + operands.push_back(parameterOperand); + + builder.create(loc, fnDecl, operands); +} + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ @@ -748,6 +806,48 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM), const Value target0, \ + const Value target1) { \ + createTwoTargetOneParameter(PARAM, {}, target0, target1, \ + QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM), const Value control, \ + const Value target0, const Value target1) { \ + createTwoTargetOneParameter(PARAM, {control}, target0, target1, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM), const ValueRange controls, \ + const Value target0, const Value target1) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createTwoTargetOneParameter(PARAM, controls, target0, target1, fnName); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXX, rxx, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index e762e8b3e1..c0df64309d 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -275,6 +275,32 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + const std::variant&(PARAM), Value qubit0, Value qubit1) { \ + create(loc, qubit0, qubit1, PARAM); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control, Value qubit0, \ + Value qubit1) { \ + return mc##OP_NAME(PARAM, {control}, qubit0, qubit1); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls, \ + Value qubit0, Value qubit1) { \ + create(loc, controls, [&](OpBuilder& b) { \ + b.create(loc, qubit0, qubit1, PARAM); \ + }); \ + return *this; \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXXOp.cpp new file mode 100644 index 0000000000..d5dde3bf34 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RXXOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RXXOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRXX(getContext(), thetaValue); +} + +void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index c7fd68be39..426a3be100 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -722,6 +722,29 @@ void addECROp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an RXX operation + * + * @details + * Translate an RXX operation from the QuantumComputation to quartz.rxx. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RXX operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addRXXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.rxx(theta, target0, target1); + } else { + builder.mcrxx(theta, posControls, target0, target1); + } +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -819,6 +842,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::ECR: addECROp(builder, *operation, qubits); break; + case qc::OpType::RXX: + addRXXOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 59aecb6acd..4b47d51be8 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2577,4 +2577,97 @@ TEST_F(CompilerPipelineTest, ECR) { }); } +TEST_F(CompilerPipelineTest, RXX) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.rxx(1.0, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rxx(1.0, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rxx(1.0, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.rxx(1.0, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, CRXX) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.crxx(1.0, 0, 1, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.crxx(1.0, reg[0], reg[1], reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.crxx(1.0, reg[0], reg[1], reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.crxx(1.0, reg[0], reg[1], reg[2]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCRXX) { + qc::QuantumComputation qc; + qc.addQubitRegister(4, "q"); + qc.mcrxx(1.0, {0, 1}, 2, 3); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(4); + b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From e45bf4bef2336c948917eedb09ea4f67b3994e3e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:15:08 +0100 Subject: [PATCH 246/419] Add support for XXPlusYY --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 152 ++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 29 +++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 118 +++++++++-- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 + .../Quartz/Builder/QuartzProgramBuilder.h | 74 +++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 27 +++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 32 ++- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 83 ++++++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 108 ++++++++-- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 185 +++++++++++++++--- .../Flux/Builder/FluxProgramBuilder.cpp | 77 ++++++++ .../Flux/IR/StandardGates/XXPlusYYOp.cpp | 56 ++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 120 ++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 30 +++ .../Quartz/IR/StandardGates/XXPlusYYOp.cpp | 56 ++++++ .../TranslateQuantumComputationToQuartz.cpp | 29 +++ .../pipeline/test_compiler_pipeline.cpp | 93 +++++++++ 17 files changed, 1201 insertions(+), 72 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/XXPlusYYOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index a2467cc901..da4c11616d 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -780,6 +780,104 @@ class FluxProgramBuilder final : public OpBuilder { #undef DECLARE_TWO_TARGET_ONE_PARAMETER + // TwoTargetTwoParameter + +#define DECLARE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @details \ + * Consumes the input qubits and produces new output qubit SSA values. The \ + * inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit0 Input qubit (must be valid/unconsumed) \ + * @param qubit1 Input qubit (must be valid/unconsumed) \ + * @return Output qubits \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, q1_out} = builder.OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * ``` \ + */ \ + std::pair OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Input control qubit (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, \ + * q1_in, q2_in); \ + * ``` \ + * ```mlir \ + * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = flux.OP_NAME(%PARAM1, %PARAM2) %q1_in, %q2_in : \ + * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * flux.yield %q1_res, %q2_res \ + * } : ({!flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ + * ({!flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * ``` \ + */ \ + std::pair> c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, Value qubit0, \ + Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @details \ + * Consumes the input control and target qubits and produces new output \ + * qubit SSA values. The inputs are validated and the tracking is updated. \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Input control qubits (must be valid/unconsumed) \ + * @param qubit0 Target qubit (must be valid/unconsumed) \ + * @param qubit1 Target qubit (must be valid/unconsumed) \ + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) \ + * \ + * @par Example: \ + * ```c++ \ + * {controls_out, {q1_out, q2_out}} = builder.mc##OP_NAME(PARAM1, PARAM2, \ + * {q0_in, q1_in}, q2_in, q3_in); \ + * ``` \ + * ```mlir \ + * %controls_out, %q1_out, %q2_out = flux.ctrl(%q0_in, %q1_in) %q2_in, \ + * %q3_in { \ + * %q2_res, %q3_res = flux.OP_NAME(%PARAM1, %PARAM2) %q2_in, %q3_in : \ + * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * flux.yield %q2_res, %q3_res \ + * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ + * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * ``` \ + */ \ + std::pair> mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + +#undef DECLARE_TWO_TARGET_TWO_PARAMETER + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// @@ -1131,6 +1229,60 @@ class FluxProgramBuilder final : public OpBuilder { const std::variant& parameter, const ValueRange controls, const Value qubit0, const Value qubit1); + /** + * @brief Helper to create a two-target, two-parameter Flux operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param qubit0 Input qubit + * @param qubit1 Input qubit + * @return Pair of (output_qubit0, output_qubit1) + */ + template + std::pair + createTwoTargetTwoParameter(const std::variant& parameter1, + const std::variant& parameter2, + const Value qubit0, const Value qubit1); + + /** + * @brief Helper to create a controlled two-target, two-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param control Input control qubit + * @param qubit0 Input target qubit + * @param qubit1 Input target qubit + * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) + */ + template + std::pair> + createControlledTwoTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const Value control, + const Value qubit0, const Value qubit1); + + /** + * @brief Helper to create a multi-controlled two-target, two-parameter Flux + * operation + * + * @tparam OpType The operation type of the Flux operation + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param controls Input control qubits + * @param qubit0 Input target qubit + * @param qubit1 Input target qubit + * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) + */ + template + std::pair> + createMultiControlledTwoTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const ValueRange controls, + const Value qubit0, const Value qubit1); + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 0553d04ea7..2565750814 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -252,6 +252,7 @@ def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; def TwoTargetOneParameter : TargetAndParameterArityTrait<2, 1>; +def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; //===----------------------------------------------------------------------===// // Unitary Operations @@ -823,6 +824,34 @@ def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> ]; } +def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX+YY gate to two qubits"; + let description = [{ + Applies an XX+YY gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.xx_plus_yy(%theta, %beta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "xx_plus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 51d825302b..163fb46c7d 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -571,7 +571,8 @@ class QIRProgramBuilder { * @brief Apply a controlled QIR_NAME operation \ * \ * @param control Control qubit \ - * @param target Target qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ * @return Reference to this builder for method chaining \ * \ * @par Example: \ @@ -583,12 +584,13 @@ class QIRProgramBuilder { * !llvm.ptr) -> () \ * ``` \ */ \ - QIRProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ + QIRProgramBuilder& c##OP_NAME(Value control, Value target0, Value target1); \ /** \ * @brief Apply a multi-controlled QIR_NAME operation \ * \ * @param controls Control qubits \ - * @param target Target qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ * @return Reference to this builder for method chaining \ * \ * @par Example: \ @@ -600,8 +602,8 @@ class QIRProgramBuilder { * (!llvm.ptr, !llvm.ptr, !llvm.ptr) -> () \ * ``` \ */ \ - QIRProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ - Value qubit1); + QIRProgramBuilder& mc##OP_NAME(ValueRange controls, Value target0, \ + Value target1); DECLARE_TWO_TARGET_ZERO_PARAMETER(swap, swap) DECLARE_TWO_TARGET_ZERO_PARAMETER(iswap, iswap) @@ -637,7 +639,8 @@ class QIRProgramBuilder { * \ * @param PARAM Rotation angle in radians \ * @param control Control qubit \ - * @param target Target qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ * @return Reference to this builder for method chaining \ * \ * @par Example: \ @@ -650,13 +653,14 @@ class QIRProgramBuilder { * ``` \ */ \ QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ - Value control, Value qubit0, Value qubit1); \ + Value control, Value target0, Value target1); \ /** \ * @brief Apply a multi-controlled QIR_NAME operation \ * \ * @param PARAM Rotation angle in radians \ * @param controls Control qubits \ - * @param target Target qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ * @return Reference to this builder for method chaining \ * \ * @par Example: \ @@ -665,18 +669,92 @@ class QIRProgramBuilder { * ``` \ * ```mlir \ * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %q3, \ - * %PARAM) : \ - * (!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ + * %PARAM) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64) -> () \ * ``` \ */ \ QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ - ValueRange controls, Value qubit0, \ - Value qubit1); + ValueRange controls, Value target0, \ + Value target1); DECLARE_TWO_TARGET_ONE_PARAMETER(rxx, rxx, theta) #undef DECLARE_TWO_TARGET_ONE_PARAMETER + // TwoTargetTwoParameter + +#define DECLARE_TWO_TARGET_TWO_PARAMETER(OP_NAME, QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%q0, %q1, %PARAM1, %PARAM2) \ + * : (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q0, %q1, %q2, %PARAM1, \ + * %PARAM2) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target0, Value target1); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param target0 Target qubit \ + * @param target1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %q2, %q3, \ + * %PARAM1, %PARAM2) : (!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64, \ + * f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value target0, \ + Value target1); + + DECLARE_TWO_TARGET_TWO_PARAMETER(xx_plus_yy, xx_plus_yy, theta, beta) + +#undef DECLARE_TWO_TARGET_TWO_PARAMETER + //===--------------------------------------------------------------------===// // Finalization //===--------------------------------------------------------------------===// @@ -805,6 +883,22 @@ class QIRProgramBuilder { const Value target0, const Value target1, StringRef fnName); + /** + * @brief Helper to create a two-target, two-parameter QIR operation + * + * @param parameter1 Operation parameter + * @param parameter2 Operation parameter + * @param controls Control qubits + * @param target0 Target qubit + * @param target1 Target qubit + * @param fnName Name of the QIR function to call + */ + void + createTwoTargetTwoParameter(const std::variant& parameter1, + const std::variant& parameter2, + const ValueRange controls, const Value target0, + const Value target1, StringRef fnName); + /** * @brief Generate array-based output recording in the output block * diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 8269824aaa..5934f2894d 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -116,6 +116,10 @@ static constexpr auto QIR_RXX = "__quantum__qis__rxx__body"; static constexpr auto QIR_CRXX = "__quantum__qis__crxx__body"; static constexpr auto QIR_CCRXX = "__quantum__qis__ccrxx__body"; static constexpr auto QIR_CCCRXX = "__quantum__qis__cccrxx__body"; +static constexpr auto QIR_XXPLUSYY = "__quantum__qis__xx_plus_yy__body"; +static constexpr auto QIR_CXXPLUSYY = "__quantum__qis__cxx_plus_yy__body"; +static constexpr auto QIR_CCXXPLUSYY = "__quantum__qis__ccxx_plus_yy__body"; +static constexpr auto QIR_CCCXXPLUSYY = "__quantum__qis__cccxx_plus_yy__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index be6d79933a..a638ab439c 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -640,6 +640,80 @@ class QuartzProgramBuilder final : public OpBuilder { #undef DECLARE_TWO_TARGET_ONE_PARAMETER + // TwoTargetTwoParameter + +#define DECLARE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM1, PARAM2, q0, q1); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param control Control qubit \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1, q2); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0) { \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !quartz.qubit, \ + * !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value qubit0, Value qubit1); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM1 Rotation angle in radians \ + * @param PARAM2 Rotation angle in radians \ + * @param controls Control qubits \ + * @param qubit0 Target qubit \ + * @param qubit1 Target qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2, q3); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !quartz.qubit, !quartz.qubit \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1); + + DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + +#undef DECLARE_TWO_TARGET_TWO_PARAMETER + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 5808b07115..1a5d729b69 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -240,6 +240,7 @@ def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; def OneTargetThreeParameter : TargetAndParameterArityTrait<1, 3>; def TwoTargetZeroParameter : TargetAndParameterArityTrait<2, 0>; def TwoTargetOneParameter : TargetAndParameterArityTrait<2, 1>; +def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; //===----------------------------------------------------------------------===// // Unitary Operations @@ -751,6 +752,32 @@ def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter] ]; } +def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX+YY gate to two qubits"; + let description = [{ + Applies an XX+YY gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.xx_plus_yy(%theta, %beta) %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "xx_plus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index dfb917ec35..ffea17a964 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -225,13 +225,31 @@ inline DenseElementsAttr getMatrixECR(MLIRContext* ctx) { inline DenseElementsAttr getMatrixRXX(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex z = 0.0 + 0i; - const std::complex c = std::cos(theta / 2.0) + 0i; - const std::complex s = -1i * std::sin(theta / 2.0); - const auto matrix = {c, z, z, s, // row 0 - z, c, s, z, // row 1 - z, s, c, z, // row 2 - s, z, z, c}; // row 3 + const std::complex m0 = 0.0 + 0i; + const std::complex mc = std::cos(theta / 2.0) + 0i; + const std::complex ms = -1i * std::sin(theta / 2.0); + const auto matrix = {mc, m0, m0, ms, // row 0 + m0, mc, ms, m0, // row 1 + m0, ms, mc, m0, // row 2 + ms, m0, m0, mc}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixXXPlusYY(MLIRContext* ctx, double theta, + double beta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex m0 = 0.0 + 0i; + const std::complex m1 = 1.0 + 0i; + const std::complex mc = std::cos(theta / 2.0) + 0i; + const std::complex msp = + -1i * std::sin(theta / 2.0) * std::exp(1i * beta); + const std::complex msm = + -1i * std::sin(theta / 2.0) * std::exp(-1i * beta); + const auto matrix = {m1, m0, m0, m0, // row 0 + m0, mc, msm, m0, // row 1 + m0, msp, mc, m0, // row 2 + m0, m0, m0, m1}; // row 3 return DenseElementsAttr::get(type, matrix); } diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index f605fb8630..63d99f4edb 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -209,6 +209,36 @@ convertTwoTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a two-target, two-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @param op The Flux operation instance to convert + * @param adaptor The OpAdaptor of the Flux operation + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertTwoTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter) { + // OpAdaptor provides the already type-converted input qubits + const auto& quartzQubit0 = adaptor.getQubit0In(); + const auto& quartzQubit1 = adaptor.getQubit1In(); + + // Create the Quartz operation (in-place, no result) + rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1, + op.getOperand(2), op.getOperand(3)); + + // Replace the output qubits with the same Quartz references + rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + + return success(); +} + } // namespace /** @@ -614,6 +644,37 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetTwoParameter(op, adaptor, \ + rewriter); \ + } \ + }; + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + /** * @brief Converts quartz.ctrl to flux.ctrl * @@ -722,17 +783,17 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns - .add( - typeConverter, context); + patterns.add( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 1dcb9d6236..c650a72f00 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -387,6 +387,58 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, return success(); } +/** + * @brief Converts a two-target, two-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + auto& qubitMap = state.qubitMap; + const auto inCtrlOp = state.inCtrlOp; + + // Get the latest Flux qubits + const auto quartzQubit0 = op->getOperand(0); + const auto quartzQubit1 = op->getOperand(1); + Value fluxQubit0 = nullptr; + Value fluxQubit1 = nullptr; + if (inCtrlOp == 0) { + fluxQubit0 = qubitMap[quartzQubit0]; + fluxQubit1 = qubitMap[quartzQubit1]; + } else { + const auto& targetsIn = state.targetsIn[inCtrlOp]; + fluxQubit0 = targetsIn[0]; + fluxQubit1 = targetsIn[1]; + } + + // Create the Flux operation (consumes input, produces output) + auto fluxOp = + rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1, + op->getOperand(2), op->getOperand(3)); + + // Update state map + if (inCtrlOp == 0) { + qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); + qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + } else { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut( + {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + + rewriter.eraseOp(op); + + return success(); +} + } // namespace /** @@ -843,6 +895,38 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * %q0_out, %q1_out = flux.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertTwoTargetTwoParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + /** * @brief Converts quartz.ctrl to flux.ctrl * @@ -990,18 +1074,18 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { // Register operation conversion patterns with state // tracking - patterns.add(typeConverter, - context, &state); + patterns.add< + ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, + ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, + ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, + ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, + ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, + ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, + ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, ConvertQuartzUOp, + ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, + ConvertQuartzECROp, ConvertQuartzRXXOp, ConvertQuartzXXPlusYYOp, + ConvertQuartzCtrlOp, ConvertQuartzYieldOp>(typeConverter, context, + &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index e77c539f76..5253da3022 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -487,6 +487,71 @@ convertTwoTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, return success(); } +/** + * @brief Converts a two-target, two-parameter Quartz operation to QIR + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param adaptor The OpAdaptor of the Quartz operation + * @param rewriter The pattern rewriter + * @param ctx The MLIR context + * @param state The lowering state + * @param fnName The name of the QIR function to call + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertTwoTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName) { + // Query state for modifier information + const auto inCtrlOp = state.inCtrlOp; + const SmallVector posCtrls = + inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; + const size_t numCtrls = posCtrls.size(); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(numCtrls + 4); + const auto ptrType = LLVM::LLVMPointerType::get(ctx); + const auto floatType = Float64Type::get(ctx); + // Add control pointers + for (size_t i = 0; i < numCtrls; ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); + // Add parameter types + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); + + // Define function signature + const auto fnSignature = + LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); + + // Declare QIR function + const auto fnDecl = + getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); + + SmallVector operands; + operands.reserve(numCtrls + 4); + operands.append(posCtrls.begin(), posCtrls.end()); + operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); + + // Clean up modifier information + if (inCtrlOp != 0) { + state.posCtrls.erase(inCtrlOp); + state.inCtrlOp--; + } + + // Replace operation with CallOp + rewriter.replaceOpWithNewOp(op, fnDecl, operands); + return success(); +} + } // namespace /** @@ -797,7 +862,7 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { // OneTargetOneParameter -#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME) \ /** \ * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ @@ -846,23 +911,23 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } \ }; -DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id, ID, i) -DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, x, X, x) -DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, y, Y, y) -DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, z, Z, z) -DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, h, H, h) -DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, s, S, s) -DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, sdg, SDG, sdg) -DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, t, T, t) -DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, tdg, TDG, tdg) -DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, sx, SX, sx) -DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg, SXDG, sxdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, ID, id, i) +DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, X, x, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, Y, y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, Z, z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(HOp, H, h, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SOp, S, s, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SdgOp, SDG, sdg, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TOp, T, t, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(TdgOp, TDG, tdg, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXOp, SX, sx, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, SXDG, sxdg, sxdg) #undef DEFINE_ONE_TARGET_ZERO_PARAMETER // OneTargetOneParameter -#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM) \ /** \ * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ @@ -912,16 +977,16 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg, SXDG, sxdg) } \ }; -DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, RX, rx, theta) -DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, RY, ry, theta) -DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, RZ, rz, theta) -DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, P, p, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, RX, rx, rx, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RYOp, RY, ry, ry, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZOp, RZ, rz, rz, theta) +DEFINE_ONE_TARGET_ONE_PARAMETER(POp, P, p, p, theta) #undef DEFINE_ONE_TARGET_ONE_PARAMETER // OneTargetTwoParameter -#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM1, PARAM2) \ /** \ * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ @@ -971,15 +1036,15 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, P, p, theta) } \ }; -DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, R, r, theta, phi) -DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, U2, u2, phi, lambda) +DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, R, r, r, theta, phi) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, U2, u2, u2, phi, lambda) #undef DEFINE_ONE_TARGET_TWO_PARAMETER // OneTargetThreeParameter -#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME_SMALL, \ - OP_NAME_BIG, QIR_NAME) \ +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME_BIG, \ + OP_NAME_SMALL, QIR_NAME) \ /** \ * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ @@ -1028,13 +1093,13 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, U2, u2, phi, lambda) } \ }; -DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, U, u3) +DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, U, u, u3) #undef DEFINE_ONE_TARGET_THREE_PARAMETER // TwoTargetZeroParameter -#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME) \ /** \ * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ @@ -1084,16 +1149,16 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, U, u3) } \ }; -DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap, SWAP, swap) -DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap, ISWAP, iswap) -DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, dcx, DCX, dcx) -DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr, ECR, ecr) +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, SWAP, swap, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, ISWAP, iswap, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCXOp, DCX, dcx, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) #undef DEFINE_TWO_TARGET_ZERO_PARAMETER // TwoTargetOneParameter -#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_SMALL, OP_NAME_BIG, \ +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM) \ /** \ * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ @@ -1143,10 +1208,68 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr, ECR, ecr) } \ }; -DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, RXX, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, RXX, rxx, rxx, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME, PARAM1, PARAM2) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL(%PARAM1, %PARAM2) %q1, %q2 : !quartz.qubit, \ + * !quartz.qubit \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%q1, %q2, %PARAM1, %PARAM2) : \ + * (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + \ + /* Query state for modifier information */ \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + \ + /* Define function name */ \ + StringRef fnName; \ + if (inCtrlOp == 0) { \ + fnName = QIR_##OP_NAME_BIG; \ + } else { \ + if (numCtrls == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (numCtrls == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (numCtrls == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + return failure(); \ + } \ + } \ + \ + return convertTwoTargetTwoParameter(op, adaptor, rewriter, getContext(), \ + state, fnName); \ + } \ + }; + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, XXPLUSYY, xx_plus_yy, xx_plus_yy, + theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + /** * @brief Inlines quartz.ctrl region removes the operation */ @@ -1536,6 +1659,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, + &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 33c54268ad..b41227cde2 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -422,6 +422,53 @@ FluxProgramBuilder::createMultiControlledTwoTargetOneParameter( return {controlsOut, {targetsOut[0], targetsOut[1]}}; } +// TwoTargetTwoParameter helpers + +template +std::pair FluxProgramBuilder::createTwoTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const Value qubit0, + const Value qubit1) { + auto op = create(loc, qubit0, qubit1, parameter1, parameter2); + const auto& qubit0Out = op.getQubit0Out(); + const auto& qubit1Out = op.getQubit1Out(); + updateQubitTracking(qubit0, qubit0Out); + updateQubitTracking(qubit1, qubit1Out); + return {qubit0Out, qubit1Out}; +} + +template +std::pair> +FluxProgramBuilder::createControlledTwoTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const Value control, + const Value qubit0, const Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(control, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], targets[1], + parameter1, parameter2); + return op->getResults(); + }); + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; +} + +template +std::pair> +FluxProgramBuilder::createMultiControlledTwoTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const ValueRange controls, + const Value qubit0, const Value qubit1) { + const auto [controlsOut, targetsOut] = + ctrl(controls, {qubit0, qubit1}, + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { + const auto op = b.create(loc, targets[0], targets[1], + parameter1, parameter2); + return op->getResults(); + }); + return {controlsOut, {targetsOut[0], targetsOut[1]}}; +} + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -590,6 +637,36 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + std::pair FluxProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit0, \ + Value qubit1) { \ + return createTwoTargetTwoParameter(PARAM1, PARAM2, qubit0, \ + qubit1); \ + } \ + std::pair> FluxProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value control, \ + Value qubit0, Value qubit1) { \ + return createControlledTwoTargetTwoParameter( \ + PARAM1, PARAM2, control, qubit0, qubit1); \ + } \ + std::pair> \ + FluxProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const ValueRange controls, Value qubit0, Value qubit1) { \ + return createMultiControlledTwoTargetTwoParameter( \ + PARAM1, PARAM2, controls, qubit0, qubit1); \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp new file mode 100644 index 0000000000..fda412d6da --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr XXPlusYYOp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto beta = getStaticParameter(getBeta()); + if (!theta || !beta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto betaValue = beta.getValueAsDouble(); + return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); +} + +void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value betaOperand = nullptr; + if (std::holds_alternative(beta)) { + betaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); + } else { + betaOperand = std::get(beta); + } + + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 1f16dd81d3..d1ff917c1b 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -583,6 +583,79 @@ void QIRProgramBuilder::createTwoTargetOneParameter( builder.create(loc, fnDecl, operands); } +void QIRProgramBuilder::createTwoTargetTwoParameter( + const std::variant& parameter1, + const std::variant& parameter2, const ValueRange controls, + const Value target0, const Value target1, StringRef fnName) { + // Save current insertion point + const OpBuilder::InsertionGuard entryGuard(builder); + + // Insert constants in entry block + builder.setInsertionPointToEnd(entryBlock); + + Value parameter1Operand; + if (std::holds_alternative(parameter1)) { + parameter1Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter1))) + .getResult(); + } else { + parameter1Operand = std::get(parameter1); + } + + Value parameter2Operand; + if (std::holds_alternative(parameter2)) { + parameter2Operand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter2))) + .getResult(); + } else { + parameter2Operand = std::get(parameter2); + } + + // Save current insertion point + const OpBuilder::InsertionGuard bodyGuard(builder); + + // Insert in body block (before branch) + builder.setInsertionPoint(bodyBlock->getTerminator()); + + // Define argument types + SmallVector argumentTypes; + argumentTypes.reserve(controls.size() + 4); + const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); + const auto floatType = Float64Type::get(builder.getContext()); + // Add control pointers + for (size_t i = 0; i < controls.size(); ++i) { + argumentTypes.push_back(ptrType); + } + // Add target pointers + argumentTypes.push_back(ptrType); + argumentTypes.push_back(ptrType); + // Add parameter types + argumentTypes.push_back(floatType); + argumentTypes.push_back(floatType); + + // Define function signature + const auto fnSignature = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); + + // Declare QIR function + auto fnDecl = + getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); + + SmallVector operands; + operands.reserve(controls.size() + 4); + operands.append(controls.begin(), controls.end()); + operands.push_back(target0); + operands.push_back(target1); + operands.push_back(parameter1Operand); + operands.push_back(parameter2Operand); + + builder.create(loc, fnDecl, operands); +} + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ @@ -848,6 +921,53 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RXX, rxx, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM1, \ + PARAM2) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value target0, \ + const Value target1) { \ + createTwoTargetTwoParameter(PARAM1, PARAM2, {}, target0, target1, \ + QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const Value control, \ + const Value target0, const Value target1) { \ + createTwoTargetTwoParameter(PARAM1, PARAM2, {control}, target0, target1, \ + QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), const ValueRange controls, \ + const Value target0, const Value target1) { \ + StringRef fnName; \ + if (controls.size() == 1) { \ + fnName = QIR_C##OP_NAME_BIG; \ + } else if (controls.size() == 2) { \ + fnName = QIR_CC##OP_NAME_BIG; \ + } else if (controls.size() == 3) { \ + fnName = QIR_CCC##OP_NAME_BIG; \ + } else { \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + createTwoTargetTwoParameter(PARAM1, PARAM2, controls, target0, target1, \ + fnName); \ + return *this; \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPLUSYY, xx_plus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index c0df64309d..83f6e331f1 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -301,6 +301,36 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit0, \ + Value qubit1) { \ + create(loc, qubit0, qubit1, PARAM1, PARAM2); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value control, Value qubit0, \ + Value qubit1) { \ + return mc##OP_NAME(PARAM1, PARAM2, {control}, qubit0, qubit1); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM1), \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1) { \ + create(loc, controls, [&](OpBuilder& b) { \ + b.create(loc, qubit0, qubit1, PARAM1, PARAM2); \ + }); \ + return *this; \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/XXPlusYYOp.cpp new file mode 100644 index 0000000000..e334caef33 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/XXPlusYYOp.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr XXPlusYYOp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto beta = getStaticParameter(getBeta()); + if (!theta || !beta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto betaValue = beta.getValueAsDouble(); + return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); +} + +void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value betaOperand = nullptr; + if (std::holds_alternative(beta)) { + betaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); + } else { + betaOperand = std::get(beta); + } + + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 426a3be100..573eb09118 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -745,6 +745,32 @@ void addRXXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an XXPlusYY operation + * + * @details + * Translate an XXPlusYY operation from the QuantumComputation to + * quartz.xx_plus_yy. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The XXPlusYY operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addXXPlusYYOp(QuartzProgramBuilder& builder, + const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& beta = operation.getParameter()[1]; + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.xx_plus_yy(theta, beta, target0, target1); + } else { + builder.mcxx_plus_yy(theta, beta, posControls, target0, target1); + } +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -845,6 +871,9 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::RXX: addRXXOp(builder, *operation, qubits); break; + case qc::OpType::XXplusYY: + addXXPlusYYOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 4b47d51be8..adf9177391 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2670,4 +2670,97 @@ TEST_F(CompilerPipelineTest, MCRXX) { }); } +TEST_F(CompilerPipelineTest, XXPLUSYY) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.xx_plus_yy(1.0, 0.5, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.xx_plus_yy(1.0, 0.5, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.xx_plus_yy(1.0, 0.5, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.xx_plus_yy(1.0, 0.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, CXXPLUSYY) { + qc::QuantumComputation qc; + qc.addQubitRegister(3, "q"); + qc.cxx_plus_yy(1.0, 0.5, 0, 1, 2); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCXXPLUSYY) { + qc::QuantumComputation qc; + qc.addQubitRegister(4, "q"); + qc.mcxx_plus_yy(1.0, 0.5, {0, 1}, 2, 3); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(4, "q"); + b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(4); + b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From f02d52e964bd889c0fd763282ecc2ec6005867db Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:44:18 +0100 Subject: [PATCH 247/419] Add support for RYY, RZX, and RZZ --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 3 + mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 81 ++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 3 + .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 12 +++ .../Quartz/Builder/QuartzProgramBuilder.h | 3 + .../mlir/Dialect/Quartz/IR/QuartzOps.td | 78 ++++++++++++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 40 ++++++++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 4 + .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 6 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 6 ++ .../Flux/Builder/FluxProgramBuilder.cpp | 3 + .../Dialect/Flux/IR/StandardGates/RYYOp.cpp | 48 ++++++++++ .../Dialect/Flux/IR/StandardGates/RZXOp.cpp | 48 ++++++++++ .../Dialect/Flux/IR/StandardGates/RZZOp.cpp | 48 ++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 3 + .../Quartz/Builder/QuartzProgramBuilder.cpp | 3 + .../Dialect/Quartz/IR/StandardGates/RYYOp.cpp | 44 +++++++++ .../Dialect/Quartz/IR/StandardGates/RZXOp.cpp | 44 +++++++++ .../Dialect/Quartz/IR/StandardGates/RZZOp.cpp | 44 +++++++++ .../TranslateQuantumComputationToQuartz.cpp | 81 ++++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 93 +++++++++++++++++++ 21 files changed, 694 insertions(+), 1 deletion(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RYYOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RZXOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/RZZOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index da4c11616d..25d90222e8 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -777,6 +777,9 @@ class FluxProgramBuilder final : public OpBuilder { Value qubit0, Value qubit1); DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #undef DECLARE_TWO_TARGET_ONE_PARAMETER diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 2565750814..b8677edddd 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -824,6 +824,87 @@ def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> ]; } +def RYYOp : FluxOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RYY gate to two qubits"; + let description = [{ + Applies an RYY gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.ryy(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ryy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RZXOp : FluxOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZX gate to two qubits"; + let description = [{ + Applies an RZX gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.rzx(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rzx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RZZOp : FluxOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZZ gate to two qubits"; + let description = [{ + Applies an RZZ gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.rzz(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rzz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 163fb46c7d..7f8d122a97 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -677,6 +677,9 @@ class QIRProgramBuilder { Value target1); DECLARE_TWO_TARGET_ONE_PARAMETER(rxx, rxx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(ryy, ryy, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(rzx, rzx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(rzz, rzz, theta) #undef DECLARE_TWO_TARGET_ONE_PARAMETER diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 5934f2894d..d547054c18 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -116,6 +116,18 @@ static constexpr auto QIR_RXX = "__quantum__qis__rxx__body"; static constexpr auto QIR_CRXX = "__quantum__qis__crxx__body"; static constexpr auto QIR_CCRXX = "__quantum__qis__ccrxx__body"; static constexpr auto QIR_CCCRXX = "__quantum__qis__cccrxx__body"; +static constexpr auto QIR_RYY = "__quantum__qis__ryy__body"; +static constexpr auto QIR_CRYY = "__quantum__qis__cryy__body"; +static constexpr auto QIR_CCRYY = "__quantum__qis__ccryy__body"; +static constexpr auto QIR_CCCRYY = "__quantum__qis__cccryy__body"; +static constexpr auto QIR_RZX = "__quantum__qis__rzx__body"; +static constexpr auto QIR_CRZX = "__quantum__qis__crzx__body"; +static constexpr auto QIR_CCRZX = "__quantum__qis__ccrzx__body"; +static constexpr auto QIR_CCCRZX = "__quantum__qis__cccrzx__body"; +static constexpr auto QIR_RZZ = "__quantum__qis__rzz__body"; +static constexpr auto QIR_CRZZ = "__quantum__qis__crzz__body"; +static constexpr auto QIR_CCRZZ = "__quantum__qis__ccrzz__body"; +static constexpr auto QIR_CCCRZZ = "__quantum__qis__cccrzz__body"; static constexpr auto QIR_XXPLUSYY = "__quantum__qis__xx_plus_yy__body"; static constexpr auto QIR_CXXPLUSYY = "__quantum__qis__cxx_plus_yy__body"; static constexpr auto QIR_CCXXPLUSYY = "__quantum__qis__ccxx_plus_yy__body"; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index a638ab439c..89c14f23d0 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -637,6 +637,9 @@ class QuartzProgramBuilder final : public OpBuilder { Value qubit1); DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) + DECLARE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #undef DECLARE_TWO_TARGET_ONE_PARAMETER diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 1a5d729b69..cd76fc115a 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -752,6 +752,84 @@ def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter] ]; } +def RYYOp : QuartzOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RYY gate to two qubits"; + let description = [{ + Applies an RYY gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.ryy(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "ryy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RZXOp : QuartzOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZX gate to two qubits"; + let description = [{ + Applies an RZX gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.rzx(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rzx"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + +def RZZOp : QuartzOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { + let summary = "Apply an RZZ gate to two qubits"; + let description = [{ + Applies an RZZ gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.rzz(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta); + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "rzz"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> + ]; +} + def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index ffea17a964..7288ab9a61 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -235,6 +235,46 @@ inline DenseElementsAttr getMatrixRXX(MLIRContext* ctx, double theta) { return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixRYY(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex m0 = 0.0 + 0i; + const std::complex mc = std::cos(theta / 2.0) + 0i; + const std::complex ms = 1i * std::sin(theta / 2.0); + const auto matrix = {mc, m0, m0, ms, // row 0 + m0, mc, -ms, m0, // row 1 + m0, -ms, mc, m0, // row 2 + ms, m0, m0, mc}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixRZX(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex m0 = 0.0 + 0i; + const std::complex mc = std::cos(theta / 2.0) + 0i; + const std::complex ms = -1i * std::sin(theta / 2.0); + const auto matrix = {mc, -ms, m0, m0, // row 0 + -ms, mc, m0, m0, // row 1 + m0, m0, mc, ms, // row 2 + m0, m0, ms, mc}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + +inline DenseElementsAttr getMatrixRZZ(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex m0 = 0.0 + 0i; + const std::complex mp = + std::exp(1i * theta / 2.0); const std::complex mm = + std::exp(-1i * theta / 2.0); + const auto matrix = {mm, m0, m0, m0, // row 0 + m0, mp, m0, m0, // row 1 + m0, m0, mp, m0, // row 2 + m0, m0, m0, mm}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixXXPlusYY(MLIRContext* ctx, double theta, double beta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 63d99f4edb..306d1317fe 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -641,6 +641,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) }; DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER @@ -792,6 +795,7 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { ConvertFluxPOp, ConvertFluxROp, ConvertFluxU2Op, ConvertFluxUOp, ConvertFluxSWAPOp, ConvertFluxiSWAPOp, ConvertFluxDCXOp, ConvertFluxECROp, ConvertFluxRXXOp, + ConvertFluxRYYOp, ConvertFluxRZXOp, ConvertFluxRZZOp, ConvertFluxXXPlusYYOp, ConvertFluxCtrlOp, ConvertFluxYieldOp>( typeConverter, context); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index c650a72f00..975694fae3 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -892,6 +892,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) }; DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER @@ -1083,7 +1086,8 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, ConvertQuartzUOp, ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, - ConvertQuartzECROp, ConvertQuartzRXXOp, ConvertQuartzXXPlusYYOp, + ConvertQuartzECROp, ConvertQuartzRXXOp, ConvertQuartzRYYOp, + ConvertQuartzRZXOp, ConvertQuartzRZZOp, ConvertQuartzXXPlusYYOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>(typeConverter, context, &state); diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 5253da3022..796fa986f3 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -1209,6 +1209,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) }; DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, RXX, rxx, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, RYY, ryy, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, RZX, rzx, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER @@ -1659,6 +1662,9 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index b41227cde2..ce4caf7eb3 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -634,6 +634,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) } DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp new file mode 100644 index 0000000000..9e78fcf33b --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr RYYOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRYY(getContext(), thetaValue); +} + +void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp new file mode 100644 index 0000000000..ed6364a8e0 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr RZXOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRZX(getContext(), thetaValue); +} + +void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp new file mode 100644 index 0000000000..99ab6c5d94 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr RZZOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRZZ(getContext(), thetaValue); +} + +void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index d1ff917c1b..cd2b6207b1 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -918,6 +918,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) } DEFINE_TWO_TARGET_ONE_PARAMETER(RXX, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYY, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZX, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 83f6e331f1..7d18ab16fe 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -298,6 +298,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) } DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZXOp, rzx, theta) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #undef DEFINE_TWO_TARGET_ONE_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RYYOp.cpp new file mode 100644 index 0000000000..b75944eb85 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RYYOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RYYOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRYY(getContext(), thetaValue); +} + +void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RZXOp.cpp new file mode 100644 index 0000000000..5078458a5f --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RZXOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RZXOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRZX(getContext(), thetaValue); +} + +void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/RZZOp.cpp new file mode 100644 index 0000000000..4624e5ba21 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/RZZOp.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr RZZOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixRZZ(getContext(), thetaValue); +} + +void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 573eb09118..ced03cb0b9 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -745,6 +745,75 @@ void addRXXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } } +/** + * @brief Adds an RYY operation + * + * @details + * Translate an RYY operation from the QuantumComputation to quartz.ryy. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RYY operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addRYYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.ryy(theta, target0, target1); + } else { + builder.mcryy(theta, posControls, target0, target1); + } +} + +/** + * @brief Adds an RZX operation + * + * @details + * Translate an RZX operation from the QuantumComputation to quartz.rzx. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RZX operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addRZXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.rzx(theta, target0, target1); + } else { + builder.mcrzx(theta, posControls, target0, target1); + } +} + +/** + * @brief Adds an RZZ operation + * + * @details + * Translate an RZZ operation from the QuantumComputation to quartz.rzz. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The RZZ operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addRZZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.rzz(theta, target0, target1); + } else { + builder.mcrzz(theta, posControls, target0, target1); + } +} + /** * @brief Adds an XXPlusYY operation * @@ -871,6 +940,18 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::RXX: addRXXOp(builder, *operation, qubits); break; + case qc::OpType::RYY: + addRYYOp(builder, *operation, qubits); + ; + break; + case qc::OpType::RZX: + addRZXOp(builder, *operation, qubits); + ; + break; + case qc::OpType::RZZ: + addRZZOp(builder, *operation, qubits); + ; + break; case qc::OpType::XXplusYY: addXXPlusYYOp(builder, *operation, qubits); break; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index adf9177391..6242fc80ff 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2670,6 +2670,99 @@ TEST_F(CompilerPipelineTest, MCRXX) { }); } +TEST_F(CompilerPipelineTest, RYY) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.ryy(1.0, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ryy(1.0, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ryy(1.0, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.ryy(1.0, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, RZX) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.rzx(1.0, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzx(1.0, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzx(1.0, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.rzx(1.0, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, RZZ) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.rzz(1.0, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzz(1.0, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzz(1.0, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.rzz(1.0, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, XXPLUSYY) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); From f4778f832543409874e057c19b995d8f1c19ffa9 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:54:57 +0100 Subject: [PATCH 248/419] Add support for XXMinusYY --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 1 + mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 28 ++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 1 + .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 ++ .../Quartz/Builder/QuartzProgramBuilder.h | 1 + .../mlir/Dialect/Quartz/IR/QuartzOps.td | 26 +++++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 31 +++++++--- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 24 ++++---- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 5 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 ++ .../Flux/Builder/FluxProgramBuilder.cpp | 1 + .../Flux/IR/StandardGates/XXMinusYYOp.cpp | 56 +++++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 1 + .../Quartz/Builder/QuartzProgramBuilder.cpp | 1 + .../Quartz/IR/StandardGates/XXMinusYYOp.cpp | 56 +++++++++++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 34 ++++++++++- .../pipeline/test_compiler_pipeline.cpp | 31 ++++++++++ 17 files changed, 281 insertions(+), 24 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/StandardGates/XXMinusYYOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 25d90222e8..edf8cfe8aa 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -878,6 +878,7 @@ class FluxProgramBuilder final : public OpBuilder { Value qubit0, Value qubit1); DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DECLARE_TWO_TARGET_TWO_PARAMETER diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index b8677edddd..a05c8aecd1 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -933,6 +933,34 @@ def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwo ]; } +def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX-YY gate to two qubits"; + let description = [{ + Applies an XX-YY gate to two qubits and returns the transformed qubits. + + Example: + ```mlir + %q0_out, %q1_out = flux.xx_minus_yy(%theta, %beta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "xx_minus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 7f8d122a97..2fe6d46fad 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -755,6 +755,7 @@ class QIRProgramBuilder { Value target1); DECLARE_TWO_TARGET_TWO_PARAMETER(xx_plus_yy, xx_plus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER(xx_minus_yy, xx_minus_yy, theta, beta) #undef DECLARE_TWO_TARGET_TWO_PARAMETER diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index d547054c18..d1d260e83b 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -132,6 +132,10 @@ static constexpr auto QIR_XXPLUSYY = "__quantum__qis__xx_plus_yy__body"; static constexpr auto QIR_CXXPLUSYY = "__quantum__qis__cxx_plus_yy__body"; static constexpr auto QIR_CCXXPLUSYY = "__quantum__qis__ccxx_plus_yy__body"; static constexpr auto QIR_CCCXXPLUSYY = "__quantum__qis__cccxx_plus_yy__body"; +static constexpr auto QIR_XXMINUSYY = "__quantum__qis__xx_minus_yy__body"; +static constexpr auto QIR_CXXMINUSYY = "__quantum__qis__cxx_minus_yy__body"; +static constexpr auto QIR_CCXXMINUSYY = "__quantum__qis__ccxx_minus_yy__body"; +static constexpr auto QIR_CCCXXMINUSYY = "__quantum__qis__cccxx_minus_yy__body"; /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 89c14f23d0..70cdade547 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -714,6 +714,7 @@ class QuartzProgramBuilder final : public OpBuilder { Value qubit0, Value qubit1); DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) + DECLARE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DECLARE_TWO_TARGET_TWO_PARAMETER diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index cd76fc115a..b3e397f3b3 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -856,6 +856,32 @@ def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetT ]; } +def XXMinusYYOp : QuartzOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { + let summary = "Apply an XX-YY gate to two qubits"; + let description = [{ + Applies an XX-YY gate to two qubits, modifying them in place. + + Example: + ```mlir + quartz.xx_minus_yy(%theta, %beta) %q0, %q1 : !flux.qubit, !flux.qubit + ``` + }]; + + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in, + Arg:$theta, + Arg:$beta); + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "xx_minus_yy"; } + }]; + + let builders = [ + OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 7288ab9a61..49d9492b69 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -265,13 +265,12 @@ inline DenseElementsAttr getMatrixRZZ(MLIRContext* ctx, double theta) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({4, 4}, complexType); const std::complex m0 = 0.0 + 0i; - const std::complex mp = - std::exp(1i * theta / 2.0); const std::complex mm = - std::exp(-1i * theta / 2.0); - const auto matrix = {mm, m0, m0, m0, // row 0 - m0, mp, m0, m0, // row 1 - m0, m0, mp, m0, // row 2 - m0, m0, m0, mm}; // row 3 + const std::complex mp = std::exp(1i * theta / 2.0); + const std::complex mm = std::exp(-1i * theta / 2.0); + const auto matrix = {mm, m0, m0, m0, // row 0 + m0, mp, m0, m0, // row 1 + m0, m0, mp, m0, // row 2 + m0, m0, m0, mm}; // row 3 return DenseElementsAttr::get(type, matrix); } @@ -293,6 +292,24 @@ inline DenseElementsAttr getMatrixXXPlusYY(MLIRContext* ctx, double theta, return DenseElementsAttr::get(type, matrix); } +inline DenseElementsAttr getMatrixXXMinusYY(MLIRContext* ctx, double theta, + double beta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({4, 4}, complexType); + const std::complex m0 = 0.0 + 0i; + const std::complex m1 = 1.0 + 0i; + const std::complex mc = std::cos(theta / 2.0) + 0i; + const std::complex msp = + std::sin(theta / 2.0) * std::exp(-1i * beta) + 0i; + const std::complex msm = + -std::sin(theta / 2.0) * std::exp(1i * beta) + 0i; + const auto matrix = {mc, m0, m0, msm, // row 0 + m0, m1, m0, m0, // row 1 + m0, m0, m1, m0, // row 2 + msp, m0, m0, mc}; // row 3 + return DenseElementsAttr::get(type, matrix); +} + inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, size_t numControls, mlir::DenseElementsAttr target) { diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 306d1317fe..d8dc8b0770 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -675,6 +675,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) }; DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER @@ -786,18 +787,17 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add( - typeConverter, context); + patterns.add< + ConvertFluxAllocOp, ConvertFluxDeallocOp, ConvertFluxStaticOp, + ConvertFluxMeasureOp, ConvertFluxResetOp, ConvertFluxIdOp, + ConvertFluxXOp, ConvertFluxYOp, ConvertFluxZOp, ConvertFluxHOp, + ConvertFluxSOp, ConvertFluxSdgOp, ConvertFluxTOp, ConvertFluxTdgOp, + ConvertFluxSXOp, ConvertFluxSXdgOp, ConvertFluxRXOp, ConvertFluxRYOp, + ConvertFluxRZOp, ConvertFluxPOp, ConvertFluxROp, ConvertFluxU2Op, + ConvertFluxUOp, ConvertFluxSWAPOp, ConvertFluxiSWAPOp, ConvertFluxDCXOp, + ConvertFluxECROp, ConvertFluxRXXOp, ConvertFluxRYYOp, ConvertFluxRZXOp, + ConvertFluxRZZOp, ConvertFluxXXPlusYYOp, ConvertFluxXXMinusYYOp, + ConvertFluxCtrlOp, ConvertFluxYieldOp>(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 975694fae3..b8c8342a01 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -927,6 +927,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) }; DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER @@ -1088,8 +1089,8 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, ConvertQuartzECROp, ConvertQuartzRXXOp, ConvertQuartzRYYOp, ConvertQuartzRZXOp, ConvertQuartzRZZOp, ConvertQuartzXXPlusYYOp, - ConvertQuartzCtrlOp, ConvertQuartzYieldOp>(typeConverter, context, - &state); + ConvertQuartzXXMinusYYOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( + typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 796fa986f3..3f60195c7a 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -1270,6 +1270,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, XXPLUSYY, xx_plus_yy, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, XXMINUSYY, xx_minus_yy, + xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER @@ -1667,6 +1669,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, + &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index ce4caf7eb3..428613a1ab 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -667,6 +667,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) } DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp new file mode 100644 index 0000000000..65bd4d2360 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +DenseElementsAttr XXMinusYYOp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto beta = getStaticParameter(getBeta()); + if (!theta || !beta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto betaValue = beta.getValueAsDouble(); + return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); +} + +void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value betaOperand = nullptr; + if (std::holds_alternative(beta)) { + betaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); + } else { + betaOperand = std::get(beta); + } + + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index cd2b6207b1..dc0adf86df 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -968,6 +968,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) } DEFINE_TWO_TARGET_TWO_PARAMETER(XXPLUSYY, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMINUSYY, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 7d18ab16fe..86b9b3beb4 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -331,6 +331,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) } DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/StandardGates/XXMinusYYOp.cpp new file mode 100644 index 0000000000..bccd242be4 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/StandardGates/XXMinusYYOp.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr XXMinusYYOp::tryGetStaticMatrix() { + const auto theta = getStaticParameter(getTheta()); + const auto beta = getStaticParameter(getBeta()); + if (!theta || !beta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + const auto betaValue = beta.getValueAsDouble(); + return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); +} + +void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const Value qubit0In, const Value qubit1In, + const std::variant& theta, + const std::variant& beta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + + Value betaOperand = nullptr; + if (std::holds_alternative(beta)) { + betaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); + } else { + betaOperand = std::get(beta); + } + + build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index ced03cb0b9..db0971cca6 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -815,14 +815,14 @@ void addRZZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, } /** - * @brief Adds an XXPlusYY operation + * @brief Adds an XX+YY operation * * @details - * Translate an XXPlusYY operation from the QuantumComputation to + * Translate an XX+YY operation from the QuantumComputation to * quartz.xx_plus_yy. * * @param builder The QuartzProgramBuilder used to create operations - * @param operation The XXPlusYY operation to translate + * @param operation The XX+YY operation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addXXPlusYYOp(QuartzProgramBuilder& builder, @@ -840,6 +840,32 @@ void addXXPlusYYOp(QuartzProgramBuilder& builder, } } +/** + * @brief Adds an XX-YY operation + * + * @details + * Translate an XX-YY operation from the QuantumComputation to + * quartz.xx_minus_yy. + * + * @param builder The QuartzProgramBuilder used to create operations + * @param operation The XX-YY operation to translate + * @param qubits Flat vector of qubit values indexed by physical qubit index + */ +void addXXMinusYYOp(QuartzProgramBuilder& builder, + const qc::Operation& operation, + const llvm::SmallVector& qubits) { + const auto& theta = operation.getParameter()[0]; + const auto& beta = operation.getParameter()[1]; + const auto& target0 = qubits[operation.getTargets()[0]]; + const auto& target1 = qubits[operation.getTargets()[1]]; + if (const auto& posControls = getPosControls(operation, qubits); + posControls.empty()) { + builder.xx_minus_yy(theta, beta, target0, target1); + } else { + builder.mcxx_minus_yy(theta, beta, posControls, target0, target1); + } +} + /** * @brief Translates operations from QuantumComputation to Quartz dialect * @@ -955,6 +981,8 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::XXplusYY: addXXPlusYYOp(builder, *operation, qubits); break; + case qc::OpType::XXminusYY: + addXXMinusYYOp(builder, *operation, qubits); default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 6242fc80ff..55e59a3d3e 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2856,4 +2856,35 @@ TEST_F(CompilerPipelineTest, MCXXPLUSYY) { }); } +TEST_F(CompilerPipelineTest, XXMINUSYY) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.xx_minus_yy(1.0, 0.5, 0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.xx_minus_yy(1.0, 0.5, reg[0], reg[1]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.xx_minus_yy(1.0, 0.5, reg[0], reg[1]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.xx_minus_yy(1.0, 0.5, reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From 0800cb7b5a16ead47165854a838f1c4efa232908 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 04:57:11 +0100 Subject: [PATCH 249/419] Remove unnecessary header inclusions --- mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp | 3 --- 5 files changed, 11 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp index 940b536a02..99569412f2 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include using namespace mlir; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp index 9e78fcf33b..4349b75ed9 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include using namespace mlir; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp index ed6364a8e0..e8be60be30 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include using namespace mlir; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp index 99ab6c5d94..ca585d6b1e 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include using namespace mlir; diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp index 23f846e00a..47566fb961 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp @@ -16,9 +16,6 @@ #include #include #include -#include -#include -#include #include using namespace mlir; From f3085a12b713cfc467c2c3eb9d760da57b6302a4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:02:27 +0100 Subject: [PATCH 250/419] Fix linter error --- .../Translation/TranslateQuantumComputationToQuartz.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index db0971cca6..40ba9383e6 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -968,21 +968,19 @@ translateOperations(QuartzProgramBuilder& builder, break; case qc::OpType::RYY: addRYYOp(builder, *operation, qubits); - ; break; case qc::OpType::RZX: addRZXOp(builder, *operation, qubits); - ; break; case qc::OpType::RZZ: addRZZOp(builder, *operation, qubits); - ; break; case qc::OpType::XXplusYY: addXXPlusYYOp(builder, *operation, qubits); break; case qc::OpType::XXminusYY: addXXMinusYYOp(builder, *operation, qubits); + break; default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported From dfa7a78d12a44163d95ba15c22ae37a490d8fac0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:47:02 +0100 Subject: [PATCH 251/419] Get rid of Flux helpers because they are no longer helpful --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 329 ------------ .../Flux/Builder/FluxProgramBuilder.cpp | 476 ++++++------------ 2 files changed, 142 insertions(+), 663 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index edf8cfe8aa..206bce75cd 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -958,335 +958,6 @@ class FluxProgramBuilder final : public OpBuilder { Location loc; ModuleOp module; - /** - * @brief Helper to create a one-target, zero-parameter Flux operation - * - * @tparam OpType The operation type of the Flux operation - * @param qubit Input qubit - * @return Output qubit - */ - template - Value createOneTargetZeroParameter(const Value qubit); - - /** - * @brief Helper to create a controlled one-target, zero-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param control Input control qubit - * @param target Input target qubit - * @return Pair of (output_control_qubit, output_target_qubit) - */ - template - std::pair - createControlledOneTargetZeroParameter(const Value control, - const Value target); - - /** - * @brief Helper to create a multi-controlled one-target, zero-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param controls Input control qubits - * @param target Input target qubit - * @return Pair of (output_control_qubits, output_target_qubit) - */ - template - std::pair - createMultiControlledOneTargetZeroParameter(const ValueRange controls, - const Value target); - - /** - * @brief Helper to create a one-target, one-parameter Flux operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter Operation parameter - * @param qubit Input qubit - * @return Output qubit - */ - template - Value - createOneTargetOneParameter(const std::variant& parameter, - const Value qubit); - - /** - * @brief Helper to create a controlled one-target, one-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter Operation parameter - * @param control Input control qubit - * @param target Input target qubit - * @return Pair of (output_control_qubit, output_target_qubit) - */ - template - std::pair createControlledOneTargetOneParameter( - const std::variant& parameter, const Value control, - const Value target); - - /** - * @brief Helper to create a multi-controlled one-target, one-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter Operation parameter - * @param controls Input control qubits - * @param target Input target qubit - * @return Pair of (output_control_qubits, output_target_qubit) - */ - template - std::pair createMultiControlledOneTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value target); - - /** - * @brief Helper to create a one-target, two-parameter Flux operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param qubit Input qubit - * @return Output qubit - */ - template - Value - createOneTargetTwoParameter(const std::variant& parameter1, - const std::variant& parameter2, - const Value qubit); - - /** - * @brief Helper to create a controlled one-target, two-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param control Input control qubit - * @param target Input target qubit - * @return Pair of (output_control_qubit, output_target_qubit) - */ - template - std::pair createControlledOneTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const Value control, - const Value target); - - /** - * @brief Helper to create a multi-controlled one-target, two-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param controls Input control qubits - * @param target Input target qubit - * @return Pair of (output_control_qubits, output_target_qubit) - */ - template - std::pair createMultiControlledOneTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const ValueRange controls, - const Value target); - - /** - * @brief Helper to create a one-target, three-parameter Flux operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param parameter3 Operation parameter - * @param qubit Input qubit - * @return Output qubit - */ - template - Value - createOneTargetThreeParameter(const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, - const Value qubit); - - /** - * @brief Helper to create a controlled one-target, three-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param parameter3 Operation parameter - * @param control Input control qubit - * @param target Input target qubit - * @return Pair of (output_control_qubit, output_target_qubit) - */ - template - std::pair createControlledOneTargetThreeParameter( - const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, const Value control, - const Value target); - - /** - * @brief Helper to create a multi-controlled one-target, three-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param parameter3 Operation parameter - * @param controls Input control qubits - * @param target Input target qubit - * @return Pair of (output_control_qubits, output_target_qubit) - */ - template - std::pair createMultiControlledOneTargetThreeParameter( - const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, const ValueRange controls, - const Value target); - - /** - * @brief Helper to create a two-target, zero-parameter Flux operation - * @tparam OpType The operation type of the Flux operation - * @param qubit0 Input qubit - * @param qubit1 Input qubit - * @return Pair of (output_qubit0, output_qubit1) - */ - template - std::pair createTwoTargetZeroParameter(const Value qubit0, - const Value qubit1); - - /** - * @brief Helper to create a controlled two-target, zero-parameter Flux - * operation - * @tparam OpType The operation type of the Flux operation - * @param control Input control qubit - * @param qubit0 Input target qubit - * @param qubit1 Input target qubit - * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) - */ - template - std::pair> - createControlledTwoTargetZeroParameter(const Value control, - const Value qubit0, - const Value qubit1); - - /** - * @brief Helper to create a multi-controlled two-target, zero-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param controls Input control qubits - * @param qubit0 Input target qubit - * @param qubit1 Input target qubit - * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) - */ - template - std::pair> - createMultiControlledTwoTargetZeroParameter(const ValueRange controls, - const Value qubit0, - const Value qubit1); - - /** - * @brief Helper to create a two-target, one-parameter Flux operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter Operation parameter - * @param qubit0 Input qubit - * @param qubit1 Input qubit - * @return Pair of (output_qubit0, output_qubit1) - */ - template - std::pair - createTwoTargetOneParameter(const std::variant& parameter, - const Value qubit0, const Value qubit1); - - /** - * @brief Helper to create a controlled two-target, one-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter Operation parameter - * @param control Input control qubit - * @param qubit0 Input target qubit - * @param qubit1 Input target qubit - * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) - */ - template - std::pair> - createControlledTwoTargetOneParameter( - const std::variant& parameter, const Value control, - const Value qubit0, const Value qubit1); - - /** - * @brief Helper to create a multi-controlled two-target, one-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter Operation parameter - * @param controls Input control qubits - * @param qubit0 Input target qubit - * @param qubit1 Input target qubit - * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) - */ - template - std::pair> - createMultiControlledTwoTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value qubit0, const Value qubit1); - - /** - * @brief Helper to create a two-target, two-parameter Flux operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param qubit0 Input qubit - * @param qubit1 Input qubit - * @return Pair of (output_qubit0, output_qubit1) - */ - template - std::pair - createTwoTargetTwoParameter(const std::variant& parameter1, - const std::variant& parameter2, - const Value qubit0, const Value qubit1); - - /** - * @brief Helper to create a controlled two-target, two-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param control Input control qubit - * @param qubit0 Input target qubit - * @param qubit1 Input target qubit - * @return Pair of (output_control_qubit, (output_qubit0, output_qubit1)) - */ - template - std::pair> - createControlledTwoTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const Value control, - const Value qubit0, const Value qubit1); - - /** - * @brief Helper to create a multi-controlled two-target, two-parameter Flux - * operation - * - * @tparam OpType The operation type of the Flux operation - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param controls Input control qubits - * @param qubit0 Input target qubit - * @param qubit1 Input target qubit - * @return Pair of (output_control_qubits, (output_qubit0, output_qubit1)) - */ - template - std::pair> - createMultiControlledTwoTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const ValueRange controls, - const Value qubit0, const Value qubit1); - //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 428613a1ab..06c79ea11a 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -171,318 +171,34 @@ Value FluxProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// -// OneTargetZeroParameter helpers - -template -Value FluxProgramBuilder::createOneTargetZeroParameter(const Value qubit) { - auto op = create(loc, qubit); - const auto& qubitOut = op.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; -} - -template -std::pair -FluxProgramBuilder::createControlledOneTargetZeroParameter(const Value control, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0]); - return op->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; -} - -template -std::pair -FluxProgramBuilder::createMultiControlledOneTargetZeroParameter( - const ValueRange controls, const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0]); - return op->getResults(); - }); - return {controlsOut, targetsOut[0]}; -} - -// OneTargetOneParameter helpers - -template -Value FluxProgramBuilder::createOneTargetOneParameter( - const std::variant& parameter, const Value qubit) { - auto op = create(loc, qubit, parameter); - const auto& qubitOut = op.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; -} - -template -std::pair -FluxProgramBuilder::createControlledOneTargetOneParameter( - const std::variant& parameter, const Value control, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], parameter); - return op->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; -} - -template -std::pair -FluxProgramBuilder::createMultiControlledOneTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], parameter); - return op->getResults(); - }); - return {controlsOut, targetsOut[0]}; -} - -// OneTargetTwoParameter helpers - -template -Value FluxProgramBuilder::createOneTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const Value qubit) { - auto op = create(loc, qubit, parameter1, parameter2); - const auto& qubitOut = op.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; -} - -template -std::pair -FluxProgramBuilder::createControlledOneTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const Value control, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = - b.create(loc, targets[0], parameter1, parameter2); - return op->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; -} - -template -std::pair -FluxProgramBuilder::createMultiControlledOneTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const ValueRange controls, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = - b.create(loc, targets[0], parameter1, parameter2); - return op->getResults(); - }); - return {controlsOut, targetsOut[0]}; -} - -// OneTargetThreeParameter helpers - -template -Value FluxProgramBuilder::createOneTargetThreeParameter( - const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, const Value qubit) { - auto op = create(loc, qubit, parameter1, parameter2, parameter3); - const auto& qubitOut = op.getQubitOut(); - updateQubitTracking(qubit, qubitOut); - return qubitOut; -} - -template -std::pair -FluxProgramBuilder::createControlledOneTargetThreeParameter( - const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, const Value control, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(control, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], parameter1, - parameter2, parameter3); - return op->getResults(); - }); - return {controlsOut[0], targetsOut[0]}; -} - -template -std::pair -FluxProgramBuilder::createMultiControlledOneTargetThreeParameter( - const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, const ValueRange controls, - const Value target) { - const auto [controlsOut, targetsOut] = - ctrl(controls, target, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], parameter1, - parameter2, parameter3); - return op->getResults(); - }); - return {controlsOut, targetsOut[0]}; -} - -// TwoTargetZeroParameter helpers - -template -std::pair -FluxProgramBuilder::createTwoTargetZeroParameter(const Value qubit0, - const Value qubit1) { - auto op = create(loc, qubit0, qubit1); - const auto& qubit0Out = op.getQubit0Out(); - const auto& qubit1Out = op.getQubit1Out(); - updateQubitTracking(qubit0, qubit0Out); - updateQubitTracking(qubit1, qubit1Out); - return {qubit0Out, qubit1Out}; -} - -template -std::pair> -FluxProgramBuilder::createControlledTwoTargetZeroParameter(const Value control, - const Value qubit0, - const Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(control, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], targets[1]); - return op->getResults(); - }); - return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; -} - -template -std::pair> -FluxProgramBuilder::createMultiControlledTwoTargetZeroParameter( - const ValueRange controls, const Value qubit0, const Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(controls, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], targets[1]); - return op->getResults(); - }); - return {controlsOut, {targetsOut[0], targetsOut[1]}}; -} - -// TwoTargetOneParameter helpers - -template -std::pair FluxProgramBuilder::createTwoTargetOneParameter( - const std::variant& parameter, const Value qubit0, - const Value qubit1) { - auto op = create(loc, qubit0, qubit1, parameter); - const auto& qubit0Out = op.getQubit0Out(); - const auto& qubit1Out = op.getQubit1Out(); - updateQubitTracking(qubit0, qubit0Out); - updateQubitTracking(qubit1, qubit1Out); - return {qubit0Out, qubit1Out}; -} - -template -std::pair> -FluxProgramBuilder::createControlledTwoTargetOneParameter( - const std::variant& parameter, const Value control, - const Value qubit0, const Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(control, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = - b.create(loc, targets[0], targets[1], parameter); - return op->getResults(); - }); - return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; -} - -template -std::pair> -FluxProgramBuilder::createMultiControlledTwoTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value qubit0, const Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(controls, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = - b.create(loc, targets[0], targets[1], parameter); - return op->getResults(); - }); - return {controlsOut, {targetsOut[0], targetsOut[1]}}; -} - -// TwoTargetTwoParameter helpers - -template -std::pair FluxProgramBuilder::createTwoTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const Value qubit0, - const Value qubit1) { - auto op = create(loc, qubit0, qubit1, parameter1, parameter2); - const auto& qubit0Out = op.getQubit0Out(); - const auto& qubit1Out = op.getQubit1Out(); - updateQubitTracking(qubit0, qubit0Out); - updateQubitTracking(qubit1, qubit1Out); - return {qubit0Out, qubit1Out}; -} - -template -std::pair> -FluxProgramBuilder::createControlledTwoTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const Value control, - const Value qubit0, const Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(control, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], targets[1], - parameter1, parameter2); - return op->getResults(); - }); - return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; -} - -template -std::pair> -FluxProgramBuilder::createMultiControlledTwoTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const ValueRange controls, - const Value qubit0, const Value qubit1) { - const auto [controlsOut, targetsOut] = - ctrl(controls, {qubit0, qubit1}, - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { - const auto op = b.create(loc, targets[0], targets[1], - parameter1, parameter2); - return op->getResults(); - }); - return {controlsOut, {targetsOut[0], targetsOut[1]}}; -} - // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ Value FluxProgramBuilder::OP_NAME(const Value qubit) { \ - return createOneTargetZeroParameter(qubit); \ + auto op = create(loc, qubit); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ } \ std::pair FluxProgramBuilder::c##OP_NAME(const Value control, \ const Value target) { \ - return createControlledOneTargetZeroParameter(control, target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0]); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ const ValueRange controls, const Value target) { \ - return createMultiControlledOneTargetZeroParameter(controls, \ - target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0]); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ } DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) @@ -504,19 +220,32 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ Value FluxProgramBuilder::OP_NAME(const std::variant&(PARAM), \ const Value qubit) { \ - return createOneTargetOneParameter(PARAM, qubit); \ + auto op = create(loc, qubit, PARAM); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ } \ std::pair FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), const Value control, \ const Value target) { \ - return createControlledOneTargetOneParameter(PARAM, control, \ - target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), const ValueRange controls, \ const Value target) { \ - return createMultiControlledOneTargetOneParameter( \ - PARAM, controls, target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ } DEFINE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) @@ -532,21 +261,36 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) Value FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value qubit) { \ - return createOneTargetTwoParameter(PARAM1, PARAM2, qubit); \ + auto op = create(loc, qubit, PARAM1, PARAM2); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ } \ std::pair FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value control, \ const Value target) { \ - return createControlledOneTargetTwoParameter(PARAM1, PARAM2, \ - control, target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const ValueRange controls, \ const Value target) { \ - return createMultiControlledOneTargetTwoParameter( \ - PARAM1, PARAM2, controls, target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ } DEFINE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) @@ -562,24 +306,38 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), const Value qubit) { \ - return createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, \ - qubit); \ + auto op = create(loc, qubit, PARAM1, PARAM2, PARAM3); \ + const auto& qubitOut = op.getQubitOut(); \ + updateQubitTracking(qubit, qubitOut); \ + return qubitOut; \ } \ std::pair FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), const Value control, \ const Value target) { \ - return createControlledOneTargetThreeParameter( \ - PARAM1, PARAM2, PARAM3, control, target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], PARAM1, \ + PARAM2, PARAM3); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), const ValueRange controls, \ const Value target) { \ - return createMultiControlledOneTargetThreeParameter( \ - PARAM1, PARAM2, PARAM3, controls, target); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, target, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], PARAM1, \ + PARAM2, PARAM3); \ + return op->getResults(); \ + }); \ + return {controlsOut, targetsOut[0]}; \ } DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) @@ -591,18 +349,35 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ std::pair FluxProgramBuilder::OP_NAME(Value qubit0, \ Value qubit1) { \ - return createTwoTargetZeroParameter(qubit0, qubit1); \ + auto op = create(loc, qubit0, qubit1); \ + const auto& qubit0Out = op.getQubit0Out(); \ + const auto& qubit1Out = op.getQubit1Out(); \ + updateQubitTracking(qubit0, qubit0Out); \ + updateQubitTracking(qubit1, qubit1Out); \ + return {qubit0Out, qubit1Out}; \ } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ const Value control, Value qubit0, Value qubit1) { \ - return createControlledTwoTargetZeroParameter(control, qubit0, \ - qubit1); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {qubit0, qubit1}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], targets[1]); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ FluxProgramBuilder::mc##OP_NAME(const ValueRange controls, Value qubit0, \ Value qubit1) { \ - return createMultiControlledTwoTargetZeroParameter( \ - controls, qubit0, qubit1); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {qubit0, qubit1}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], targets[1]); \ + return op->getResults(); \ + }); \ + return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ } DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) @@ -617,20 +392,37 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ std::pair FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ - return createTwoTargetOneParameter(PARAM, qubit0, qubit1); \ + auto op = create(loc, qubit0, qubit1, PARAM); \ + const auto& qubit0Out = op.getQubit0Out(); \ + const auto& qubit1Out = op.getQubit1Out(); \ + updateQubitTracking(qubit0, qubit0Out); \ + updateQubitTracking(qubit1, qubit1Out); \ + return {qubit0Out, qubit1Out}; \ } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), const Value control, \ Value qubit0, Value qubit1) { \ - return createControlledTwoTargetOneParameter(PARAM, control, \ - qubit0, qubit1); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {qubit0, qubit1}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], targets[1], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), \ const ValueRange controls, Value qubit0, Value qubit1) { \ - return createMultiControlledTwoTargetOneParameter( \ - PARAM, controls, qubit0, qubit1); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {qubit0, qubit1}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], targets[1], PARAM); \ + return op->getResults(); \ + }); \ + return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ } DEFINE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) @@ -647,23 +439,39 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ - return createTwoTargetTwoParameter(PARAM1, PARAM2, qubit0, \ - qubit1); \ + auto op = create(loc, qubit0, qubit1, PARAM1, PARAM2); \ + const auto& qubit0Out = op.getQubit0Out(); \ + const auto& qubit1Out = op.getQubit1Out(); \ + updateQubitTracking(qubit0, qubit0Out); \ + updateQubitTracking(qubit1, qubit1Out); \ + return {qubit0Out, qubit1Out}; \ } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value control, \ Value qubit0, Value qubit1) { \ - return createControlledTwoTargetTwoParameter( \ - PARAM1, PARAM2, control, qubit0, qubit1); \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {qubit0, qubit1}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], targets[1], \ + PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const ValueRange controls, Value qubit0, Value qubit1) { \ - return createMultiControlledTwoTargetTwoParameter( \ - PARAM1, PARAM2, controls, qubit0, qubit1); \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {qubit0, qubit1}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], targets[1], \ + PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ + return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ } DEFINE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) From 46dece7972dac003d460efe75fef754ce6bc0680 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 28 Nov 2025 19:57:18 +0100 Subject: [PATCH 252/419] Simplify QIR helpers --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 95 +--- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 427 ++-------------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 459 ++---------------- 3 files changed, 83 insertions(+), 898 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 2fe6d46fad..ac3660d654 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -809,99 +809,16 @@ class QIRProgramBuilder { QIRMetadata metadata_; /** - * @brief Helper to create a one-target, zero-parameter QIR operation + * @brief Helper to create a LLVM CallOp * + * @param parameters Operation parameters * @param controls Control qubits - * @param target Target qubit + * @param targets Target qubits * @param fnName Name of the QIR function to call */ - void createOneTargetZeroParameter(const ValueRange controls, - const Value target, StringRef fnName); - /** - * @brief Helper to create a one-target, one-parameter QIR operation - * - * @param parameter Operation parameter - * @param controls Control qubits - * @param target Target qubit - * @param fnName Name of the QIR function to call - */ - void createOneTargetOneParameter(const std::variant& parameter, - const ValueRange controls, - const Value target, StringRef fnName); - - /** - * @brief Helper to create a one-target, two-parameter QIR operation - * - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param controls Control qubits - * @param target Target qubit - * @param fnName Name of the QIR function to call - */ - void - createOneTargetTwoParameter(const std::variant& parameter1, - const std::variant& parameter2, - const ValueRange controls, const Value target, - StringRef fnName); - - /** - * @brief Helper to create a one-target, three-parameter QIR operation - * - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param parameter3 Operation parameter - * @param controls Control qubits - * @param target Target qubit - * @param fnName Name of the QIR function to call - */ - void - createOneTargetThreeParameter(const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, - const ValueRange controls, const Value target, - StringRef fnName); - - /** - * @brief Helper to create a two-target, zero-parameter QIR operation - * - * @param controls Control qubits - * @param target0 Target qubit - * @param target1 Target qubit - * @param fnName Name of the QIR function to call - */ - void createTwoTargetZeroParameter(const ValueRange controls, - const Value target0, const Value target1, - StringRef fnName); - - /** - * @brief Helper to create a two-target, one-parameter QIR operation - * - * @param parameter Operation parameter - * @param controls Control qubits - * @param target0 Target qubit - * @param target1 Target qubit - * @param fnName Name of the QIR function to call - */ - void createTwoTargetOneParameter(const std::variant& parameter, - const ValueRange controls, - const Value target0, const Value target1, - StringRef fnName); - - /** - * @brief Helper to create a two-target, two-parameter QIR operation - * - * @param parameter1 Operation parameter - * @param parameter2 Operation parameter - * @param controls Control qubits - * @param target0 Target qubit - * @param target1 Target qubit - * @param fnName Name of the QIR function to call - */ - void - createTwoTargetTwoParameter(const std::variant& parameter1, - const std::variant& parameter2, - const ValueRange controls, const Value target0, - const Value target1, StringRef fnName); + void createCallOp(const SmallVector>& parameters, + const ValueRange controls, + const SmallVector& targets, StringRef fnName); /** * @brief Generate array-based output recording in the output block diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 3f60195c7a..da053f338c 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -113,7 +113,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { }; /** - * @brief Converts a one-target, zero-parameter Quartz operation to QIR + * @brief Helper to convert a Quartz operation to a LLVM CallOp * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation @@ -123,14 +123,16 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @param ctx The MLIR context * @param state The lowering state * @param fnName The name of the QIR function to call + * @param numTargets The number of targets + * @param numParams The number of parameters * @return LogicalResult Success or failure of the conversion */ template LogicalResult -convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { +convertUnitaryToCallOp(QuartzOpType& op, QuartzOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, MLIRContext* ctx, + LoweringState& state, StringRef fnName, + size_t numTargets, size_t numParams) { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; const SmallVector posCtrls = @@ -139,394 +141,21 @@ convertOneTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, // Define argument types SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 1); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - - // Define function signature - const auto fnSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 1); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); -} - -/** - * @brief Converts a one-target, one-parameter Quartz operation to QIR - * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation - * @param rewriter The pattern rewriter - * @param ctx The MLIR context - * @param state The lowering state - * @param fnName The name of the QIR function to call - * @return LogicalResult Success or failure of the conversion - */ -template -LogicalResult -convertOneTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const size_t numCtrls = posCtrls.size(); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 2); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add parameter type - argumentTypes.push_back(Float64Type::get(ctx)); - - // Define function signature - const auto fnSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 2); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); -} - -/** - * @brief Converts a one-target, two-parameter Quartz operation to QIR - * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation - * @param rewriter The pattern rewriter - * @param ctx The MLIR context - * @param state The lowering state - * @param fnName The name of the QIR function to call - * @return LogicalResult Success or failure of the conversion - */ -template -LogicalResult -convertOneTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const size_t numCtrls = posCtrls.size(); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 3); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - const auto floatType = Float64Type::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add parameter types - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); - - // Define function signature - const auto fnSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 3); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); -} - -/** - * @brief Converts a one-target, three-parameter Quartz operation to QIR - * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation - * @param rewriter The pattern rewriter - * @param ctx The MLIR context - * @param state The lowering state - * @param fnName The name of the QIR function to call - * @return LogicalResult Success or failure of the conversion - */ -template -LogicalResult -convertOneTargetThreeParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const size_t numCtrls = posCtrls.size(); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 4); + argumentTypes.reserve(numParams + numCtrls + numTargets); const auto ptrType = LLVM::LLVMPointerType::get(ctx); const auto floatType = Float64Type::get(ctx); // Add control pointers for (size_t i = 0; i < numCtrls; ++i) { argumentTypes.push_back(ptrType); } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add parameter types - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); - - // Define function signature - const auto fnSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 4); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); -} - -/** - * @brief Converts a two-target, zero-parameter Quartz operation to QIR - * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation - * @param rewriter The pattern rewriter - * @param ctx The MLIR context - * @param state The lowering state - * @param fnName The name of the QIR function to call - * @return LogicalResult Success or failure of the conversion - */ -template -LogicalResult -convertTwoTargetZeroParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const size_t numCtrls = posCtrls.size(); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 2); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); - - // Define function signature - const auto fnSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 2); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); -} - -/** - * @brief Converts a two-target, one-parameter Quartz operation to QIR - * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation - * @param rewriter The pattern rewriter - * @param ctx The MLIR context - * @param state The lowering state - * @param fnName The name of the QIR function to call - * @return LogicalResult Success or failure of the conversion - */ -template -LogicalResult -convertTwoTargetOneParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const size_t numCtrls = posCtrls.size(); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 3); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { - argumentTypes.push_back(ptrType); - } // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); - // Add parameter type - argumentTypes.push_back(Float64Type::get(ctx)); - - // Define function signature - const auto fnSignature = - LLVM::LLVMFunctionType::get(LLVM::LLVMVoidType::get(ctx), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); - - SmallVector operands; - operands.reserve(numCtrls + 3); - operands.append(posCtrls.begin(), posCtrls.end()); - operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); - - // Clean up modifier information - if (inCtrlOp != 0) { - state.posCtrls.erase(inCtrlOp); - state.inCtrlOp--; - } - - // Replace operation with CallOp - rewriter.replaceOpWithNewOp(op, fnDecl, operands); - return success(); -} - -/** - * @brief Converts a two-target, two-parameter Quartz operation to QIR - * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation - * @param rewriter The pattern rewriter - * @param ctx The MLIR context - * @param state The lowering state - * @param fnName The name of the QIR function to call - * @return LogicalResult Success or failure of the conversion - */ -template -LogicalResult -convertTwoTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName) { - // Query state for modifier information - const auto inCtrlOp = state.inCtrlOp; - const SmallVector posCtrls = - inCtrlOp != 0 ? state.posCtrls[inCtrlOp] : SmallVector{}; - const size_t numCtrls = posCtrls.size(); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(numCtrls + 4); - const auto ptrType = LLVM::LLVMPointerType::get(ctx); - const auto floatType = Float64Type::get(ctx); - // Add control pointers - for (size_t i = 0; i < numCtrls; ++i) { + for (size_t i = 0; i < numTargets; ++i) { argumentTypes.push_back(ptrType); } - // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); // Add parameter types - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); + for (size_t i = 0; i < numParams; ++i) { + argumentTypes.push_back(floatType); + } // Define function signature const auto fnSignature = @@ -537,7 +166,7 @@ convertTwoTargetTwoParameter(QuartzOpType& op, QuartzOpAdaptorType& adaptor, getOrCreateFunctionDeclaration(rewriter, op, fnName, fnSignature); SmallVector operands; - operands.reserve(numCtrls + 4); + operands.reserve(numParams + numCtrls + numTargets); operands.append(posCtrls.begin(), posCtrls.end()); operands.append(adaptor.getOperands().begin(), adaptor.getOperands().end()); @@ -860,7 +489,7 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } }; -// OneTargetOneParameter +// OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME) \ @@ -906,8 +535,8 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } \ } \ \ - return convertOneTargetZeroParameter(op, adaptor, rewriter, \ - getContext(), state, fnName); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 1, 0); \ } \ }; @@ -972,8 +601,8 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, SXDG, sxdg, sxdg) } \ } \ \ - return convertOneTargetOneParameter(op, adaptor, rewriter, getContext(), \ - state, fnName); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 1, 1); \ } \ }; @@ -1031,8 +660,8 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, P, p, p, theta) } \ } \ \ - return convertOneTargetTwoParameter(op, adaptor, rewriter, getContext(), \ - state, fnName); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 1, 2); \ } \ }; @@ -1088,8 +717,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, U2, u2, u2, phi, lambda) } \ } \ \ - return convertOneTargetThreeParameter( \ - op, adaptor, rewriter, getContext(), state, fnName); \ + return convertUnitaryToCallOp( \ + op, adaptor, rewriter, getContext(), state, fnName, 1, 3); \ } \ }; @@ -1144,8 +773,8 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, U, u, u3) } \ } \ \ - return convertTwoTargetZeroParameter(op, adaptor, rewriter, \ - getContext(), state, fnName); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 2, 0); \ } \ }; @@ -1203,8 +832,8 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) } \ } \ \ - return convertTwoTargetOneParameter(op, adaptor, rewriter, getContext(), \ - state, fnName); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 2, 1); \ } \ }; @@ -1263,8 +892,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) } \ } \ \ - return convertTwoTargetTwoParameter(op, adaptor, rewriter, getContext(), \ - state, fnName); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 2, 2); \ } \ }; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index dc0adf86df..ad09b72f26 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -236,213 +236,30 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// -// Helper methods - -void QIRProgramBuilder::createOneTargetZeroParameter(const ValueRange controls, - const Value target, - StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 1); - const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - // Add control pointers - for (size_t i = 0; i < controls.size(); ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - SmallVector operands; - operands.reserve(controls.size() + 1); - operands.append(controls.begin(), controls.end()); - operands.push_back(target); - - builder.create(loc, fnDecl, operands); -} - -void QIRProgramBuilder::createOneTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value target, StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value parameterOperand; - if (std::holds_alternative(parameter)) { - parameterOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter))) - .getResult(); - } else { - parameterOperand = std::get(parameter); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 2); - const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - // Add control pointers - for (size_t i = 0; i < controls.size(); ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add parameter type - argumentTypes.push_back(Float64Type::get(builder.getContext())); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); - - // Declare QIR function - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - SmallVector operands; - operands.reserve(controls.size() + 2); - operands.append(controls.begin(), controls.end()); - operands.push_back(target); - operands.push_back(parameterOperand); - - builder.create(loc, fnDecl, operands); -} - -void QIRProgramBuilder::createOneTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const ValueRange controls, - const Value target, StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value parameter1Operand; - if (std::holds_alternative(parameter1)) { - parameter1Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter1))) - .getResult(); - } else { - parameter1Operand = std::get(parameter1); - } - - Value parameter2Operand; - if (std::holds_alternative(parameter2)) { - parameter2Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter2))) - .getResult(); - } else { - parameter2Operand = std::get(parameter2); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 3); - const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - const auto floatType = Float64Type::get(builder.getContext()); - // Add control pointers - for (size_t i = 0; i < controls.size(); ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add parameter types - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); - - // Declare QIR function - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - SmallVector operands; - operands.reserve(controls.size() + 3); - operands.append(controls.begin(), controls.end()); - operands.push_back(target); - operands.push_back(parameter1Operand); - operands.push_back(parameter2Operand); - - builder.create(loc, fnDecl, operands); -} - -void QIRProgramBuilder::createOneTargetThreeParameter( - const std::variant& parameter1, - const std::variant& parameter2, - const std::variant& parameter3, const ValueRange controls, - const Value target, StringRef fnName) { +void QIRProgramBuilder::createCallOp( + const SmallVector>& parameters, + const ValueRange controls, const SmallVector& targets, + StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard entryGuard(builder); // Insert constants in entry block builder.setInsertionPointToEnd(entryBlock); - Value parameter1Operand; - if (std::holds_alternative(parameter1)) { - parameter1Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter1))) - .getResult(); - } else { - parameter1Operand = std::get(parameter1); - } - - Value parameter2Operand; - if (std::holds_alternative(parameter2)) { - parameter2Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter2))) - .getResult(); - } else { - parameter2Operand = std::get(parameter2); - } - - Value parameter3Operand; - if (std::holds_alternative(parameter3)) { - parameter3Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter3))) - .getResult(); - } else { - parameter3Operand = std::get(parameter3); + SmallVector parameterOperands; + parameterOperands.reserve(parameters.size()); + for (const auto& parameter : parameters) { + Value parameterOperand; + if (std::holds_alternative(parameter)) { + parameterOperand = + builder + .create( + loc, builder.getF64FloatAttr(std::get(parameter))) + .getResult(); + } else { + parameterOperand = std::get(parameter); + } + parameterOperands.push_back(parameterOperand); } // Save current insertion point @@ -453,189 +270,21 @@ void QIRProgramBuilder::createOneTargetThreeParameter( // Define argument types SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 4); + argumentTypes.reserve(parameters.size() + controls.size() + targets.size()); const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); const auto floatType = Float64Type::get(builder.getContext()); // Add control pointers for (size_t i = 0; i < controls.size(); ++i) { argumentTypes.push_back(ptrType); } - // Add target pointer - argumentTypes.push_back(ptrType); - // Add parameter types - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); - - // Declare QIR function - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - SmallVector operands; - operands.reserve(controls.size() + 4); - operands.append(controls.begin(), controls.end()); - operands.push_back(target); - operands.push_back(parameter1Operand); - operands.push_back(parameter2Operand); - operands.push_back(parameter3Operand); - - builder.create(loc, fnDecl, operands); -} - -void QIRProgramBuilder::createTwoTargetZeroParameter(const ValueRange controls, - const Value target0, - const Value target1, - StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 2); - const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - // Add control pointers - for (size_t i = 0; i < controls.size(); ++i) { - argumentTypes.push_back(ptrType); - } // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); - - // Declare QIR function - const auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - SmallVector operands; - operands.reserve(controls.size() + 2); - operands.append(controls.begin(), controls.end()); - operands.push_back(target0); - operands.push_back(target1); - - builder.create(loc, fnDecl, operands); -} - -void QIRProgramBuilder::createTwoTargetOneParameter( - const std::variant& parameter, const ValueRange controls, - const Value target0, const Value target1, StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value parameterOperand; - if (std::holds_alternative(parameter)) { - parameterOperand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter))) - .getResult(); - } else { - parameterOperand = std::get(parameter); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 3); - const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - // Add control pointers - for (size_t i = 0; i < controls.size(); ++i) { + for (size_t i = 0; i < targets.size(); ++i) { argumentTypes.push_back(ptrType); } - // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); - // Add parameter type - argumentTypes.push_back(Float64Type::get(builder.getContext())); - - // Define function signature - const auto fnSignature = LLVM::LLVMFunctionType::get( - LLVM::LLVMVoidType::get(builder.getContext()), argumentTypes); - - // Declare QIR function - auto fnDecl = - getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); - - SmallVector operands; - operands.reserve(controls.size() + 3); - operands.append(controls.begin(), controls.end()); - operands.push_back(target0); - operands.push_back(target1); - operands.push_back(parameterOperand); - - builder.create(loc, fnDecl, operands); -} - -void QIRProgramBuilder::createTwoTargetTwoParameter( - const std::variant& parameter1, - const std::variant& parameter2, const ValueRange controls, - const Value target0, const Value target1, StringRef fnName) { - // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); - - // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); - - Value parameter1Operand; - if (std::holds_alternative(parameter1)) { - parameter1Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter1))) - .getResult(); - } else { - parameter1Operand = std::get(parameter1); - } - - Value parameter2Operand; - if (std::holds_alternative(parameter2)) { - parameter2Operand = - builder - .create( - loc, builder.getF64FloatAttr(std::get(parameter2))) - .getResult(); - } else { - parameter2Operand = std::get(parameter2); - } - - // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); - - // Insert in body block (before branch) - builder.setInsertionPoint(bodyBlock->getTerminator()); - - // Define argument types - SmallVector argumentTypes; - argumentTypes.reserve(controls.size() + 4); - const auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); - const auto floatType = Float64Type::get(builder.getContext()); - // Add control pointers - for (size_t i = 0; i < controls.size(); ++i) { - argumentTypes.push_back(ptrType); - } - // Add target pointers - argumentTypes.push_back(ptrType); - argumentTypes.push_back(ptrType); // Add parameter types - argumentTypes.push_back(floatType); - argumentTypes.push_back(floatType); + for (size_t i = 0; i < parameters.size(); ++i) { + argumentTypes.push_back(floatType); + } // Define function signature const auto fnSignature = LLVM::LLVMFunctionType::get( @@ -646,12 +295,10 @@ void QIRProgramBuilder::createTwoTargetTwoParameter( getOrCreateFunctionDeclaration(builder, module, fnName, fnSignature); SmallVector operands; - operands.reserve(controls.size() + 4); + operands.reserve(parameters.size() + controls.size() + targets.size()); operands.append(controls.begin(), controls.end()); - operands.push_back(target0); - operands.push_back(target1); - operands.push_back(parameter1Operand); - operands.push_back(parameter2Operand); + operands.append(targets.begin(), targets.end()); + operands.append(parameterOperands.begin(), parameterOperands.end()); builder.create(loc, fnDecl, operands); } @@ -660,13 +307,13 @@ void QIRProgramBuilder::createTwoTargetTwoParameter( #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(const Value qubit) { \ - createOneTargetZeroParameter({}, qubit, QIR_##OP_NAME_BIG); \ + createCallOp({}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL(const Value control, \ const Value target) { \ - createOneTargetZeroParameter({control}, target, QIR_C##OP_NAME_BIG); \ + createCallOp({}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -684,7 +331,7 @@ void QIRProgramBuilder::createTwoTargetTwoParameter( "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createOneTargetZeroParameter(controls, target, fnName); \ + createCallOp({}, controls, {target}, fnName); \ return *this; \ } @@ -707,14 +354,14 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ const std::variant&(PARAM), const Value qubit) { \ - createOneTargetOneParameter(PARAM, {}, qubit, QIR_##OP_NAME_BIG); \ + createCallOp({PARAM}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM), const Value control, \ const Value target) { \ - createOneTargetOneParameter(PARAM, {control}, target, QIR_C##OP_NAME_BIG); \ + createCallOp({PARAM}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -733,7 +380,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createOneTargetOneParameter(PARAM, controls, target, fnName); \ + createCallOp({PARAM}, controls, {target}, fnName); \ return *this; \ } @@ -751,7 +398,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value qubit) { \ - createOneTargetTwoParameter(PARAM1, PARAM2, {}, qubit, QIR_##OP_NAME_BIG); \ + createCallOp({PARAM1, PARAM2}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ @@ -759,8 +406,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value control, \ const Value target) { \ - createOneTargetTwoParameter(PARAM1, PARAM2, {control}, target, \ - QIR_C##OP_NAME_BIG); \ + createCallOp({PARAM1, PARAM2}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -780,7 +426,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createOneTargetTwoParameter(PARAM1, PARAM2, controls, target, fnName); \ + createCallOp({PARAM1, PARAM2}, controls, {target}, fnName); \ return *this; \ } @@ -797,8 +443,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), const Value qubit) { \ - createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, {}, qubit, \ - QIR_##OP_NAME_BIG); \ + createCallOp({PARAM1, PARAM2, PARAM3}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ @@ -807,8 +452,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), const Value control, \ const Value target) { \ - createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, {control}, target, \ - QIR_C##OP_NAME_BIG); \ + createCallOp({PARAM1, PARAM2, PARAM3}, {control}, {target}, \ + QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -829,8 +474,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createOneTargetThreeParameter(PARAM1, PARAM2, PARAM3, controls, target, \ - fnName); \ + createCallOp({PARAM1, PARAM2, PARAM3}, controls, {target}, fnName); \ return *this; \ } @@ -843,14 +487,13 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(const Value target0, \ const Value target1) { \ - createTwoTargetZeroParameter({}, target0, target1, QIR_##OP_NAME_BIG); \ + createCallOp({}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const Value control, const Value target0, const Value target1) { \ - createTwoTargetZeroParameter({control}, target0, target1, \ - QIR_C##OP_NAME_BIG); \ + createCallOp({}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -868,7 +511,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createTwoTargetZeroParameter(controls, target0, target1, fnName); \ + createCallOp({}, controls, {target0, target1}, fnName); \ return *this; \ } @@ -885,16 +528,14 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ const std::variant&(PARAM), const Value target0, \ const Value target1) { \ - createTwoTargetOneParameter(PARAM, {}, target0, target1, \ - QIR_##OP_NAME_BIG); \ + createCallOp({PARAM}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM), const Value control, \ const Value target0, const Value target1) { \ - createTwoTargetOneParameter(PARAM, {control}, target0, target1, \ - QIR_C##OP_NAME_BIG); \ + createCallOp({PARAM}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -913,7 +554,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createTwoTargetOneParameter(PARAM, controls, target0, target1, fnName); \ + createCallOp({PARAM}, controls, {target0, target1}, fnName); \ return *this; \ } @@ -932,8 +573,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value target0, \ const Value target1) { \ - createTwoTargetTwoParameter(PARAM1, PARAM2, {}, target0, target1, \ - QIR_##OP_NAME_BIG); \ + createCallOp({PARAM1, PARAM2}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ \ @@ -941,8 +581,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value control, \ const Value target0, const Value target1) { \ - createTwoTargetTwoParameter(PARAM1, PARAM2, {control}, target0, target1, \ - QIR_C##OP_NAME_BIG); \ + createCallOp({PARAM1, PARAM2}, {control}, {target0, target1}, \ + QIR_C##OP_NAME_BIG); \ return *this; \ } \ \ @@ -962,8 +602,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ - createTwoTargetTwoParameter(PARAM1, PARAM2, controls, target0, target1, \ - fnName); \ + createCallOp({PARAM1, PARAM2}, controls, {target0, target1}, fnName); \ return *this; \ } From 171adda7de8a41bce28d867b55b6efe7da26fe52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 23:35:50 +0000 Subject: [PATCH 253/419] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/unittests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt index bd0adcaaf0..43ffbbd241 100644 --- a/mlir/unittests/CMakeLists.txt +++ b/mlir/unittests/CMakeLists.txt @@ -12,5 +12,5 @@ add_subdirectory(translation) add_custom_target(mqt-core-mlir-unittests) -add_dependencies(mqt-core-mlir-unittests mqt-core-mlir-compiler-pipeline-test mqt-core-mlir-translation-test - mqt-core-mlir-wireiterator-test) +add_dependencies(mqt-core-mlir-unittests mqt-core-mlir-compiler-pipeline-test + mqt-core-mlir-translation-test mqt-core-mlir-wireiterator-test) From 06b576c6b92cd972caf0569d7e33ac30c45143ad Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 01:12:30 +0100 Subject: [PATCH 254/419] Use macros for translation --- .../TranslateQuantumComputationToQuartz.cpp | 983 +++++------------- 1 file changed, 276 insertions(+), 707 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 40ba9383e6..813f499f2b 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -245,626 +245,251 @@ getPosControls(const qc::Operation& operation, return controls; } -/** - * @brief Adds an Id operation - * - * @details - * Translates an Id operation from the QuantumComputation to quartz.id. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The Id operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addIdOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.id(target); - } else { - builder.mcid(posControls, target); - } -} - -/** - * @brief Adds an X operation - * - * @details - * Translates an X operation from the QuantumComputation to quartz.x. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The X operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.x(target); - } else { - builder.mcx(posControls, target); - } -} - -/** - * @brief Adds a Y operation - * - * @details - * Translates a Y operation from the QuantumComputation to quartz.y. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The Y operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.y(target); - } else { - builder.mcy(posControls, target); - } -} - -/** - * @brief Adds a Z operation - * - * @details - * Translates a Z operation from the QuantumComputation to quartz.z. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The Z operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.z(target); - } else { - builder.mcz(posControls, target); - } -} - -/** - * @brief Adds an H operation - * - * @details - * Translates an H operation from the QuantumComputation to quartz.h. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The H operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addHOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.h(target); - } else { - builder.mch(posControls, target); - } -} - -/** - * @brief Adds an S operation - * - * @details - * Translates an S operation from the QuantumComputation to quartz.s. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The S operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addSOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.s(target); - } else { - builder.mcs(posControls, target); - } -} - -/** - * @brief Adds an Sdg operation - * - * @details - * Translates an Sdg operation from the QuantumComputation to quartz.sdg. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The Sdg operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addSdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.sdg(target); - } else { - builder.mcsdg(posControls, target); - } -} - -/** - * @brief Adds a T operation - * - * @details - * Translates a T operation from the QuantumComputation to quartz.t. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The T operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addTOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.t(target); - } else { - builder.mct(posControls, target); - } -} - -/** - * @brief Adds a Tdg operation - * - * @details - * Translates a Tdg operation from the QuantumComputation to quartz.tdg. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The Tdg operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addTdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.tdg(target); - } else { - builder.mctdg(posControls, target); - } -} - -/** - * @brief Adds an SX operation - * - * @details - * Translates an SX operation from the QuantumComputation to quartz.sx. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The SX operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addSXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.sx(target); - } else { - builder.mcsx(posControls, target); - } -} - -/** - * @brief Adds an SXdg operation - * - * @details - * Translates an SXdg operation from the QuantumComputation to quartz.sxdg. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The SXdg operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addSXdgOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.sxdg(target); - } else { - builder.mcsxdg(posControls, target); - } -} - -/** - * @brief Adds an RX operation - * - * @details - * Translates an RX operation from the QuantumComputation to quartz.rx. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RX operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.rx(theta, target); - } else { - builder.mcrx(theta, posControls, target); - } -} - -/** - * @brief Adds an RY operation - * - * @details - * Translates an RY operation from the QuantumComputation to quartz.ry. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RY operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.ry(theta, target); - } else { - builder.mcry(theta, posControls, target); - } -} - -/** - * @brief Adds an RZ operation - * - * @details - * Translates an RZ operation from the QuantumComputation to quartz.rz. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RZ operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.rz(theta, target); - } else { - builder.mcrz(theta, posControls, target); - } -} - -/** - * @brief Adds a P operation - * - * @details - * Translates a P operation from the QuantumComputation to quartz.p. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The P operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.p(theta, target); - } else { - builder.mcp(theta, posControls, target); - } -} - -/** - * @brief Adds an R operation - * - * @details - * Translates an R operation from the QuantumComputation to quartz.r. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The R operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addROp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& phi = operation.getParameter()[1]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.r(theta, phi, target); - } else { - builder.mcr(theta, phi, posControls, target); - } -} - -/** - * @brief Adds a U2 operation - * - * @details - * Translate a U2 operation from the QuantumComputation to quartz.u2. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The U2 operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addU2Op(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& phi = operation.getParameter()[0]; - const auto& lambda = operation.getParameter()[1]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.u2(phi, lambda, target); - } else { - builder.mcu2(phi, lambda, posControls, target); - } -} - -/** - * @brief Adds a U operation - * - * @details - * Translate a U operation from the QuantumComputation to quartz.u. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The U operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addUOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& phi = operation.getParameter()[1]; - const auto& lambda = operation.getParameter()[2]; - const auto& target = qubits[operation.getTargets()[0]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.u(theta, phi, lambda, target); - } else { - builder.mcu(theta, phi, lambda, posControls, target); - } -} - -/** - * @brief Adds a SWAP operation - * - * @details - * Translate a SWAP operation from the QuantumComputation to quartz.swap. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The SWAP operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.swap(target0, target1); - } else { - builder.mcswap(posControls, target0, target1); - } -} - -/** - * @brief Adds an iSWAP operation - * - * @details - * Translate an iSWAP operation from the QuantumComputation to quartz.iswap. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The iSWAP operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addiSWAPOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.iswap(target0, target1); - } else { - builder.mciswap(posControls, target0, target1); - } -} - -/** - * @brief Adds a DCX operation - * - * @details - * Translate a DCX operation from the QuantumComputation to quartz.dcx. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The DCX operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addDCXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.dcx(target0, target1); - } else { - builder.mcdcx(posControls, target0, target1); - } -} - -/** - * @brief Adds an ECR operation - * - * @details - * Translate an ECR operation from the QuantumComputation to quartz.ecr. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The ECR operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addECROp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.ecr(target0, target1); - } else { - builder.mcecr(posControls, target0, target1); - } -} - -/** - * @brief Adds an RXX operation - * - * @details - * Translate an RXX operation from the QuantumComputation to quartz.rxx. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RXX operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRXXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.rxx(theta, target0, target1); - } else { - builder.mcrxx(theta, posControls, target0, target1); - } -} - -/** - * @brief Adds an RYY operation - * - * @details - * Translate an RYY operation from the QuantumComputation to quartz.ryy. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RYY operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRYYOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.ryy(theta, target0, target1); - } else { - builder.mcryy(theta, posControls, target0, target1); - } -} - -/** - * @brief Adds an RZX operation - * - * @details - * Translate an RZX operation from the QuantumComputation to quartz.rzx. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RZX operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRZXOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.rzx(theta, target0, target1); - } else { - builder.mcrzx(theta, posControls, target0, target1); - } -} - -/** - * @brief Adds an RZZ operation - * - * @details - * Translate an RZZ operation from the QuantumComputation to quartz.rzz. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The RZZ operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addRZZOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.rzz(theta, target0, target1); - } else { - builder.mcrzz(theta, posControls, target0, target1); - } -} - -/** - * @brief Adds an XX+YY operation - * - * @details - * Translate an XX+YY operation from the QuantumComputation to - * quartz.xx_plus_yy. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The XX+YY operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addXXPlusYYOp(QuartzProgramBuilder& builder, - const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& beta = operation.getParameter()[1]; - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.xx_plus_yy(theta, beta, target0, target1); - } else { - builder.mcxx_plus_yy(theta, beta, posControls, target0, target1); - } -} - -/** - * @brief Adds an XX-YY operation - * - * @details - * Translate an XX-YY operation from the QuantumComputation to - * quartz.xx_minus_yy. - * - * @param builder The QuartzProgramBuilder used to create operations - * @param operation The XX-YY operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index - */ -void addXXMinusYYOp(QuartzProgramBuilder& builder, - const qc::Operation& operation, - const llvm::SmallVector& qubits) { - const auto& theta = operation.getParameter()[0]; - const auto& beta = operation.getParameter()[1]; - const auto& target0 = qubits[operation.getTargets()[0]]; - const auto& target1 = qubits[operation.getTargets()[1]]; - if (const auto& posControls = getPosControls(operation, qubits); - posControls.empty()) { - builder.xx_minus_yy(theta, beta, target0, target1); - } else { - builder.mcxx_minus_yy(theta, beta, posControls, target0, target1); - } -} +// OneTargetZeroParameter + +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(target); \ + } else { \ + builder.mc##OP_QUARTZ(posControls, target); \ + } \ + } + +DEFINE_ONE_TARGET_ZERO_PARAMETER(I, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(X, x) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Y, y) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Z, z) +DEFINE_ONE_TARGET_ZERO_PARAMETER(H, h) +DEFINE_ONE_TARGET_ZERO_PARAMETER(S, s) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Sdg, sdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(T, t) +DEFINE_ONE_TARGET_ZERO_PARAMETER(Tdg, tdg) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SX, sx) +DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) + +#undef DEFINE_ONE_TARGET_ZERO_PARAMETER + +// OneTargetOneParameter + +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& param = operation.getParameter()[0]; \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(param, target); \ + } else { \ + builder.mc##OP_QUARTZ(param, posControls, target); \ + } \ + } + +DEFINE_ONE_TARGET_ONE_PARAMETER(RX, rx) +DEFINE_ONE_TARGET_ONE_PARAMETER(RY, ry) +DEFINE_ONE_TARGET_ONE_PARAMETER(RZ, rz) +DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) + +// OneTargetTwoParameter + +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& param1 = operation.getParameter()[0]; \ + const auto& param2 = operation.getParameter()[1]; \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(param1, param2, target); \ + } else { \ + builder.mc##OP_QUARTZ(param1, param2, posControls, target); \ + } \ + } + +DEFINE_ONE_TARGET_TWO_PARAMETER(R, r) +DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) + +#undef DEFINE_ONE_TARGET_TWO_PARAMETER + +// OneTargetThreeParameter + +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& param1 = operation.getParameter()[0]; \ + const auto& param2 = operation.getParameter()[1]; \ + const auto& param3 = operation.getParameter()[2]; \ + const auto& target = qubits[operation.getTargets()[0]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(param1, param2, param3, target); \ + } else { \ + builder.mc##OP_QUARTZ(param1, param2, param3, posControls, target); \ + } \ + } + +DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) + +#undef DEFINE_ONE_TARGET_THREE_PARAMETER + +// TwoTargetZeroParameter + +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& target0 = qubits[operation.getTargets()[0]]; \ + const auto& target1 = qubits[operation.getTargets()[1]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(target0, target1); \ + } else { \ + builder.mc##OP_QUARTZ(posControls, target0, target1); \ + } \ + } + +DEFINE_TWO_TARGET_ZERO_PARAMETER(SWAP, swap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(iSWAP, iswap) +DEFINE_TWO_TARGET_ZERO_PARAMETER(DCX, dcx) +DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) + +#undef DEFINE_TWO_TARGET_ZERO_PARAMETER + +// TwoTargetOneParameter + +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& param = operation.getParameter()[0]; \ + const auto& target0 = qubits[operation.getTargets()[0]]; \ + const auto& target1 = qubits[operation.getTargets()[1]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(param, target0, target1); \ + } else { \ + builder.mc##OP_QUARTZ(param, posControls, target0, target1); \ + } \ + } + +DEFINE_TWO_TARGET_ONE_PARAMETER(RXX, rxx) +DEFINE_TWO_TARGET_ONE_PARAMETER(RYY, ryy) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZX, rzx) +DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) + +#undef DEFINE_TWO_TARGET_ONE_PARAMETER + +// TwoTargetTwoParameter + +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_QC, OP_QUARTZ) \ + /** \ + * @brief Adds a quartz.OP_QUARTZ operation \ + * \ + * @details \ + * Translates an OP_QC operation from the QuantumComputation to \ + * quartz.OP_QUARTZ. \ + * \ + * @param builder The QuartzProgramBuilder used to create operations \ + * @param operation The OP_QC operation to translate \ + * @param qubits Flat vector of qubit values indexed by physical qubit index \ + */ \ + void add##OP_QC##Op(QuartzProgramBuilder& builder, \ + const qc::Operation& operation, \ + const llvm::SmallVector& qubits) { \ + const auto& param1 = operation.getParameter()[0]; \ + const auto& param2 = operation.getParameter()[1]; \ + const auto& target0 = qubits[operation.getTargets()[0]]; \ + const auto& target1 = qubits[operation.getTargets()[1]]; \ + if (const auto& posControls = getPosControls(operation, qubits); \ + posControls.empty()) { \ + builder.OP_QUARTZ(param1, param2, target0, target1); \ + } else { \ + builder.mc##OP_QUARTZ(param1, param2, posControls, target0, target1); \ + } \ + } + +DEFINE_TWO_TARGET_TWO_PARAMETER(XXplusYY, xx_plus_yy) +DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) + +#undef DEFINE_TWO_TARGET_TWO_PARAMETER + +#define ADD_OP_CASE(OP_QC) \ + case qc::OpType::OP_QC: \ + add##OP_QC##Op(builder, *operation, qubits); \ + break; /** * @brief Translates operations from QuantumComputation to Quartz dialect @@ -894,93 +519,35 @@ translateOperations(QuartzProgramBuilder& builder, case qc::OpType::Measure: addMeasureOp(builder, *operation, qubits, bitMap); break; - case qc::OpType::Reset: - addResetOp(builder, *operation, qubits); - break; - case qc::OpType::I: - addIdOp(builder, *operation, qubits); - break; - case qc::OpType::X: - addXOp(builder, *operation, qubits); - break; - case qc::OpType::Y: - addYOp(builder, *operation, qubits); - break; - case qc::OpType::Z: - addZOp(builder, *operation, qubits); - break; - case qc::OpType::H: - addHOp(builder, *operation, qubits); - break; - case qc::OpType::S: - addSOp(builder, *operation, qubits); - break; - case qc::OpType::Sdg: - addSdgOp(builder, *operation, qubits); - break; - case qc::OpType::T: - addTOp(builder, *operation, qubits); - break; - case qc::OpType::Tdg: - addTdgOp(builder, *operation, qubits); - break; - case qc::OpType::SX: - addSXOp(builder, *operation, qubits); - break; - case qc::OpType::SXdg: - addSXdgOp(builder, *operation, qubits); - break; - case qc::OpType::RX: - addRXOp(builder, *operation, qubits); - break; - case qc::OpType::RY: - addRYOp(builder, *operation, qubits); - break; - case qc::OpType::RZ: - addRZOp(builder, *operation, qubits); - break; - case qc::OpType::P: - addPOp(builder, *operation, qubits); - break; - case qc::OpType::R: - addROp(builder, *operation, qubits); - break; - case qc::OpType::U2: - addU2Op(builder, *operation, qubits); - break; - case qc::OpType::U: - addUOp(builder, *operation, qubits); - break; - case qc::OpType::SWAP: - addSWAPOp(builder, *operation, qubits); - break; - case qc::OpType::iSWAP: - addiSWAPOp(builder, *operation, qubits); - break; - case qc::OpType::DCX: - addDCXOp(builder, *operation, qubits); - break; - case qc::OpType::ECR: - addECROp(builder, *operation, qubits); - break; - case qc::OpType::RXX: - addRXXOp(builder, *operation, qubits); - break; - case qc::OpType::RYY: - addRYYOp(builder, *operation, qubits); - break; - case qc::OpType::RZX: - addRZXOp(builder, *operation, qubits); - break; - case qc::OpType::RZZ: - addRZZOp(builder, *operation, qubits); - break; - case qc::OpType::XXplusYY: - addXXPlusYYOp(builder, *operation, qubits); - break; - case qc::OpType::XXminusYY: - addXXMinusYYOp(builder, *operation, qubits); - break; + ADD_OP_CASE(Reset) + ADD_OP_CASE(I) + ADD_OP_CASE(X) + ADD_OP_CASE(Y) + ADD_OP_CASE(Z) + ADD_OP_CASE(H) + ADD_OP_CASE(S) + ADD_OP_CASE(Sdg) + ADD_OP_CASE(T) + ADD_OP_CASE(Tdg) + ADD_OP_CASE(SX) + ADD_OP_CASE(SXdg) + ADD_OP_CASE(RX) + ADD_OP_CASE(RY) + ADD_OP_CASE(RZ) + ADD_OP_CASE(P) + ADD_OP_CASE(R) + ADD_OP_CASE(U2) + ADD_OP_CASE(U) + ADD_OP_CASE(SWAP) + ADD_OP_CASE(iSWAP) + ADD_OP_CASE(DCX) + ADD_OP_CASE(ECR) + ADD_OP_CASE(RXX) + ADD_OP_CASE(RYY) + ADD_OP_CASE(RZX) + ADD_OP_CASE(RZZ) + ADD_OP_CASE(XXplusYY) + ADD_OP_CASE(XXminusYY) default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported @@ -991,6 +558,8 @@ translateOperations(QuartzProgramBuilder& builder, return success(); } +#undef ADD_OP_CASE + } // namespace /** From eb7b62e7fba5aa33006a2a4a9242a403f0228f0c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:18:52 +0100 Subject: [PATCH 255/419] Implement all merge and inverse-pair canonicalizations --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 60 ++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 10 + .../Dialect/Flux/IR/StandardGates/ECROp.cpp | 25 +++ .../Dialect/Flux/IR/StandardGates/RXXOp.cpp | 24 +++ .../Dialect/Flux/IR/StandardGates/RYYOp.cpp | 24 +++ .../Dialect/Flux/IR/StandardGates/RZXOp.cpp | 24 +++ .../Dialect/Flux/IR/StandardGates/RZZOp.cpp | 24 +++ .../Dialect/Flux/IR/StandardGates/SWAPOp.cpp | 22 +-- .../pipeline/test_compiler_pipeline.cpp | 182 +++++++++++++----- 9 files changed, 328 insertions(+), 67 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index 18285d87b5..d11ca5ee4e 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -39,6 +39,35 @@ removeInversePairOneTargetZeroParameter(OpType op, return success(); } +/** + * @brief Remove a pair of inverse two-target, zero-parameter operations + * + * @tparam InverseOpType The type of the inverse operation. + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeInversePairTwoTargetZeroParameter(OpType op, + mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the inverse operation + auto prevOp = op.getQubit0In().template getDefiningOp(); + if (!prevOp) { + return failure(); + } + if (op.getQubit1In() != prevOp.getQubit1Out()) { + return failure(); + } + + // Remove both operations + rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + rewriter.replaceOp(op, {op.getQubit0In(), op.getQubit1In()}); + + return success(); +} + /** * @brief Merge two compatible one-target, one-parameter operations * @@ -67,4 +96,35 @@ mergeOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return success(); } +/** + * @brief Merge two compatible two-target, one-parameter operations + * + * @tparam OpType The type of the operation to be merged. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the merge. + */ +template +inline mlir::LogicalResult +mergeTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the same operation + auto prevOp = op.getQubit0In().template getDefiningOp(); + if (!prevOp) { + return failure(); + } + if (op.getQubit1In() != prevOp.getQubit1Out()) { + return failure(); + } + + // Compute and set new theta + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); + op->setOperand(2, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + + return success(); +} + } // namespace mlir::flux diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index a05c8aecd1..680c939143 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -795,6 +795,8 @@ def ECROp : FluxOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ecr"; } }]; + + let hasCanonicalizer = 1; } def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { @@ -822,6 +824,8 @@ def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let builders = [ OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> ]; + + let hasCanonicalizer = 1; } def RYYOp : FluxOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { @@ -849,6 +853,8 @@ def RYYOp : FluxOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let builders = [ OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> ]; + + let hasCanonicalizer = 1; } def RZXOp : FluxOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { @@ -876,6 +882,8 @@ def RZXOp : FluxOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let builders = [ OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> ]; + + let hasCanonicalizer = 1; } def RZZOp : FluxOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { @@ -903,6 +911,8 @@ def RZZOp : FluxOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let builders = [ OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta)> ]; + + let hasCanonicalizer = 1; } def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp index 073723b38a..4b7bc9618a 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp @@ -8,15 +8,40 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" #include +#include +#include +#include +#include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Remove subsequent ECR operations on the same qubits. + */ +struct RemoveSubsequentECR final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ECROp op, + PatternRewriter& rewriter) const override { + return removeInversePairTwoTargetZeroParameter(op, rewriter); + } +}; +} // namespace + DenseElementsAttr ECROp::tryGetStaticMatrix() { return getMatrixECR(getContext()); } + +void ECROp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp index 99569412f2..05c601e350 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp @@ -17,12 +17,31 @@ #include #include #include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Merge subsequent RXX operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRXX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXXOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + DenseElementsAttr RXXOp::tryGetStaticMatrix() { const auto& theta = getStaticParameter(getTheta()); if (!theta) { @@ -44,3 +63,8 @@ void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, } build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } + +void RXXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp index 4349b75ed9..6f90d60d70 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp @@ -17,12 +17,31 @@ #include #include #include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Merge subsequent RYY operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYYOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + DenseElementsAttr RYYOp::tryGetStaticMatrix() { const auto& theta = getStaticParameter(getTheta()); if (!theta) { @@ -44,3 +63,8 @@ void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, } build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } + +void RYYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp index e8be60be30..fa40a906bd 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp @@ -17,12 +17,31 @@ #include #include #include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Merge subsequent RZX operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRZX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZXOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + DenseElementsAttr RZXOp::tryGetStaticMatrix() { const auto& theta = getStaticParameter(getTheta()); if (!theta) { @@ -44,3 +63,8 @@ void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, } build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } + +void RZXOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp index ca585d6b1e..6cf5aa4ee6 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp @@ -17,12 +17,31 @@ #include #include #include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Merge subsequent RZZ operations on the same qubits by adding their + * angles. + */ +struct MergeSubsequentRZZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZZOp op, + PatternRewriter& rewriter) const override { + return mergeTwoTargetOneParameter(op, rewriter); + } +}; + +} // namespace + DenseElementsAttr RZZOp::tryGetStaticMatrix() { const auto& theta = getStaticParameter(getTheta()); if (!theta) { @@ -44,3 +63,8 @@ void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, } build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } + +void RZZOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp index 842aef7c53..e36d30ee12 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" @@ -21,31 +22,22 @@ using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + /** - * @brief Remove subsequent SWAP operations on the same qubit. + * @brief Remove subsequent SWAP operations on the same qubits. */ struct RemoveSubsequentSWAP final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(SWAPOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is a SWAP operation - auto prevOp = op.getQubit0In().getDefiningOp(); - if (!prevOp) { - return failure(); - } - if (op.getQubit1In() != prevOp.getQubit1Out()) { - return failure(); - } - - // Remove both SWAP operations - rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); - rewriter.replaceOp(op, {op.getQubit0In(), op.getQubit1In()}); - - return success(); + return removeInversePairTwoTargetZeroParameter(op, rewriter); } }; +} // namespace + DenseElementsAttr SWAPOp::tryGetStaticMatrix() { return getMatrixSWAP(getContext()); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 55e59a3d3e..29e1f5c444 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2550,30 +2550,48 @@ TEST_F(CompilerPipelineTest, ECR) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.ecr(0, 1); + qc.ecr(0, 1); + qc.ecr(0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ecr(q0, q1); + b.ecr(q0, q1); + b.ecr(q0, q1); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.ecr(q0, q1); + std::tie(q0, q1) = b.ecr(q0, q1); + b.ecr(q0, q1); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.ecr(reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.ecr(reg[0], reg[1]); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.ecr(reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -2581,30 +2599,45 @@ TEST_F(CompilerPipelineTest, RXX) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.rxx(1.0, 0, 1); + qc.rxx(0.5, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.rxx(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.rxx(1.0, q0, q1); + b.rxx(0.5, q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.rxx(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.rxx(1.0, q0, q1); + b.rxx(0.5, q0, q1); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rxx(1.5, reg[0], reg[1]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rxx(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.rxx(1.0, reg[0], reg[1]); + b.rxx(1.5, reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -2674,30 +2707,45 @@ TEST_F(CompilerPipelineTest, RYY) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.ryy(1.0, 0, 1); + qc.ryy(0.5, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.ryy(1.0, q0, q1); + b.ryy(0.5, q0, q1); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.ryy(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.ryy(1.0, q0, q1); + b.ryy(0.5, q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.ryy(1.0, reg[0], reg[1]); + b.ryy(1.5, reg[0], reg[1]); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.ryy(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.ryy(1.0, reg[0], reg[1]); + b.ryy(1.5, reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -2705,30 +2753,45 @@ TEST_F(CompilerPipelineTest, RZX) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.rzx(1.0, 0, 1); + qc.rzx(0.5, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.rzx(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.rzx(1.0, q0, q1); + b.rzx(0.5, q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.rzx(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.rzx(1.0, q0, q1); + b.rzx(0.5, q0, q1); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzx(1.5, reg[0], reg[1]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzx(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.rzx(1.0, reg[0], reg[1]); + b.rzx(1.5, reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -2736,30 +2799,45 @@ TEST_F(CompilerPipelineTest, RZZ) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.rzz(1.0, 0, 1); + qc.rzz(0.5, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.rzz(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.rzz(1.0, q0, q1); + b.rzz(0.5, q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.rzz(1.0, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.rzz(1.0, q0, q1); + b.rzz(0.5, q0, q1); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzz(1.5, reg[0], reg[1]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.rzz(1.5, reg[0], reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.rzz(1.0, reg[0], reg[1]); + b.rzz(1.5, reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } From b73803d885d70cd93cc62a7e0ce67801d566fa0b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:57:14 +0100 Subject: [PATCH 256/419] Add canonicalizations for XXPlusYY and XXMinusYY --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 8 +- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 4 + .../Flux/IR/StandardGates/XXMinusYYOp.cpp | 55 +++++++++++ .../Flux/IR/StandardGates/XXPlusYYOp.cpp | 55 +++++++++++ .../pipeline/test_compiler_pipeline.cpp | 98 ++++++++++++++----- 5 files changed, 196 insertions(+), 24 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index d11ca5ee4e..5d2cd0cdf5 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -57,6 +57,8 @@ removeInversePairTwoTargetZeroParameter(OpType op, if (!prevOp) { return failure(); } + + // Confirm operations act on same qubits if (op.getQubit1In() != prevOp.getQubit1Out()) { return failure(); } @@ -85,7 +87,7 @@ mergeOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return failure(); } - // Compute and set new theta + // Compute and set new angle auto newParameter = rewriter.create( op.getLoc(), op.getOperand(1), prevOp.getOperand(1)); op->setOperand(1, newParameter.getResult()); @@ -112,11 +114,13 @@ mergeTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { if (!prevOp) { return failure(); } + + // Confirm operations act on same qubits if (op.getQubit1In() != prevOp.getQubit1Out()) { return failure(); } - // Compute and set new theta + // Compute and set new angle auto newParameter = rewriter.create( op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); op->setOperand(2, newParameter.getResult()); diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 680c939143..904016c34f 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -941,6 +941,8 @@ def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwo let builders = [ OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> ]; + + let hasCanonicalizer = 1; } def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { @@ -969,6 +971,8 @@ def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetT let builders = [ OpBuilder<(ins "Value":$qubit0_in, "Value":$qubit1_in, "const std::variant&":$theta, "const std::variant&":$beta)> ]; + + let hasCanonicalizer = 1; } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp index 65bd4d2360..15e9adf0fb 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp @@ -8,19 +8,69 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" #include #include #include +#include #include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Merge subsequent XXMinusYY operations on the same qubits by adding + * their thetas. + */ +struct MergeSubsequentXXMinusYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XXMinusYYOp op, + PatternRewriter& rewriter) const override { + auto prevOp = op.getQubit0In().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Confirm operations act on same qubits + if (op.getQubit1In() != prevOp.getQubit1Out()) { + return failure(); + } + + // Confirm betas are equal + auto beta = XXMinusYYOp::getStaticParameter(op.getBeta()); + auto prevBeta = XXMinusYYOp::getStaticParameter(prevOp.getBeta()); + if (beta && prevBeta) { + if (beta.getValueAsDouble() != prevBeta.getValueAsDouble()) { + return failure(); + } + } else { + return failure(); + } + + // Compute and set new theta + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); + op->setOperand(2, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + + return success(); + } +}; + +} // namespace + DenseElementsAttr XXMinusYYOp::tryGetStaticMatrix() { const auto theta = getStaticParameter(getTheta()); const auto beta = getStaticParameter(getBeta()); @@ -54,3 +104,8 @@ void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); } + +void XXMinusYYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp index fda412d6da..a7af4caa58 100644 --- a/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp @@ -8,19 +8,69 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/MatrixUtils.h" #include #include #include +#include #include +#include +#include #include using namespace mlir; using namespace mlir::flux; using namespace mlir::utils; +namespace { + +/** + * @brief Merge subsequent XXPlusYY operations on the same qubits by adding + * their thetas. + */ +struct MergeSubsequentXXPlusYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(XXPlusYYOp op, + PatternRewriter& rewriter) const override { + auto prevOp = op.getQubit0In().getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Confirm operations act on same qubits + if (op.getQubit1In() != prevOp.getQubit1Out()) { + return failure(); + } + + // Confirm betas are equal + auto beta = XXPlusYYOp::getStaticParameter(op.getBeta()); + auto prevBeta = XXPlusYYOp::getStaticParameter(prevOp.getBeta()); + if (beta && prevBeta) { + if (beta.getValueAsDouble() != prevBeta.getValueAsDouble()) { + return failure(); + } + } else { + return failure(); + } + + // Compute and set new theta + auto newParameter = rewriter.create( + op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); + op->setOperand(2, newParameter.getResult()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + + return success(); + } +}; + +} // namespace + DenseElementsAttr XXPlusYYOp::tryGetStaticMatrix() { const auto theta = getStaticParameter(getTheta()); const auto beta = getStaticParameter(getBeta()); @@ -54,3 +104,8 @@ void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); } + +void XXPlusYYOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 29e1f5c444..cab8f830e1 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2845,30 +2845,57 @@ TEST_F(CompilerPipelineTest, XXPLUSYY) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.xx_plus_yy(1.0, 0.5, 0, 1); + qc.xx_plus_yy(0.5, 0.5, 0, 1); + qc.xx_plus_yy(1.0, 1.0, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.xx_plus_yy(1.0, 0.5, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.xx_plus_yy(1.0, 0.5, q0, q1); + b.xx_plus_yy(0.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_plus_yy(1.0, 0.5, q0, q1); + std::tie(q0, q1) = b.xx_plus_yy(0.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.xx_plus_yy(1.0, 0.5, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_plus_yy(1.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.xx_plus_yy(1.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.xx_plus_yy(1.0, 0.5, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.xx_plus_yy(1.5, 0.5, q0, q1); + b.xx_plus_yy(1.0, 1.0, q0, q1); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } @@ -2938,30 +2965,57 @@ TEST_F(CompilerPipelineTest, XXMINUSYY) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.xx_minus_yy(1.0, 0.5, 0, 1); + qc.xx_minus_yy(0.5, 0.5, 0, 1); + qc.xx_minus_yy(1.0, 1.0, 0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.xx_minus_yy(1.0, 0.5, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.xx_minus_yy(1.0, 0.5, q0, q1); + b.xx_minus_yy(0.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_minus_yy(1.0, 0.5, q0, q1); + std::tie(q0, q1) = b.xx_minus_yy(0.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.xx_minus_yy(1.0, 0.5, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + std::tie(q0, q1) = b.xx_minus_yy(1.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.xx_minus_yy(1.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.xx_minus_yy(1.0, 0.5, reg[0], reg[1]); + auto q0 = reg[0]; + auto q1 = reg[1]; + b.xx_minus_yy(1.5, 0.5, q0, q1); + b.xx_minus_yy(1.0, 1.0, q0, q1); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } From 7e6ce392c424e0d3d2835e44c96288d0d89dac1d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:16:41 +0100 Subject: [PATCH 257/419] Fix equivalence checking of constants --- .../pipeline/test_compiler_pipeline.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index cab8f830e1..e482944c6c 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -129,6 +129,28 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, return false; } + // Check arith::ConstantOp + if (auto lhsConst = llvm::dyn_cast(lhs)) { + auto rhsConst = llvm::dyn_cast(rhs); + if (!rhsConst) { + return false; + } + if (lhsConst.getValue() != rhsConst.getValue()) { + return false; + } + } + + // Check LLVM::ConstantOp + if (auto lhsConst = llvm::dyn_cast(lhs)) { + auto rhsConst = llvm::dyn_cast(rhs); + if (!rhsConst) { + return false; + } + if (lhsConst.getValue() != rhsConst.getValue()) { + return false; + } + } + // Check number of operands and results if (lhs->getNumOperands() != rhs->getNumOperands() || lhs->getNumResults() != rhs->getNumResults() || From b5bc9c4dfb237f6ffbc2581b2070a0ec5e8ce750 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:26:27 +0100 Subject: [PATCH 258/419] Further modularize operations --- mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 7 +- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 105 ------------------ .../Dialect/Flux/IR/Operations/MeasureOp.cpp | 39 +++++++ .../Dialect/Flux/IR/Operations/ResetOp.cpp | 47 ++++++++ .../{ => Operations}/StandardGates/DCXOp.cpp | 0 .../{ => Operations}/StandardGates/ECROp.cpp | 0 .../IR/{ => Operations}/StandardGates/HOp.cpp | 0 .../{ => Operations}/StandardGates/IdOp.cpp | 0 .../IR/{ => Operations}/StandardGates/POp.cpp | 0 .../IR/{ => Operations}/StandardGates/ROp.cpp | 0 .../{ => Operations}/StandardGates/RXOp.cpp | 0 .../{ => Operations}/StandardGates/RXXOp.cpp | 0 .../{ => Operations}/StandardGates/RYOp.cpp | 0 .../{ => Operations}/StandardGates/RYYOp.cpp | 0 .../{ => Operations}/StandardGates/RZOp.cpp | 0 .../{ => Operations}/StandardGates/RZXOp.cpp | 0 .../{ => Operations}/StandardGates/RZZOp.cpp | 0 .../IR/{ => Operations}/StandardGates/SOp.cpp | 0 .../{ => Operations}/StandardGates/SWAPOp.cpp | 0 .../{ => Operations}/StandardGates/SXOp.cpp | 0 .../{ => Operations}/StandardGates/SXdgOp.cpp | 0 .../{ => Operations}/StandardGates/SdgOp.cpp | 0 .../IR/{ => Operations}/StandardGates/TOp.cpp | 0 .../{ => Operations}/StandardGates/TdgOp.cpp | 0 .../{ => Operations}/StandardGates/U2Op.cpp | 0 .../IR/{ => Operations}/StandardGates/UOp.cpp | 0 .../IR/{ => Operations}/StandardGates/XOp.cpp | 0 .../StandardGates/XXMinusYYOp.cpp | 0 .../StandardGates/XXPlusYYOp.cpp | 0 .../IR/{ => Operations}/StandardGates/YOp.cpp | 0 .../IR/{ => Operations}/StandardGates/ZOp.cpp | 0 .../StandardGates/iSWAPOp.cpp | 0 .../Flux/IR/QubitManagement/AllocOp.cpp | 39 +++++++ .../Flux/IR/QubitManagement/DeallocOp.cpp | 50 +++++++++ mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 7 +- .../Quartz/IR/Operations/MeasureOp.cpp | 39 +++++++ .../{ => Operations}/StandardGates/DCXOp.cpp | 0 .../{ => Operations}/StandardGates/ECROp.cpp | 0 .../IR/{ => Operations}/StandardGates/HOp.cpp | 0 .../{ => Operations}/StandardGates/IdOp.cpp | 0 .../StandardGates/PhaseOp.cpp | 0 .../IR/{ => Operations}/StandardGates/ROp.cpp | 0 .../{ => Operations}/StandardGates/RXOp.cpp | 0 .../{ => Operations}/StandardGates/RXXOp.cpp | 0 .../{ => Operations}/StandardGates/RYOp.cpp | 0 .../{ => Operations}/StandardGates/RYYOp.cpp | 0 .../{ => Operations}/StandardGates/RZOp.cpp | 0 .../{ => Operations}/StandardGates/RZXOp.cpp | 0 .../{ => Operations}/StandardGates/RZZOp.cpp | 0 .../IR/{ => Operations}/StandardGates/SOp.cpp | 0 .../{ => Operations}/StandardGates/SWAPOp.cpp | 0 .../{ => Operations}/StandardGates/SXOp.cpp | 0 .../{ => Operations}/StandardGates/SXdgOp.cpp | 0 .../{ => Operations}/StandardGates/SdgOp.cpp | 0 .../IR/{ => Operations}/StandardGates/TOp.cpp | 0 .../{ => Operations}/StandardGates/TdgOp.cpp | 0 .../{ => Operations}/StandardGates/U2Op.cpp | 0 .../IR/{ => Operations}/StandardGates/UOp.cpp | 0 .../IR/{ => Operations}/StandardGates/XOp.cpp | 0 .../StandardGates/XXMinusYYOp.cpp | 0 .../StandardGates/XXPlusYYOp.cpp | 0 .../IR/{ => Operations}/StandardGates/YOp.cpp | 0 .../IR/{ => Operations}/StandardGates/ZOp.cpp | 0 .../StandardGates/iSWAPOp.cpp | 0 mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp | 49 -------- .../Quartz/IR/QubitManagement/AllocOp.cpp | 39 +++++++ 66 files changed, 263 insertions(+), 158 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/DCXOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/ECROp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/HOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/IdOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/POp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/ROp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RXOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RXXOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RYOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RYYOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RZOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RZXOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/RZZOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/SOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/SWAPOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/SXOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/SXdgOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/SdgOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/TOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/TdgOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/U2Op.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/UOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/XOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/XXMinusYYOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/XXPlusYYOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/YOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/ZOp.cpp (100%) rename mlir/lib/Dialect/Flux/IR/{ => Operations}/StandardGates/iSWAPOp.cpp (100%) create mode 100644 mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp create mode 100644 mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/DCXOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/ECROp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/HOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/IdOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/PhaseOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/ROp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RXOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RXXOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RYOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RYYOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RZOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RZXOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/RZZOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/SOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/SWAPOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/SXOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/SXdgOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/SdgOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/TOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/TdgOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/U2Op.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/UOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/XOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/XXMinusYYOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/XXPlusYYOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/YOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/ZOp.cpp (100%) rename mlir/lib/Dialect/Quartz/IR/{ => Operations}/StandardGates/iSWAPOp.cpp (100%) create mode 100644 mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt index 5772e45b3d..69cd64d492 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -7,13 +7,16 @@ # Licensed under the MIT License file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") -file(GLOB_RECURSE STANDARD_GATES "${CMAKE_CURRENT_SOURCE_DIR}/StandardGates/*.cpp") +file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Operations/**/*.cpp") +file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") add_mlir_dialect_library( MLIRFluxDialect FluxOps.cpp ${MODIFIERS} - ${STANDARD_GATES} + ${OPERATIONS} + ${QUBIT_MANAGEMENT} ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux DEPENDS diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index fd00fc0560..03d8d4e123 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -65,108 +65,3 @@ void FluxDialect::initialize() { #define GET_OP_CLASSES #include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" - -LogicalResult AllocOp::verify() { - const auto registerName = getRegisterName(); - const auto registerSize = getRegisterSize(); - const auto registerIndex = getRegisterIndex(); - - const auto hasSize = registerSize.has_value(); - const auto hasIndex = registerIndex.has_value(); - const auto hasName = registerName.has_value(); - - if (hasName != hasSize || hasName != hasIndex) { - return emitOpError("register attributes must all be present or all absent"); - } - - if (hasName) { - if (*registerIndex >= *registerSize) { - return emitOpError("register_index (") - << *registerIndex << ") must be less than register_size (" - << *registerSize << ")"; - } - } - return success(); -} - -LogicalResult MeasureOp::verify() { - const auto registerName = getRegisterName(); - const auto registerSize = getRegisterSize(); - const auto registerIndex = getRegisterIndex(); - - const auto hasSize = registerSize.has_value(); - const auto hasIndex = registerIndex.has_value(); - const auto hasName = registerName.has_value(); - - if (hasName != hasSize || hasName != hasIndex) { - return emitOpError("register attributes must all be present or all absent"); - } - - if (hasName) { - if (*registerIndex >= *registerSize) { - return emitOpError("register_index (") - << *registerIndex << ") must be less than register_size (" - << *registerSize << ")"; - } - } - return success(); -} - -//===----------------------------------------------------------------------===// -// Canonicalization Patterns -//===----------------------------------------------------------------------===// - -namespace { - -/** - * @brief Remove matching allocation and deallocation pairs without operations - * between them. - */ -struct RemoveAllocDeallocPair final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(DeallocOp op, - PatternRewriter& rewriter) const override { - // Check if the predecessor is an AllocOp - auto allocOp = op.getQubit().getDefiningOp(); - if (!allocOp) { - return failure(); - } - - // Remove the AllocOp and the DeallocOp - rewriter.eraseOp(op); - rewriter.eraseOp(allocOp); - return success(); - } -}; - -/** - * @brief Remove reset operations that immediately follow an allocation. - */ -struct RemoveResetAfterAlloc final : OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(ResetOp op, - PatternRewriter& rewriter) const override { - // Check if the predecessor is an AllocOp - if (auto allocOp = op.getQubitIn().getDefiningOp(); !allocOp) { - return failure(); - } - - // Remove the ResetOp - rewriter.replaceOp(op, op.getQubitIn()); - return success(); - } -}; - -} // namespace - -void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} - -void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); -} diff --git a/mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp new file mode 100644 index 0000000000..c9d6ecbf5e --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include + +using namespace mlir; +using namespace mlir::flux; + +LogicalResult MeasureOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp new file mode 100644 index 0000000000..489e667db5 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; + +namespace { + +/** + * @brief Remove reset operations that immediately follow an allocation. + */ +struct RemoveResetAfterAlloc final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ResetOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an AllocOp + if (auto allocOp = op.getQubitIn().getDefiningOp(); !allocOp) { + return failure(); + } + + // Remove the ResetOp + rewriter.replaceOp(op, op.getQubitIn()); + return success(); + } +}; + +} // namespace + +void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/DCXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/DCXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/DCXOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/DCXOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/ECROp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/HOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/IdOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/POp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/ROp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RXOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RXXOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RYOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RYYOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RZOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RZXOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/RZZOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/SOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/SWAPOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/SXOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/SXdgOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/SdgOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/TOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/TdgOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/U2Op.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/UOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/XOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/XXMinusYYOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/XXPlusYYOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/YOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/ZOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/StandardGates/iSWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/iSWAPOp.cpp similarity index 100% rename from mlir/lib/Dialect/Flux/IR/StandardGates/iSWAPOp.cpp rename to mlir/lib/Dialect/Flux/IR/Operations/StandardGates/iSWAPOp.cpp diff --git a/mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp b/mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp new file mode 100644 index 0000000000..7885ed146d --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include + +using namespace mlir; +using namespace mlir::flux; + +LogicalResult AllocOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp new file mode 100644 index 0000000000..18ab51a235 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/IR/FluxDialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; + +namespace { + +/** + * @brief Remove matching allocation and deallocation pairs without operations + * between them. + */ +struct RemoveAllocDeallocPair final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(DeallocOp op, + PatternRewriter& rewriter) const override { + // Check if the predecessor is an AllocOp + auto allocOp = op.getQubit().getDefiningOp(); + if (!allocOp) { + return failure(); + } + + // Remove the AllocOp and the DeallocOp + rewriter.eraseOp(op); + rewriter.eraseOp(allocOp); + return success(); + } +}; + +} // namespace + +void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt index 69555a228d..47dbbc8267 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -7,13 +7,16 @@ # Licensed under the MIT License file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") -file(GLOB_RECURSE STANDARD_GATES "${CMAKE_CURRENT_SOURCE_DIR}/StandardGates/*.cpp") +file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Operations/**/*.cpp") +file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") add_mlir_dialect_library( MLIRQuartzDialect QuartzOps.cpp ${MODIFIERS} - ${STANDARD_GATES} + ${OPERATIONS} + ${QUBIT_MANAGEMENT} ADDITIONAL_HEADER_DIRS ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz DEPENDS diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp new file mode 100644 index 0000000000..30a6eed8c5 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include + +using namespace mlir; +using namespace mlir::quartz; + +LogicalResult MeasureOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/DCXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/DCXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/DCXOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/DCXOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ECROp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/ECROp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ECROp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/HOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/HOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/HOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/IdOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/IdOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/IdOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/PhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/PhaseOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/ROp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RXOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RXXOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RYOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RYYOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RZOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RZXOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/RZZOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/SOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SWAPOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/SWAPOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SWAPOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/SXOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXdgOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/SXdgOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXdgOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SdgOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/SdgOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SdgOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/TOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TdgOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/TdgOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TdgOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/U2Op.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/UOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/XOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/XXMinusYYOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/XXPlusYYOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/YOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/YOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/YOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ZOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/ZOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ZOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/StandardGates/iSWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/iSWAPOp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/StandardGates/iSWAPOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/iSWAPOp.cpp diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp index 7519ff0c75..f8c8547231 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp @@ -17,9 +17,6 @@ #include // IWYU pragma: end_keep -#include -#include - using namespace mlir; using namespace mlir::quartz; @@ -63,49 +60,3 @@ void QuartzDialect::initialize() { #define GET_OP_CLASSES #include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" - -LogicalResult AllocOp::verify() { - const auto registerName = getRegisterName(); - const auto registerSize = getRegisterSize(); - const auto registerIndex = getRegisterIndex(); - - const auto hasSize = registerSize.has_value(); - const auto hasIndex = registerIndex.has_value(); - const auto hasName = registerName.has_value(); - - if (hasName != hasSize || hasName != hasIndex) { - return emitOpError("register attributes must all be present or all absent"); - } - - if (hasName) { - if (*registerIndex >= *registerSize) { - return emitOpError("register_index (") - << *registerIndex << ") must be less than register_size (" - << *registerSize << ")"; - } - } - return success(); -} - -LogicalResult MeasureOp::verify() { - const auto registerName = getRegisterName(); - const auto registerSize = getRegisterSize(); - const auto registerIndex = getRegisterIndex(); - - const auto hasSize = registerSize.has_value(); - const auto hasIndex = registerIndex.has_value(); - const auto hasName = registerName.has_value(); - - if (hasName != hasSize || hasName != hasIndex) { - return emitOpError("register attributes must all be present or all absent"); - } - - if (hasName) { - if (*registerIndex >= *registerSize) { - return emitOpError("register_index (") - << *registerIndex << ") must be less than register_size (" - << *registerSize << ")"; - } - } - return success(); -} diff --git a/mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp b/mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp new file mode 100644 index 0000000000..b49bc7aa0b --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" + +#include + +using namespace mlir; +using namespace mlir::quartz; + +LogicalResult AllocOp::verify() { + const auto registerName = getRegisterName(); + const auto registerSize = getRegisterSize(); + const auto registerIndex = getRegisterIndex(); + + const auto hasSize = registerSize.has_value(); + const auto hasIndex = registerIndex.has_value(); + const auto hasName = registerName.has_value(); + + if (hasName != hasSize || hasName != hasIndex) { + return emitOpError("register attributes must all be present or all absent"); + } + + if (hasName) { + if (*registerIndex >= *registerSize) { + return emitOpError("register_index (") + << *registerIndex << ") must be less than register_size (" + << *registerSize << ")"; + } + } + return success(); +} From 614e2ca347961fb8b72ed57ed95533edf035a721 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:54:16 +0100 Subject: [PATCH 259/419] Use macros for QIR function names --- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 212 ++++++++---------- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 142 +----------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 107 ++------- 3 files changed, 122 insertions(+), 339 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index d1d260e83b..ba83ca3516 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -17,125 +17,111 @@ namespace mlir::qir { -// QIR function name constants +// QIR function names + +#define ADD_STANDARD_GATE(NAME_BIG, NAME_SMALL) \ + static constexpr auto QIR_##NAME_BIG = \ + "__quantum__qis__" #NAME_SMALL "__body"; \ + static constexpr auto QIR_C##NAME_BIG = \ + "__quantum__qis__c" #NAME_SMALL "__body"; \ + static constexpr auto QIR_CC##NAME_BIG = \ + "__quantum__qis__cc" #NAME_SMALL "__body"; \ + static constexpr auto QIR_CCC##NAME_BIG = \ + "__quantum__qis__ccc" #NAME_SMALL "__body"; + static constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; static constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; -static constexpr auto QIR_ID = "__quantum__qis__i__body"; -static constexpr auto QIR_CID = "__quantum__qis__ci__body"; -static constexpr auto QIR_CCID = "__quantum__qis__cci__body"; -static constexpr auto QIR_CCCID = "__quantum__qis__ccci__body"; -static constexpr auto QIR_X = "__quantum__qis__x__body"; -static constexpr auto QIR_CX = "__quantum__qis__cx__body"; -static constexpr auto QIR_CCX = "__quantum__qis__ccx__body"; -static constexpr auto QIR_CCCX = "__quantum__qis__cccx__body"; -static constexpr auto QIR_Y = "__quantum__qis__y__body"; -static constexpr auto QIR_CY = "__quantum__qis__cy__body"; -static constexpr auto QIR_CCY = "__quantum__qis__ccy__body"; -static constexpr auto QIR_CCCY = "__quantum__qis__cccy__body"; -static constexpr auto QIR_Z = "__quantum__qis__z__body"; -static constexpr auto QIR_CZ = "__quantum__qis__cz__body"; -static constexpr auto QIR_CCZ = "__quantum__qis__ccz__body"; -static constexpr auto QIR_CCCZ = "__quantum__qis__cccz__body"; -static constexpr auto QIR_H = "__quantum__qis__h__body"; -static constexpr auto QIR_CH = "__quantum__qis__ch__body"; -static constexpr auto QIR_CCH = "__quantum__qis__cch__body"; -static constexpr auto QIR_CCCH = "__quantum__qis__ccch__body"; -static constexpr auto QIR_S = "__quantum__qis__s__body"; -static constexpr auto QIR_CS = "__quantum__qis__cs__body"; -static constexpr auto QIR_CCS = "__quantum__qis__ccs__body"; -static constexpr auto QIR_CCCS = "__quantum__qis__cccs__body"; -static constexpr auto QIR_SDG = "__quantum__qis__sdg__body"; -static constexpr auto QIR_CSDG = "__quantum__qis__csdg__body"; -static constexpr auto QIR_CCSDG = "__quantum__qis__ccsdg__body"; -static constexpr auto QIR_CCCSDG = "__quantum__qis__cccsdg__body"; -static constexpr auto QIR_T = "__quantum__qis__t__body"; -static constexpr auto QIR_CT = "__quantum__qis__ct__body"; -static constexpr auto QIR_CCT = "__quantum__qis__cct__body"; -static constexpr auto QIR_CCCT = "__quantum__qis__ccct__body"; -static constexpr auto QIR_TDG = "__quantum__qis__tdg__body"; -static constexpr auto QIR_CTDG = "__quantum__qis__ctdg__body"; -static constexpr auto QIR_CCTDG = "__quantum__qis__cctdg__body"; -static constexpr auto QIR_CCCTDG = "__quantum__qis__ccctdg__body"; -static constexpr auto QIR_SX = "__quantum__qis__sx__body"; -static constexpr auto QIR_CSX = "__quantum__qis__csx__body"; -static constexpr auto QIR_CCSX = "__quantum__qis__ccsx__body"; -static constexpr auto QIR_CCCSX = "__quantum__qis__cccsx__body"; -static constexpr auto QIR_SXDG = "__quantum__qis__sxdg__body"; -static constexpr auto QIR_CSXDG = "__quantum__qis__csxdg__body"; -static constexpr auto QIR_CCSXDG = "__quantum__qis__ccsxdg__body"; -static constexpr auto QIR_CCCSXDG = "__quantum__qis__cccsxdg__body"; -static constexpr auto QIR_RX = "__quantum__qis__rx__body"; -static constexpr auto QIR_CRX = "__quantum__qis__crx__body"; -static constexpr auto QIR_CCRX = "__quantum__qis__ccrx__body"; -static constexpr auto QIR_CCCRX = "__quantum__qis__cccrx__body"; -static constexpr auto QIR_RY = "__quantum__qis__ry__body"; -static constexpr auto QIR_CRY = "__quantum__qis__cry__body"; -static constexpr auto QIR_CCRY = "__quantum__qis__ccry__body"; -static constexpr auto QIR_CCCRY = "__quantum__qis__cccry__body"; -static constexpr auto QIR_RZ = "__quantum__qis__rz__body"; -static constexpr auto QIR_CRZ = "__quantum__qis__crz__body"; -static constexpr auto QIR_CCRZ = "__quantum__qis__ccrz__body"; -static constexpr auto QIR_CCCRZ = "__quantum__qis__cccrz__body"; -static constexpr auto QIR_P = "__quantum__qis__p__body"; -static constexpr auto QIR_CP = "__quantum__qis__cp__body"; -static constexpr auto QIR_CCP = "__quantum__qis__ccp__body"; -static constexpr auto QIR_CCCP = "__quantum__qis__cccp__body"; -static constexpr auto QIR_R = "__quantum__qis__r__body"; -static constexpr auto QIR_CR = "__quantum__qis__cr__body"; -static constexpr auto QIR_CCR = "__quantum__qis__ccr__body"; -static constexpr auto QIR_CCCR = "__quantum__qis__cccr__body"; -static constexpr auto QIR_U2 = "__quantum__qis__u2__body"; -static constexpr auto QIR_CU2 = "__quantum__qis__cu2__body"; -static constexpr auto QIR_CCU2 = "__quantum__qis__ccu2__body"; -static constexpr auto QIR_CCCU2 = "__quantum__qis__cccu2__body"; -static constexpr auto QIR_U = "__quantum__qis__u3__body"; -static constexpr auto QIR_CU = "__quantum__qis__cu3__body"; -static constexpr auto QIR_CCU = "__quantum__qis__ccu3__body"; -static constexpr auto QIR_CCCU = "__quantum__qis__cccu3__body"; -static constexpr auto QIR_SWAP = "__quantum__qis__swap__body"; -static constexpr auto QIR_CSWAP = "__quantum__qis__cswap__body"; -static constexpr auto QIR_CCSWAP = "__quantum__qis__ccswap__body"; -static constexpr auto QIR_CCCSWAP = "__quantum__qis__cccswap__body"; -static constexpr auto QIR_ISWAP = "__quantum__qis__iswap__body"; -static constexpr auto QIR_CISWAP = "__quantum__qis__ciswap__body"; -static constexpr auto QIR_CCISWAP = "__quantum__qis__cciswap__body"; -static constexpr auto QIR_CCCISWAP = "__quantum__qis__ccciswap__body"; -static constexpr auto QIR_DCX = "__quantum__qis__dcx__body"; -static constexpr auto QIR_CDCX = "__quantum__qis__cdcx__body"; -static constexpr auto QIR_CCDCX = "__quantum__qis__ccdcx__body"; -static constexpr auto QIR_CCCDCX = "__quantum__qis__cccdcx__body"; -static constexpr auto QIR_ECR = "__quantum__qis__ecr__body"; -static constexpr auto QIR_CECR = "__quantum__qis__cecr__body"; -static constexpr auto QIR_CCECR = "__quantum__qis__ccecr__body"; -static constexpr auto QIR_CCCECR = "__quantum__qis__cccecr__body"; -static constexpr auto QIR_RXX = "__quantum__qis__rxx__body"; -static constexpr auto QIR_CRXX = "__quantum__qis__crxx__body"; -static constexpr auto QIR_CCRXX = "__quantum__qis__ccrxx__body"; -static constexpr auto QIR_CCCRXX = "__quantum__qis__cccrxx__body"; -static constexpr auto QIR_RYY = "__quantum__qis__ryy__body"; -static constexpr auto QIR_CRYY = "__quantum__qis__cryy__body"; -static constexpr auto QIR_CCRYY = "__quantum__qis__ccryy__body"; -static constexpr auto QIR_CCCRYY = "__quantum__qis__cccryy__body"; -static constexpr auto QIR_RZX = "__quantum__qis__rzx__body"; -static constexpr auto QIR_CRZX = "__quantum__qis__crzx__body"; -static constexpr auto QIR_CCRZX = "__quantum__qis__ccrzx__body"; -static constexpr auto QIR_CCCRZX = "__quantum__qis__cccrzx__body"; -static constexpr auto QIR_RZZ = "__quantum__qis__rzz__body"; -static constexpr auto QIR_CRZZ = "__quantum__qis__crzz__body"; -static constexpr auto QIR_CCRZZ = "__quantum__qis__ccrzz__body"; -static constexpr auto QIR_CCCRZZ = "__quantum__qis__cccrzz__body"; -static constexpr auto QIR_XXPLUSYY = "__quantum__qis__xx_plus_yy__body"; -static constexpr auto QIR_CXXPLUSYY = "__quantum__qis__cxx_plus_yy__body"; -static constexpr auto QIR_CCXXPLUSYY = "__quantum__qis__ccxx_plus_yy__body"; -static constexpr auto QIR_CCCXXPLUSYY = "__quantum__qis__cccxx_plus_yy__body"; -static constexpr auto QIR_XXMINUSYY = "__quantum__qis__xx_minus_yy__body"; -static constexpr auto QIR_CXXMINUSYY = "__quantum__qis__cxx_minus_yy__body"; -static constexpr auto QIR_CCXXMINUSYY = "__quantum__qis__ccxx_minus_yy__body"; -static constexpr auto QIR_CCCXXMINUSYY = "__quantum__qis__cccxx_minus_yy__body"; +ADD_STANDARD_GATE(I, i) +ADD_STANDARD_GATE(X, x) +ADD_STANDARD_GATE(Y, y) +ADD_STANDARD_GATE(Z, z) +ADD_STANDARD_GATE(H, h) +ADD_STANDARD_GATE(S, s) +ADD_STANDARD_GATE(SDG, sdg) +ADD_STANDARD_GATE(T, t) +ADD_STANDARD_GATE(TDG, tdg) +ADD_STANDARD_GATE(SX, sx) +ADD_STANDARD_GATE(SXDG, sxdg) +ADD_STANDARD_GATE(RX, rx) +ADD_STANDARD_GATE(RY, ry) +ADD_STANDARD_GATE(RZ, rz) +ADD_STANDARD_GATE(P, p) +ADD_STANDARD_GATE(R, r) +ADD_STANDARD_GATE(U2, u2) +ADD_STANDARD_GATE(U, u3) +ADD_STANDARD_GATE(SWAP, swap) +ADD_STANDARD_GATE(ISWAP, iswap) +ADD_STANDARD_GATE(DCX, dcx) +ADD_STANDARD_GATE(ECR, ecr) +ADD_STANDARD_GATE(RXX, rxx) +ADD_STANDARD_GATE(RYY, ryy) +ADD_STANDARD_GATE(RZX, rzx) +ADD_STANDARD_GATE(RZZ, rzz) +ADD_STANDARD_GATE(XXPLUSYY, xx_plus_yy) +ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) + +#undef ADD_STANDARD_GATE + +// Functions for getting QIR function names + +#define DEFINE_GETTER(NAME) \ + /** \ + * @brief Gets the QIR function name for NAME \ + * \ + * @param numControls Number of control qubits \ + * @return The QIR function name \ + */ \ + inline StringRef getFnName##NAME(size_t numControls) { \ + switch (numControls) { \ + case 0: \ + return QIR_##NAME; \ + case 1: \ + return QIR_C##NAME; \ + case 2: \ + return QIR_CC##NAME; \ + case 3: \ + return QIR_CCC##NAME; \ + default: \ + llvm::report_fatal_error( \ + "Multi-controlled with more than 3 controls are currently not " \ + "supported"); \ + } \ + } + +DEFINE_GETTER(I) +DEFINE_GETTER(X) +DEFINE_GETTER(Y) +DEFINE_GETTER(Z) +DEFINE_GETTER(H) +DEFINE_GETTER(S) +DEFINE_GETTER(SDG) +DEFINE_GETTER(T) +DEFINE_GETTER(TDG) +DEFINE_GETTER(SX) +DEFINE_GETTER(SXDG) +DEFINE_GETTER(RX) +DEFINE_GETTER(RY) +DEFINE_GETTER(RZ) +DEFINE_GETTER(P) +DEFINE_GETTER(R) +DEFINE_GETTER(U2) +DEFINE_GETTER(U) +DEFINE_GETTER(SWAP) +DEFINE_GETTER(ISWAP) +DEFINE_GETTER(DCX) +DEFINE_GETTER(ECR) +DEFINE_GETTER(RXX) +DEFINE_GETTER(RYY) +DEFINE_GETTER(RZX) +DEFINE_GETTER(RZZ) +DEFINE_GETTER(XXPLUSYY) +DEFINE_GETTER(XXMINUSYY) + +#undef DEFINE_GETTER /** * @brief State object for tracking QIR metadata during conversion diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index da053f338c..6219137949 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -513,34 +513,16 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ state, fnName, 1, 0); \ } \ }; -DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, ID, id, i) +DEFINE_ONE_TARGET_ZERO_PARAMETER(IdOp, I, id, i) DEFINE_ONE_TARGET_ZERO_PARAMETER(XOp, X, x, x) DEFINE_ONE_TARGET_ZERO_PARAMETER(YOp, Y, y, y) DEFINE_ONE_TARGET_ZERO_PARAMETER(ZOp, Z, z, z) @@ -579,28 +561,10 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, SXDG, sxdg, sxdg) matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ state, fnName, 1, 1); \ } \ @@ -638,28 +602,10 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, P, p, p, theta) matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ state, fnName, 1, 2); \ } \ @@ -695,28 +641,10 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, U2, u2, u2, phi, lambda) matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp( \ op, adaptor, rewriter, getContext(), state, fnName, 1, 3); \ } \ @@ -751,28 +679,10 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, U, u, u3) matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ state, fnName, 2, 0); \ } \ @@ -810,28 +720,10 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ state, fnName, 2, 1); \ } \ @@ -870,28 +762,10 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ auto& state = getState(); \ - \ - /* Query state for modifier information */ \ const auto inCtrlOp = state.inCtrlOp; \ const size_t numCtrls = \ inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - \ - /* Define function name */ \ - StringRef fnName; \ - if (inCtrlOp == 0) { \ - fnName = QIR_##OP_NAME_BIG; \ - } else { \ - if (numCtrls == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (numCtrls == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (numCtrls == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - return failure(); \ - } \ - } \ - \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ state, fnName, 2, 2); \ } \ diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index ad09b72f26..f76d7d1413 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -319,23 +319,12 @@ void QIRProgramBuilder::createCallOp( \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const ValueRange controls, const Value target) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({}, controls, {target}, fnName); \ + createCallOp({}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } -DEFINE_ONE_TARGET_ZERO_PARAMETER(ID, id) +DEFINE_ONE_TARGET_ZERO_PARAMETER(I, id) DEFINE_ONE_TARGET_ZERO_PARAMETER(X, x) DEFINE_ONE_TARGET_ZERO_PARAMETER(Y, y) DEFINE_ONE_TARGET_ZERO_PARAMETER(Z, z) @@ -368,19 +357,8 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM), const ValueRange controls, \ const Value target) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({PARAM}, controls, {target}, fnName); \ + createCallOp({PARAM}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } @@ -414,19 +392,8 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), const ValueRange controls, \ const Value target) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({PARAM1, PARAM2}, controls, {target}, fnName); \ + createCallOp({PARAM1, PARAM2}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } @@ -462,19 +429,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), const ValueRange controls, \ const Value target) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({PARAM1, PARAM2, PARAM3}, controls, {target}, fnName); \ + createCallOp({PARAM1, PARAM2, PARAM3}, controls, {target}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } @@ -499,19 +455,8 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const ValueRange controls, const Value target0, const Value target1) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({}, controls, {target0, target1}, fnName); \ + createCallOp({}, controls, {target0, target1}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } @@ -542,19 +487,8 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM), const ValueRange controls, \ const Value target0, const Value target1) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({PARAM}, controls, {target0, target1}, fnName); \ + createCallOp({PARAM}, controls, {target0, target1}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } @@ -590,19 +524,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), const ValueRange controls, \ const Value target0, const Value target1) { \ - StringRef fnName; \ - if (controls.size() == 1) { \ - fnName = QIR_C##OP_NAME_BIG; \ - } else if (controls.size() == 2) { \ - fnName = QIR_CC##OP_NAME_BIG; \ - } else if (controls.size() == 3) { \ - fnName = QIR_CCC##OP_NAME_BIG; \ - } else { \ - llvm::report_fatal_error( \ - "Multi-controlled with more than 3 controls are currently not " \ - "supported"); \ - } \ - createCallOp({PARAM1, PARAM2}, controls, {target0, target1}, fnName); \ + createCallOp({PARAM1, PARAM2}, controls, {target0, target1}, \ + getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ } From bdade00fd2dbd240d6a7a44303043ef4832e4a37 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:11:53 +0100 Subject: [PATCH 260/419] Run linter on all files --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab5a9142a6..97752009d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,7 +160,7 @@ jobs: cmake-args: -DBUILD_MQT_CORE_BENCHMARKS=ON -DBUILD_MQT_CORE_MLIR=ON -DBUILD_MQT_CORE_BINDINGS=ON clang-version: 21 build-project: true - files-changed-only: true + files-changed-only: false setup-python: true install-pkgs: "pybind11==3.0.1" cpp-linter-extra-args: "-std=c++20" From 1280e4ca8c205fe90b4fe2ebbf64f2dcbaa31f92 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:26:14 +0100 Subject: [PATCH 261/419] Add square-root canonicalizations --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 38 +++ .../Flux/IR/Operations/StandardGates/SOp.cpp | 14 +- .../Flux/IR/Operations/StandardGates/SXOp.cpp | 14 +- .../IR/Operations/StandardGates/SXdgOp.cpp | 15 +- .../IR/Operations/StandardGates/SdgOp.cpp | 14 +- .../Flux/IR/Operations/StandardGates/TOp.cpp | 14 +- .../IR/Operations/StandardGates/TdgOp.cpp | 15 +- .../pipeline/test_compiler_pipeline.cpp | 276 ++++++++++-------- 8 files changed, 276 insertions(+), 124 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index 5d2cd0cdf5..c5cbb67a99 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -70,9 +70,44 @@ removeInversePairTwoTargetZeroParameter(OpType op, return success(); } +/** + * @brief Merge two compatible one-target, zero-parameter operations + * + * @details + * The two operations are replaced by a single operation corresponding to their + * square. + * + * @tparam SquareOpType The type of the square operation to be created. + * @tparam OpType The type of the operation to be merged. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the merge. + */ +template +inline mlir::LogicalResult +mergeOneTargetZeroParameter(OpType op, mlir::PatternRewriter& rewriter) { + // Check if the predecessor is the same operation + auto prevOp = op.getQubitIn().template getDefiningOp(); + if (!prevOp) { + return failure(); + } + + // Replace operation with square operation + auto squareOp = rewriter.create(op.getLoc(), op.getQubitIn()); + rewriter.replaceOp(op, squareOp.getQubitOut()); + + // Trivialize predecessor + rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + + return success(); +} + /** * @brief Merge two compatible one-target, one-parameter operations * + * @details + * The new parameter is computed as the sum of the two original parameters. + * * @tparam OpType The type of the operation to be merged. * @param op The operation instance. * @param rewriter The pattern rewriter. @@ -101,6 +136,9 @@ mergeOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { /** * @brief Merge two compatible two-target, one-parameter operations * + * @details + * The new parameter is computed as the sum of the two original parameters. + * * @tparam OpType The type of the operation to be merged. * @param op The operation instance. * @param rewriter The pattern rewriter. diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp index ae4f8aaf99..141452673a 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp @@ -36,11 +36,23 @@ struct RemoveSAfterSdg final : OpRewritePattern { } }; +/** + * @brief Merge subsequent S operations on the same qubit into a Z operation. + */ +struct MergeSubsequentS final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } void SOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp index d47b9a2595..db1ec916ed 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp @@ -36,6 +36,18 @@ struct RemoveSXAfterSXdg final : OpRewritePattern { } }; +/** + * @brief Merge subsequent SX operations on the same qubit into an X operation. + */ +struct MergeSubsequentSX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr SXOp::tryGetStaticMatrix() { @@ -44,5 +56,5 @@ DenseElementsAttr SXOp::tryGetStaticMatrix() { void SXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp index aaca5d8ba1..8bc319fa28 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp @@ -36,6 +36,19 @@ struct RemoveSXdgAfterSX final : OpRewritePattern { } }; +/** + * @brief Merge subsequent SXdg operations on the same qubit into an X + * operation. + */ +struct MergeSubsequentSXdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SXdgOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr SXdgOp::tryGetStaticMatrix() { @@ -44,5 +57,5 @@ DenseElementsAttr SXdgOp::tryGetStaticMatrix() { void SXdgOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp index f96fed9a6e..5687bed997 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp @@ -36,6 +36,18 @@ struct RemoveSdgAfterS final : OpRewritePattern { } }; +/** + * @brief Merge subsequent Sdg operations on the same qubit into a Z operation. + */ +struct MergeSubsequentSdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(SdgOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr SdgOp::tryGetStaticMatrix() { @@ -44,5 +56,5 @@ DenseElementsAttr SdgOp::tryGetStaticMatrix() { void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp index c266858e7a..50aa06278e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp @@ -36,11 +36,23 @@ struct RemoveTAfterTdg final : OpRewritePattern { } }; +/** + * @brief Merge subsequent T operations on the same qubit into an S operation. + */ +struct MergeSubsequentT final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } void TOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp index 0453fbecd1..0d40c79953 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp @@ -36,6 +36,19 @@ struct RemoveTdgAfterT final : OpRewritePattern { } }; +/** + * @brief Merge subsequent Tdg operations on the same qubit into an Sdg + * operation. + */ +struct MergeSubsequentTdg final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(TdgOp op, + PatternRewriter& rewriter) const override { + return mergeOneTargetZeroParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr TdgOp::tryGetStaticMatrix() { @@ -44,5 +57,5 @@ DenseElementsAttr TdgOp::tryGetStaticMatrix() { void TdgOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index e482944c6c..abeb428e2b 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1277,7 +1277,7 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { // # Temporary Unitary Operation Tests // ################################################## -TEST_F(CompilerPipelineTest, Id) { +TEST_F(CompilerPipelineTest, I) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.i(0); @@ -1288,13 +1288,11 @@ TEST_F(CompilerPipelineTest, Id) { const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.id(q); + b.id(reg[0]); }); const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.id(q); + b.id(reg[0]); }); verifyAllStages({ @@ -1306,7 +1304,7 @@ TEST_F(CompilerPipelineTest, Id) { }); } -TEST_F(CompilerPipelineTest, CId) { +TEST_F(CompilerPipelineTest, CI) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.ci(0, 1); @@ -1353,25 +1351,22 @@ TEST_F(CompilerPipelineTest, X) { }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.x(q0); - const auto q2 = b.x(q1); - b.x(q2); + auto q = reg[0]; + q = b.x(q); + q = b.x(q); + b.x(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.x(q0); + b.x(reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.x(q); + b.x(reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - const auto q = reg[0]; - b.x(q); + b.x(reg[0]); }); verifyAllStages({ @@ -1510,25 +1505,22 @@ TEST_F(CompilerPipelineTest, Y) { }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.y(q0); - const auto q2 = b.y(q1); - b.y(q2); + auto q = reg[0]; + q = b.y(q); + q = b.y(q); + b.y(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.y(q0); + b.y(reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.y(q); + b.y(reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - const auto q = reg[0]; - b.y(q); + b.y(reg[0]); }); verifyAllStages({ @@ -1560,25 +1552,22 @@ TEST_F(CompilerPipelineTest, Z) { }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.z(q0); - const auto q2 = b.z(q1); - b.z(q2); + auto q = reg[0]; + q = b.z(q); + q = b.z(q); + b.z(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.z(q0); + b.z(reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.z(q); + b.z(reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - const auto q = reg[0]; - b.z(q); + b.z(reg[0]); }); verifyAllStages({ @@ -1610,25 +1599,22 @@ TEST_F(CompilerPipelineTest, H) { }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.h(q0); - const auto q2 = b.h(q1); - b.h(q2); + auto q = reg[0]; + q = b.h(q); + q = b.h(q); + b.h(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.h(q0); + b.h(reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.h(q); + b.h(reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - const auto q = reg[0]; - b.h(q); + b.h(reg[0]); }); verifyAllStages({ @@ -1644,8 +1630,10 @@ TEST_F(CompilerPipelineTest, S) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.s(0); - qc.s(0); qc.sdg(0); + qc.s(0); + qc.s(0); + qc.s(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1655,29 +1643,36 @@ TEST_F(CompilerPipelineTest, S) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.s(q); - b.s(q); b.sdg(q); + b.s(q); + b.s(q); + b.s(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.s(q0); - const auto q2 = b.s(q1); - b.sdg(q2); + auto q = reg[0]; + q = b.s(q); + q = b.sdg(q); + q = b.s(q); + q = b.s(q); + q = b.s(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.s(q0); + auto q = reg[0]; + q = b.z(q); + b.s(q); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; + b.z(q); b.s(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; + b.z(q); b.s(q); }); @@ -1694,8 +1689,10 @@ TEST_F(CompilerPipelineTest, Sdg) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.sdg(0); - qc.sdg(0); qc.s(0); + qc.sdg(0); + qc.sdg(0); + qc.sdg(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1705,29 +1702,36 @@ TEST_F(CompilerPipelineTest, Sdg) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sdg(q); - b.sdg(q); b.s(q); + b.sdg(q); + b.sdg(q); + b.sdg(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.sdg(q0); - const auto q2 = b.sdg(q1); - b.s(q2); + auto q = reg[0]; + q = b.sdg(q); + q = b.s(q); + q = b.sdg(q); + q = b.sdg(q); + q = b.sdg(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.sdg(q0); + auto q = reg[0]; + q = b.z(q); + b.sdg(q); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; + b.z(q); b.sdg(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; + b.z(q); b.sdg(q); }); @@ -1744,8 +1748,10 @@ TEST_F(CompilerPipelineTest, T) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.t(0); - qc.t(0); qc.tdg(0); + qc.t(0); + qc.t(0); + qc.t(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1755,29 +1761,36 @@ TEST_F(CompilerPipelineTest, T) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.t(q); - b.t(q); b.tdg(q); + b.t(q); + b.t(q); + b.t(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.t(q0); - const auto q2 = b.t(q1); - b.tdg(q2); + auto q = reg[0]; + q = b.t(q); + q = b.tdg(q); + q = b.t(q); + q = b.t(q); + b.t(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.t(q0); + auto q = reg[0]; + q = b.s(q); + q = b.t(q); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; + b.s(q); b.t(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; + b.s(q); b.t(q); }); @@ -1794,8 +1807,10 @@ TEST_F(CompilerPipelineTest, Tdg) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.tdg(0); - qc.tdg(0); qc.t(0); + qc.tdg(0); + qc.tdg(0); + qc.tdg(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1805,29 +1820,36 @@ TEST_F(CompilerPipelineTest, Tdg) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.tdg(q); - b.tdg(q); b.t(q); + b.tdg(q); + b.tdg(q); + b.tdg(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.tdg(q0); - const auto q2 = b.tdg(q1); - b.t(q2); + auto q = reg[0]; + q = b.tdg(q); + q = b.t(q); + q = b.tdg(q); + q = b.tdg(q); + b.tdg(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.tdg(q0); + auto q = reg[0]; + q = b.sdg(q); + b.tdg(q); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; + b.sdg(q); b.tdg(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; + b.sdg(q); b.tdg(q); }); @@ -1844,8 +1866,10 @@ TEST_F(CompilerPipelineTest, SX) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.sx(0); - qc.sx(0); qc.sxdg(0); + qc.sx(0); + qc.sx(0); + qc.sx(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1855,29 +1879,36 @@ TEST_F(CompilerPipelineTest, SX) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sx(q); - b.sx(q); b.sxdg(q); + b.sx(q); + b.sx(q); + b.sx(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.sx(q0); - const auto q2 = b.sx(q1); - b.sxdg(q2); + auto q = reg[0]; + q = b.sx(q); + q = b.sxdg(q); + q = b.sx(q); + q = b.sx(q); + q = b.sx(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.sx(q0); + auto q = reg[0]; + q = b.x(q); + b.sx(q); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; + b.x(q); b.sx(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; + b.x(q); b.sx(q); }); @@ -1894,8 +1925,10 @@ TEST_F(CompilerPipelineTest, SXdg) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.sxdg(0); - qc.sxdg(0); qc.sx(0); + qc.sxdg(0); + qc.sxdg(0); + qc.sxdg(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); @@ -1905,29 +1938,36 @@ TEST_F(CompilerPipelineTest, SXdg) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sxdg(q); - b.sxdg(q); b.sx(q); + b.sxdg(q); + b.sxdg(q); + b.sxdg(q); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - const auto q1 = b.sxdg(q0); - const auto q2 = b.sxdg(q1); - b.sx(q2); + auto q = reg[0]; + q = b.sxdg(q); + q = b.sx(q); + q = b.sxdg(q); + q = b.sxdg(q); + q = b.sxdg(q); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - const auto q0 = reg[0]; - b.sxdg(q0); + auto q = reg[0]; + q = b.x(q); + b.sxdg(q); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; + b.x(q); b.sxdg(q); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); const auto q = reg[0]; + b.x(q); b.sxdg(q); }); @@ -2408,8 +2448,8 @@ TEST_F(CompilerPipelineTest, SWAP) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.swap(q0, q1); b.swap(q0, q1); b.swap(q0, q1); @@ -2581,8 +2621,8 @@ TEST_F(CompilerPipelineTest, ECR) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.ecr(q0, q1); b.ecr(q0, q1); b.ecr(q0, q1); @@ -2629,8 +2669,8 @@ TEST_F(CompilerPipelineTest, RXX) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.rxx(1.0, q0, q1); b.rxx(0.5, q0, q1); }); @@ -2737,8 +2777,8 @@ TEST_F(CompilerPipelineTest, RYY) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.ryy(1.0, q0, q1); b.ryy(0.5, q0, q1); }); @@ -2783,8 +2823,8 @@ TEST_F(CompilerPipelineTest, RZX) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.rzx(1.0, q0, q1); b.rzx(0.5, q0, q1); }); @@ -2829,8 +2869,8 @@ TEST_F(CompilerPipelineTest, RZZ) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.rzz(1.0, q0, q1); b.rzz(0.5, q0, q1); }); @@ -2876,8 +2916,8 @@ TEST_F(CompilerPipelineTest, XXPLUSYY) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.xx_plus_yy(1.0, 0.5, q0, q1); b.xx_plus_yy(0.5, 0.5, q0, q1); b.xx_plus_yy(1.0, 1.0, q0, q1); @@ -2899,15 +2939,15 @@ TEST_F(CompilerPipelineTest, XXPLUSYY) { }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.xx_plus_yy(1.5, 0.5, q0, q1); b.xx_plus_yy(1.0, 1.0, q0, q1); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.xx_plus_yy(1.5, 0.5, q0, q1); b.xx_plus_yy(1.0, 1.0, q0, q1); }); @@ -2996,8 +3036,8 @@ TEST_F(CompilerPipelineTest, XXMINUSYY) { const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.xx_minus_yy(1.0, 0.5, q0, q1); b.xx_minus_yy(0.5, 0.5, q0, q1); b.xx_minus_yy(1.0, 1.0, q0, q1); @@ -3019,15 +3059,15 @@ TEST_F(CompilerPipelineTest, XXMINUSYY) { }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.xx_minus_yy(1.5, 0.5, q0, q1); b.xx_minus_yy(1.0, 1.0, q0, q1); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - auto q0 = reg[0]; - auto q1 = reg[1]; + const auto q0 = reg[0]; + const auto q1 = reg[1]; b.xx_minus_yy(1.5, 0.5, q0, q1); b.xx_minus_yy(1.0, 1.0, q0, q1); }); From db73361f0c11376c7fe2dcd53b83fc183d770f3d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:43:02 +0100 Subject: [PATCH 262/419] Remove unnecessary headers --- mlir/lib/Dialect/Flux/IR/FluxOps.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp index 03d8d4e123..b1c7527174 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/Flux/IR/FluxOps.cpp @@ -18,12 +18,6 @@ #include // IWYU pragma: end_keep -#include -#include -#include -#include -#include - using namespace mlir; using namespace mlir::flux; From b2876b917577f7760269c3064b888cbd3840450c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:45:37 +0100 Subject: [PATCH 263/419] Maybe fix linter error --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index abeb428e2b..e1fe433403 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -135,7 +135,7 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, if (!rhsConst) { return false; } - if (lhsConst.getValue() != rhsConst.getValue()) { + if (lhsConst.getValue() != static_cast(rhsConst.getValue())) { return false; } } From d5f3e5779fda1f018eeceded08909893e677a77a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:01:54 +0100 Subject: [PATCH 264/419] Add canonicalization patterns for removing trivial rotations --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 52 +++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 2 + .../Flux/IR/Operations/StandardGates/RXOp.cpp | 14 +- .../IR/Operations/StandardGates/RXXOp.cpp | 14 +- .../Flux/IR/Operations/StandardGates/RYOp.cpp | 14 +- .../IR/Operations/StandardGates/RYYOp.cpp | 14 +- .../Flux/IR/Operations/StandardGates/RZOp.cpp | 14 +- .../IR/Operations/StandardGates/RZXOp.cpp | 14 +- .../IR/Operations/StandardGates/RZZOp.cpp | 14 +- .../Quartz/IR/QubitManagement/DeallocOp.cpp | 55 ++++++ .../pipeline/test_compiler_pipeline.cpp | 186 ++++++++++++------ 11 files changed, 321 insertions(+), 72 deletions(-) create mode 100644 mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index c5cbb67a99..d955b0407e 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -169,4 +169,56 @@ mergeTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return success(); } +/** + * @brief Remove a trivial one-target, one-parameter operation + * + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeTrivialOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + const auto paramAttr = OpType::getStaticParameter(op.getOperand(1)); + if (!paramAttr) { + return failure(); + } + + const auto paramValue = paramAttr.getValueAsDouble(); + if (paramValue != 0.0) { + return failure(); + } + + rewriter.replaceOp(op, op.getQubitIn()); + + return success(); +} + +/** + * @brief Remove a trivial two-target, one-parameter operation + * + * @tparam OpType The type of the operation to be checked. + * @param op The operation instance. + * @param rewriter The pattern rewriter. + * @return LogicalResult Success or failure of the removal. + */ +template +inline mlir::LogicalResult +removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { + const auto paramAttr = OpType::getStaticParameter(op.getOperand(2)); + if (!paramAttr) { + return failure(); + } + + const auto paramValue = paramAttr.getValueAsDouble(); + if (paramValue != 0.0) { + return failure(); + } + + rewriter.replaceOp(op, {op.getQubit0In(), op.getQubit1In()}); + + return success(); +} + } // namespace mlir::flux diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index b3e397f3b3..cb0687222c 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -144,6 +144,8 @@ def DeallocOp : QuartzOp<"dealloc", [MemoryEffects<[MemFree]>]> { let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; + + let hasCanonicalizer = 1; } def StaticOp : QuartzOp<"static", [Pure]> { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index 52ffc626cb..1da7d612a4 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRX final : OpRewritePattern { } }; +/** + * @brief Remove trivial RX operations. + */ +struct RemoveTrivialRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXOp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RXOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index 05c601e350..c5b0599968 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRXX final : OpRewritePattern { } }; +/** + * @brief Remove trivial RXX operations. + */ +struct RemoveTrivialRXX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RXXOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RXXOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RXXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index 0ba46ddb4c..890c61beb8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRY final : OpRewritePattern { } }; +/** + * @brief Remove trivial RY operations. + */ +struct RemoveTrivialRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYOp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RYOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RYOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index 6f90d60d70..72df144edf 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRYY final : OpRewritePattern { } }; +/** + * @brief Remove trivial RYY operations. + */ +struct RemoveTrivialRYY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RYYOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RYYOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RYYOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index 96fc1da591..ef0a8d27bf 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRZ final : OpRewritePattern { } }; +/** + * @brief Remove trivial RZ operations. + */ +struct RemoveTrivialRZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZOp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RZOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RZOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index fa40a906bd..ff7fe80d86 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRZX final : OpRewritePattern { } }; +/** + * @brief Remove trivial RZX operations. + */ +struct RemoveTrivialRZX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZXOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RZXOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RZXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index 6cf5aa4ee6..e7805ffb80 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -40,6 +40,18 @@ struct MergeSubsequentRZZ final : OpRewritePattern { } }; +/** + * @brief Remove trivial RZZ operations. + */ +struct RemoveTrivialRZZ final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(RZZOp op, + PatternRewriter& rewriter) const override { + return removeTrivialTwoTargetOneParameter(op, rewriter); + } +}; + } // namespace DenseElementsAttr RZZOp::tryGetStaticMatrix() { @@ -66,5 +78,5 @@ void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, void RZZOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp new file mode 100644 index 0000000000..cd82973483 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/quartz/IR/QuartzDialect.h" + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; + +namespace { + +/** + * @brief Remove matching allocation and deallocation pairs without operations + * between them. + */ +struct RemoveAllocDeallocPair final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(DeallocOp op, + PatternRewriter& rewriter) const override { + // Get the AllocOp defining the qubit + auto allocOp = op.getQubit().getDefiningOp(); + if (!allocOp) { + return failure(); + } + + // Check if the qubit has no other uses + if (!op.getQubit().hasOneUse()) { + return failure(); + } + + // Remove the AllocOp and the DeallocOp + rewriter.eraseOp(op); + rewriter.eraseOp(allocOp); + return success(); + } +}; + +} // namespace + +void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index e1fe433403..1d2fb7a794 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -635,7 +635,7 @@ class CompilerPipelineTest : public testing::Test { static void runCanonicalizationPasses(ModuleOp module) { PassManager pm(module.getContext()); pm.addPass(createCanonicalizerPass()); - pm.addPass(createCSEPass()); + pm.addPass(createRemoveDeadValuesPass()); if (pm.run(module).failed()) { llvm::errs() << "Failed to run canonicalization passes\n"; } @@ -1982,36 +1982,44 @@ TEST_F(CompilerPipelineTest, SXdg) { TEST_F(CompilerPipelineTest, RX) { qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); + qc.addQubitRegister(2, "q"); qc.rx(1.0, 0); qc.rx(0.5, 0); + qc.rx(1.0, 1); + qc.rx(-1.0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.rx(1.0, q); - b.rx(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.rx(1.0, q0); + b.rx(0.5, q0); + b.rx(1.0, q1); + b.rx(-1.0, q1); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; - q = b.rx(1.0, q); - b.rx(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.rx(1.0, q0); + b.rx(0.5, q0); + q1 = b.rx(1.0, q1); + b.rx(-1.0, q1); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.rx(1.5, reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.rx(1.5, reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(1); + auto reg = b.allocQubitRegister(2); b.rx(1.5, reg[0]); }); @@ -2088,36 +2096,44 @@ TEST_F(CompilerPipelineTest, MCRX) { TEST_F(CompilerPipelineTest, RY) { qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); + qc.addQubitRegister(2, "q"); qc.ry(1.0, 0); qc.ry(0.5, 0); + qc.ry(1.0, 1); + qc.ry(-1.0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.ry(1.0, q); - b.ry(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.ry(1.0, q0); + b.ry(0.5, q0); + b.ry(1.0, q1); + b.ry(-1.0, q1); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; - q = b.ry(1.0, q); - b.ry(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.ry(1.0, q0); + b.ry(0.5, q0); + q1 = b.ry(1.0, q1); + b.ry(-1.0, q1); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.ry(1.5, reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.ry(1.5, reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(1); + auto reg = b.allocQubitRegister(2); b.ry(1.5, reg[0]); }); @@ -2132,36 +2148,44 @@ TEST_F(CompilerPipelineTest, RY) { TEST_F(CompilerPipelineTest, RZ) { qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); + qc.addQubitRegister(2, "q"); qc.rz(1.0, 0); qc.rz(0.5, 0); + qc.rz(1.0, 1); + qc.rz(-1.0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.rz(1.0, q); - b.rz(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.rz(1.0, q0); + b.rz(0.5, q0); + b.rz(1.0, q1); + b.rz(-1.0, q1); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; - q = b.rz(1.0, q); - b.rz(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.rz(1.0, q0); + b.rz(0.5, q0); + q1 = b.rz(1.0, q1); + b.rz(-1.0, q1); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.rz(1.5, reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.rz(1.5, reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(1); + auto reg = b.allocQubitRegister(2); b.rz(1.5, reg[0]); }); @@ -2659,38 +2683,46 @@ TEST_F(CompilerPipelineTest, ECR) { TEST_F(CompilerPipelineTest, RXX) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); + qc.addQubitRegister(3, "q"); qc.rxx(1.0, 0, 1); qc.rxx(0.5, 0, 1); + qc.rxx(1.0, 1, 2); + qc.rxx(-1.0, 1, 2); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; + const auto q2 = reg[2]; b.rxx(1.0, q0, q1); b.rxx(0.5, q0, q1); + b.rxx(1.0, q1, q2); + b.rxx(-1.0, q1, q2); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; + auto q2 = reg[2]; std::tie(q0, q1) = b.rxx(1.0, q0, q1); - b.rxx(0.5, q0, q1); + std::tie(q0, q1) = b.rxx(0.5, q0, q1); + std::tie(q1, q2) = b.rxx(1.0, q1, q2); + b.rxx(-1.0, q1, q2); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.rxx(1.5, reg[0], reg[1]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.rxx(1.5, reg[0], reg[1]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); + auto reg = b.allocQubitRegister(3); b.rxx(1.5, reg[0], reg[1]); }); @@ -2767,38 +2799,46 @@ TEST_F(CompilerPipelineTest, MCRXX) { TEST_F(CompilerPipelineTest, RYY) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); + qc.addQubitRegister(3, "q"); qc.ryy(1.0, 0, 1); qc.ryy(0.5, 0, 1); + qc.ryy(1.0, 1, 2); + qc.ryy(-1.0, 1, 2); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; + const auto q2 = reg[2]; b.ryy(1.0, q0, q1); b.ryy(0.5, q0, q1); + b.ryy(1.0, q1, q2); + b.ryy(-1.0, q1, q2); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; + auto q2 = reg[2]; std::tie(q0, q1) = b.ryy(1.0, q0, q1); - b.ryy(0.5, q0, q1); + std::tie(q0, q1) = b.ryy(0.5, q0, q1); + std::tie(q1, q2) = b.ryy(1.0, q1, q2); + b.ryy(-1.0, q1, q2); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.ryy(1.5, reg[0], reg[1]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.ryy(1.5, reg[0], reg[1]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); + auto reg = b.allocQubitRegister(3); b.ryy(1.5, reg[0], reg[1]); }); @@ -2813,38 +2853,46 @@ TEST_F(CompilerPipelineTest, RYY) { TEST_F(CompilerPipelineTest, RZX) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); + qc.addQubitRegister(3, "q"); qc.rzx(1.0, 0, 1); qc.rzx(0.5, 0, 1); + qc.rzx(1.0, 1, 2); + qc.rzx(-1.0, 1, 2); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; + const auto q2 = reg[2]; b.rzx(1.0, q0, q1); b.rzx(0.5, q0, q1); + b.rzx(1.0, q1, q2); + b.rzx(-1.0, q1, q2); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; + auto q2 = reg[2]; std::tie(q0, q1) = b.rzx(1.0, q0, q1); - b.rzx(0.5, q0, q1); + std::tie(q0, q1) = b.rzx(0.5, q0, q1); + std::tie(q1, q2) = b.rzx(1.0, q1, q2); + b.rzx(-1.0, q1, q2); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.rzx(1.5, reg[0], reg[1]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.rzx(1.5, reg[0], reg[1]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); + auto reg = b.allocQubitRegister(3); b.rzx(1.5, reg[0], reg[1]); }); @@ -2859,38 +2907,46 @@ TEST_F(CompilerPipelineTest, RZX) { TEST_F(CompilerPipelineTest, RZZ) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); + qc.addQubitRegister(3, "q"); qc.rzz(1.0, 0, 1); qc.rzz(0.5, 0, 1); + qc.rzz(1.0, 1, 2); + qc.rzz(-1.0, 1, 2); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; + const auto q2 = reg[2]; b.rzz(1.0, q0, q1); b.rzz(0.5, q0, q1); + b.rzz(1.0, q1, q2); + b.rzz(-1.0, q1, q2); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; + auto q2 = reg[2]; std::tie(q0, q1) = b.rzz(1.0, q0, q1); - b.rzz(0.5, q0, q1); + std::tie(q0, q1) = b.rzz(0.5, q0, q1); + std::tie(q1, q2) = b.rzz(1.0, q1, q2); + b.rzz(-1.0, q1, q2); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.rzz(1.5, reg[0], reg[1]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + auto reg = b.allocQubitRegister(3, "q"); b.rzz(1.5, reg[0], reg[1]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); + auto reg = b.allocQubitRegister(3); b.rzz(1.5, reg[0], reg[1]); }); From 26429296f4dcd60fa5eeeb02840fbc76c18cd0d7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:15:07 +0100 Subject: [PATCH 265/419] Add initial support for GPhase --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 58 ++++++++++++++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 27 +++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 59 ++++++++++++++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 15 ++-- .../Quartz/Builder/QuartzProgramBuilder.h | 59 ++++++++++++++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 25 ++++++ mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 6 ++ .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 70 +++++++++++++--- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 73 ++++++++++++++--- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 38 +++++++++ .../Flux/Builder/FluxProgramBuilder.cpp | 32 ++++++++ .../IR/Operations/StandardGates/GPhaseOp.cpp | 79 +++++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 38 +++++---- .../Quartz/Builder/QuartzProgramBuilder.cpp | 23 ++++++ .../IR/Operations/StandardGates/GPhaseOp.cpp | 43 ++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 3 + .../pipeline/test_compiler_pipeline.cpp | 43 ++++++++-- 17 files changed, 643 insertions(+), 48 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 206bce75cd..72a63c9f19 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -240,6 +240,64 @@ class FluxProgramBuilder final : public OpBuilder { // Unitary Operations //===--------------------------------------------------------------------===// + // ZeroTargetOneParameter + +#define DECLARE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) : () \ + * ``` \ + */ \ + void OP_NAME(const std::variant&(PARAM)); \ + /** \ + * @brief Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Input control qubit \ + * @return Output control qubit \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, control); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%control) { \ + * quartz.OP_NAME(%PARAM) : () \ + * } \ + */ \ + Value c##OP_NAME(const std::variant&(PARAM), Value control); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @return Output control qubits \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {control1, control2}); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%control1, %control2) { \ + * quartz.OP_NAME(%PARAM) : () \ + * } \ + */ \ + ValueRange mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls); + + DECLARE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DECLARE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 904016c34f..d192ee17b9 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -246,6 +246,7 @@ class TargetAndParameterArityTrait let cppNamespace = "::mlir::flux"; } +def ZeroTargetOneParameter : TargetAndParameterArityTrait<0, 1>; def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; @@ -258,6 +259,32 @@ def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; // Unitary Operations //===----------------------------------------------------------------------===// +def GPhaseOp : FluxOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemAlloc]>]> { + let summary = "Apply a global phase to the state"; + let description = [{ + Applies a global phase to the state. + + Example: + ```mlir + flux.gphase(%theta) : () + ``` + }]; + + let arguments = (ins Arg:$theta); + let assemblyFormat = "$theta attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "gphase"; } + }]; + + let builders = [ + OpBuilder<(ins "const std::variant&":$theta)> + ]; + + let hasCanonicalizer = 1; +} + def IdOp : FluxOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Id gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index ac3660d654..428b3abfa4 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -263,6 +263,65 @@ class QIRProgramBuilder { // Unitary Operations //===--------------------------------------------------------------------===// + // ZeroTargetOneParameter + +#define DECLARE_ZERO_TARGET_ONE_PARAMETER(OP_NAME, QIR_NAME, PARAM) \ + /** \ + * @brief Apply a QIR QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__##QIR_NAME##__body(%PARAM) : (f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ + /** \ + * @brief Apply a controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, {control1, control2}); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%control1, %control2, \ + * %PARAM) : (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control); \ + /** \ + * @brief Apply a multi-controlled QIR_NAME operation \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {control1, control2}); \ + * ``` \ + * ```mlir \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%control1, %control2, \ + * %PARAM) : (!llvm.ptr, !llvm.ptr, f64) -> () \ + * ``` \ + */ \ + QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls); + + DECLARE_ZERO_TARGET_ONE_PARAMETER(gphase, gphase, theta) + +#undef DECLARE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_NAME, QIR_NAME) \ diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index ba83ca3516..91ed732818 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -19,6 +19,13 @@ namespace mlir::qir { // QIR function names +static constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; +static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; +static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; +static constexpr auto QIR_ARRAY_RECORD_OUTPUT = + "__quantum__rt__array_record_output"; +static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; + #define ADD_STANDARD_GATE(NAME_BIG, NAME_SMALL) \ static constexpr auto QIR_##NAME_BIG = \ "__quantum__qis__" #NAME_SMALL "__body"; \ @@ -29,12 +36,7 @@ namespace mlir::qir { static constexpr auto QIR_CCC##NAME_BIG = \ "__quantum__qis__ccc" #NAME_SMALL "__body"; -static constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; -static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; -static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; -static constexpr auto QIR_ARRAY_RECORD_OUTPUT = - "__quantum__rt__array_record_output"; -static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; +ADD_STANDARD_GATE(GPHASE, gphase) ADD_STANDARD_GATE(I, i) ADD_STANDARD_GATE(X, x) ADD_STANDARD_GATE(Y, y) @@ -92,6 +94,7 @@ ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) } \ } +DEFINE_GETTER(GPHASE) DEFINE_GETTER(I) DEFINE_GETTER(X) DEFINE_GETTER(Y) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 70cdade547..b961d98206 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -228,6 +228,65 @@ class QuartzProgramBuilder final : public OpBuilder { // Unitary Operations //===--------------------------------------------------------------------===// + // ZeroTargetOneParameter + +#define DECLARE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Apply a OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.OP_NAME(PARAM); \ + * ``` \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) : () \ + * ``` \ + */ \ + QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ + /** \ + * Apply a controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param control Control qubit \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.c##OP_NAME(PARAM, {q0, q1}); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM) : () \ + * } \ + */ \ + QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control); \ + /** \ + * @brief Apply a multi-controlled OP_CLASS \ + * \ + * @param PARAM Rotation angle in radians \ + * @param controls Control qubits \ + * @return Reference to this builder for method chaining \ + * \ + * @par Example: \ + * ```c++ \ + * builder.mc##OP_NAME(PARAM, {q0, q1}); \ + * ``` \ + * ```mlir \ + * quartz.ctrl(%q0, %q1) { \ + * quartz.OP_NAME(%PARAM) : () \ + * } \ + */ \ + QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls); + + DECLARE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DECLARE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DECLARE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index cb0687222c..3ec875c819 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -236,6 +236,7 @@ class TargetAndParameterArityTrait let cppNamespace = "::mlir::quartz"; } +def ZeroTargetOneParameter : TargetAndParameterArityTrait<0, 1>; def OneTargetZeroParameter : TargetAndParameterArityTrait<1, 0>; def OneTargetOneParameter : TargetAndParameterArityTrait<1, 1>; def OneTargetTwoParameter : TargetAndParameterArityTrait<1, 2>; @@ -248,6 +249,30 @@ def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; // Unitary Operations //===----------------------------------------------------------------------===// +def GPhaseOp : QuartzOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemAlloc]>]> { + let summary = "Apply a global phase to the state"; + let description = [{ + Applies a global phase to the state. + + Example: + ```mlir + quartz.gphase(%theta) : () + ``` + }]; + + let arguments = (ins Arg:$theta); + let assemblyFormat = "$theta attr-dict"; + + let extraClassDeclaration = [{ + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "gphase"; } + }]; + + let builders = [ + OpBuilder<(ins "const std::variant&":$theta)> + ]; +} + def IdOp : QuartzOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Id gate to a qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h index 49d9492b69..7f49c8a1c7 100644 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h @@ -18,6 +18,12 @@ namespace mlir::utils { using namespace std::complex_literals; +inline DenseElementsAttr getMatrixGPhase(MLIRContext* ctx, double theta) { + const auto& complexType = ComplexType::get(Float64Type::get(ctx)); + const auto& type = RankedTensorType::get({1, 1}, complexType); + return DenseElementsAttr::get(type, {std::exp(1i * theta)}); +} + inline DenseElementsAttr getMatrixId(MLIRContext* ctx) { const auto& complexType = ComplexType::get(Float64Type::get(ctx)); const auto& type = RankedTensorType::get({2, 2}, complexType); diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index d8dc8b0770..18cd5b6efb 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -36,6 +36,24 @@ using namespace quartz; namespace { +/** + * @brief Converts a zero-target, one-parameter Flux operation to Quartz + * + * @tparam QuartzOpType The operation type of the Quartz operation + * @tparam FluxOpType The operation type of the Flux operation + * @param op The Flux operation instance to convert + * @param rewriter The pattern rewriter + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult +convertZeroTargetOneParameter(FluxOpType& op, + ConversionPatternRewriter& rewriter) { + rewriter.create(op.getLoc(), op.getOperand()); + rewriter.eraseOp(op); + return success(); +} + /** * @brief Converts a one-target, zero-parameter Flux operation to Quartz * @@ -442,6 +460,35 @@ struct ConvertFluxResetOp final : OpConversionPattern { } }; +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * flux.OP_NAME(%PARAM) : () \ + * ``` \ + * is converted to \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) : () \ + * ``` \ + */ \ + struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + using OpConversionPattern::OpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertZeroTargetOneParameter(op, rewriter); \ + } \ + }; + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -787,17 +834,18 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns.add< - ConvertFluxAllocOp, ConvertFluxDeallocOp, ConvertFluxStaticOp, - ConvertFluxMeasureOp, ConvertFluxResetOp, ConvertFluxIdOp, - ConvertFluxXOp, ConvertFluxYOp, ConvertFluxZOp, ConvertFluxHOp, - ConvertFluxSOp, ConvertFluxSdgOp, ConvertFluxTOp, ConvertFluxTdgOp, - ConvertFluxSXOp, ConvertFluxSXdgOp, ConvertFluxRXOp, ConvertFluxRYOp, - ConvertFluxRZOp, ConvertFluxPOp, ConvertFluxROp, ConvertFluxU2Op, - ConvertFluxUOp, ConvertFluxSWAPOp, ConvertFluxiSWAPOp, ConvertFluxDCXOp, - ConvertFluxECROp, ConvertFluxRXXOp, ConvertFluxRYYOp, ConvertFluxRZXOp, - ConvertFluxRZZOp, ConvertFluxXXPlusYYOp, ConvertFluxXXMinusYYOp, - ConvertFluxCtrlOp, ConvertFluxYieldOp>(typeConverter, context); + patterns + .add(typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index b8c8342a01..79b4cd0e9b 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -108,6 +108,25 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +/** + * @brief Converts a zero-target, one-parameter Quartz operation to Flux + * + * @tparam FluxOpType The operation type of the Flux operation + * @tparam QuartzOpType The operation type of the Quartz operation + * @param op The Quartz operation instance to convert + * @param rewriter The pattern rewriter + * @param state The lowering state + * @return LogicalResult Success or failure of the conversion + */ +template +LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + rewriter.create(op.getLoc(), op.getOperand()); + rewriter.eraseOp(op); + return success(); +} + /** * @brief Converts a one-target, zero-parameter Quartz operation to Flux * @@ -687,6 +706,37 @@ struct ConvertQuartzResetOp final } }; +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + /** \ + * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME(%PARAM) : () \ + * ``` \ + * is converted to \ + * ```mlir \ + * flux.OP_NAME(%PARAM) : () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + ConversionPatternRewriter& rewriter) const override { \ + return convertZeroTargetOneParameter(op, rewriter, \ + getState()); \ + } \ + }; + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ @@ -1080,17 +1130,18 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { // tracking patterns.add< ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, - ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzIdOp, - ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, ConvertQuartzHOp, - ConvertQuartzSOp, ConvertQuartzSdgOp, ConvertQuartzTOp, - ConvertQuartzTdgOp, ConvertQuartzSXOp, ConvertQuartzSXdgOp, - ConvertQuartzRXOp, ConvertQuartzRYOp, ConvertQuartzRZOp, - ConvertQuartzPOp, ConvertQuartzROp, ConvertQuartzU2Op, ConvertQuartzUOp, - ConvertQuartzSWAPOp, ConvertQuartziSWAPOp, ConvertQuartzDCXOp, - ConvertQuartzECROp, ConvertQuartzRXXOp, ConvertQuartzRYYOp, - ConvertQuartzRZXOp, ConvertQuartzRZZOp, ConvertQuartzXXPlusYYOp, - ConvertQuartzXXMinusYYOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( - typeConverter, context, &state); + ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzGPhaseOp, + ConvertQuartzIdOp, ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, + ConvertQuartzHOp, ConvertQuartzSOp, ConvertQuartzSdgOp, + ConvertQuartzTOp, ConvertQuartzTdgOp, ConvertQuartzSXOp, + ConvertQuartzSXdgOp, ConvertQuartzRXOp, ConvertQuartzRYOp, + ConvertQuartzRZOp, ConvertQuartzPOp, ConvertQuartzROp, + ConvertQuartzU2Op, ConvertQuartzUOp, ConvertQuartzSWAPOp, + ConvertQuartziSWAPOp, ConvertQuartzDCXOp, ConvertQuartzECROp, + ConvertQuartzRXXOp, ConvertQuartzRYYOp, ConvertQuartzRZXOp, + ConvertQuartzRZZOp, ConvertQuartzXXPlusYYOp, ConvertQuartzXXMinusYYOp, + ConvertQuartzCtrlOp, ConvertQuartzYieldOp>(typeConverter, context, + &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 6219137949..cff5c5ecb4 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -489,6 +489,43 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } }; +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ + QIR_NAME, PARAM) \ + /** \ + * @brief Converts quartz.OP_NAME_SMALL to QIR QIR_NAME \ + * \ + * @par Example: \ + * ```mlir \ + * quartz.OP_NAME_SMALL(%PARAM) : () \ + * ``` \ + * is converted to \ + * ```mlir \ + * llvm.call @__quantum__qis__QIR_NAME__body(%PARAM) : (f64) -> () \ + * ``` \ + */ \ + struct ConvertQuartz##OP_CLASS##QIR final \ + : StatefulOpConversionPattern { \ + using StatefulOpConversionPattern::StatefulOpConversionPattern; \ + \ + LogicalResult \ + matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ + ConversionPatternRewriter& rewriter) const override { \ + auto& state = getState(); \ + const auto inCtrlOp = state.inCtrlOp; \ + const size_t numCtrls = \ + inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ + const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ + state, fnName, 0, 1); \ + } \ + }; + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, GPHASE, gphase, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ @@ -1144,6 +1181,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 06c79ea11a..40c8bf4590 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -171,6 +171,38 @@ Value FluxProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + void FluxProgramBuilder::OP_NAME( \ + const std::variant&(PARAM)) { \ + create(loc, PARAM); \ + } \ + Value FluxProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), const Value control) { \ + const auto [controlsOut, targetsOut] = \ + ctrl(control, {}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + b.create(loc, PARAM); \ + return {}; \ + }); \ + return controlsOut[0]; \ + } \ + ValueRange FluxProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), const ValueRange controls) { \ + const auto [controlsOut, targetsOut] = \ + ctrl(controls, {}, \ + [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + b.create(loc, PARAM); \ + return {}; \ + }); \ + return controlsOut; \ + } + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp new file mode 100644 index 0000000000..f8fb1cde5f --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +namespace { + +/** + * @brief Remove trivial GPhase operations. + */ +struct RemoveTrivialGPhase final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(GPhaseOp op, + PatternRewriter& rewriter) const override { + const auto thetaAttr = GPhaseOp::getStaticParameter(op.getTheta()); + if (!thetaAttr) { + return failure(); + } + + const auto thetaValue = thetaAttr.getValueAsDouble(); + if (thetaValue != 0.0) { + return failure(); + } + + rewriter.eraseOp(op); + return success(); + } +}; + +} // namespace + +DenseElementsAttr GPhaseOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixGPhase(getContext(), thetaValue); +} + +void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, thetaOperand); +} + +void GPhaseOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index f76d7d1413..27c7783328 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -303,6 +303,30 @@ void QIRProgramBuilder::createCallOp( builder.create(loc, fnDecl, operands); } +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ + const std::variant&(PARAM)) { \ + createCallOp({PARAM}, {}, {}, QIR_##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ + const std::variant&(PARAM), const Value control) { \ + createCallOp({PARAM}, {control}, {}, QIR_C##OP_NAME_BIG); \ + return *this; \ + } \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ + const std::variant&(PARAM), const ValueRange controls) { \ + createCallOp({PARAM}, controls, {}, \ + getFnName##OP_NAME_BIG(controls.size())); \ + return *this; \ + } + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPHASE, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ @@ -310,13 +334,11 @@ void QIRProgramBuilder::createCallOp( createCallOp({}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL(const Value control, \ const Value target) { \ createCallOp({}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const ValueRange controls, const Value target) { \ createCallOp({}, controls, {target}, \ @@ -346,14 +368,12 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) createCallOp({PARAM}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM), const Value control, \ const Value target) { \ createCallOp({PARAM}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM), const ValueRange controls, \ const Value target) { \ @@ -379,7 +399,6 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) createCallOp({PARAM1, PARAM2}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value control, \ @@ -387,7 +406,6 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) createCallOp({PARAM1, PARAM2}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const ValueRange controls, \ @@ -413,7 +431,6 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) createCallOp({PARAM1, PARAM2, PARAM3}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ @@ -423,7 +440,6 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ @@ -446,13 +462,11 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) createCallOp({}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const Value control, const Value target0, const Value target1) { \ createCallOp({}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const ValueRange controls, const Value target0, const Value target1) { \ createCallOp({}, controls, {target0, target1}, \ @@ -476,14 +490,12 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) createCallOp({PARAM}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM), const Value control, \ const Value target0, const Value target1) { \ createCallOp({PARAM}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM), const ValueRange controls, \ const Value target0, const Value target1) { \ @@ -510,7 +522,6 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) createCallOp({PARAM1, PARAM2}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const Value control, \ @@ -519,7 +530,6 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) QIR_C##OP_NAME_BIG); \ return *this; \ } \ - \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), const ValueRange controls, \ diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 86b9b3beb4..ec6911f41f 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -124,6 +124,29 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { // Unitary Operations //===----------------------------------------------------------------------===// +// ZeroTargetOneParameter + +#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ + QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + const std::variant&(PARAM)) { \ + create(loc, PARAM); \ + return *this; \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + const std::variant&(PARAM), Value control) { \ + return mc##OP_NAME(PARAM, {control}); \ + } \ + QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + const std::variant&(PARAM), ValueRange controls) { \ + create(loc, controls, \ + [&](OpBuilder& b) { b.create(loc, PARAM); }); \ + return *this; \ + } + +DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) + +#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp new file mode 100644 index 0000000000..045f0a8ef7 --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +DenseElementsAttr GPhaseOp::tryGetStaticMatrix() { + const auto& theta = getStaticParameter(getTheta()); + if (!theta) { + return nullptr; + } + const auto thetaValue = theta.getValueAsDouble(); + return getMatrixGPhase(getContext(), thetaValue); +} + +void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const std::variant& theta) { + Value thetaOperand = nullptr; + if (std::holds_alternative(theta)) { + thetaOperand = odsBuilder.create( + odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); + } else { + thetaOperand = std::get(theta); + } + build(odsBuilder, odsState, thetaOperand); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 813f499f2b..15e21c1b1d 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -514,6 +514,9 @@ translateOperations(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation, const llvm::SmallVector& qubits, const BitIndexVec& bitMap) { + if (quantumComputation.hasGlobalPhase()) { + builder.gphase(quantumComputation.getGlobalPhase()); + } for (const auto& operation : quantumComputation) { switch (operation->getType()) { case qc::OpType::Measure: diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 1d2fb7a794..0a406287d9 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1277,7 +1277,38 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { // # Temporary Unitary Operation Tests // ################################################## -TEST_F(CompilerPipelineTest, I) { +TEST_F(CompilerPipelineTest, GPhase) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.gphase(1.0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.gphase(1.0); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.gphase(1.0); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.gphase(1.0); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, Id) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.i(0); @@ -1304,7 +1335,7 @@ TEST_F(CompilerPipelineTest, I) { }); } -TEST_F(CompilerPipelineTest, CI) { +TEST_F(CompilerPipelineTest, CId) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.ci(0, 1); @@ -2959,7 +2990,7 @@ TEST_F(CompilerPipelineTest, RZZ) { }); } -TEST_F(CompilerPipelineTest, XXPLUSYY) { +TEST_F(CompilerPipelineTest, XXPlusYY) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.xx_plus_yy(1.0, 0.5, 0, 1); @@ -3017,7 +3048,7 @@ TEST_F(CompilerPipelineTest, XXPLUSYY) { }); } -TEST_F(CompilerPipelineTest, CXXPLUSYY) { +TEST_F(CompilerPipelineTest, CXXPlusYY) { qc::QuantumComputation qc; qc.addQubitRegister(3, "q"); qc.cxx_plus_yy(1.0, 0.5, 0, 1, 2); @@ -3048,7 +3079,7 @@ TEST_F(CompilerPipelineTest, CXXPLUSYY) { }); } -TEST_F(CompilerPipelineTest, MCXXPLUSYY) { +TEST_F(CompilerPipelineTest, MCXXPlusYY) { qc::QuantumComputation qc; qc.addQubitRegister(4, "q"); qc.mcxx_plus_yy(1.0, 0.5, {0, 1}, 2, 3); @@ -3079,7 +3110,7 @@ TEST_F(CompilerPipelineTest, MCXXPLUSYY) { }); } -TEST_F(CompilerPipelineTest, XXMINUSYY) { +TEST_F(CompilerPipelineTest, XXMinusYY) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); qc.xx_minus_yy(1.0, 0.5, 0, 1); From 73d8471a6d8d3e559af89c8b1f871442cecfedfb Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:15:19 +0100 Subject: [PATCH 266/419] Revert "Maybe fix linter error" This reverts commit b2876b917577f7760269c3064b888cbd3840450c. --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 0a406287d9..26f5bf104e 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -135,7 +135,7 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, if (!rhsConst) { return false; } - if (lhsConst.getValue() != static_cast(rhsConst.getValue())) { + if (lhsConst.getValue() != rhsConst.getValue()) { return false; } } From 4c8e0423ff4ab0a57146d299f1bea81b238ee428 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:35:36 +0100 Subject: [PATCH 267/419] Fix docstrings for GPhase --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 24 ++++++++++--------- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 12 +++++----- .../Quartz/Builder/QuartzProgramBuilder.h | 6 ++--- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 4 ++-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 ++-- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 2 +- 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 72a63c9f19..75ab80c23e 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -254,7 +254,7 @@ class FluxProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM) : () \ + * flux.OP_NAME(%PARAM) \ * ``` \ */ \ void OP_NAME(const std::variant&(PARAM)); \ @@ -267,12 +267,13 @@ class FluxProgramBuilder final : public OpBuilder { * \ * @par Example: \ * ```c++ \ - * builder.c##OP_NAME(PARAM, control); \ + * q_out = builder.c##OP_NAME(PARAM, q_in); \ * ``` \ * ```mlir \ - * quartz.ctrl(%control) { \ - * quartz.OP_NAME(%PARAM) : () \ - * } \ + * %q_out = flux.ctrl(%q_in) { \ + * flux.OP_NAME(%PARAM) \ + * flux.yield \ + * } : ({!flux.qubit}, {}) -> ({!flux.qubit}, {}) \ */ \ Value c##OP_NAME(const std::variant&(PARAM), Value control); \ /** \ @@ -284,12 +285,13 @@ class FluxProgramBuilder final : public OpBuilder { * \ * @par Example: \ * ```c++ \ - * builder.mc##OP_NAME(PARAM, {control1, control2}); \ + * {q0_out, q1_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}); \ * ``` \ * ```mlir \ - * quartz.ctrl(%control1, %control2) { \ - * quartz.OP_NAME(%PARAM) : () \ - * } \ + * %q0_out, %q1_out = flux.ctrl(%q0_in, %q1_in) { \ + * flux.OP_NAME(%PARAM) \ + * flux.yield \ + * } : ({!flux.qubit, !flux.qubit}, {}) -> ({!flux.qubit, !flux.qubit}, {}) \ */ \ ValueRange mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls); @@ -610,7 +612,7 @@ class FluxProgramBuilder final : public OpBuilder { * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ * %q1_res = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1_in : !flux.qubit \ * -> !flux.qubit \ - * flux.yield %q1_res \ + * flux.yield %q1_res \ * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ * ``` \ */ \ @@ -642,7 +644,7 @@ class FluxProgramBuilder final : public OpBuilder { * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ * %q2_res = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2_in : !flux.qubit \ * -> !flux.qubit \ - * flux.yield %q2_res \ + * flux.yield %q2_res \ * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ * !flux.qubit}, {!flux.qubit}) \ * ``` \ diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 428b3abfa4..a05eb4b6dd 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -290,11 +290,11 @@ class QIRProgramBuilder { * \ * @par Example: \ * ```c++ \ - * builder.c##OP_NAME(PARAM, {control1, control2}); \ + * builder.c##OP_NAME(PARAM, q); \ * ``` \ * ```mlir \ - * llvm.call @__quantum__qis__c##QIR_NAME##__body(%control1, %control2, \ - * %PARAM) : (!llvm.ptr, !llvm.ptr, f64) -> () \ + * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q, %PARAM) : (!llvm.ptr, \ + * f64) -> () \ * ``` \ */ \ QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ @@ -308,11 +308,11 @@ class QIRProgramBuilder { * \ * @par Example: \ * ```c++ \ - * builder.mc##OP_NAME(PARAM, {control1, control2}); \ + * builder.mc##OP_NAME(PARAM, {q0, q1}); \ * ``` \ * ```mlir \ - * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%control1, %control2, \ - * %PARAM) : (!llvm.ptr, !llvm.ptr, f64) -> () \ + * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %PARAM) : \ + * (!llvm.ptr, !llvm.ptr, f64) -> () \ * ``` \ */ \ QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index b961d98206..703ca088c9 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -242,7 +242,7 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM) : () \ + * quartz.OP_NAME(%PARAM) \ * ``` \ */ \ QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ @@ -259,7 +259,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` \ * ```mlir \ * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM) : () \ + * quartz.OP_NAME(%PARAM) \ * } \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ @@ -277,7 +277,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` \ * ```mlir \ * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM) : () \ + * quartz.OP_NAME(%PARAM) \ * } \ */ \ QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 18cd5b6efb..f4c1b1fa4e 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -468,11 +468,11 @@ struct ConvertFluxResetOp final : OpConversionPattern { * \ * @par Example: \ * ```mlir \ - * flux.OP_NAME(%PARAM) : () \ + * flux.OP_NAME(%PARAM) \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM) : () \ + * quartz.OP_NAME(%PARAM) \ * ``` \ */ \ struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 79b4cd0e9b..84d2128f79 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -714,11 +714,11 @@ struct ConvertQuartzResetOp final * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM) : () \ + * quartz.OP_NAME(%PARAM) \ * ``` \ * is converted to \ * ```mlir \ - * flux.OP_NAME(%PARAM) : () \ + * flux.OP_NAME(%PARAM) \ * ``` \ */ \ struct ConvertQuartz##OP_CLASS final \ diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index cff5c5ecb4..314ff67c32 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -498,7 +498,7 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM) : () \ + * quartz.OP_NAME_SMALL(%PARAM) \ * ``` \ * is converted to \ * ```mlir \ From b14efecf28a1970eb48475fd706d66545b2985a7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:45:46 +0100 Subject: [PATCH 268/419] Fix header path --- mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp index cd82973483..91b7bedbe3 100644 --- a/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include #include From 3a7df64c49af1b7a825409ac3203cb6479923771 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:23:01 +0100 Subject: [PATCH 269/419] Use MemWrite instead of MemAlloc --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 +- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index d192ee17b9..7e839dc7cc 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -259,7 +259,7 @@ def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; // Unitary Operations //===----------------------------------------------------------------------===// -def GPhaseOp : FluxOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemAlloc]>]> { +def GPhaseOp : FluxOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { let summary = "Apply a global phase to the state"; let description = [{ Applies a global phase to the state. diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 3ec875c819..d8c698ffc8 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -249,7 +249,7 @@ def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; // Unitary Operations //===----------------------------------------------------------------------===// -def GPhaseOp : QuartzOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemAlloc]>]> { +def GPhaseOp : QuartzOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { let summary = "Apply a global phase to the state"; let description = [{ Applies a global phase to the state. From d8fd8a0b41fe8fb549041e2f078b54a673a10391 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:28:42 +0100 Subject: [PATCH 270/419] Fix linter errors --- mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp | 2 +- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 9 ++++----- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index f4c1b1fa4e..fb9c75b1ac 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -479,7 +479,7 @@ struct ConvertFluxResetOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(flux::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ return convertZeroTargetOneParameter(op, rewriter); \ } \ diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 84d2128f79..494b38a7e3 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -119,9 +119,9 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +LogicalResult +convertZeroTargetOneParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter) { rewriter.create(op.getLoc(), op.getOperand()); rewriter.eraseOp(op); return success(); @@ -728,8 +728,7 @@ struct ConvertQuartzResetOp final LogicalResult \ matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertZeroTargetOneParameter(op, rewriter, \ - getState()); \ + return convertZeroTargetOneParameter(op, rewriter); \ } \ }; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 40c8bf4590..d067483c1a 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -182,7 +182,7 @@ Value FluxProgramBuilder::reset(Value qubit) { const std::variant&(PARAM), const Value control) { \ const auto [controlsOut, targetsOut] = \ ctrl(control, {}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, const ValueRange /*targets*/) -> ValueRange { \ b.create(loc, PARAM); \ return {}; \ }); \ @@ -192,7 +192,7 @@ Value FluxProgramBuilder::reset(Value qubit) { const std::variant&(PARAM), const ValueRange controls) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, const ValueRange /*targets*/) -> ValueRange { \ b.create(loc, PARAM); \ return {}; \ }); \ From 5b0aeecdc37bcc8e95d74fb583686f375029426b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:34:44 +0100 Subject: [PATCH 271/419] Another attempt at fixing the linter error about slicing --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 26f5bf104e..350b9c4b8a 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -135,7 +135,7 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, if (!rhsConst) { return false; } - if (lhsConst.getValue() != rhsConst.getValue()) { + if (!(lhsConst.getValue() == rhsConst.getValue())) { return false; } } From f80bc01704f1d504e832ecc07ef9ce35c379079b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:08:32 +0100 Subject: [PATCH 272/419] Revert "Another attempt at fixing the linter error about slicing" This reverts commit 5b0aeecdc37bcc8e95d74fb583686f375029426b. --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 350b9c4b8a..26f5bf104e 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -135,7 +135,7 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, if (!rhsConst) { return false; } - if (!(lhsConst.getValue() == rhsConst.getValue())) { + if (lhsConst.getValue() != rhsConst.getValue()) { return false; } } From d94463cec7ed0cf6e5aefc1d0b0346cee5b79e34 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:12:53 +0100 Subject: [PATCH 273/419] Fix conversion of controlled GPhase operations --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 494b38a7e3..4374faf4d5 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -119,11 +119,22 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @return LogicalResult Success or failure of the conversion */ template -LogicalResult -convertZeroTargetOneParameter(QuartzOpType& op, - ConversionPatternRewriter& rewriter) { +LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, + ConversionPatternRewriter& rewriter, + LoweringState& state) { + const auto inCtrlOp = state.inCtrlOp; + rewriter.create(op.getLoc(), op.getOperand()); + + // Update the state + if (inCtrlOp != 0) { + state.targetsIn.erase(inCtrlOp); + const SmallVector targetsOut({}); + state.targetsOut.try_emplace(inCtrlOp, targetsOut); + } + rewriter.eraseOp(op); + return success(); } @@ -728,7 +739,8 @@ struct ConvertQuartzResetOp final LogicalResult \ matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertZeroTargetOneParameter(op, rewriter); \ + return convertZeroTargetOneParameter(op, rewriter, \ + getState()); \ } \ }; From 280ec0500561c7743676ed8bbf7db5bedd332603 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:24:17 +0100 Subject: [PATCH 274/419] Streamline qubit variables --- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index d8c698ffc8..c9395bd8c9 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -680,9 +680,9 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParamet ``` }]; - let arguments = (ins Arg:$qubit0, - Arg:$qubit1); - let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -701,9 +701,9 @@ def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParam ``` }]; - let arguments = (ins Arg:$qubit0, - Arg:$qubit1); - let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -722,9 +722,9 @@ def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter ``` }]; - let arguments = (ins Arg:$qubit0, - Arg:$qubit1); - let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); @@ -743,9 +743,9 @@ def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter ``` }]; - let arguments = (ins Arg:$qubit0, - Arg:$qubit1); - let assemblyFormat = "$qubit0 $qubit1 attr-dict"; + let arguments = (ins Arg:$qubit0_in, + Arg:$qubit1_in); + let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ DenseElementsAttr tryGetStaticMatrix(); From 9d557b13e31872904b8c815e36dfaa37ac0edc9f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:31:39 +0100 Subject: [PATCH 275/419] Add support for barriers --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 19 +++ mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 43 +++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 19 +++ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 + .../Quartz/Builder/QuartzProgramBuilder.h | 18 +++ .../mlir/Dialect/Quartz/IR/QuartzOps.td | 44 ++++++- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 39 +++++- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 51 +++++++- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 15 +++ .../Flux/Builder/FluxProgramBuilder.cpp | 11 ++ mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 60 ++++----- .../IR/Operations/StandardGates/BarrierOp.cpp | 114 ++++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 7 ++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 7 ++ .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 56 +++++---- .../IR/Operations/StandardGates/BarrierOp.cpp | 68 +++++++++++ .../TranslateQuantumComputationToQuartz.cpp | 12 ++ .../pipeline/test_compiler_pipeline.cpp | 62 ++++++++++ 18 files changed, 584 insertions(+), 63 deletions(-) create mode 100644 mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp create mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 75ab80c23e..d5a9555f59 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -942,6 +942,25 @@ class FluxProgramBuilder final : public OpBuilder { #undef DECLARE_TWO_TARGET_TWO_PARAMETER + // BarrierOp + + /** + * @brief Apply a BarrierOp + * + * @param qubits Input qubits (must be valid/unconsumed) + * @return Output qubits + * + * @par Example: + * ```c++ + * builder.barrier({q0, q1}); + * ``` + * ```mlir + * flux.barrier %q0, %q1 : !flux.qubit, !flux.qubit -> !flux.qubit, + * !flux.qubit + * ``` + */ + ValueRange barrier(ValueRange qubits); + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 7e839dc7cc..c392ce6395 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -1002,6 +1002,49 @@ def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetT let hasCanonicalizer = 1; } +def BarrierOp : FluxOp<"barrier", traits = [UnitaryOpInterface]> { + let summary = "Apply a barrier gate to a set of qubits"; + let description = [{ + Applies a barrier gate to a set of qubits and returns the transformed qubits. + + Example: + ```mlir + %q_out = flux.barrier %q_in : !flux.qubit -> !flux.qubit + ``` + }]; + + let arguments = (ins Arg, "the target qubits", [MemRead]>:$qubits_in); + let results = (outs Variadic:$qubits_out); + let assemblyFormat = "$qubits_in attr-dict `:` type($qubits_in) `->` type($qubits_out)"; + + let extraClassDeclaration = [{ + size_t getNumQubits(); + size_t getNumTargets(); + size_t getNumControls(); + size_t getNumPosControls(); + size_t getNumNegControls(); + Value getInputQubit(size_t i); + Value getOutputQubit(size_t i); + Value getInputTarget(size_t i); + Value getOutputTarget(size_t i); + Value getInputPosControl(size_t i); + Value getOutputPosControl(size_t i); + Value getInputNegControl(size_t i); + Value getOutputNegControl(size_t i); + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + size_t getNumParams(); + Value getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "barrier"; } + }]; + + let builders = [ + OpBuilder<(ins "ValueRange":$qubits)> + ]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index a05eb4b6dd..0a471afdc0 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -816,6 +816,25 @@ class QIRProgramBuilder { DECLARE_TWO_TARGET_TWO_PARAMETER(xx_plus_yy, xx_plus_yy, theta, beta) DECLARE_TWO_TARGET_TWO_PARAMETER(xx_minus_yy, xx_minus_yy, theta, beta) + // BarrierOp + + /** + * @brief Apply a barrier operation + * + * @param qubits Target qubits + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.barrier({q0, q1}); + * ``` + * ```mlir + * llvm.call @__quantum__qis__barrier__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) + * -> () + * ``` + */ + QIRProgramBuilder& barrier(ValueRange qubits); + #undef DECLARE_TWO_TARGET_TWO_PARAMETER //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 91ed732818..9ed87e9559 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -68,6 +68,8 @@ ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) #undef ADD_STANDARD_GATE +static constexpr auto QIR_BARRIER = "__quantum__qis__barrier__body"; + // Functions for getting QIR function names #define DEFINE_GETTER(NAME) \ diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 703ca088c9..28d6ae9e68 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -777,6 +777,24 @@ class QuartzProgramBuilder final : public OpBuilder { #undef DECLARE_TWO_TARGET_TWO_PARAMETER + // BarrierOp + + /** + * @brief Apply a BarrierOp + * + * @param qubits Target qubits + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.barrier({q0, q1}); + * ``` + * ```mlir + * quartz.barrier %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + */ + QuartzProgramBuilder& barrier(ValueRange qubits); + //===--------------------------------------------------------------------===// // Modifiers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index c9395bd8c9..8b851c6383 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -909,6 +909,38 @@ def XXMinusYYOp : QuartzOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTarge ]; } +def BarrierOp : QuartzOp<"barrier", traits = [UnitaryOpInterface]> { + let summary = "Apply a barrier gate to a set of qubits"; + let description = [{ + Applies a barrier gate to a set of qubits, modifying them in place. + + Example: + ```mlir + quartz.barrier %q : !quartz.qubit + ``` + }]; + + let arguments = (ins Arg, "the target qubits", [MemRead, MemWrite]>:$qubits); + let assemblyFormat = "$qubits attr-dict"; + + let extraClassDeclaration = [{ + size_t getNumQubits(); + size_t getNumTargets(); + size_t getNumControls(); + size_t getNumPosControls(); + size_t getNumNegControls(); + Value getQubit(size_t i); + Value getTarget(size_t i); + Value getPosControl(size_t i); + Value getNegControl(size_t i); + size_t getNumParams(); + Value getParameter(size_t i); + bool hasStaticUnitary(); + DenseElementsAttr tryGetStaticMatrix(); + static StringRef getBaseSymbol() { return "barrier"; } + }]; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// @@ -942,15 +974,10 @@ def CtrlOp : QuartzOp<"ctrl", ``` }]; - let arguments = (ins Arg, "the control qubits", [MemRead,MemWrite]>:$controls); + let arguments = (ins Arg, "the control qubits", [MemRead, MemWrite]>:$controls); let regions = (region SizedRegion<1>:$body); let assemblyFormat = "`(` $controls `)` $body attr-dict"; - let builders = [ - OpBuilder<(ins "ValueRange":$controls, "UnitaryOpInterface":$bodyUnitary)>, - OpBuilder<(ins "ValueRange":$controls, "const std::function&":$bodyBuilder)> - ]; - let extraClassDeclaration = [{ [[nodiscard]] UnitaryOpInterface getBodyUnitary(); size_t getNumQubits(); @@ -969,6 +996,11 @@ def CtrlOp : QuartzOp<"ctrl", static StringRef getBaseSymbol() { return "ctrl"; } }]; + let builders = [ + OpBuilder<(ins "ValueRange":$controls, "UnitaryOpInterface":$bodyUnitary)>, + OpBuilder<(ins "ValueRange":$controls, "const std::function&":$bodyBuilder)> + ]; + let hasCanonicalizer = 1; let hasVerifier = 1; } diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index fb9c75b1ac..8e8f448bf7 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -726,6 +726,40 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +/** + * @brief Converts flux.barrier to quartz.barrier + * + * @par Example: + * ```mlir + * %q0_out, %q1_out = flux.barrier %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit + * ``` + * is converted to + * ```mlir + * quartz.barrier %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + */ +struct ConvertFluxBarrierOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(flux::BarrierOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // OpAdaptor provides the already type-converted qubits + const auto& quartzQubits = adaptor.getQubitsIn(); + + // Create quartz.barrier operation + rewriter.create(op.getLoc(), quartzQubits); + + // Replace the output qubits with the same quartz references + rewriter.replaceOp(op, quartzQubits); + + return success(); + } +}; + /** * @brief Converts quartz.ctrl to flux.ctrl * @@ -760,7 +794,7 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { auto& dstRegion = fluxOp.getBody(); rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); - // Replace the output qubits with the same quartz references + // Replace the output qubits with the same Quartz references rewriter.replaceOp(op, adaptor.getOperands()); return success(); @@ -845,7 +879,8 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { ConvertFluxiSWAPOp, ConvertFluxDCXOp, ConvertFluxECROp, ConvertFluxRXXOp, ConvertFluxRYYOp, ConvertFluxRZXOp, ConvertFluxRZZOp, ConvertFluxXXPlusYYOp, ConvertFluxXXMinusYYOp, - ConvertFluxCtrlOp, ConvertFluxYieldOp>(typeConverter, context); + ConvertFluxBarrierOp, ConvertFluxCtrlOp, ConvertFluxYieldOp>( + typeConverter, context); // Conversion of flux types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 4374faf4d5..6a5d7e17a5 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -992,6 +992,53 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +/** + * @brief Converts quartz.barrier to flux.barrier + * + * @par Example: + * ```mlir + * quartz.barrier %q0, %q1 : !quartz.qubit, !quartz.qubit + * ``` + * is converted to + * ```mlir + * %q0_out, %q1_out = flux.barrier %q0_in, %q1_in : !quartz.qubit, !quartz.qubit + * -> !quartz.qubit, !quartz.qubit + * ``` + */ +struct ConvertQuartzBarrierOp final + : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(quartz::BarrierOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + auto& qubitMap = state.qubitMap; + + // Get Flux qubits from state map + const auto& quartzQubits = op.getQubits(); + SmallVector fluxQubits; + fluxQubits.reserve(quartzQubits.size()); + for (const auto& quartzQubit : quartzQubits) { + fluxQubits.push_back(qubitMap[quartzQubit]); + } + + // Create flux.barrier + auto fluxOp = rewriter.create(op.getLoc(), fluxQubits); + + // Update state map + for (const auto& [quartzQubit, fluxQubitOut] : + llvm::zip(quartzQubits, fluxOp.getQubitsOut())) { + qubitMap[quartzQubit] = fluxQubitOut; + } + + rewriter.eraseOp(op); + return success(); + } +}; + /** * @brief Converts quartz.ctrl to flux.ctrl * @@ -1151,8 +1198,8 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { ConvertQuartziSWAPOp, ConvertQuartzDCXOp, ConvertQuartzECROp, ConvertQuartzRXXOp, ConvertQuartzRYYOp, ConvertQuartzRZXOp, ConvertQuartzRZZOp, ConvertQuartzXXPlusYYOp, ConvertQuartzXXMinusYYOp, - ConvertQuartzCtrlOp, ConvertQuartzYieldOp>(typeConverter, context, - &state); + ConvertQuartzBarrierOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( + typeConverter, context, &state); // Conversion of quartz types in func.func signatures // Note: This currently has limitations with signature diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 314ff67c32..3794fdab58 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -815,6 +815,20 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, XXMINUSYY, xx_minus_yy, #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +struct ConvertQuartzBarrierQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(BarrierOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), + getState(), QIR_BARRIER, + op.getQubits().size(), 0); + } +}; + /** * @brief Inlines quartz.ctrl region removes the operation */ @@ -1212,6 +1226,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { &state); quartzPatterns.add(typeConverter, ctx, &state); + quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); // Gate operations will be added here as the dialect expands diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index d067483c1a..7d17248ad9 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -511,6 +511,17 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { + auto op = create(loc, qubits); + const auto& qubitsOut = op.getQubitsOut(); + for (const auto& [inputQubit, outputQubit] : llvm::zip(qubits, qubitsOut)) { + updateQubitTracking(inputQubit, outputQubit); + } + return qubitsOut; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 73e111bfb7..494b99b61b 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -101,33 +101,6 @@ struct CtrlInlineId final : OpRewritePattern { } // namespace -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const ValueRange controls, const ValueRange targets, - UnitaryOpInterface bodyUnitary) { - build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); - - // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); - odsBuilder.create(odsState.location, op->getResults()); -} - -void CtrlOp::build( - OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, - const ValueRange targets, - const std::function& bodyBuilder) { - build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); - - // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - auto targetsOut = bodyBuilder(odsBuilder, targets); - odsBuilder.create(odsState.location, targetsOut); -} - UnitaryOpInterface CtrlOp::getBodyUnitary() { return llvm::dyn_cast(&getBody().front().front()); } @@ -227,6 +200,33 @@ DenseElementsAttr CtrlOp::tryGetStaticMatrix() { getBodyUnitary().tryGetStaticMatrix()); } +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, const ValueRange targets, + UnitaryOpInterface bodyUnitary) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location, op->getResults()); +} + +void CtrlOp::build( + OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, + const ValueRange targets, + const std::function& bodyBuilder) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + // Move the unitary op into the block + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto targetsOut = bodyBuilder(odsBuilder, targets); + odsBuilder.create(odsState.location, targetsOut); +} + LogicalResult CtrlOp::verify() { auto& block = getBody().front(); if (block.getOperations().size() != 2) { @@ -240,7 +240,6 @@ LogicalResult CtrlOp::verify() { return emitOpError( "second operation in body region must be a yield operation"); } - // The yield operation must yield as many values as there are targets if (block.back().getNumOperands() != getNumTargets()) { return emitOpError("yield operation must yield ") << getNumTargets() << " values, but found " @@ -271,6 +270,11 @@ LogicalResult CtrlOp::verify() { return emitOpError("duplicate qubit found"); } } + + if (llvm::isa(bodyUnitary.getOperation())) { + return emitOpError("BarrierOp cannot be controlled"); + } + return success(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp new file mode 100644 index 0000000000..5099d2cb33 --- /dev/null +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Flux/FluxUtils.h" +#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::flux; +using namespace mlir::utils; + +size_t BarrierOp::getNumQubits() { return getNumTargets(); } + +size_t BarrierOp::getNumTargets() { return getQubitsIn().size(); } + +size_t BarrierOp::getNumControls() { return 0; } + +size_t BarrierOp::getNumPosControls() { return 0; } + +size_t BarrierOp::getNumNegControls() { return 0; } + +Value BarrierOp::getInputQubit(const size_t i) { return getInputTarget(i); } + +Value BarrierOp::getOutputQubit(const size_t i) { return getOutputTarget(i); } + +Value BarrierOp::getInputTarget(const size_t i) { + if (i < getNumTargets()) { + return getQubitsIn()[i]; + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value BarrierOp::getOutputTarget(const size_t i) { + if (i < getNumTargets()) { + return getQubitsOut()[i]; + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value BarrierOp::getInputPosControl(const size_t i) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getOutputPosControl(const size_t i) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getInputNegControl(const size_t i) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getOutputNegControl(const size_t i) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getInputForOutput(const Value output) { + for (size_t i = 0; i < getNumTargets(); ++i) { + if (output == getQubitsOut()[i]) { + return getQubitsIn()[i]; + } + } + llvm::report_fatal_error("Given qubit is not an output of the operation"); +} + +Value BarrierOp::getOutputForInput(const Value input) { + for (size_t i = 0; i < getNumTargets(); ++i) { + if (input == getQubitsIn()[i]) { + return getQubitsOut()[i]; + } + } + llvm::report_fatal_error("Given qubit is not an input of the operation"); +} + +size_t BarrierOp::getNumParams() { return 0; } + +bool BarrierOp::hasStaticUnitary() { return true; } + +Value BarrierOp::getParameter(const size_t i) { + llvm::reportFatalUsageError("BarrierOp has no parameters"); +} + +DenseElementsAttr BarrierOp::tryGetStaticMatrix() { + return getMatrixId(getContext()); +} + +void BarrierOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange qubits) { + SmallVector resultTypes; + resultTypes.reserve(qubits.size()); + for (const Value qubit : qubits) { + resultTypes.push_back(qubit.getType()); + } + build(odsBuilder, odsState, resultTypes, qubits); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 27c7783328..5953bd4204 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -544,6 +544,13 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMINUSYY, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +QIRProgramBuilder& QIRProgramBuilder::barrier(const ValueRange qubits) { + createCallOp({}, {}, qubits, QIR_BARRIER); + return *this; +} + //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index ec6911f41f..5322763be9 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -358,6 +358,13 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +QuartzProgramBuilder& QuartzProgramBuilder::barrier(ValueRange qubits) { + create(loc, qubits); + return *this; +} + //===----------------------------------------------------------------------===// // Modifiers //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 255a27c3fa..d257d98638 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -73,31 +73,6 @@ struct RemoveTrivialCtrl final : OpRewritePattern { } // namespace -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const ValueRange controls, UnitaryOpInterface bodyUnitary) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); - - // Move the unitary op into the block - odsBuilder.setInsertionPointToStart(&block); - odsBuilder.clone(*bodyUnitary.getOperation()); - odsBuilder.create(odsState.location); -} - -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const ValueRange controls, - const std::function& bodyBuilder) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); - odsBuilder.setInsertionPointToStart(&block); - bodyBuilder(odsBuilder); - odsBuilder.create(odsState.location); -} - UnitaryOpInterface CtrlOp::getBodyUnitary() { return llvm::dyn_cast(&getBody().front().front()); } @@ -150,6 +125,31 @@ DenseElementsAttr CtrlOp::tryGetStaticMatrix() { getBodyUnitary().tryGetStaticMatrix()); } +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, UnitaryOpInterface bodyUnitary) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + // Move the unitary op into the block + odsBuilder.setInsertionPointToStart(&block); + odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location); +} + +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const ValueRange controls, + const std::function& bodyBuilder) { + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(odsBuilder); + odsBuilder.create(odsState.location); +} + LogicalResult CtrlOp::verify() { auto& block = getBody().front(); if (block.getOperations().size() != 2) { @@ -163,6 +163,7 @@ LogicalResult CtrlOp::verify() { return emitOpError( "second operation in body region must be a yield operation"); } + llvm::SmallPtrSet uniqueQubits; for (const auto& control : getControls()) { if (!uniqueQubits.insert(control).second) { @@ -176,6 +177,11 @@ LogicalResult CtrlOp::verify() { return emitOpError("duplicate qubit found"); } } + + if (llvm::isa(bodyUnitary.getOperation())) { + return emitOpError("BarrierOp cannot be controlled"); + } + return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp new file mode 100644 index 0000000000..dc0ae2133a --- /dev/null +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/MatrixUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::quartz; +using namespace mlir::utils; + +size_t BarrierOp::getNumQubits() { return getNumTargets(); } + +size_t BarrierOp::getNumTargets() { return getQubits().size(); } + +size_t BarrierOp::getNumControls() { return 0; } + +size_t BarrierOp::getNumPosControls() { return 0; } + +size_t BarrierOp::getNumNegControls() { return 0; } + +Value BarrierOp::getQubit(const size_t i) { return getTarget(i); } + +Value BarrierOp::getTarget(const size_t i) { + if (i < getNumTargets()) { + return getQubits()[i]; + } + llvm::reportFatalUsageError("Invalid qubit index"); +} + +Value BarrierOp::getPosControl(const size_t i) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +Value BarrierOp::getNegControl(const size_t i) { + llvm::reportFatalUsageError("BarrierOp cannot be controlled"); +} + +size_t BarrierOp::getNumParams() { return 0; } + +bool BarrierOp::hasStaticUnitary() { return true; } + +Value BarrierOp::getParameter(const size_t i) { + llvm::reportFatalUsageError("BarrierOp does not have parameters"); +} + +DenseElementsAttr BarrierOp::tryGetStaticMatrix() { + return getMatrixId(getContext()); +} diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 15e21c1b1d..5a003ed017 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -486,6 +486,17 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) #undef DEFINE_TWO_TARGET_TWO_PARAMETER +// BarrierOp + +void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { + llvm::SmallVector targets; + for (const auto& targetIdx : operation.getTargets()) { + targets.push_back(qubits[targetIdx]); + } + builder.barrier(targets); +} + #define ADD_OP_CASE(OP_QC) \ case qc::OpType::OP_QC: \ add##OP_QC##Op(builder, *operation, qubits); \ @@ -551,6 +562,7 @@ translateOperations(QuartzProgramBuilder& builder, ADD_OP_CASE(RZZ) ADD_OP_CASE(XXplusYY) ADD_OP_CASE(XXminusYY) + ADD_OP_CASE(Barrier) default: // Unsupported operation - skip for now // As the Quartz dialect is expanded, more operations will be supported diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 26f5bf104e..21450df000 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -3168,4 +3168,66 @@ TEST_F(CompilerPipelineTest, XXMinusYY) { }); } +TEST_F(CompilerPipelineTest, Barrier1) { + qc::QuantumComputation qc; + qc.addQubitRegister(1, "q"); + qc.barrier(0); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.barrier(reg[0]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.barrier(reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.barrier(reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + +TEST_F(CompilerPipelineTest, Barrier2) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.barrier({0, 1}); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.barrier({reg[0], reg[1]}); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.barrier({reg[0], reg[1]}); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.barrier({reg[0], reg[1]}); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From 957b5d89bd4528ce78e5077a2d7167a784282282 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:19:46 +0100 Subject: [PATCH 276/419] Remove matrix extraction for now --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 13 - .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 12 - mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 33 -- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 13 - .../Dialect/Quartz/IR/QuartzInterfaces.td | 12 - .../mlir/Dialect/Quartz/IR/QuartzOps.td | 38 +- mlir/include/mlir/Dialect/Utils/MatrixUtils.h | 360 ------------------ mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 9 - .../IR/Operations/StandardGates/BarrierOp.cpp | 8 - .../IR/Operations/StandardGates/DCXOp.cpp | 22 -- .../IR/Operations/StandardGates/ECROp.cpp | 7 +- .../IR/Operations/StandardGates/GPhaseOp.cpp | 11 - .../Flux/IR/Operations/StandardGates/HOp.cpp | 4 - .../Flux/IR/Operations/StandardGates/IdOp.cpp | 6 - .../Flux/IR/Operations/StandardGates/POp.cpp | 11 - .../Flux/IR/Operations/StandardGates/ROp.cpp | 13 - .../Flux/IR/Operations/StandardGates/RXOp.cpp | 11 - .../IR/Operations/StandardGates/RXXOp.cpp | 11 - .../Flux/IR/Operations/StandardGates/RYOp.cpp | 11 - .../IR/Operations/StandardGates/RYYOp.cpp | 11 - .../Flux/IR/Operations/StandardGates/RZOp.cpp | 11 - .../IR/Operations/StandardGates/RZXOp.cpp | 11 - .../IR/Operations/StandardGates/RZZOp.cpp | 11 - .../Flux/IR/Operations/StandardGates/SOp.cpp | 4 - .../IR/Operations/StandardGates/SWAPOp.cpp | 6 - .../Flux/IR/Operations/StandardGates/SXOp.cpp | 6 - .../IR/Operations/StandardGates/SXdgOp.cpp | 6 - .../IR/Operations/StandardGates/SdgOp.cpp | 6 - .../Flux/IR/Operations/StandardGates/TOp.cpp | 4 - .../IR/Operations/StandardGates/TdgOp.cpp | 6 - .../Flux/IR/Operations/StandardGates/U2Op.cpp | 13 - .../Flux/IR/Operations/StandardGates/UOp.cpp | 15 - .../Flux/IR/Operations/StandardGates/XOp.cpp | 4 - .../Operations/StandardGates/XXMinusYYOp.cpp | 13 - .../Operations/StandardGates/XXPlusYYOp.cpp | 13 - .../Flux/IR/Operations/StandardGates/YOp.cpp | 4 - .../Flux/IR/Operations/StandardGates/ZOp.cpp | 4 - .../IR/Operations/StandardGates/iSWAPOp.cpp | 22 -- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 9 - .../IR/Operations/StandardGates/BarrierOp.cpp | 8 - .../IR/Operations/StandardGates/DCXOp.cpp | 22 -- .../IR/Operations/StandardGates/ECROp.cpp | 22 -- .../IR/Operations/StandardGates/GPhaseOp.cpp | 11 - .../IR/Operations/StandardGates/HOp.cpp | 20 - .../IR/Operations/StandardGates/IdOp.cpp | 22 -- .../IR/Operations/StandardGates/PhaseOp.cpp | 11 - .../IR/Operations/StandardGates/ROp.cpp | 13 - .../IR/Operations/StandardGates/RXOp.cpp | 11 - .../IR/Operations/StandardGates/RXXOp.cpp | 11 - .../IR/Operations/StandardGates/RYOp.cpp | 11 - .../IR/Operations/StandardGates/RYYOp.cpp | 11 - .../IR/Operations/StandardGates/RZOp.cpp | 11 - .../IR/Operations/StandardGates/RZXOp.cpp | 11 - .../IR/Operations/StandardGates/RZZOp.cpp | 11 - .../IR/Operations/StandardGates/SOp.cpp | 20 - .../IR/Operations/StandardGates/SWAPOp.cpp | 22 -- .../IR/Operations/StandardGates/SXOp.cpp | 22 -- .../IR/Operations/StandardGates/SXdgOp.cpp | 22 -- .../IR/Operations/StandardGates/SdgOp.cpp | 22 -- .../IR/Operations/StandardGates/TOp.cpp | 20 - .../IR/Operations/StandardGates/TdgOp.cpp | 22 -- .../IR/Operations/StandardGates/U2Op.cpp | 13 - .../IR/Operations/StandardGates/UOp.cpp | 15 - .../IR/Operations/StandardGates/XOp.cpp | 20 - .../Operations/StandardGates/XXMinusYYOp.cpp | 13 - .../Operations/StandardGates/XXPlusYYOp.cpp | 13 - .../IR/Operations/StandardGates/YOp.cpp | 20 - .../IR/Operations/StandardGates/ZOp.cpp | 20 - .../IR/Operations/StandardGates/iSWAPOp.cpp | 22 -- 69 files changed, 6 insertions(+), 1270 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/Utils/MatrixUtils.h delete mode 100644 mlir/lib/Dialect/Flux/IR/Operations/StandardGates/DCXOp.cpp delete mode 100644 mlir/lib/Dialect/Flux/IR/Operations/StandardGates/iSWAPOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/DCXOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ECROp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/HOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/IdOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SWAPOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXdgOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SdgOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TdgOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/YOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ZOp.cpp delete mode 100644 mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/iSWAPOp.cpp diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index 417b756356..cd3bd443a7 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -119,19 +119,6 @@ template class TargetAndParameterArityTrait { return dyn_cast(constantOp.getValue()); } - bool hasStaticUnitary() { - if constexpr (P == 0) { - return true; - } - const auto& op = this->getOperation(); - for (size_t i = 0; i < P; ++i) { - if (!getStaticParameter(op->getOperand(T + i))) { - return false; - } - } - return true; - } - Value getInputForOutput(Value output) { const auto& op = this->getOperation(); for (size_t i = 0; i < T; ++i) { diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 9323746f90..692991fb9f 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -118,18 +118,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "bool", "isTwoQubit", (ins), [{ return getNumQubits(impl, tablegen_opaque_val) == 2; }] >, - /// TODO: Add more convenience methods as necessary - - // Matrix extraction - InterfaceMethod< - "Returns true if the operation has a static unitary matrix.", - "bool", "hasStaticUnitary", (ins) - >, - InterfaceMethod< - "Attempts to extract the static unitary matrix. " - "Returns std::nullopt if the operation is symbolic or dynamic.", - "DenseElementsAttr", "tryGetStaticMatrix", (ins) - >, // Identification InterfaceMethod< diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index c392ce6395..af8120f56d 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -274,7 +274,6 @@ def GPhaseOp : FluxOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParam let assemblyFormat = "$theta attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "gphase"; } }]; @@ -301,7 +300,6 @@ def IdOp : FluxOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "id"; } }]; @@ -324,7 +322,6 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; @@ -347,7 +344,6 @@ def YOp : FluxOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "y"; } }]; @@ -370,7 +366,6 @@ def ZOp : FluxOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "z"; } }]; @@ -393,7 +388,6 @@ def HOp : FluxOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "h"; } }]; @@ -416,7 +410,6 @@ def SOp : FluxOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "s"; } }]; @@ -439,7 +432,6 @@ def SdgOp : FluxOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "sdg"; } }]; @@ -462,7 +454,6 @@ def TOp : FluxOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "t"; } }]; @@ -485,7 +476,6 @@ def TdgOp : FluxOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "tdg"; } }]; @@ -508,7 +498,6 @@ def SXOp : FluxOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "sx"; } }]; @@ -531,7 +520,6 @@ def SXdgOp : FluxOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "sxdg"; } }]; @@ -555,7 +543,6 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } }]; @@ -583,7 +570,6 @@ def RYOp : FluxOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ry"; } }]; @@ -611,7 +597,6 @@ def RZOp : FluxOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rz"; } }]; @@ -639,7 +624,6 @@ def POp : FluxOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "p"; } }]; @@ -668,7 +652,6 @@ def ROp : FluxOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "r"; } }]; @@ -695,7 +678,6 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } }]; @@ -725,7 +707,6 @@ def UOp : FluxOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u"; } }]; @@ -751,7 +732,6 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; @@ -775,7 +755,6 @@ def iSWAPOp : FluxOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParamet let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "iswap"; } }]; } @@ -797,7 +776,6 @@ def DCXOp : FluxOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "dcx"; } }]; } @@ -819,7 +797,6 @@ def ECROp : FluxOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ecr"; } }]; @@ -844,7 +821,6 @@ def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rxx"; } }]; @@ -873,7 +849,6 @@ def RYYOp : FluxOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ryy"; } }]; @@ -902,7 +877,6 @@ def RZXOp : FluxOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rzx"; } }]; @@ -931,7 +905,6 @@ def RZZOp : FluxOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rzz"; } }]; @@ -961,7 +934,6 @@ def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwo let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "xx_plus_yy"; } }]; @@ -991,7 +963,6 @@ def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetT let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "xx_minus_yy"; } }]; @@ -1035,8 +1006,6 @@ def BarrierOp : FluxOp<"barrier", traits = [UnitaryOpInterface]> { Value getOutputForInput(Value input); size_t getNumParams(); Value getParameter(size_t i); - bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "barrier"; } }]; @@ -1117,8 +1086,6 @@ def CtrlOp : FluxOp<"ctrl", traits = Value getOutputForInput(Value input); size_t getNumParams(); Value getParameter(size_t i); - bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 3a3928a371..ce65516325 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -110,19 +110,6 @@ template class TargetAndParameterArityTrait { } return dyn_cast(constantOp.getValue()); } - - bool hasStaticUnitary() { - if constexpr (P == 0) { - return true; - } - const auto& op = this->getOperation(); - for (size_t i = 0; i < P; ++i) { - if (!getStaticParameter(op->getOperand(T + i))) { - return false; - } - } - return true; - } }; }; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index c1d720b1c9..76f80d409b 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -93,18 +93,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "bool", "isTwoQubit", (ins), [{ return getNumQubits(impl, tablegen_opaque_val) == 2; }] >, - /// TODO: I am fairly sure that there are quite some further convenience methods that would be helpful here, e.g., whether it is a two-qubit gate - - // Matrix extraction - InterfaceMethod< - "Returns true if the operation has a static unitary matrix.", - "bool", "hasStaticUnitary", (ins) - >, - InterfaceMethod< - "Attempts to extract the static unitary matrix. " - "Returns std::nullopt if the operation is symbolic or dynamic.", - "DenseElementsAttr", "tryGetStaticMatrix", (ins) - >, // Identification InterfaceMethod< diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 8b851c6383..fc9243c571 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -264,7 +264,6 @@ def GPhaseOp : QuartzOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOnePar let assemblyFormat = "$theta attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "gphase"; } }]; @@ -288,7 +287,6 @@ def IdOp : QuartzOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "id"; } }]; } @@ -308,7 +306,6 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "x"; } }]; } @@ -328,7 +325,6 @@ def YOp : QuartzOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "y"; } }]; } @@ -348,7 +344,6 @@ def ZOp : QuartzOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "z"; } }]; } @@ -368,7 +363,6 @@ def HOp : QuartzOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "h"; } }]; } @@ -388,7 +382,6 @@ def SOp : QuartzOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "s"; } }]; } @@ -408,7 +401,6 @@ def SdgOp : QuartzOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "sdg"; } }]; } @@ -428,7 +420,6 @@ def TOp : QuartzOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "t"; } }]; } @@ -448,7 +439,6 @@ def TdgOp : QuartzOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "tdg"; } }]; } @@ -468,7 +458,6 @@ def SXOp : QuartzOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "sx"; } }]; } @@ -488,7 +477,6 @@ def SXdgOp : QuartzOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParamet let assemblyFormat = "$qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "sxdg"; } }]; } @@ -509,7 +497,6 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rx"; } }]; @@ -534,7 +521,6 @@ def RYOp : QuartzOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ry"; } }]; @@ -559,7 +545,6 @@ def RZOp : QuartzOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rz"; } }]; @@ -584,7 +569,6 @@ def POp : QuartzOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "p"; } }]; @@ -608,8 +592,8 @@ def ROp : QuartzOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { Arg:$theta, Arg:$phi); let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict"; + let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "r"; } }]; @@ -633,8 +617,8 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> Arg:$phi, Arg:$lambda); let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict"; + let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u2"; } }]; @@ -659,8 +643,8 @@ def UOp : QuartzOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> Arg:$phi, Arg:$lambda); let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict"; + let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "u"; } }]; @@ -685,7 +669,6 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParamet let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "swap"; } }]; } @@ -706,7 +689,6 @@ def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParam let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "iswap"; } }]; } @@ -727,7 +709,6 @@ def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "dcx"; } }]; } @@ -748,7 +729,6 @@ def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ecr"; } }]; } @@ -770,7 +750,6 @@ def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter] let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rxx"; } }]; @@ -796,7 +775,6 @@ def RYYOp : QuartzOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter] let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ryy"; } }]; @@ -822,7 +800,6 @@ def RZXOp : QuartzOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter] let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rzx"; } }]; @@ -848,7 +825,6 @@ def RZZOp : QuartzOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter] let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "rzz"; } }]; @@ -873,8 +849,8 @@ def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetT Arg:$theta, Arg:$beta); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "xx_plus_yy"; } }]; @@ -899,8 +875,8 @@ def XXMinusYYOp : QuartzOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTarge Arg:$theta, Arg:$beta); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let extraClassDeclaration = [{ - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "xx_minus_yy"; } }]; @@ -935,8 +911,6 @@ def BarrierOp : QuartzOp<"barrier", traits = [UnitaryOpInterface]> { Value getNegControl(size_t i); size_t getNumParams(); Value getParameter(size_t i); - bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "barrier"; } }]; } @@ -991,8 +965,6 @@ def CtrlOp : QuartzOp<"ctrl", Value getNegControl(size_t i); size_t getNumParams(); Value getParameter(size_t i); - bool hasStaticUnitary(); - DenseElementsAttr tryGetStaticMatrix(); static StringRef getBaseSymbol() { return "ctrl"; } }]; diff --git a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h b/mlir/include/mlir/Dialect/Utils/MatrixUtils.h deleted file mode 100644 index 7f49c8a1c7..0000000000 --- a/mlir/include/mlir/Dialect/Utils/MatrixUtils.h +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include -#include -#include - -namespace mlir::utils { - -using namespace std::complex_literals; - -inline DenseElementsAttr getMatrixGPhase(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({1, 1}, complexType); - return DenseElementsAttr::get(type, {std::exp(1i * theta)}); -} - -inline DenseElementsAttr getMatrixId(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, 1.0 + 0i}; // row 1 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixX(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& matrix = {0.0 + 0i, 1.0 + 0i, // row 0 - 1.0 + 0i, 0.0 + 0i}; // row 1 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixY(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& matrix = {0.0 + 0i, 0.0 - 1i, // row 0 - 0.0 + 1i, 0.0 + 0i}; // row 1 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixZ(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, -1.0 + 0i}; // row 1 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixH(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = 1.0 / std::sqrt(2) + 0i; - const std::complex m11 = -1.0 / std::sqrt(2) + 0i; - return DenseElementsAttr::get(type, {m00, m00, m00, m11}); -} - -inline DenseElementsAttr getMatrixS(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, 0.0 + 1i}; // row 1 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixSdg(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const auto& matrix = {1.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, 0.0 - 1i}; // row 1 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixT(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = 1.0 + 0i; - const std::complex m01 = 0.0 + 0i; - const std::complex m11 = std::exp(1i * std::numbers::pi / 4.0); - return DenseElementsAttr::get(type, {m00, m01, m01, m11}); -} - -inline DenseElementsAttr getMatrixTdg(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = 1.0 + 0i; - const std::complex m01 = 0.0 + 0i; - const std::complex m11 = std::exp(-1i * std::numbers::pi / 4.0); - return DenseElementsAttr::get(type, {m00, m01, m01, m11}); -} - -inline DenseElementsAttr getMatrixSX(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = (1.0 + 1i) / 2.0; - const std::complex m01 = (1.0 - 1i) / 2.0; - return DenseElementsAttr::get(type, {m00, m01, m01, m00}); -} - -inline DenseElementsAttr getMatrixSXdg(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = (1.0 - 1i) / 2.0; - const std::complex m01 = (1.0 + 1i) / 2.0; - return DenseElementsAttr::get(type, {m00, m01, m01, m00}); -} - -inline DenseElementsAttr getMatrixRX(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::cos(theta / 2.0) + 0i; - const std::complex m01 = -1i * std::sin(theta / 2.0); - return DenseElementsAttr::get(type, {m00, m01, m01, m00}); -} - -inline DenseElementsAttr getMatrixRY(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::cos(theta / 2.0) + 0i; - const std::complex m01 = -std::sin(theta / 2.0) + 0i; - return DenseElementsAttr::get(type, {m00, m01, -m01, m00}); -} - -inline DenseElementsAttr getMatrixRZ(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::exp(-1i * theta / 2.0); - const std::complex m01 = 0.0 + 0i; - const std::complex m11 = std::exp(1i * theta / 2.0); - return DenseElementsAttr::get(type, {m00, m01, m01, m11}); -} - -inline DenseElementsAttr getMatrixP(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = 1.0 + 0i; - const std::complex m01 = 0.0 + 0i; - const std::complex m11 = std::exp(1i * theta); - return DenseElementsAttr::get(type, {m00, m01, m01, m11}); -} - -inline DenseElementsAttr getMatrixU2(MLIRContext* ctx, double phi, - double lambda) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = 1.0 / std::sqrt(2) + 0i; - const std::complex m01 = -std::exp(1i * lambda) / std::sqrt(2); - const std::complex m10 = std::exp(1i * phi) / std::sqrt(2); - const std::complex m11 = std::exp(1i * (phi + lambda)) / std::sqrt(2); - return DenseElementsAttr::get(type, {m00, m01, m10, m11}); -} - -inline DenseElementsAttr getMatrixU(MLIRContext* ctx, double theta, double phi, - double lambda) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::cos(theta / 2.0) + 0i; - const std::complex m01 = - -std::exp(1i * lambda) * std::sin(theta / 2.0); - const std::complex m10 = std::exp(1i * phi) * std::sin(theta / 2.0); - const std::complex m11 = - std::exp(1i * (phi + lambda)) * std::cos(theta / 2.0); - return DenseElementsAttr::get(type, {m00, m01, m10, m11}); -} - -inline DenseElementsAttr getMatrixR(MLIRContext* ctx, double theta, - double phi) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({2, 2}, complexType); - const std::complex m00 = std::cos(theta / 2.0) + 0i; - const std::complex m01 = - -1i * std::exp(-1i * phi) * std::sin(theta / 2.0); - const std::complex m10 = - -1i * std::exp(1i * phi) * std::sin(theta / 2.0); - const std::complex m11 = std::cos(theta / 2.0) + 0i; - return DenseElementsAttr::get(type, {m00, m01, m10, m11}); -} - -inline DenseElementsAttr getMatrixSWAP(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const auto matrix = {1.0 + 0i, 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, // row 1 - 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 2 - 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, 1.0 + 0i}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixiSWAP(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const auto matrix = {1.0 + 0i, 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, 0.0 + 0i, 0.0 + 1i, 0.0 + 0i, // row 1 - 0.0 + 0i, 0.0 + 1i, 0.0 + 0i, 0.0 + 0i, // row 2 - 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, 1.0 + 0i}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixDCX(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const auto matrix = {1.0 + 0i, 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, // row 0 - 0.0 + 0i, 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, // row 1 - 0.0 + 0i, 0.0 + 0i, 0.0 + 0i, 1.0 + 0i, // row 2 - 0.0 + 0i, 1.0 + 0i, 0.0 + 0i, 0.0 + 0i}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixECR(MLIRContext* ctx) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex m1 = 1.0 / std::sqrt(2) + 0i; - const std::complex mi = 0.0 + 1i / std::sqrt(2); - const auto matrix = {m0, m0, m1, mi, // row 0 - m0, m0, mi, m1, // row 1 - m1, -mi, m0, m0, // row 2 - -mi, m1, m0, m0}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixRXX(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex mc = std::cos(theta / 2.0) + 0i; - const std::complex ms = -1i * std::sin(theta / 2.0); - const auto matrix = {mc, m0, m0, ms, // row 0 - m0, mc, ms, m0, // row 1 - m0, ms, mc, m0, // row 2 - ms, m0, m0, mc}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixRYY(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex mc = std::cos(theta / 2.0) + 0i; - const std::complex ms = 1i * std::sin(theta / 2.0); - const auto matrix = {mc, m0, m0, ms, // row 0 - m0, mc, -ms, m0, // row 1 - m0, -ms, mc, m0, // row 2 - ms, m0, m0, mc}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixRZX(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex mc = std::cos(theta / 2.0) + 0i; - const std::complex ms = -1i * std::sin(theta / 2.0); - const auto matrix = {mc, -ms, m0, m0, // row 0 - -ms, mc, m0, m0, // row 1 - m0, m0, mc, ms, // row 2 - m0, m0, ms, mc}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixRZZ(MLIRContext* ctx, double theta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex mp = std::exp(1i * theta / 2.0); - const std::complex mm = std::exp(-1i * theta / 2.0); - const auto matrix = {mm, m0, m0, m0, // row 0 - m0, mp, m0, m0, // row 1 - m0, m0, mp, m0, // row 2 - m0, m0, m0, mm}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixXXPlusYY(MLIRContext* ctx, double theta, - double beta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex m1 = 1.0 + 0i; - const std::complex mc = std::cos(theta / 2.0) + 0i; - const std::complex msp = - -1i * std::sin(theta / 2.0) * std::exp(1i * beta); - const std::complex msm = - -1i * std::sin(theta / 2.0) * std::exp(-1i * beta); - const auto matrix = {m1, m0, m0, m0, // row 0 - m0, mc, msm, m0, // row 1 - m0, msp, mc, m0, // row 2 - m0, m0, m0, m1}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixXXMinusYY(MLIRContext* ctx, double theta, - double beta) { - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({4, 4}, complexType); - const std::complex m0 = 0.0 + 0i; - const std::complex m1 = 1.0 + 0i; - const std::complex mc = std::cos(theta / 2.0) + 0i; - const std::complex msp = - std::sin(theta / 2.0) * std::exp(-1i * beta) + 0i; - const std::complex msm = - -std::sin(theta / 2.0) * std::exp(1i * beta) + 0i; - const auto matrix = {mc, m0, m0, msm, // row 0 - m0, m1, m0, m0, // row 1 - m0, m0, m1, m0, // row 2 - msp, m0, m0, mc}; // row 3 - return DenseElementsAttr::get(type, matrix); -} - -inline DenseElementsAttr getMatrixCtrl(mlir::MLIRContext* ctx, - size_t numControls, - mlir::DenseElementsAttr target) { - // Get dimensions of target matrix - const auto& targetType = llvm::dyn_cast(target.getType()); - if (!targetType || targetType.getRank() != 2 || - targetType.getDimSize(0) != targetType.getDimSize(1)) { - llvm::report_fatal_error("Invalid target matrix"); - } - const auto targetDim = targetType.getDimSize(0); - - // Get values of target matrix - const auto& targetMatrix = target.getValues>(); - - // Define dimensions and type of output matrix - const auto dim = static_cast(std::pow(2, numControls) * targetDim); - const auto& complexType = ComplexType::get(Float64Type::get(ctx)); - const auto& type = RankedTensorType::get({dim, dim}, complexType); - - // Allocate output matrix - std::vector> matrix; - matrix.reserve(dim * dim); - - // Fill output matrix - for (int64_t i = 0; i < dim; ++i) { - for (int64_t j = 0; j < dim; ++j) { - if (i < (dim - targetDim) && j < (dim - targetDim)) { - matrix.push_back((i == j) ? 1.0 : 0.0); - } else if (i >= (dim - targetDim) && j >= (dim - targetDim)) { - matrix.push_back(targetMatrix[(i - (dim - targetDim)) * targetDim + - (j - (dim - targetDim))]); - } else { - matrix.push_back(0.0); - } - } - } - - ArrayRef> matrixRef(matrix); - return DenseElementsAttr::get(type, matrixRef); -} - -} // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 494b99b61b..2950e43cd6 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -27,7 +26,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -189,17 +187,10 @@ Value CtrlOp::getOutputForInput(const Value input) { size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } -bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } - Value CtrlOp::getParameter(const size_t i) { return getBodyUnitary().getParameter(i); } -DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - return getMatrixCtrl(getContext(), getNumPosControls(), - getBodyUnitary().tryGetStaticMatrix()); -} - void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, const ValueRange targets, UnitaryOpInterface bodyUnitary) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index 5099d2cb33..15c5b992c7 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -27,7 +26,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; size_t BarrierOp::getNumQubits() { return getNumTargets(); } @@ -93,16 +91,10 @@ Value BarrierOp::getOutputForInput(const Value input) { size_t BarrierOp::getNumParams() { return 0; } -bool BarrierOp::hasStaticUnitary() { return true; } - Value BarrierOp::getParameter(const size_t i) { llvm::reportFatalUsageError("BarrierOp has no parameters"); } -DenseElementsAttr BarrierOp::tryGetStaticMatrix() { - return getMatrixId(getContext()); -} - void BarrierOp::build(OpBuilder& odsBuilder, OperationState& odsState, const ValueRange qubits) { SmallVector resultTypes; diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/DCXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/DCXOp.cpp deleted file mode 100644 index e319d95d3c..0000000000 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/DCXOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::flux; -using namespace mlir::utils; - -DenseElementsAttr DCXOp::tryGetStaticMatrix() { - return getMatrixDCX(getContext()); -} diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp index 4b7bc9618a..dfc1a9af84 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -35,11 +33,8 @@ struct RemoveSubsequentECR final : OpRewritePattern { return removeInversePairTwoTargetZeroParameter(op, rewriter); } }; -} // namespace -DenseElementsAttr ECROp::tryGetStaticMatrix() { - return getMatrixECR(getContext()); -} +} // namespace void ECROp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index f8fb1cde5f..e5cb92693d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -52,15 +50,6 @@ struct RemoveTrivialGPhase final : OpRewritePattern { } // namespace -DenseElementsAttr GPhaseOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixGPhase(getContext(), thetaValue); -} - void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, const std::variant& theta) { Value thetaOperand = nullptr; diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp index 0c72063a39..e2a7fd9341 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -38,8 +36,6 @@ struct RemoveSubsequentH final : OpRewritePattern { } // namespace -DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } - void HOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp index 0d16126ecf..bbf47f74ac 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,7 +18,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -38,10 +36,6 @@ struct RemoveId final : OpRewritePattern { } // namespace -DenseElementsAttr IdOp::tryGetStaticMatrix() { - return getMatrixId(getContext()); -} - void IdOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index a859f65150..397e8300b3 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -42,15 +40,6 @@ struct MergeSubsequentP final : OpRewritePattern { } // namespace -DenseElementsAttr POp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixP(getContext(), thetaValue); -} - void POp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { Value thetaOperand = nullptr; diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 53bccb2d3b..e2e93d7090 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,18 +18,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; - -DenseElementsAttr ROp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto phi = getStaticParameter(getPhi()); - if (!theta || !phi) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto phiValue = phi.getValueAsDouble(); - return getMatrixR(getContext(), thetaValue, phiValue); -} void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index 1da7d612a4..a2fa44123c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRX final : OpRewritePattern { } // namespace -DenseElementsAttr RXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRX(getContext(), thetaValue); -} - void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index c5b0599968..4c8a51fc02 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRXX final : OpRewritePattern { } // namespace -DenseElementsAttr RXXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRXX(getContext(), thetaValue); -} - void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index 890c61beb8..f077669293 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRY final : OpRewritePattern { } // namespace -DenseElementsAttr RYOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRY(getContext(), thetaValue); -} - void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index 72df144edf..4e0c23614e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRYY final : OpRewritePattern { } // namespace -DenseElementsAttr RYYOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRYY(getContext(), thetaValue); -} - void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index ef0a8d27bf..44b0be92ea 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRZ final : OpRewritePattern { } // namespace -DenseElementsAttr RZOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRZ(getContext(), thetaValue); -} - void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index ff7fe80d86..32e41356cd 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRZX final : OpRewritePattern { } // namespace -DenseElementsAttr RZXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRZX(getContext(), thetaValue); -} - void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index e7805ffb80..4c7b59fed5 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -54,15 +52,6 @@ struct RemoveTrivialRZZ final : OpRewritePattern { } // namespace -DenseElementsAttr RZZOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRZZ(getContext(), thetaValue); -} - void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp index 141452673a..d01d0a4474 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -50,8 +48,6 @@ struct MergeSubsequentS final : OpRewritePattern { } // namespace -DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } - void SOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp index e36d30ee12..72d551878d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -38,10 +36,6 @@ struct RemoveSubsequentSWAP final : OpRewritePattern { } // namespace -DenseElementsAttr SWAPOp::tryGetStaticMatrix() { - return getMatrixSWAP(getContext()); -} - void SWAPOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp index db1ec916ed..cfbc7d1028 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -50,10 +48,6 @@ struct MergeSubsequentSX final : OpRewritePattern { } // namespace -DenseElementsAttr SXOp::tryGetStaticMatrix() { - return getMatrixSX(getContext()); -} - void SXOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp index 8bc319fa28..02d9cd1bc8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -51,10 +49,6 @@ struct MergeSubsequentSXdg final : OpRewritePattern { } // namespace -DenseElementsAttr SXdgOp::tryGetStaticMatrix() { - return getMatrixSXdg(getContext()); -} - void SXdgOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp index 5687bed997..be52fbcca5 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -50,10 +48,6 @@ struct MergeSubsequentSdg final : OpRewritePattern { } // namespace -DenseElementsAttr SdgOp::tryGetStaticMatrix() { - return getMatrixSdg(getContext()); -} - void SdgOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp index 50aa06278e..0097156c74 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -50,8 +48,6 @@ struct MergeSubsequentT final : OpRewritePattern { } // namespace -DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } - void TOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp index 0d40c79953..f67215ec08 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -51,10 +49,6 @@ struct MergeSubsequentTdg final : OpRewritePattern { } // namespace -DenseElementsAttr TdgOp::tryGetStaticMatrix() { - return getMatrixTdg(getContext()); -} - void TdgOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 4d016aae49..dd76baedb0 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -57,17 +55,6 @@ struct ReplaceTrivialU2 final : OpRewritePattern { } // namespace -DenseElementsAttr U2Op::tryGetStaticMatrix() { - const auto phi = getStaticParameter(getPhi()); - const auto lambda = getStaticParameter(getLambda()); - if (!phi || !lambda) { - return nullptr; - } - const auto phiValue = phi.getValueAsDouble(); - const auto lambdaValue = lambda.getValueAsDouble(); - return getMatrixU2(getContext(), phiValue, lambdaValue); -} - void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& phi, const std::variant& lambda) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index 47566fb961..e5729fba08 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,20 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; - -DenseElementsAttr UOp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto phi = getStaticParameter(getPhi()); - const auto lambda = getStaticParameter(getLambda()); - if (!theta || !phi || !lambda) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto phiValue = phi.getValueAsDouble(); - const auto lambdaValue = lambda.getValueAsDouble(); - return getMatrixU(getContext(), thetaValue, phiValue, lambdaValue); -} void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp index 46bb025dbc..e8a6108320 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -38,8 +36,6 @@ struct RemoveSubsequentX final : OpRewritePattern { } // namespace -DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } - void XOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index 15e9adf0fb..38eeff5e08 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -71,17 +69,6 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { } // namespace -DenseElementsAttr XXMinusYYOp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto beta = getStaticParameter(getBeta()); - if (!theta || !beta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto betaValue = beta.getValueAsDouble(); - return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); -} - void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index a7af4caa58..4667fa7a3f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -23,7 +22,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -71,17 +69,6 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { } // namespace -DenseElementsAttr XXPlusYYOp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto beta = getStaticParameter(getBeta()); - if (!theta || !beta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto betaValue = beta.getValueAsDouble(); - return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); -} - void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp index d13fa625bc..584300da1b 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -38,8 +36,6 @@ struct RemoveSubsequentY final : OpRewritePattern { } // namespace -DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } - void YOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp index 05b4213d44..af5c9512a0 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -20,7 +19,6 @@ using namespace mlir; using namespace mlir::flux; -using namespace mlir::utils; namespace { @@ -38,8 +36,6 @@ struct RemoveSubsequentZ final : OpRewritePattern { } // namespace -DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } - void ZOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/iSWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/iSWAPOp.cpp deleted file mode 100644 index 0de914665e..0000000000 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/iSWAPOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::flux; -using namespace mlir::utils; - -DenseElementsAttr iSWAPOp::tryGetStaticMatrix() { - return getMatrixiSWAP(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index d257d98638..aa3e5a4066 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -26,7 +25,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; namespace { @@ -114,17 +112,10 @@ Value CtrlOp::getNegControl(const size_t i) { size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } -bool CtrlOp::hasStaticUnitary() { return getBodyUnitary().hasStaticUnitary(); } - Value CtrlOp::getParameter(const size_t i) { return getBodyUnitary().getParameter(i); } -DenseElementsAttr CtrlOp::tryGetStaticMatrix() { - return getMatrixCtrl(getContext(), getNumPosControls(), - getBodyUnitary().tryGetStaticMatrix()); -} - void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, UnitaryOpInterface bodyUnitary) { const OpBuilder::InsertionGuard guard(odsBuilder); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp index dc0ae2133a..aa5c7c63b6 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -26,7 +25,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; size_t BarrierOp::getNumQubits() { return getNumTargets(); } @@ -57,12 +55,6 @@ Value BarrierOp::getNegControl(const size_t i) { size_t BarrierOp::getNumParams() { return 0; } -bool BarrierOp::hasStaticUnitary() { return true; } - Value BarrierOp::getParameter(const size_t i) { llvm::reportFatalUsageError("BarrierOp does not have parameters"); } - -DenseElementsAttr BarrierOp::tryGetStaticMatrix() { - return getMatrixId(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/DCXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/DCXOp.cpp deleted file mode 100644 index 48e1c0d921..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/DCXOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr DCXOp::tryGetStaticMatrix() { - return getMatrixDCX(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ECROp.cpp deleted file mode 100644 index ff925c0042..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ECROp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr ECROp::tryGetStaticMatrix() { - return getMatrixECR(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp index 045f0a8ef7..9c9745d5cb 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr GPhaseOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixGPhase(getContext(), thetaValue); -} void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/HOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/HOp.cpp deleted file mode 100644 index 791cb37feb..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/HOp.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr HOp::tryGetStaticMatrix() { return getMatrixH(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/IdOp.cpp deleted file mode 100644 index 2b01b03cc6..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/IdOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr IdOp::tryGetStaticMatrix() { - return getMatrixId(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp index 506a5861c6..0ab83b6bfa 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr POp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixP(getContext(), thetaValue); -} void POp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index 8fee48d788..2b5919e55b 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,18 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr ROp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto phi = getStaticParameter(getPhi()); - if (!theta || !phi) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto phiValue = phi.getValueAsDouble(); - return getMatrixR(getContext(), thetaValue, phiValue); -} void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index b1dbbfa9f6..ed25c0ffa0 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRX(getContext(), thetaValue); -} void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index d5dde3bf34..5981914b95 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RXXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRXX(getContext(), thetaValue); -} void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index ee71374783..011d23d46f 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RYOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRY(getContext(), thetaValue); -} void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index b75944eb85..64a77a54e7 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RYYOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRYY(getContext(), thetaValue); -} void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index b1586e3dbb..5eb462cd7d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RZOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRZ(getContext(), thetaValue); -} void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 5078458a5f..1e83a1db59 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RZXOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRZX(getContext(), thetaValue); -} void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index 4624e5ba21..b61f2f030d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,16 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr RZZOp::tryGetStaticMatrix() { - const auto& theta = getStaticParameter(getTheta()); - if (!theta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - return getMatrixRZZ(getContext(), thetaValue); -} void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SOp.cpp deleted file mode 100644 index a0eba979f0..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SOp.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr SOp::tryGetStaticMatrix() { return getMatrixS(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SWAPOp.cpp deleted file mode 100644 index c3f2b01ec4..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SWAPOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr SWAPOp::tryGetStaticMatrix() { - return getMatrixSWAP(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXOp.cpp deleted file mode 100644 index 38a07bf840..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr SXOp::tryGetStaticMatrix() { - return getMatrixSX(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXdgOp.cpp deleted file mode 100644 index ae535a853f..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SXdgOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr SXdgOp::tryGetStaticMatrix() { - return getMatrixSXdg(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SdgOp.cpp deleted file mode 100644 index dcf0f1f2a8..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/SdgOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr SdgOp::tryGetStaticMatrix() { - return getMatrixSdg(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TOp.cpp deleted file mode 100644 index 58ff397214..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TOp.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr TOp::tryGetStaticMatrix() { return getMatrixT(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TdgOp.cpp deleted file mode 100644 index bed0eae7c6..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/TdgOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr TdgOp::tryGetStaticMatrix() { - return getMatrixTdg(getContext()); -} diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index 043aace08e..b96411dbb2 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,18 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr U2Op::tryGetStaticMatrix() { - const auto phi = getStaticParameter(getPhi()); - const auto lambda = getStaticParameter(getLambda()); - if (!phi || !lambda) { - return nullptr; - } - const auto phiValue = phi.getValueAsDouble(); - const auto lambdaValue = lambda.getValueAsDouble(); - return getMatrixU2(getContext(), phiValue, lambdaValue); -} void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& phi, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index bd1feed7af..23b652270c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,20 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr UOp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto phi = getStaticParameter(getPhi()); - const auto lambda = getStaticParameter(getLambda()); - if (!theta || !phi || !lambda) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto phiValue = phi.getValueAsDouble(); - const auto lambdaValue = lambda.getValueAsDouble(); - return getMatrixU(getContext(), thetaValue, phiValue, lambdaValue); -} void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XOp.cpp deleted file mode 100644 index efba2f635f..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XOp.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr XOp::tryGetStaticMatrix() { return getMatrixX(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index bccd242be4..2e8463b2a9 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,18 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr XXMinusYYOp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto beta = getStaticParameter(getBeta()); - if (!theta || !beta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto betaValue = beta.getValueAsDouble(); - return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); -} void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index e334caef33..a5d979dc82 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -9,7 +9,6 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" #include #include @@ -19,18 +18,6 @@ using namespace mlir; using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr XXPlusYYOp::tryGetStaticMatrix() { - const auto theta = getStaticParameter(getTheta()); - const auto beta = getStaticParameter(getBeta()); - if (!theta || !beta) { - return nullptr; - } - const auto thetaValue = theta.getValueAsDouble(); - const auto betaValue = beta.getValueAsDouble(); - return getMatrixXXPlusYY(getContext(), thetaValue, betaValue); -} void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/YOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/YOp.cpp deleted file mode 100644 index adf92e63ad..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/YOp.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr YOp::tryGetStaticMatrix() { return getMatrixY(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ZOp.cpp deleted file mode 100644 index 71eee1c654..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ZOp.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr ZOp::tryGetStaticMatrix() { return getMatrixZ(getContext()); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/iSWAPOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/iSWAPOp.cpp deleted file mode 100644 index 13ad45d1e0..0000000000 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/iSWAPOp.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Utils/MatrixUtils.h" - -#include - -using namespace mlir; -using namespace mlir::quartz; -using namespace mlir::utils; - -DenseElementsAttr iSWAPOp::tryGetStaticMatrix() { - return getMatrixiSWAP(getContext()); -} From 1eb3607a149e5419ebd9f1f0419106e164abee3e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:50:15 +0100 Subject: [PATCH 277/419] Fix linter errors --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 18 +++++++++--------- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 14 +++++++------- .../IR/Operations/StandardGates/BarrierOp.cpp | 16 +++++----------- .../Flux/IR/Operations/StandardGates/ECROp.cpp | 1 - .../Flux/IR/Operations/StandardGates/HOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/IdOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/SOp.cpp | 1 - .../IR/Operations/StandardGates/SWAPOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/SXOp.cpp | 1 - .../IR/Operations/StandardGates/SXdgOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/SdgOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/TOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/TdgOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/XOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/YOp.cpp | 1 - .../Flux/IR/Operations/StandardGates/ZOp.cpp | 1 - .../IR/Operations/StandardGates/BarrierOp.cpp | 14 +++----------- 17 files changed, 24 insertions(+), 51 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index af8120f56d..2932284da5 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -991,21 +991,21 @@ def BarrierOp : FluxOp<"barrier", traits = [UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits(); size_t getNumTargets(); - size_t getNumControls(); - size_t getNumPosControls(); - size_t getNumNegControls(); + static size_t getNumControls(); + static size_t getNumPosControls(); + static size_t getNumNegControls(); Value getInputQubit(size_t i); Value getOutputQubit(size_t i); Value getInputTarget(size_t i); Value getOutputTarget(size_t i); - Value getInputPosControl(size_t i); - Value getOutputPosControl(size_t i); - Value getInputNegControl(size_t i); - Value getOutputNegControl(size_t i); + static Value getInputPosControl(size_t i); + static Value getOutputPosControl(size_t i); + static Value getInputNegControl(size_t i); + static Value getOutputNegControl(size_t i); Value getInputForOutput(Value output); Value getOutputForInput(Value input); - size_t getNumParams(); - Value getParameter(size_t i); + static size_t getNumParams(); + static Value getParameter(size_t i); static StringRef getBaseSymbol() { return "barrier"; } }]; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index fc9243c571..350470c77f 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -902,15 +902,15 @@ def BarrierOp : QuartzOp<"barrier", traits = [UnitaryOpInterface]> { let extraClassDeclaration = [{ size_t getNumQubits(); size_t getNumTargets(); - size_t getNumControls(); - size_t getNumPosControls(); - size_t getNumNegControls(); + static size_t getNumControls(); + static size_t getNumPosControls(); + static size_t getNumNegControls(); Value getQubit(size_t i); Value getTarget(size_t i); - Value getPosControl(size_t i); - Value getNegControl(size_t i); - size_t getNumParams(); - Value getParameter(size_t i); + static Value getPosControl(size_t i); + static Value getNegControl(size_t i); + static size_t getNumParams(); + static Value getParameter(size_t i); static StringRef getBaseSymbol() { return "barrier"; } }]; } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index 15c5b992c7..ee6756dbf6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -12,17 +12,11 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include -#include #include -#include #include #include -#include -#include #include -#include #include -#include using namespace mlir; using namespace mlir::flux; @@ -55,19 +49,19 @@ Value BarrierOp::getOutputTarget(const size_t i) { llvm::reportFatalUsageError("Invalid qubit index"); } -Value BarrierOp::getInputPosControl(const size_t i) { +Value BarrierOp::getInputPosControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } -Value BarrierOp::getOutputPosControl(const size_t i) { +Value BarrierOp::getOutputPosControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } -Value BarrierOp::getInputNegControl(const size_t i) { +Value BarrierOp::getInputNegControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } -Value BarrierOp::getOutputNegControl(const size_t i) { +Value BarrierOp::getOutputNegControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } @@ -91,7 +85,7 @@ Value BarrierOp::getOutputForInput(const Value input) { size_t BarrierOp::getNumParams() { return 0; } -Value BarrierOp::getParameter(const size_t i) { +Value BarrierOp::getParameter(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp has no parameters"); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp index dfc1a9af84..b5b416c103 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp index e2a7fd9341..a53a77e8e1 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp index bbf47f74ac..245102810d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp index d01d0a4474..b3636af804 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp index 72d551878d..f795578a31 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp index cfbc7d1028..818f1af953 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp index 02d9cd1bc8..cd662e2211 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp index be52fbcca5..0b620d55d3 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp index 0097156c74..e0fd1f70e8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp index f67215ec08..c43e162762 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp index e8a6108320..06ed2a74ca 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp index 584300da1b..5946636c18 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp index af5c9512a0..16bd3f5d62 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp @@ -11,7 +11,6 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp index aa5c7c63b6..794c1df9ef 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp @@ -11,17 +11,9 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include -#include #include -#include #include -#include -#include -#include #include -#include -#include -#include using namespace mlir; using namespace mlir::quartz; @@ -45,16 +37,16 @@ Value BarrierOp::getTarget(const size_t i) { llvm::reportFatalUsageError("Invalid qubit index"); } -Value BarrierOp::getPosControl(const size_t i) { +Value BarrierOp::getPosControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } -Value BarrierOp::getNegControl(const size_t i) { +Value BarrierOp::getNegControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } size_t BarrierOp::getNumParams() { return 0; } -Value BarrierOp::getParameter(const size_t i) { +Value BarrierOp::getParameter(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp does not have parameters"); } From 754372e25c06778c768429bcc3b86896a5e7e402 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:20:03 +0100 Subject: [PATCH 278/419] Add barrier canonicalization and remove it from QIR --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 + .../Dialect/QIR/Builder/QIRProgramBuilder.h | 19 ------ .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 - .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 20 +++--- .../IR/Operations/StandardGates/BarrierOp.cpp | 59 +++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 7 -- .../pipeline/test_compiler_pipeline.cpp | 66 ++++++++++++------- 7 files changed, 112 insertions(+), 63 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 2932284da5..cbfb6d05ef 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -1012,6 +1012,8 @@ def BarrierOp : FluxOp<"barrier", traits = [UnitaryOpInterface]> { let builders = [ OpBuilder<(ins "ValueRange":$qubits)> ]; + + let hasCanonicalizer = 1; } //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 0a471afdc0..a05eb4b6dd 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -816,25 +816,6 @@ class QIRProgramBuilder { DECLARE_TWO_TARGET_TWO_PARAMETER(xx_plus_yy, xx_plus_yy, theta, beta) DECLARE_TWO_TARGET_TWO_PARAMETER(xx_minus_yy, xx_minus_yy, theta, beta) - // BarrierOp - - /** - * @brief Apply a barrier operation - * - * @param qubits Target qubits - * @return Reference to this builder for method chaining - * - * @par Example: - * ```c++ - * builder.barrier({q0, q1}); - * ``` - * ```mlir - * llvm.call @__quantum__qis__barrier__body(%q0, %q1) : (!llvm.ptr, !llvm.ptr) - * -> () - * ``` - */ - QIRProgramBuilder& barrier(ValueRange qubits); - #undef DECLARE_TWO_TARGET_TWO_PARAMETER //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 9ed87e9559..91ed732818 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -68,8 +68,6 @@ ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) #undef ADD_STANDARD_GATE -static constexpr auto QIR_BARRIER = "__quantum__qis__barrier__body"; - // Functions for getting QIR function names #define DEFINE_GETTER(NAME) \ diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 3794fdab58..07d0e6874e 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -817,15 +817,17 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, XXMINUSYY, xx_minus_yy, // BarrierOp +/** + * @brief Erases quartz.barrier operation, as it is a no-op in QIR + */ struct ConvertQuartzBarrierQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(BarrierOp op, OpAdaptor adaptor, + matchAndRewrite(BarrierOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), - getState(), QIR_BARRIER, - op.getQubits().size(), 0); + rewriter.eraseOp(op); + return success(); } }; @@ -885,11 +887,6 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { * 4. Set QIR metadata attributes * 5. Convert arith and cf dialects to LLVM * 6. Reconcile unrealized casts - * - * Current scope: - * - Quartz operations: static, alloc, dealloc, measure, reset - * - Gate operations will be added as the dialect expands - * - Supports both static and dynamic qubit management */ struct QuartzToQIR final : impl::QuartzToQIRBase { using QuartzToQIRBase::QuartzToQIRBase; @@ -1136,8 +1133,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * in the entry block. * * **Stage 3: Quartz to LLVM** - * Convert Quartz dialect operations to QIR calls (static, alloc, dealloc, - * measure, reset) and add output recording to the output block. + * Convert Quartz dialect operations to QIR calls and add output recording to + * the output block. * * **Stage 4: QIR attributes** * Add QIR base profile metadata to the main function, including @@ -1229,7 +1226,6 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); quartzPatterns.add(typeConverter, ctx, &state); - // Gate operations will be added here as the dialect expands if (applyPartialConversion(moduleOp, target, std::move(quartzPatterns)) .failed()) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index ee6756dbf6..9a03b8ec51 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -15,12 +15,66 @@ #include #include #include +#include #include +#include #include +#include using namespace mlir; using namespace mlir::flux; +namespace { + +/** + * @brief Merge subsequent barriers on the same qubits into a single barrier. + */ +struct MergeSubsequentBarrier final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(BarrierOp op, + PatternRewriter& rewriter) const override { + const auto& qubitsIn = op.getQubitsIn(); + + auto anythingToMerge = false; + DenseMap newQubitsOutMap; + + SmallVector newQubitsIn; + SmallVector indicesToFill; + + for (size_t i = 0; i < qubitsIn.size(); ++i) { + if (qubitsIn[i].getDefiningOp()) { + anythingToMerge = true; + newQubitsOutMap[i] = qubitsIn[i]; + } else { + newQubitsIn.push_back(qubitsIn[i]); + indicesToFill.push_back(i); + } + } + + if (!anythingToMerge) { + return failure(); + } + + auto newBarrier = rewriter.create(op.getLoc(), newQubitsIn); + + for (size_t i = 0; i < indicesToFill.size(); ++i) { + newQubitsOutMap[indicesToFill[i]] = newBarrier.getQubitsOut()[i]; + } + + SmallVector newQubitsOut; + newQubitsOut.reserve(op.getQubitsIn().size()); + for (size_t i = 0; i < op.getQubitsIn().size(); ++i) { + newQubitsOut.push_back(newQubitsOutMap[i]); + } + + rewriter.replaceOp(op, newQubitsOut); + return success(); + } +}; + +} // namespace + size_t BarrierOp::getNumQubits() { return getNumTargets(); } size_t BarrierOp::getNumTargets() { return getQubitsIn().size(); } @@ -98,3 +152,8 @@ void BarrierOp::build(OpBuilder& odsBuilder, OperationState& odsState, } build(odsBuilder, odsState, resultTypes, qubits); } + +void BarrierOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 5953bd4204..27c7783328 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -544,13 +544,6 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMINUSYY, xx_minus_yy, theta, beta) #undef DEFINE_TWO_TARGET_TWO_PARAMETER -// BarrierOp - -QIRProgramBuilder& QIRProgramBuilder::barrier(const ValueRange qubits) { - createCallOp({}, {}, qubits, QIR_BARRIER); - return *this; -} - //===----------------------------------------------------------------------===// // Finalization //===----------------------------------------------------------------------===// diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 21450df000..8a26492092 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -3172,61 +3172,81 @@ TEST_F(CompilerPipelineTest, Barrier1) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); qc.barrier(0); + qc.barrier(0); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); - b.barrier(reg[0]); + const auto q = reg[0]; + b.barrier(q); + b.barrier(q); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + auto qubitsOut = b.barrier(reg[0]); + b.barrier(qubitsOut[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.barrier(reg[0]); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(1); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); b.barrier(reg[0]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = emptyQIR.get(), }); } TEST_F(CompilerPipelineTest, Barrier2) { qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); + qc.addQubitRegister(3, "q"); qc.barrier({0, 1}); + qc.barrier({1, 2}); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.barrier({reg[0], reg[1]}); + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + const auto q2 = reg[2]; + b.barrier({q0, q1}); + b.barrier({q1, q2}); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + auto qubitsOut = b.barrier({reg[0], reg[1]}); + b.barrier({qubitsOut[1], reg[2]}); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); b.barrier({reg[0], reg[1]}); + b.barrier(reg[2]); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(2); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); b.barrier({reg[0], reg[1]}); + b.barrier(reg[2]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = emptyQIR.get(), }); } From 984212d0a5130843dffb30f7d966ff40c10fe355 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:20:52 +0100 Subject: [PATCH 279/419] Add Bell test --- .../pipeline/test_compiler_pipeline.cpp | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 8a26492092..18dcd8529c 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -3250,4 +3250,43 @@ TEST_F(CompilerPipelineTest, Barrier2) { }); } +TEST_F(CompilerPipelineTest, Bell) { + qc::QuantumComputation qc; + qc.addQubitRegister(2, "q"); + qc.h(0); + qc.cx(0, 1); + + const auto module = importQuantumCircuit(qc); + ASSERT_TRUE(module); + ASSERT_TRUE(runPipeline(module.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.h(q0); + b.cx(q0, q1); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.h(q0); + b.cx(q0, q1); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.h(reg[0]); + b.cx(reg[0], reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + } // namespace From d01d26024e4e637523dbf2ab5cd5c90f3d46b5b7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:04:49 +0100 Subject: [PATCH 280/419] Add current version of cgphase test --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 4 +-- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 4 +-- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 4 +-- .../pipeline/test_compiler_pipeline.cpp | 30 +++++++++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index d5a9555f59..8d3d4fad43 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -273,7 +273,7 @@ class FluxProgramBuilder final : public OpBuilder { * %q_out = flux.ctrl(%q_in) { \ * flux.OP_NAME(%PARAM) \ * flux.yield \ - * } : ({!flux.qubit}, {}) -> ({!flux.qubit}, {}) \ + * } : ({!flux.qubit}) -> ({!flux.qubit}) \ */ \ Value c##OP_NAME(const std::variant&(PARAM), Value control); \ /** \ @@ -291,7 +291,7 @@ class FluxProgramBuilder final : public OpBuilder { * %q0_out, %q1_out = flux.ctrl(%q0_in, %q1_in) { \ * flux.OP_NAME(%PARAM) \ * flux.yield \ - * } : ({!flux.qubit, !flux.qubit}, {}) -> ({!flux.qubit, !flux.qubit}, {}) \ + * } : ({!flux.qubit, !flux.qubit}) -> ({!flux.qubit, !flux.qubit}) \ */ \ ValueRange mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls); diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index cbfb6d05ef..8f77e9a3a3 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -1064,9 +1064,9 @@ def CtrlOp : FluxOp<"ctrl", traits = let assemblyFormat = [{ `(` $controls_in `)` $targets_in $body attr-dict `:` - `(` `{` type($controls_in) `}` `,` `{` type($targets_in) `}` `)` + `(` `{` type($controls_in) `}` ( `,` `{` type($targets_in)^ `}` )? `)` `->` - `(` `{` type($controls_out) `}` `,` `{` type($targets_out) `}` `)` + `(` `{` type($controls_out) `}` ( `,` `{` type($targets_out)^ `}` )? `)` }]; let extraClassDeclaration = [{ diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 6a5d7e17a5..a39e7bfbdf 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -129,7 +129,7 @@ LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, // Update the state if (inCtrlOp != 0) { state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({}); + const SmallVector targetsOut; state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -1054,7 +1054,7 @@ struct ConvertQuartzBarrierOp final * %controls_out, %targets_out = flux.ctrl(%q0_in) %q1_in { * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit * flux.yield %q1_res - * } : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) * ``` */ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 18dcd8529c..006c028bf5 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1308,6 +1308,36 @@ TEST_F(CompilerPipelineTest, GPhase) { }); } +TEST_F(CompilerPipelineTest, CGPhase) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.cgphase(1.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.cgphase(1.0, reg[0]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.cgphase(1.0, reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.cgphase(1.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, Id) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From 319c19d312bf86ebe29a1aa8acc7d14c4839daa6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:09:00 +0100 Subject: [PATCH 281/419] Add test for already implemented U2 canonicalization --- .../pipeline/test_compiler_pipeline.cpp | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 006c028bf5..e00a1c5975 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -2427,6 +2428,44 @@ TEST_F(CompilerPipelineTest, U2) { }); } +TEST_F(CompilerPipelineTest, U2ToRY) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, 0.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, 0.0, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, 0.0, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(std::numbers::pi / 2.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(std::numbers::pi / 2.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(std::numbers::pi / 2.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, U) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From e3604204b18b9d15596ef8a70229cb0a93c58ef2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:18:08 +0100 Subject: [PATCH 282/419] Add remaining U2 canonicalizations --- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 66 ++++++++++++++-- .../pipeline/test_compiler_pipeline.cpp | 76 +++++++++++++++++++ 2 files changed, 137 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index dd76baedb0..68d7e74270 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -26,9 +26,9 @@ using namespace mlir::flux; namespace { /** - * @brief Replace trivial U2 operations with RY operations. + * @brief Replace U2(0, pi) with H. */ -struct ReplaceTrivialU2 final : OpRewritePattern { +struct ReplaceU2WithH final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(U2Op op, @@ -41,11 +41,39 @@ struct ReplaceTrivialU2 final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (phiValue != 0.0 || lambdaValue != 0.0) { + if (phiValue != 0.0 || lambdaValue != std::numbers::pi) { + return failure(); + } + + auto hOp = rewriter.create(op.getLoc(), op.getQubitIn()); + rewriter.replaceOp(op, hOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U2(-pi / 2, pi / 2) with RX(pi / 2). + */ +struct ReplaceU2WithRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(U2Op op, + PatternRewriter& rewriter) const override { + const auto phi = U2Op::getStaticParameter(op.getPhi()); + const auto lambda = U2Op::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { return failure(); } - auto rxOp = rewriter.create(op.getLoc(), op.getQubitIn(), + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (phiValue != -std::numbers::pi / 2.0 || + lambdaValue != std::numbers::pi / 2.0) { + return failure(); + } + + auto rxOp = rewriter.create(op.getLoc(), op.getQubitIn(), std::numbers::pi / 2.0); rewriter.replaceOp(op, rxOp.getResult()); @@ -53,6 +81,34 @@ struct ReplaceTrivialU2 final : OpRewritePattern { } }; +/** + * @brief Replace U2(0, 0) with RY(pi / 2). + */ +struct ReplaceU2WithRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(U2Op op, + PatternRewriter& rewriter) const override { + const auto phi = U2Op::getStaticParameter(op.getPhi()); + const auto lambda = U2Op::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (phiValue != 0.0 || lambdaValue != 0.0) { + return failure(); + } + + auto ryOp = rewriter.create(op.getLoc(), op.getQubitIn(), + std::numbers::pi / 2.0); + rewriter.replaceOp(op, ryOp.getResult()); + + return success(); + } +}; + } // namespace void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, @@ -80,5 +136,5 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, void U2Op::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index e00a1c5975..4b22f8f04d 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2428,6 +2428,82 @@ TEST_F(CompilerPipelineTest, U2) { }); } +TEST_F(CompilerPipelineTest, U2ToH) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, std::numbers::pi, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, std::numbers::pi, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(0.0, std::numbers::pi, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.h(reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.h(reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.h(reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, U2ToRX) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(std::numbers::pi / 2.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(std::numbers::pi / 2.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rx(std::numbers::pi / 2.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, U2ToRY) { auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); From 280d3a6593028be4339e8423c4b5fd3d667829d2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:25:26 +0100 Subject: [PATCH 283/419] Add R canonicalizations --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 + .../Flux/IR/Operations/StandardGates/ROp.cpp | 65 ++++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 76 +++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 8f77e9a3a3..16eb1da7c3 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -658,6 +658,8 @@ def ROp : FluxOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let builders = [ OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi)> ]; + + let hasCanonicalizer = 1; } def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index e2e93d7090..a5a604968e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -13,12 +13,72 @@ #include #include #include +#include #include +#include +#include +#include #include using namespace mlir; using namespace mlir::flux; +namespace { + +/** + * @brief Replace R(theta, 0) with RX(theta). + */ +struct ReplaceRWithRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ROp op, + PatternRewriter& rewriter) const override { + const auto phi = ROp::getStaticParameter(op.getPhi()); + if (!phi) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + if (phiValue != 0.0) { + return failure(); + } + + auto rxOp = + rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.replaceOp(op, rxOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace R(theta, pi / 2) with RY(theta). + */ +struct ReplaceRWithRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(ROp op, + PatternRewriter& rewriter) const override { + const auto phi = ROp::getStaticParameter(op.getPhi()); + if (!phi) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + if (phiValue != std::numbers::pi / 2.0) { + return failure(); + } + + auto ryOp = + rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.replaceOp(op, ryOp.getResult()); + + return success(); + } +}; + +} // namespace + void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi) { @@ -40,3 +100,8 @@ void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); } + +void ROp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 4b22f8f04d..d57a8f98fc 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2335,6 +2335,82 @@ TEST_F(CompilerPipelineTest, R) { }); } +TEST_F(CompilerPipelineTest, RToRX) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.0, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, 0.0, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rx(1.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, RToRY) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, std::numbers::pi / 2, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, std::numbers::pi / 2, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.r(1.0, std::numbers::pi / 2, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(1.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, CR) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); From 3c0baaa6c4f4d9b1ca7489ff627b00789c038339 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:33:17 +0100 Subject: [PATCH 284/419] Add U canonicalizations --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 + .../Flux/IR/Operations/StandardGates/UOp.cpp | 97 +++++++++++++++ .../pipeline/test_compiler_pipeline.cpp | 114 ++++++++++++++++++ 3 files changed, 213 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 16eb1da7c3..cf7c1354c4 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -715,6 +715,8 @@ def UOp : FluxOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let builders = [ OpBuilder<(ins "Value":$qubit_in, "const std::variant&":$theta, "const std::variant&":$phi, "const std::variant&":$lambda)> ]; + + let hasCanonicalizer = 1; } def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index e5729fba08..2aa3e65b33 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -15,11 +15,103 @@ #include #include #include +#include +#include +#include #include using namespace mlir; using namespace mlir::flux; +namespace { + +/** + * @brief Replace U(0, 0, lambda) with P(lambda). + */ +struct ReplaceUWithP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UOp op, + PatternRewriter& rewriter) const override { + const auto theta = UOp::getStaticParameter(op.getTheta()); + const auto phi = UOp::getStaticParameter(op.getPhi()); + if (!theta || !phi) { + return failure(); + } + + const auto thetaValue = theta.getValueAsDouble(); + const auto phiValue = phi.getValueAsDouble(); + if (thetaValue != 0.0 || phiValue != 0.0) { + return failure(); + } + + auto pOp = + rewriter.create(op.getLoc(), op.getQubitIn(), op.getLambda()); + rewriter.replaceOp(op, pOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U(theta, -pi / 2, pi / 2) with RX(theta). + */ +struct ReplaceUWithRX final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UOp op, + PatternRewriter& rewriter) const override { + const auto phi = UOp::getStaticParameter(op.getPhi()); + const auto lambda = UOp::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (phiValue != -std::numbers::pi / 2.0 || + lambdaValue != std::numbers::pi / 2.0) { + return failure(); + } + + auto rxOp = + rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.replaceOp(op, rxOp.getResult()); + + return success(); + } +}; + +/** + * @brief Replace U(theta, 0, 0) with RY(theta). + */ +struct ReplaceUWithRY final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UOp op, + PatternRewriter& rewriter) const override { + const auto phi = UOp::getStaticParameter(op.getPhi()); + const auto lambda = UOp::getStaticParameter(op.getLambda()); + if (!phi || !lambda) { + return failure(); + } + + const auto phiValue = phi.getValueAsDouble(); + const auto lambdaValue = lambda.getValueAsDouble(); + if (phiValue != 0.0 || lambdaValue != 0.0) { + return failure(); + } + + auto ryOp = + rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.replaceOp(op, ryOp.getResult()); + + return success(); + } +}; + +} // namespace + void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi, @@ -51,3 +143,8 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); } + +void UOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index d57a8f98fc..073dd28613 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2649,6 +2649,120 @@ TEST_F(CompilerPipelineTest, U) { }); } +TEST_F(CompilerPipelineTest, UToP) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(0.0, 0.0, 1.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(0.0, 0.0, 1.0, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(0.0, 0.0, 1.0, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.p(1.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, UToRX) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.rx(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.rx(1.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, UToRY) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.0, 0.0, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.0, 0.0, reg[0]); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.u(1.0, 0.0, 0.0, reg[0]); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.ry(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.ry(1.0, reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + TEST_F(CompilerPipelineTest, CU) { qc::QuantumComputation qc; qc.addQubitRegister(2, "q"); From a640013754d8ad9b4212547263c0c7fa3117cae2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:39:51 +0100 Subject: [PATCH 285/419] Look forward instead of backward for barrier canonicalization --- .../Flux/IR/Operations/StandardGates/BarrierOp.cpp | 4 +++- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index 9a03b8ec51..9171c86d36 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -43,7 +44,8 @@ struct MergeSubsequentBarrier final : OpRewritePattern { SmallVector indicesToFill; for (size_t i = 0; i < qubitsIn.size(); ++i) { - if (qubitsIn[i].getDefiningOp()) { + if (llvm::isa( + *op.getOutputForInput(qubitsIn[i]).getUsers().begin())) { anythingToMerge = true; newQubitsOutMap[i] = qubitsIn[i]; } else { diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 073dd28613..f179b2e066 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -3567,13 +3567,13 @@ TEST_F(CompilerPipelineTest, Barrier2) { }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); - b.barrier({reg[0], reg[1]}); - b.barrier(reg[2]); + b.barrier(reg[0]); + b.barrier({reg[1], reg[2]}); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); - b.barrier({reg[0], reg[1]}); - b.barrier(reg[2]); + b.barrier(reg[0]); + b.barrier({reg[1], reg[2]}); }); verifyAllStages({ From 93b53c5399dc8bcad0ec9a96e0417e881f1170aa Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:03:17 +0100 Subject: [PATCH 286/419] Inline controlled GPhaseOps in Flux --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 32 ++++++++- .../pipeline/test_compiler_pipeline.cpp | 67 ++++++++++++++++--- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 2950e43cd6..3f441da921 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -73,6 +73,34 @@ struct RemoveTrivialCtrl final : OpRewritePattern { } }; +/** + * @brief Inline controlled GPhase operations. + */ +struct CtrlInlineGPhase final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + auto gPhaseOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!gPhaseOp) { + return failure(); + } + + SmallVector newOperands; + newOperands.reserve(op.getNumPosControls()); + for (size_t i = 0; i < op.getNumPosControls(); ++i) { + auto pOp = rewriter.create(op.getLoc(), op.getInputPosControl(i), + gPhaseOp.getTheta()); + newOperands.push_back(pOp.getQubitOut()); + } + + rewriter.replaceOp(op, newOperands); + + return success(); + } +}; + /** * @brief Inline controlled identity operations. */ @@ -271,5 +299,7 @@ LogicalResult CtrlOp::verify() { void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results + .add( + context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index f179b2e066..6c60b702ef 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1317,25 +1317,74 @@ TEST_F(CompilerPipelineTest, CGPhase) { ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.cgphase(1.0, reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.cgphase(1.0, reg[0]); }); - const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.p(1.0, reg[0]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); - b.cgphase(1.0, reg[0]); + b.p(1.0, reg[0]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), - .qirConversion = qir.get(), + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), + }); +} + +TEST_F(CompilerPipelineTest, MCGPhase) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.mcgphase(1.0, {reg[0], reg[1]}); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.mcgphase(1.0, {reg[0], reg[1]}); + }); + const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.mcgphase(1.0, {reg[0], reg[1]}); + }); + const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.p(1.0, reg[0]); + b.p(1.0, reg[1]); + }); + const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(2, "q"); + b.p(1.0, reg[0]); + b.p(1.0, reg[1]); + }); + const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.p(1.0, reg[0]); + b.p(1.0, reg[1]); + }); + + verifyAllStages({ + .quartzImport = quartzInit.get(), + .fluxConversion = fluxInit.get(), + .optimization = fluxOpt.get(), + .quartzConversion = quartzOpt.get(), + .qirConversion = qirOpt.get(), }); } From 248b4113168e0b82665162b5b9417b0a75764f44 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:12:25 +0100 Subject: [PATCH 287/419] Drop support for controlled global phases in QIR --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 73 ++++--------------- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 4 +- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 60 +++++++-------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 28 ++----- 4 files changed, 50 insertions(+), 115 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index a05eb4b6dd..abde16badc 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -263,64 +263,23 @@ class QIRProgramBuilder { // Unitary Operations //===--------------------------------------------------------------------===// - // ZeroTargetOneParameter + // GPhaseOp -#define DECLARE_ZERO_TARGET_ONE_PARAMETER(OP_NAME, QIR_NAME, PARAM) \ - /** \ - * @brief Apply a QIR QIR_NAME operation \ - * \ - * @param PARAM Rotation angle in radians \ - * @return Reference to this builder for method chaining \ - * \ - * @par Example: \ - * ```c++ \ - * builder.OP_NAME(PARAM); \ - * ``` \ - * ```mlir \ - * llvm.call @__quantum__qis__##QIR_NAME##__body(%PARAM) : (f64) -> () \ - * ``` \ - */ \ - QIRProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ - /** \ - * @brief Apply a controlled QIR_NAME operation \ - * \ - * @param PARAM Rotation angle in radians \ - * @param control Control qubit \ - * @return Reference to this builder for method chaining \ - * \ - * @par Example: \ - * ```c++ \ - * builder.c##OP_NAME(PARAM, q); \ - * ``` \ - * ```mlir \ - * llvm.call @__quantum__qis__c##QIR_NAME##__body(%q, %PARAM) : (!llvm.ptr, \ - * f64) -> () \ - * ``` \ - */ \ - QIRProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ - Value control); \ - /** \ - * @brief Apply a multi-controlled QIR_NAME operation \ - * \ - * @param PARAM Rotation angle in radians \ - * @param controls Control qubits \ - * @return Reference to this builder for method chaining \ - * \ - * @par Example: \ - * ```c++ \ - * builder.mc##OP_NAME(PARAM, {q0, q1}); \ - * ``` \ - * ```mlir \ - * llvm.call @__quantum__qis__cc##QIR_NAME##__body(%q0, %q1, %PARAM) : \ - * (!llvm.ptr, !llvm.ptr, f64) -> () \ - * ``` \ - */ \ - QIRProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ - ValueRange controls); - - DECLARE_ZERO_TARGET_ONE_PARAMETER(gphase, gphase, theta) - -#undef DECLARE_ZERO_TARGET_ONE_PARAMETER + /** + * @brief Apply a QIR gphase operation + * + * @param theta Rotation angle in radians + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.gphase(theta); + * ``` + * ```mlir + * llvm.call @__quantum__qis__gphase__body(%theta) : (f64) -> () + * ``` + */ + QIRProgramBuilder& gphase(const std::variant& theta); // OneTargetZeroParameter diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 91ed732818..d0eafd6326 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -26,6 +26,8 @@ static constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; +static constexpr auto QIR_GPHASE = "__quantum__qis__gphase__body"; + #define ADD_STANDARD_GATE(NAME_BIG, NAME_SMALL) \ static constexpr auto QIR_##NAME_BIG = \ "__quantum__qis__" #NAME_SMALL "__body"; \ @@ -36,7 +38,6 @@ static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; static constexpr auto QIR_CCC##NAME_BIG = \ "__quantum__qis__ccc" #NAME_SMALL "__body"; -ADD_STANDARD_GATE(GPHASE, gphase) ADD_STANDARD_GATE(I, i) ADD_STANDARD_GATE(X, x) ADD_STANDARD_GATE(Y, y) @@ -94,7 +95,6 @@ ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) } \ } -DEFINE_GETTER(GPHASE) DEFINE_GETTER(I) DEFINE_GETTER(X) DEFINE_GETTER(Y) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 07d0e6874e..3059d472eb 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -489,42 +489,34 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { } }; -// ZeroTargetOneParameter +// GPhaseOp -#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ - QIR_NAME, PARAM) \ - /** \ - * @brief Converts quartz.OP_NAME_SMALL to QIR QIR_NAME \ - * \ - * @par Example: \ - * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM) \ - * ``` \ - * is converted to \ - * ```mlir \ - * llvm.call @__quantum__qis__QIR_NAME__body(%PARAM) : (f64) -> () \ - * ``` \ - */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ - : StatefulOpConversionPattern { \ - using StatefulOpConversionPattern::StatefulOpConversionPattern; \ - \ - LogicalResult \ - matchAndRewrite(OP_CLASS op, OpAdaptor adaptor, \ - ConversionPatternRewriter& rewriter) const override { \ - auto& state = getState(); \ - const auto inCtrlOp = state.inCtrlOp; \ - const size_t numCtrls = \ - inCtrlOp != 0 ? state.posCtrls[inCtrlOp].size() : 0; \ - const auto fnName = getFnName##OP_NAME_BIG(numCtrls); \ - return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), \ - state, fnName, 0, 1); \ - } \ - }; - -DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, GPHASE, gphase, gphase, theta) +/** + * @brief Converts quartz.gphase to QIR gphase + * + * @par Example: + * ```mlir + * quartz.gphase(%theta) + * ``` + * is converted to + * ```mlir + * llvm.call @__quantum__qis__gphase__body(%theta) : (f64) -> () + * ``` + */ +struct ConvertQuartzGPhaseOpQIR final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; -#undef DEFINE_ZERO_TARGET_ONE_PARAMETER + LogicalResult + matchAndRewrite(GPhaseOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + if (state.inCtrlOp != 0) { + return failure(); + } + return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), state, + QIR_GPHASE, 0, 1); + } +}; // OneTargetZeroParameter diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 27c7783328..9843f48c10 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -303,29 +303,13 @@ void QIRProgramBuilder::createCallOp( builder.create(loc, fnDecl, operands); } -// ZeroTargetOneParameter +// GPhaseOp -#define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ - QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ - const std::variant&(PARAM)) { \ - createCallOp({PARAM}, {}, {}, QIR_##OP_NAME_BIG); \ - return *this; \ - } \ - QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ - const std::variant&(PARAM), const Value control) { \ - createCallOp({PARAM}, {control}, {}, QIR_C##OP_NAME_BIG); \ - return *this; \ - } \ - QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ - const std::variant&(PARAM), const ValueRange controls) { \ - createCallOp({PARAM}, controls, {}, \ - getFnName##OP_NAME_BIG(controls.size())); \ - return *this; \ - } - -DEFINE_ZERO_TARGET_ONE_PARAMETER(GPHASE, gphase, theta) - -#undef DEFINE_ZERO_TARGET_ONE_PARAMETER +QIRProgramBuilder& +QIRProgramBuilder::gphase(const std::variant& theta) { + createCallOp({theta}, {}, {}, QIR_GPHASE); + return *this; +} // OneTargetZeroParameter From f7187042ae056227edd1df140c6821a4e7957c16 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:19:42 +0100 Subject: [PATCH 288/419] Inline controlled GPhaseOps in Quartz --- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 27 ++++++++++- .../pipeline/test_compiler_pipeline.cpp | 48 +++++++------------ 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index aa3e5a4066..772ea54827 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -69,6 +69,31 @@ struct RemoveTrivialCtrl final : OpRewritePattern { } }; +/** + * @brief Inline controlled GPhase operations. + */ +struct CtrlInlineGPhase final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + auto gPhaseOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!gPhaseOp) { + return failure(); + } + + for (size_t i = 0; i < op.getNumControls(); ++i) { + auto pOp = rewriter.create(op.getLoc(), op.getPosControl(i), + gPhaseOp.getTheta()); + } + + rewriter.eraseOp(op); + + return success(); + } +}; + } // namespace UnitaryOpInterface CtrlOp::getBodyUnitary() { @@ -178,5 +203,5 @@ LogicalResult CtrlOp::verify() { void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 6c60b702ef..6b4418a1b5 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1317,33 +1317,25 @@ TEST_F(CompilerPipelineTest, CGPhase) { ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - b.cgphase(1.0, reg[0]); - }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - b.cgphase(1.0, reg[0]); - }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.p(1.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.p(1.0, reg[0]); }); - const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(1); b.p(1.0, reg[0]); }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), - .qirConversion = qirOpt.get(), + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), }); } @@ -1355,36 +1347,28 @@ TEST_F(CompilerPipelineTest, MCGPhase) { ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.mcgphase(1.0, {reg[0], reg[1]}); - }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(2, "q"); - b.mcgphase(1.0, {reg[0], reg[1]}); - }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.p(1.0, reg[0]); b.p(1.0, reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.p(1.0, reg[0]); b.p(1.0, reg[1]); }); - const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.p(1.0, reg[0]); b.p(1.0, reg[1]); }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), - .qirConversion = qirOpt.get(), + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), }); } From 29e5db231293653cccea6adde3a96a949375953a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:33:24 +0100 Subject: [PATCH 289/419] Fix linter errors --- mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 772ea54827..d70f9bdb8b 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -84,8 +84,8 @@ struct CtrlInlineGPhase final : OpRewritePattern { } for (size_t i = 0; i < op.getNumControls(); ++i) { - auto pOp = rewriter.create(op.getLoc(), op.getPosControl(i), - gPhaseOp.getTheta()); + rewriter.create(op.getLoc(), op.getPosControl(i), + gPhaseOp.getTheta()); } rewriter.eraseOp(op); From 50933e275371a34552f81b05ebdf00e553f1eeae Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:34:12 +0100 Subject: [PATCH 290/419] Ignore slicing error for now --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 6b4418a1b5..a5ff524a13 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -136,6 +136,7 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, if (!rhsConst) { return false; } + // NOLINTNEXTLINE(cppcoreguidelines-slicing) if (lhsConst.getValue() != rhsConst.getValue()) { return false; } From 87bdd11ff20f26a3800493f56660540b75c0098b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:04:15 +0100 Subject: [PATCH 291/419] Remove everything related to memref --- mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td | 1 - mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 1 - mlir/lib/Dialect/Flux/Builder/CMakeLists.txt | 1 - mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 1 - mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt | 1 - mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 1 - mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt | 1 - mlir/tools/mqt-cc/mqt-cc.cpp | 2 -- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 8 +++----- 9 files changed, 3 insertions(+), 14 deletions(-) diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td index 506a79584c..7a93ea0482 100644 --- a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td +++ b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td @@ -44,7 +44,6 @@ def QuartzToQIR : Pass<"quartz-to-qir"> { // Define dependent dialects let dependentDialects = [ - "mlir::memref::MemRefDialect", "mlir::LLVM::LLVMDialect", "mlir::QuartzDialect", ]; diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 3059d472eb..1637f922a3 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt b/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt index 352d0f0da0..6be18bdc72 100644 --- a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt @@ -13,7 +13,6 @@ add_mlir_library( PUBLIC MLIRArithDialect MLIRFuncDialect - MLIRMemRefDialect MLIRSCFDialect MLIRFluxDialect) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 7d17248ad9..8f467712b5 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt b/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt index 619920a027..7ec284a994 100644 --- a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt @@ -13,7 +13,6 @@ add_mlir_library( PUBLIC MLIRArithDialect MLIRFuncDialect - MLIRMemRefDialect MLIRSCFDialect MLIRQuartzDialect) diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 5322763be9..9615c630d4 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt b/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt index d1718b8885..70ef2a68bb 100644 --- a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt @@ -14,7 +14,6 @@ add_mlir_library( LINK_LIBS MLIRArithDialect MLIRFuncDialect - MLIRMemRefDialect MLIRSCFDialect MLIRQuartzDialect MQT::CoreIR) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index f363ed4d47..72d8b5bfac 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -112,7 +111,6 @@ int main(int argc, char** argv) { registry.insert(); registry.insert(); registry.insert(); - registry.insert(); registry.insert(); registry.insert(); diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index a5ff524a13..7af618410b 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -544,10 +543,9 @@ class CompilerPipelineTest : public testing::Test { void SetUp() override { // Register all dialects needed for the full compilation pipeline DialectRegistry registry; - registry - .insert(); + registry.insert(); context = std::make_unique(); context->appendDialectRegistry(registry); From c892f421905ec63d1c560ec33e76046a1b578500 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:06:15 +0100 Subject: [PATCH 292/419] Fix docstrings and descriptions --- mlir/include/mlir/Compiler/CompilerPipeline.h | 2 +- .../mlir/Dialect/Flux/IR/FluxInterfaces.td | 1 - mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 2 +- .../Dialect/Quartz/IR/QuartzInterfaces.td | 1 - .../mlir/Dialect/Quartz/IR/QuartzOps.td | 54 +++++++++---------- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 21 ++++---- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 6 +-- 7 files changed, 42 insertions(+), 45 deletions(-) diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index d86bd1ea46..fb28411ea4 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -100,7 +100,7 @@ class QuantumCompilerPipeline { * Automatically configures the PassManager with: * - Timing statistics if enableTiming is true * - Pass statistics if enableStatistics is true - * - IR printing options if printIRAfterAll or printIRAfterFailure is true + * - IR printing after each stage if printIRAfterAllStages is true * * @param module The MLIR module to compile * @param record Optional pointer to record intermediate states diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td index 692991fb9f..2559ca41d6 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td @@ -100,7 +100,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the i-th parameter.", "Value", "getParameter", (ins "size_t":$i) >, - /// TODO: Add convenience methods as necessary // Convenience methods InterfaceMethod< diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index cf7c1354c4..5dc52bd8c0 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -736,7 +736,7 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; let extraClassDeclaration = [{ - static StringRef getBaseSymbol() { return "x"; } + static StringRef getBaseSymbol() { return "swap"; } }]; let hasCanonicalizer = 1; diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td index 76f80d409b..298a3d0fab 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td @@ -75,7 +75,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the i-th parameter.", "Value", "getParameter", (ins "size_t":$i) >, - /// TODO: Add convenience methods as necessary // Convenience methods InterfaceMethod< diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 350470c77f..6be240e165 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -298,7 +298,7 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { Example: ```mlir - quartz.x %q : !flux.qubit + quartz.x %q : !quartz.qubit ``` }]; @@ -317,7 +317,7 @@ def YOp : QuartzOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { Example: ```mlir - quartz.y %q : !flux.qubit + quartz.y %q : !quartz.qubit ``` }]; @@ -336,7 +336,7 @@ def ZOp : QuartzOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { Example: ```mlir - quartz.z %q : !flux.qubit + quartz.z %q : !quartz.qubit ``` }]; @@ -355,7 +355,7 @@ def HOp : QuartzOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { Example: ```mlir - quartz.h %q : !flux.qubit + quartz.h %q : !quartz.qubit ``` }]; @@ -374,7 +374,7 @@ def SOp : QuartzOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { Example: ```mlir - quartz.s %q : !flux.qubit + quartz.s %q : !quartz.qubit ``` }]; @@ -393,7 +393,7 @@ def SdgOp : QuartzOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter Example: ```mlir - quartz.sdg %q : !flux.qubit + quartz.sdg %q : !quartz.qubit ``` }]; @@ -412,7 +412,7 @@ def TOp : QuartzOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { Example: ```mlir - quartz.t %q : !flux.qubit + quartz.t %q : !quartz.qubit ``` }]; @@ -431,7 +431,7 @@ def TdgOp : QuartzOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter Example: ```mlir - quartz.tdg %q : !flux.qubit + quartz.tdg %q : !quartz.qubit ``` }]; @@ -450,7 +450,7 @@ def SXOp : QuartzOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> Example: ```mlir - quartz.sx %q : !flux.qubit + quartz.sx %q : !quartz.qubit ``` }]; @@ -469,7 +469,7 @@ def SXdgOp : QuartzOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParamet Example: ```mlir - quartz.sxdg %q : !flux.qubit + quartz.sxdg %q : !quartz.qubit ``` }]; @@ -488,7 +488,7 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> Example: ```mlir - quartz.rx(%theta) %q : !flux.qubit + quartz.rx(%theta) %q : !quartz.qubit ``` }]; @@ -512,7 +512,7 @@ def RYOp : QuartzOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> Example: ```mlir - quartz.ry(%theta) %q : !flux.qubit + quartz.ry(%theta) %q : !quartz.qubit ``` }]; @@ -536,7 +536,7 @@ def RZOp : QuartzOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> Example: ```mlir - quartz.rz(%theta) %q : !flux.qubit + quartz.rz(%theta) %q : !quartz.qubit ``` }]; @@ -560,7 +560,7 @@ def POp : QuartzOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { Example: ```mlir - quartz.p(%theta) %q : !flux.qubit + quartz.p(%theta) %q : !quartz.qubit ``` }]; @@ -584,7 +584,7 @@ def ROp : QuartzOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { Example: ```mlir - quartz.r(%theta, %phi) %q : !flux.qubit + quartz.r(%theta, %phi) %q : !quartz.qubit ``` }]; @@ -609,7 +609,7 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> Example: ```mlir - quartz.u2(%phi, %lambda) %q : !flux.qubit + quartz.u2(%phi, %lambda) %q : !quartz.qubit ``` }]; @@ -634,7 +634,7 @@ def UOp : QuartzOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> Example: ```mlir - quartz.u(%theta, %phi, %lambda) %q : !flux.qubit + quartz.u(%theta, %phi, %lambda) %q : !quartz.qubit ``` }]; @@ -660,7 +660,7 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParamet Example: ```mlir - quartz.swap %q0, %q1 : !flux.qubit, !flux.qubit + quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -680,7 +680,7 @@ def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParam Example: ```mlir - quartz.iswap %q0, %q1 : !flux.qubit, !flux.qubit + quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -700,7 +700,7 @@ def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter Example: ```mlir - quartz.dcx %q0, %q1 : !flux.qubit, !flux.qubit + quartz.dcx %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -720,7 +720,7 @@ def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter Example: ```mlir - quartz.ecr %q0, %q1 : !flux.qubit, !flux.qubit + quartz.ecr %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -740,7 +740,7 @@ def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter] Example: ```mlir - quartz.rxx(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + quartz.rxx(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -765,7 +765,7 @@ def RYYOp : QuartzOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter] Example: ```mlir - quartz.ryy(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + quartz.ryy(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -790,7 +790,7 @@ def RZXOp : QuartzOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter] Example: ```mlir - quartz.rzx(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + quartz.rzx(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -815,7 +815,7 @@ def RZZOp : QuartzOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter] Example: ```mlir - quartz.rzz(%theta) %q0, %q1 : !flux.qubit, !flux.qubit + quartz.rzz(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -840,7 +840,7 @@ def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetT Example: ```mlir - quartz.xx_plus_yy(%theta, %beta) %q0, %q1 : !flux.qubit, !flux.qubit + quartz.xx_plus_yy(%theta, %beta) %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; @@ -866,7 +866,7 @@ def XXMinusYYOp : QuartzOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTarge Example: ```mlir - quartz.xx_minus_yy(%theta, %beta) %q0, %q1 : !flux.qubit, !flux.qubit + quartz.xx_minus_yy(%theta, %beta) %q0, %q1 : !quartz.qubit, !quartz.qubit ``` }]; diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 8e8f448bf7..c2ffb91c86 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -59,7 +59,7 @@ convertZeroTargetOneParameter(FluxOpType& op, * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -87,7 +87,7 @@ convertOneTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -115,7 +115,7 @@ convertOneTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -140,11 +140,11 @@ convertOneTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, } /** - * @brief Converts a two-target, zero-parameter Flux operation to Quartz + * @brief Converts a one-target, three-parameter Flux operation to Quartz * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -173,7 +173,7 @@ convertOneTargetThreeParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -202,7 +202,7 @@ convertTwoTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -232,7 +232,7 @@ convertTwoTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, * * @tparam QuartzOpType The operation type of the Quartz operation * @tparam FluxOpType The operation type of the Flux operation - * @param FluxOpAdaptorType The OpAdaptor type of the Flux operation + * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation * @param op The Flux operation instance to convert * @param adaptor The OpAdaptor of the Flux operation * @param rewriter The pattern rewriter @@ -761,7 +761,7 @@ struct ConvertFluxBarrierOp final : OpConversionPattern { }; /** - * @brief Converts quartz.ctrl to flux.ctrl + * @brief Converts flux.ctrl to quartz.ctrl * * @par Example: * ```mlir @@ -773,8 +773,7 @@ struct ConvertFluxBarrierOp final : OpConversionPattern { * is converted to * ```mlir * quartz.ctrl(%q0) { - * quartz.x %q1 - * quartz.yield + * quartz.x %q1 : !quartz.qubit * } * ``` */ diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index a39e7bfbdf..1742b713d9 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -155,7 +155,7 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the atest Flux qubit + // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); Value fluxQubit = nullptr; if (inCtrlOp == 0) { @@ -1003,8 +1003,8 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) * ``` * is converted to * ```mlir - * %q0_out, %q1_out = flux.barrier %q0_in, %q1_in : !quartz.qubit, !quartz.qubit - * -> !quartz.qubit, !quartz.qubit + * %q0_out, %q1_out = flux.barrier %q0_in, %q1_in : !flux.qubit, !flux.qubit -> + * !flux.qubit, !flux.qubit * ``` */ struct ConvertQuartzBarrierOp final From 0d8c771bf9ca0ce7d824cc44eba60a0a2ddae12d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:20:28 +0100 Subject: [PATCH 293/419] Trivial code simplifications --- mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp | 4 +--- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 2 +- .../Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp | 4 ++-- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp | 4 ++-- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp | 6 +++--- .../Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp | 4 ++-- .../Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp | 4 ++-- .../Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp | 2 +- .../Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp | 2 +- mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp | 4 ++-- .../lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp | 2 +- .../Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp | 2 +- .../lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp | 2 +- .../Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp | 2 +- .../lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp | 2 +- .../Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp | 2 +- .../Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp | 2 +- .../lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp | 4 ++-- mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp | 6 +++--- .../Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp | 4 ++-- .../Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp | 4 ++-- 30 files changed, 42 insertions(+), 44 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index c2ffb91c86..5bfad96257 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -25,8 +25,6 @@ #include #include -namespace {} - namespace mlir { using namespace flux; using namespace quartz; @@ -907,7 +905,7 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { if (failed(applyPartialConversion(module, target, std::move(patterns)))) { signalPassFailure(); } - }; + } }; } // namespace mlir diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 1742b713d9..3cf6f95734 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -1229,7 +1229,7 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { if (failed(applyPartialConversion(module, target, std::move(patterns)))) { signalPassFailure(); } - }; + } }; } // namespace mlir diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index e5cb92693d..0adcd74c5c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -52,7 +52,7 @@ struct RemoveTrivialGPhase final : OpRewritePattern { void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index 397e8300b3..c50fd9ec0f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -42,7 +42,7 @@ struct MergeSubsequentP final : OpRewritePattern { void POp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index a5a604968e..5729b3b03d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -82,7 +82,7 @@ struct ReplaceRWithRY final : OpRewritePattern { void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -90,7 +90,7 @@ void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value phiOperand = nullptr; + Value phiOperand; if (std::holds_alternative(phi)) { phiOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index a2fa44123c..cbb0060b4a 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRX final : OpRewritePattern { void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index 4c8a51fc02..92a8088c60 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRXX final : OpRewritePattern { void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index f077669293..6fd449ef74 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRY final : OpRewritePattern { void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index 4e0c23614e..dadfa2efff 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRYY final : OpRewritePattern { void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index 44b0be92ea..f20f89917b 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRZ final : OpRewritePattern { void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index 32e41356cd..2ea3b30481 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRZX final : OpRewritePattern { void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index 4c7b59fed5..d40c7ca8e6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -55,7 +55,7 @@ struct RemoveTrivialRZZ final : OpRewritePattern { void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 68d7e74270..89c0ed0d7c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -114,7 +114,7 @@ struct ReplaceU2WithRY final : OpRewritePattern { void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& phi, const std::variant& lambda) { - Value phiOperand = nullptr; + Value phiOperand; if (std::holds_alternative(phi)) { phiOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); @@ -122,7 +122,7 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, phiOperand = std::get(phi); } - Value lambdaOperand = nullptr; + Value lambdaOperand; if (std::holds_alternative(lambda)) { lambdaOperand = odsBuilder.create( odsState.location, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index 2aa3e65b33..8f6e26f96b 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -116,7 +116,7 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -124,7 +124,7 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value phiOperand = nullptr; + Value phiOperand; if (std::holds_alternative(phi)) { phiOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); @@ -132,7 +132,7 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, phiOperand = std::get(phi); } - Value lambdaOperand = nullptr; + Value lambdaOperand; if (std::holds_alternative(lambda)) { lambdaOperand = odsBuilder.create( odsState.location, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index 38eeff5e08..ccdf0c16b2 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -73,7 +73,7 @@ void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -81,7 +81,7 @@ void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value betaOperand = nullptr; + Value betaOperand; if (std::holds_alternative(beta)) { betaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 4667fa7a3f..e70dbbc96c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -73,7 +73,7 @@ void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -81,7 +81,7 @@ void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value betaOperand = nullptr; + Value betaOperand; if (std::holds_alternative(beta)) { betaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp index 9c9745d5cb..ef312ac969 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -21,7 +21,7 @@ using namespace mlir::quartz; void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp index 0ab83b6bfa..171c173519 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp @@ -21,7 +21,7 @@ using namespace mlir::quartz; void POp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index 2b5919e55b..0f45242800 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -30,7 +30,7 @@ void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value phiOperand = nullptr; + Value phiOperand; if (std::holds_alternative(phi)) { phiOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index ed25c0ffa0..81e0c4f09b 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index 5981914b95..281c658e0c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index 011d23d46f..e54c7996f5 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index 64a77a54e7..5d44604f4d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index 5eb462cd7d..da158030b3 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 1e83a1db59..76519ceb1d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index b61f2f030d..0f85cd6e7e 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index b96411dbb2..2ae578206c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -22,7 +22,7 @@ using namespace mlir::quartz; void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& phi, const std::variant& lambda) { - Value phiOperand = nullptr; + Value phiOperand; if (std::holds_alternative(phi)) { phiOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); @@ -30,7 +30,7 @@ void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, phiOperand = std::get(phi); } - Value lambdaOperand = nullptr; + Value lambdaOperand; if (std::holds_alternative(lambda)) { lambdaOperand = odsBuilder.create( odsState.location, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index 23b652270c..562edb1a8e 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -23,7 +23,7 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -31,7 +31,7 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value phiOperand = nullptr; + Value phiOperand; if (std::holds_alternative(phi)) { phiOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); @@ -39,7 +39,7 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, phiOperand = std::get(phi); } - Value lambdaOperand = nullptr; + Value lambdaOperand; if (std::holds_alternative(lambda)) { lambdaOperand = odsBuilder.create( odsState.location, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index 2e8463b2a9..1d7e5e6951 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -23,7 +23,7 @@ void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -31,7 +31,7 @@ void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value betaOperand = nullptr; + Value betaOperand; if (std::holds_alternative(beta)) { betaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index a5d979dc82..fd968b54a3 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -23,7 +23,7 @@ void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand = nullptr; + Value thetaOperand; if (std::holds_alternative(theta)) { thetaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); @@ -31,7 +31,7 @@ void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, thetaOperand = std::get(theta); } - Value betaOperand = nullptr; + Value betaOperand; if (std::holds_alternative(beta)) { betaOperand = odsBuilder.create( odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); From d9507b674aed16043b64d4b9deaaabbc856f36f7 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:22:59 +0100 Subject: [PATCH 294/419] Add assertion messages --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 2 +- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 8d3d4fad43..39f6d34724 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -154,7 +154,7 @@ class FluxProgramBuilder final : public OpBuilder { * @return A Bit structure representing the specified bit */ Bit operator[](const int64_t index) const { - assert(index < size); + assert(index < size && "Bit index out of bounds"); return { .registerName = name, .registerSize = size, .registerIndex = index}; } diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index abde16badc..0e6e72b27f 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -150,7 +150,7 @@ class QIRProgramBuilder { * @return A Bit structure representing the specified bit */ Bit operator[](const int64_t index) const { - assert(index < size); + assert(index < size && "Bit index out of bounds"); return { .registerName = name, .registerSize = size, .registerIndex = index}; } From d9ed92fc6726efc3d27acc596dde9bec5f3f5e44 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:25:20 +0100 Subject: [PATCH 295/419] Add bounds checks on qubit index --- mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h | 6 ++++++ mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index cd3bd443a7..debf233483 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -76,12 +76,18 @@ template class TargetAndParameterArityTrait { if constexpr (T == 0) { llvm::reportFatalUsageError("Operation does not have qubits"); } + if (i >= T) { + llvm::reportFatalUsageError("Qubit index out of bounds"); + } return this->getOperation()->getOperand(i); } Value getOutputQubit(size_t i) { if constexpr (T == 0) { llvm::reportFatalUsageError("Operation does not have qubits"); } + if (i >= T) { + llvm::reportFatalUsageError("Qubit index out of bounds"); + } return this->getOperation()->getResult(i); } diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index ce65516325..807fd6657e 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -77,12 +77,18 @@ template class TargetAndParameterArityTrait { if constexpr (T == 0) { llvm::reportFatalUsageError("Operation does not have qubits"); } + if (i >= T) { + llvm::reportFatalUsageError("Qubit index out of bounds"); + } return this->getOperation()->getOperand(i); } Value getTarget(size_t i) { if constexpr (T == 0) { llvm::reportFatalUsageError("Operation does not have targets"); } + if (i >= T) { + llvm::reportFatalUsageError("Target index out of bounds"); + } return this->getOperation()->getOperand(i); } From d4a1c8f429600788663cd564761951518cdd25dc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:27:23 +0100 Subject: [PATCH 296/419] Make QIR builder private again --- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 0e6e72b27f..bbf2e48fa8 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -795,11 +795,11 @@ class QIRProgramBuilder { */ OwningOpRef finalize(); +private: OpBuilder builder; ModuleOp module; Location loc; -private: /// Track allocated classical Registers SmallVector allocatedClassicalRegisters; From a89530eb6d55816e39135041e7138de1cbf4af3b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:30:35 +0100 Subject: [PATCH 297/419] Rename TRANSLATION_HEADERS_SOURCE --- mlir/lib/Dialect/Flux/Builder/CMakeLists.txt | 4 ++-- mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt b/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt index 6be18bdc72..c9acd94cc7 100644 --- a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt @@ -17,10 +17,10 @@ add_mlir_library( MLIRFluxDialect) # collect header files -file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE +file(GLOB_RECURSE BUILDER_HEADERS_SOURCE ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Flux/Builder/*.h) # add public headers using file sets target_sources( MLIRFluxProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${TRANSLATION_HEADERS_SOURCE}) + ${BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt b/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt index 7ec284a994..d3a129dccb 100644 --- a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt @@ -17,10 +17,10 @@ add_mlir_library( MLIRQuartzDialect) # collect header files -file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE +file(GLOB_RECURSE BUILDER_HEADERS_SOURCE ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/Builder/*.h) # add public headers using file sets target_sources( MLIRQuartzProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${TRANSLATION_HEADERS_SOURCE}) + ${BUILDER_HEADERS_SOURCE}) From 8fafdc28ff50d8173dd353e380ec1a2fb664298d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:32:01 +0100 Subject: [PATCH 298/419] Remove redundant glob patterns --- mlir/lib/Dialect/Flux/IR/CMakeLists.txt | 3 +-- mlir/lib/Dialect/Quartz/IR/CMakeLists.txt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt index 69cd64d492..7b2a47961f 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Flux/IR/CMakeLists.txt @@ -7,8 +7,7 @@ # Licensed under the MIT License file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") -file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/Operations/**/*.cpp") +file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp") file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") add_mlir_dialect_library( diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt index 47dbbc8267..a97a13acc6 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt @@ -7,8 +7,7 @@ # Licensed under the MIT License file(GLOB_RECURSE MODIFIERS "${CMAKE_CURRENT_SOURCE_DIR}/Modifiers/*.cpp") -file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/Operations/**/*.cpp") +file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp") file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") add_mlir_dialect_library( From 6aed309c67744bd9f91184dfd3770b2e476e3044 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 5 Dec 2025 19:36:42 +0100 Subject: [PATCH 299/419] Add RemoveTrivialP pattern --- .../Flux/IR/Operations/StandardGates/POp.cpp | 14 +++++++- .../pipeline/test_compiler_pipeline.cpp | 32 ++++++++++++------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index c50fd9ec0f..dc7de74787 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -38,6 +38,18 @@ struct MergeSubsequentP final : OpRewritePattern { } }; +/** + * @brief Remove trivial P operations. + */ +struct RemoveTrivialP final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(POp op, + PatternRewriter& rewriter) const override { + return removeTrivialOneTargetOneParameter(op, rewriter); + } +}; + } // namespace void POp::build(OpBuilder& odsBuilder, OperationState& odsState, @@ -54,5 +66,5 @@ void POp::build(OpBuilder& odsBuilder, OperationState& odsState, void POp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 7af618410b..51fe8bc36d 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -2294,36 +2294,44 @@ TEST_F(CompilerPipelineTest, RZ) { TEST_F(CompilerPipelineTest, P) { qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); + qc.addQubitRegister(2, "q"); qc.p(1.0, 0); qc.p(0.5, 0); + qc.p(1.0, 1); + qc.p(-1.0, 1); const auto module = importQuantumCircuit(qc); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - const auto q = reg[0]; - b.p(1.0, q); - b.p(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + const auto q0 = reg[0]; + const auto q1 = reg[1]; + b.p(1.0, q0); + b.p(0.5, q0); + b.p(1.0, q1); + b.p(-1.0, q1); }); const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); - auto q = reg[0]; - q = b.p(1.0, q); - b.p(0.5, q); + auto reg = b.allocQubitRegister(2, "q"); + auto q0 = reg[0]; + auto q1 = reg[1]; + q0 = b.p(1.0, q0); + b.p(0.5, q0); + q1 = b.p(1.0, q1); + b.p(-1.0, q1); }); const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.p(1.5, reg[0]); }); const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - auto reg = b.allocQubitRegister(1, "q"); + auto reg = b.allocQubitRegister(2, "q"); b.p(1.5, reg[0]); }); const auto qirOpt = buildQIR([](qir::QIRProgramBuilder& b) { - auto reg = b.allocQubitRegister(1); + auto reg = b.allocQubitRegister(2); b.p(1.5, reg[0]); }); From aa6627fa92a2eb64e1ccc704b980b5f8f308ac12 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:19:43 +0100 Subject: [PATCH 300/419] Remove unnecessary header --- mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 807fd6657e..78d07e6546 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -23,7 +23,6 @@ #include #include -#include #include #include #include From b6b62c1ca0b15740f3ec60c19b7ebe6990c13b9f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:23:47 +0100 Subject: [PATCH 301/419] Simplify Value initializations --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 3cf6f95734..1071a7b961 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -157,7 +157,7 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; + Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; } else { @@ -200,7 +200,7 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; + Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; } else { @@ -244,7 +244,7 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; + Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; } else { @@ -289,7 +289,7 @@ convertOneTargetThreeParameter(QuartzOpType& op, // Get the latest Flux qubit const auto quartzQubit = op->getOperand(0); - Value fluxQubit = nullptr; + Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; } else { @@ -335,8 +335,8 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, // Get the latest Flux qubits const auto quartzQubit0 = op->getOperand(0); const auto quartzQubit1 = op->getOperand(1); - Value fluxQubit0 = nullptr; - Value fluxQubit1 = nullptr; + Value fluxQubit0; + Value fluxQubit1; if (inCtrlOp == 0) { fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; @@ -386,8 +386,8 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, // Get the latest Flux qubits const auto quartzQubit0 = op->getOperand(0); const auto quartzQubit1 = op->getOperand(1); - Value fluxQubit0 = nullptr; - Value fluxQubit1 = nullptr; + Value fluxQubit0; + Value fluxQubit1; if (inCtrlOp == 0) { fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; @@ -437,8 +437,8 @@ LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, // Get the latest Flux qubits const auto quartzQubit0 = op->getOperand(0); const auto quartzQubit1 = op->getOperand(1); - Value fluxQubit0 = nullptr; - Value fluxQubit1 = nullptr; + Value fluxQubit0; + Value fluxQubit1; if (inCtrlOp == 0) { fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; From b7b22a89d42027764047743598383168c89d863f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:38:28 +0100 Subject: [PATCH 302/419] Improve comparison of floating-point values --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 7 +++++-- mlir/include/mlir/Dialect/Utils/Utils.h | 17 +++++++++++++++++ .../IR/Operations/StandardGates/GPhaseOp.cpp | 4 +++- .../Flux/IR/Operations/StandardGates/ROp.cpp | 6 ++++-- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 11 +++++++---- .../Flux/IR/Operations/StandardGates/UOp.cpp | 10 ++++++---- .../IR/Operations/StandardGates/XXMinusYYOp.cpp | 5 ++++- .../IR/Operations/StandardGates/XXPlusYYOp.cpp | 5 ++++- 8 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 mlir/include/mlir/Dialect/Utils/Utils.h diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index d955b0407e..07dc3e4b5b 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -9,10 +9,13 @@ */ #include +#include #include namespace mlir::flux { +using namespace mlir::utils; + /** * @brief Remove a pair of inverse one-target, zero-parameter operations * @@ -186,7 +189,7 @@ removeTrivialOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { } const auto paramValue = paramAttr.getValueAsDouble(); - if (paramValue != 0.0) { + if (std::abs(paramValue) > TOLERANCE) { return failure(); } @@ -212,7 +215,7 @@ removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { } const auto paramValue = paramAttr.getValueAsDouble(); - if (paramValue != 0.0) { + if (std::abs(paramValue) > TOLERANCE) { return failure(); } diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h new file mode 100644 index 0000000000..183701daf3 --- /dev/null +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +namespace mlir::utils { + +constexpr double TOLERANCE = 1e-12; + +} // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index 0adcd74c5c..746a784ffc 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -39,7 +41,7 @@ struct RemoveTrivialGPhase final : OpRewritePattern { } const auto thetaValue = thetaAttr.getValueAsDouble(); - if (thetaValue != 0.0) { + if (std::abs(thetaValue) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 5729b3b03d..90612b3c84 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -39,7 +41,7 @@ struct ReplaceRWithRX final : OpRewritePattern { } const auto phiValue = phi.getValueAsDouble(); - if (phiValue != 0.0) { + if (std::abs(phiValue) > TOLERANCE) { return failure(); } @@ -65,7 +67,7 @@ struct ReplaceRWithRY final : OpRewritePattern { } const auto phiValue = phi.getValueAsDouble(); - if (phiValue != std::numbers::pi / 2.0) { + if (std::abs(phiValue - std::numbers::pi / 2.0) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 89c0ed0d7c..6ae18b8754 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -41,7 +43,8 @@ struct ReplaceU2WithH final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (phiValue != 0.0 || lambdaValue != std::numbers::pi) { + if (std::abs(phiValue) > TOLERANCE || + std::abs(lambdaValue - std::numbers::pi) > TOLERANCE) { return failure(); } @@ -68,8 +71,8 @@ struct ReplaceU2WithRX final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (phiValue != -std::numbers::pi / 2.0 || - lambdaValue != std::numbers::pi / 2.0) { + if (std::abs(phiValue + std::numbers::pi / 2.0) > TOLERANCE || + std::abs(lambdaValue - std::numbers::pi / 2.0) > TOLERANCE) { return failure(); } @@ -97,7 +100,7 @@ struct ReplaceU2WithRY final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (phiValue != 0.0 || lambdaValue != 0.0) { + if (std::abs(phiValue) > TOLERANCE || std::abs(lambdaValue) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index 8f6e26f96b..d456a4a03e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -41,7 +43,7 @@ struct ReplaceUWithP final : OpRewritePattern { const auto thetaValue = theta.getValueAsDouble(); const auto phiValue = phi.getValueAsDouble(); - if (thetaValue != 0.0 || phiValue != 0.0) { + if (std::abs(thetaValue) > TOLERANCE || std::abs(phiValue) > TOLERANCE) { return failure(); } @@ -69,8 +71,8 @@ struct ReplaceUWithRX final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (phiValue != -std::numbers::pi / 2.0 || - lambdaValue != std::numbers::pi / 2.0) { + if (std::abs(phiValue + std::numbers::pi / 2.0) > TOLERANCE || + std::abs(lambdaValue - std::numbers::pi / 2.0) > TOLERANCE) { return failure(); } @@ -98,7 +100,7 @@ struct ReplaceUWithRY final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (phiValue != 0.0 || lambdaValue != 0.0) { + if (std::abs(phiValue) > TOLERANCE || std::abs(lambdaValue) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index ccdf0c16b2..5d04e4185e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -48,7 +50,8 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { auto beta = XXMinusYYOp::getStaticParameter(op.getBeta()); auto prevBeta = XXMinusYYOp::getStaticParameter(prevOp.getBeta()); if (beta && prevBeta) { - if (beta.getValueAsDouble() != prevBeta.getValueAsDouble()) { + if (std::abs(beta.getValueAsDouble() - prevBeta.getValueAsDouble()) > + TOLERANCE) { return failure(); } } else { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index e70dbbc96c..6675cf6d25 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -48,7 +50,8 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { auto beta = XXPlusYYOp::getStaticParameter(op.getBeta()); auto prevBeta = XXPlusYYOp::getStaticParameter(prevOp.getBeta()); if (beta && prevBeta) { - if (beta.getValueAsDouble() != prevBeta.getValueAsDouble()) { + if (std::abs(beta.getValueAsDouble() - prevBeta.getValueAsDouble()) > + TOLERANCE) { return failure(); } } else { From dded22bbae16870bfb3500193f578a81dcc549d0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:45:15 +0100 Subject: [PATCH 303/419] Remove state from addInitialize() --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 1637f922a3..335bce3787 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -966,8 +966,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * @param main The main LLVM function * @param ctx The MLIR context */ - static void addInitialize(LLVM::LLVMFuncOp& main, MLIRContext* ctx, - LoweringState* /*state*/) { + static void addInitialize(LLVM::LLVMFuncOp& main, MLIRContext* ctx) { auto moduleOp = main->getParentOfType(); auto& firstBlock = *(main.getBlocks().begin()); OpBuilder builder(main.getBody()); @@ -1169,8 +1168,9 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { } ensureBlocks(main); + addInitialize(main, ctx); + LoweringState state; - addInitialize(main, ctx, &state); // Stage 3: Convert Quartz dialect to LLVM (QIR calls) { From 09b0086b714433a50cce84a1ec34d5febbc57a6f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:48:15 +0100 Subject: [PATCH 304/419] Fix module initialization --- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index 8e5f130e1f..e104d76d6e 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -93,12 +93,11 @@ LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, const OpBuilder::InsertionGuard insertGuard(builder); // Create the declaration at the end of the module - if (auto module = dyn_cast(op)) { - builder.setInsertionPointToEnd(module.getBody()); - } else { + auto module = dyn_cast(op); + if (!module) { module = op->getParentOfType(); - builder.setInsertionPointToEnd(module.getBody()); } + builder.setInsertionPointToEnd(module.getBody()); fnDecl = builder.create(op->getLoc(), fnName, fnType); From f091b44ba227c27b385b9b554b9a02ea84a39264 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:59:13 +0100 Subject: [PATCH 305/419] Add guard to FluxUtils.h --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index 07dc3e4b5b..14bf607285 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -8,6 +8,8 @@ * Licensed under the MIT License */ +#pragma once + #include #include #include From c9d4bd5ec5cd69cc6bddfd0d980529249128ec62 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:02:21 +0100 Subject: [PATCH 306/419] Improve comparison of betas --- .../Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp | 2 +- .../lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index 5d04e4185e..a952c91f94 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -54,7 +54,7 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { TOLERANCE) { return failure(); } - } else { + } else if (op.getBeta() != prevOp.getBeta()) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 6675cf6d25..30c68a3897 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -54,7 +54,7 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { TOLERANCE) { return failure(); } - } else { + } else if (op.getBeta() != prevOp.getBeta()) { return failure(); } From 1458542a158777cd57d08ac275c544b785addf3a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:05:21 +0100 Subject: [PATCH 307/419] Do not using namespace in FulxUtils.h --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index 14bf607285..d2f75e672e 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -16,8 +16,6 @@ namespace mlir::flux { -using namespace mlir::utils; - /** * @brief Remove a pair of inverse one-target, zero-parameter operations * @@ -191,7 +189,7 @@ removeTrivialOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { } const auto paramValue = paramAttr.getValueAsDouble(); - if (std::abs(paramValue) > TOLERANCE) { + if (std::abs(paramValue) > utils::TOLERANCE) { return failure(); } @@ -217,7 +215,7 @@ removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { } const auto paramValue = paramAttr.getValueAsDouble(); - if (std::abs(paramValue) > TOLERANCE) { + if (std::abs(paramValue) > utils::TOLERANCE) { return failure(); } From 61dcd021081a52226621ffc4e280bce2910416ea Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:15:10 +0100 Subject: [PATCH 308/419] Replace asserts with fatal errors --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 4 +++- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 4 +++- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 4 +++- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 +++- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 39f6d34724..557b2d739a 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -154,7 +154,9 @@ class FluxProgramBuilder final : public OpBuilder { * @return A Bit structure representing the specified bit */ Bit operator[](const int64_t index) const { - assert(index < size && "Bit index out of bounds"); + if (index < 0 || index >= size) { + llvm::report_fatal_error("Bit index out of bounds"); + } return { .registerName = name, .registerSize = size, .registerIndex = index}; } diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index bbf2e48fa8..7fb83b810c 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -150,7 +150,9 @@ class QIRProgramBuilder { * @return A Bit structure representing the specified bit */ Bit operator[](const int64_t index) const { - assert(index < size && "Bit index out of bounds"); + if (index < 0 || index >= size) { + llvm::report_fatal_error("Bit index out of bounds"); + } return { .registerName = name, .registerSize = size, .registerIndex = index}; } diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 28d6ae9e68..79038049a7 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -146,7 +146,9 @@ class QuartzProgramBuilder final : public OpBuilder { * @return A Bit structure representing the specified bit */ Bit operator[](const int64_t index) const { - assert(0 <= index && index < size); + if (index < 0 || index >= size) { + llvm::report_fatal_error("Bit index out of bounds"); + } return { .registerName = name, .registerSize = size, .registerIndex = index}; } diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 335bce3787..670f7e36a3 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -246,7 +246,9 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { // Register is already tracked // The pointer was created by the step below const auto globalIndex = it->second + registerIndex; - assert(ptrMap.contains(globalIndex)); + if (!ptrMap.contains(globalIndex)) { + llvm::report_fatal_error("Pointer not found"); + } rewriter.replaceOp(op, ptrMap.at(globalIndex)); return success(); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 9843f48c10..0803453877 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -200,7 +200,9 @@ QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, const auto& registerName = bit.registerName; const auto registerIndex = bit.registerIndex; const auto key = std::make_pair(registerName, registerIndex); - assert(registerResultMap.contains(key)); + if (!registerResultMap.contains(key)) { + llvm::report_fatal_error("Result pointer not found"); + } const auto resultValue = registerResultMap.at(key); // Create mz call From 7a8f91f3c8f588d416dff3aaf01cfdacb2ebae28 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:17:44 +0100 Subject: [PATCH 309/419] Use reportFatalUsageError where it makes sense --- .../mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 2 +- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 +- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 2 +- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 8 ++++---- .../Flux/IR/Operations/StandardGates/BarrierOp.cpp | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 557b2d739a..d6a444203c 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -155,7 +155,7 @@ class FluxProgramBuilder final : public OpBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - llvm::report_fatal_error("Bit index out of bounds"); + llvm::reportFatalUsageError("Bit index out of bounds"); } return { .registerName = name, .registerSize = size, .registerIndex = index}; diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 7fb83b810c..01166f9bac 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -151,7 +151,7 @@ class QIRProgramBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - llvm::report_fatal_error("Bit index out of bounds"); + llvm::reportFatalUsageError("Bit index out of bounds"); } return { .registerName = name, .registerSize = size, .registerIndex = index}; diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index d0eafd6326..8daaa579f2 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -89,7 +89,7 @@ ADD_STANDARD_GATE(XXMINUSYY, xx_minus_yy) case 3: \ return QIR_CCC##NAME; \ default: \ - llvm::report_fatal_error( \ + llvm::reportFatalUsageError( \ "Multi-controlled with more than 3 controls are currently not " \ "supported"); \ } \ diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 79038049a7..e28744b59e 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -147,7 +147,7 @@ class QuartzProgramBuilder final : public OpBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - llvm::report_fatal_error("Bit index out of bounds"); + llvm::reportFatalUsageError("Bit index out of bounds"); } return { .registerName = name, .registerSize = size, .registerIndex = index}; diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 3f441da921..ffd253150c 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -153,7 +153,7 @@ Value CtrlOp::getInputQubit(const size_t i) { if (numPosControls <= i && i < getNumQubits()) { return getBodyUnitary().getInputQubit(i - numPosControls); } - llvm::report_fatal_error("Invalid qubit index"); + llvm::reportFatalUsageError("Invalid qubit index"); } Value CtrlOp::getOutputQubit(const size_t i) { @@ -164,7 +164,7 @@ Value CtrlOp::getOutputQubit(const size_t i) { if (numPosControls <= i && i < getNumQubits()) { return getBodyUnitary().getOutputQubit(i - numPosControls); } - llvm::report_fatal_error("Invalid qubit index"); + llvm::reportFatalUsageError("Invalid qubit index"); } Value CtrlOp::getInputTarget(const size_t i) { return getTargetsIn()[i]; } @@ -196,7 +196,7 @@ Value CtrlOp::getInputForOutput(const Value output) { return getTargetsIn()[i]; } } - llvm::report_fatal_error("Given qubit is not an output of the operation"); + llvm::reportFatalUsageError("Given qubit is not an output of the operation"); } Value CtrlOp::getOutputForInput(const Value input) { @@ -210,7 +210,7 @@ Value CtrlOp::getOutputForInput(const Value input) { return getTargetsOut()[i]; } } - llvm::report_fatal_error("Given qubit is not an input of the operation"); + llvm::reportFatalUsageError("Given qubit is not an input of the operation"); } size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index 9171c86d36..1fdef88a7d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -127,7 +127,7 @@ Value BarrierOp::getInputForOutput(const Value output) { return getQubitsIn()[i]; } } - llvm::report_fatal_error("Given qubit is not an output of the operation"); + llvm::reportFatalUsageError("Given qubit is not an output of the operation"); } Value BarrierOp::getOutputForInput(const Value input) { @@ -136,7 +136,7 @@ Value BarrierOp::getOutputForInput(const Value input) { return getQubitsOut()[i]; } } - llvm::report_fatal_error("Given qubit is not an input of the operation"); + llvm::reportFatalUsageError("Given qubit is not an input of the operation"); } size_t BarrierOp::getNumParams() { return 0; } From c4a3e6ab95453e53dfe2aff87ae936b280984ed9 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:24:10 +0100 Subject: [PATCH 310/419] Remove unnecessary flushes --- mlir/lib/Compiler/CompilerPipeline.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 8497a69cdc..6fad74f4c2 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -52,7 +52,6 @@ void prettyPrintStage(ModuleOp module, const llvm::StringRef stageName, std::string irString; llvm::raw_string_ostream irStream(irString); module.print(irStream); - irStream.flush(); // Print the IR with box lines and wrapping printBoxText(irString); @@ -257,7 +256,6 @@ std::string captureIR(ModuleOp module) { std::string result; llvm::raw_string_ostream os(result); module.print(os); - os.flush(); return result; } From a6e0decfccf97978c4f6d27d1607317a04dd2747 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:26:49 +0100 Subject: [PATCH 311/419] Use getNumPosControls() --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 4 ++-- mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index ffd253150c..19cf45589f 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -65,7 +65,7 @@ struct RemoveTrivialCtrl final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - if (op.getNumControls() > 0) { + if (op.getNumPosControls() > 0) { return failure(); } rewriter.replaceOp(op, op.getBodyUnitary()); @@ -116,7 +116,7 @@ struct CtrlInlineId final : OpRewritePattern { auto idOp = rewriter.create(op.getLoc(), op.getTargetsIn().front()); SmallVector newOperands; - newOperands.reserve(op.getNumControls() + 1); + newOperands.reserve(op.getNumPosControls() + 1); newOperands.append(op.getControlsIn().begin(), op.getControlsIn().end()); newOperands.push_back(idOp.getQubitOut()); rewriter.replaceOp(op, newOperands); diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index d70f9bdb8b..2fff7a2ead 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -61,7 +61,7 @@ struct RemoveTrivialCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(CtrlOp ctrlOp, PatternRewriter& rewriter) const override { - if (ctrlOp.getNumControls() > 0) { + if (ctrlOp.getNumPosControls() > 0) { return failure(); } rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); @@ -83,7 +83,7 @@ struct CtrlInlineGPhase final : OpRewritePattern { return failure(); } - for (size_t i = 0; i < op.getNumControls(); ++i) { + for (size_t i = 0; i < op.getNumPosControls(); ++i) { rewriter.create(op.getLoc(), op.getPosControl(i), gPhaseOp.getTheta()); } From e8533d2ceb662295b31fb1b5f1c2db281e58c343 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:48:53 +0100 Subject: [PATCH 312/419] Simplify builders by defining a helper function --- mlir/include/mlir/Dialect/Utils/Utils.h | 21 +++++++++++++ .../IR/Operations/StandardGates/GPhaseOp.cpp | 8 +---- .../Flux/IR/Operations/StandardGates/POp.cpp | 10 ++----- .../Flux/IR/Operations/StandardGates/ROp.cpp | 18 ++--------- .../Flux/IR/Operations/StandardGates/RXOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RXXOp.cpp | 10 ++----- .../Flux/IR/Operations/StandardGates/RYOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RYYOp.cpp | 10 ++----- .../Flux/IR/Operations/StandardGates/RZOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RZXOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RZZOp.cpp | 10 ++----- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 19 ++---------- .../Flux/IR/Operations/StandardGates/UOp.cpp | 28 ++--------------- .../Operations/StandardGates/XXMinusYYOp.cpp | 18 ++--------- .../Operations/StandardGates/XXPlusYYOp.cpp | 18 ++--------- .../IR/Operations/StandardGates/GPhaseOp.cpp | 10 ++----- .../IR/Operations/StandardGates/PhaseOp.cpp | 10 ++----- .../IR/Operations/StandardGates/ROp.cpp | 20 +++---------- .../IR/Operations/StandardGates/RXOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RXXOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RYOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RYYOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RZOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RZXOp.cpp | 10 ++----- .../IR/Operations/StandardGates/RZZOp.cpp | 10 ++----- .../IR/Operations/StandardGates/U2Op.cpp | 21 +++---------- .../IR/Operations/StandardGates/UOp.cpp | 30 ++++--------------- .../Operations/StandardGates/XXMinusYYOp.cpp | 20 +++---------- .../Operations/StandardGates/XXPlusYYOp.cpp | 20 +++---------- 29 files changed, 105 insertions(+), 306 deletions(-) diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 183701daf3..fecf733bd6 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -14,4 +14,25 @@ namespace mlir::utils { constexpr double TOLERANCE = 1e-12; +/** + * @brief Convert a variant parameter (double or Value) to a Value + * + * @param odsBuilder The operation builder. + * @param odsState The operation state. + * @param parameter The parameter as a variant (double or Value). + * @return Value The parameter as a Value. + */ +inline Value variantToValue(OpBuilder& odsBuilder, OperationState& odsState, + const std::variant& parameter) { + Value operand; + if (std::holds_alternative(parameter)) { + operand = odsBuilder.create( + odsState.location, + odsBuilder.getF64FloatAttr(std::get(parameter))); + } else { + operand = std::get(parameter); + } + return operand; +} + } // namespace mlir::utils diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index 746a784ffc..52bb0f5dc4 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -54,13 +54,7 @@ struct RemoveTrivialGPhase final : OpRewritePattern { void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index dc7de74787..8aca29a781 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -54,13 +56,7 @@ struct RemoveTrivialP final : OpRewritePattern { void POp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 90612b3c84..5fa3e36d34 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -84,22 +84,8 @@ struct ReplaceRWithRY final : OpRewritePattern { void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index cbb0060b4a..0871eb6c5f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRX final : OpRewritePattern { void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index 92a8088c60..83482649f0 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRXX final : OpRewritePattern { void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index 6fd449ef74..df7c69d6bb 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRY final : OpRewritePattern { void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index dadfa2efff..afab2fb4c6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRYY final : OpRewritePattern { void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index f20f89917b..76c9b17d4d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRZ final : OpRewritePattern { void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index 2ea3b30481..85155441d9 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRZX final : OpRewritePattern { void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index d40c7ca8e6..781f524131 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -22,6 +23,7 @@ using namespace mlir; using namespace mlir::flux; +using namespace mlir::utils; namespace { @@ -55,13 +57,7 @@ struct RemoveTrivialRZZ final : OpRewritePattern { void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 6ae18b8754..0058fcba5b 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -117,23 +117,8 @@ struct ReplaceU2WithRY final : OpRewritePattern { void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& phi, const std::variant& lambda) { - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand; - if (std::holds_alternative(lambda)) { - lambdaOperand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(lambda))); - } else { - lambdaOperand = std::get(lambda); - } - + const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); + const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index d456a4a03e..54b5492dd2 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -118,31 +118,9 @@ void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand; - if (std::holds_alternative(lambda)) { - lambdaOperand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(lambda))); - } else { - lambdaOperand = std::get(lambda); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); + const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index a952c91f94..7cda2d7336 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -76,22 +76,8 @@ void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value betaOperand; - if (std::holds_alternative(beta)) { - betaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); - } else { - betaOperand = std::get(beta); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 30c68a3897..0c2bef2032 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -76,22 +76,8 @@ void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value betaOperand; - if (std::holds_alternative(beta)) { - betaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); - } else { - betaOperand = std::get(beta); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp index ef312ac969..a050bdb32e 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,15 +19,10 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp index 171c173519..f0dbb07119 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,15 +19,10 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void POp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index 0f45242800..b1e22bea29 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,25 +19,12 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index 81e0c4f09b..14cd4bd932 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index 281c658e0c..206de67cd7 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index e54c7996f5..494898bb76 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index 5d44604f4d..187fa4be76 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index da158030b3..1266d1677c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 76519ceb1d..163263b054 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index 0f85cd6e7e..b50bd9879c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,16 +19,11 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index 2ae578206c..c30b4d5a7c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,26 +19,12 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& phi, const std::variant& lambda) { - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand; - if (std::holds_alternative(lambda)) { - lambdaOperand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(lambda))); - } else { - lambdaOperand = std::get(lambda); - } - + const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); + const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index 562edb1a8e..8199d0718a 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,35 +19,14 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value phiOperand; - if (std::holds_alternative(phi)) { - phiOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(phi))); - } else { - phiOperand = std::get(phi); - } - - Value lambdaOperand; - if (std::holds_alternative(lambda)) { - lambdaOperand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(lambda))); - } else { - lambdaOperand = std::get(lambda); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); + const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index 1d7e5e6951..878f6297af 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,26 +19,13 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value betaOperand; - if (std::holds_alternative(beta)) { - betaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); - } else { - betaOperand = std::get(beta); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index fd968b54a3..c395a81abe 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -9,6 +9,7 @@ */ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -18,26 +19,13 @@ using namespace mlir; using namespace mlir::quartz; +using namespace mlir::utils; void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - Value thetaOperand; - if (std::holds_alternative(theta)) { - thetaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(theta))); - } else { - thetaOperand = std::get(theta); - } - - Value betaOperand; - if (std::holds_alternative(beta)) { - betaOperand = odsBuilder.create( - odsState.location, odsBuilder.getF64FloatAttr(std::get(beta))); - } else { - betaOperand = std::get(beta); - } - + const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); + const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); } From 96411ee2b28f11806849c1ff0dde3d3dad362eee Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:49:19 +0100 Subject: [PATCH 313/419] Rename PhaseOp.cpp --- .../Quartz/IR/Operations/StandardGates/{PhaseOp.cpp => POp.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/{PhaseOp.cpp => POp.cpp} (100%) diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp similarity index 100% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/PhaseOp.cpp rename to mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp From 813f2d72b66bdee5e2e9ccbed5220661932d8dc4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:52:15 +0100 Subject: [PATCH 314/419] Rename odsBuilder and odsState arguments --- mlir/include/mlir/Dialect/Utils/Utils.h | 11 ++++---- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 28 +++++++++---------- .../IR/Operations/StandardGates/BarrierOp.cpp | 4 +-- .../IR/Operations/StandardGates/GPhaseOp.cpp | 6 ++-- .../Flux/IR/Operations/StandardGates/POp.cpp | 8 +++--- .../Flux/IR/Operations/StandardGates/ROp.cpp | 10 +++---- .../Flux/IR/Operations/StandardGates/RXOp.cpp | 7 ++--- .../IR/Operations/StandardGates/RXXOp.cpp | 6 ++-- .../Flux/IR/Operations/StandardGates/RYOp.cpp | 7 ++--- .../IR/Operations/StandardGates/RYYOp.cpp | 6 ++-- .../Flux/IR/Operations/StandardGates/RZOp.cpp | 7 ++--- .../IR/Operations/StandardGates/RZXOp.cpp | 6 ++-- .../IR/Operations/StandardGates/RZZOp.cpp | 6 ++-- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 10 +++---- .../Flux/IR/Operations/StandardGates/UOp.cpp | 12 ++++---- .../Operations/StandardGates/XXMinusYYOp.cpp | 8 +++--- .../Operations/StandardGates/XXPlusYYOp.cpp | 8 +++--- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 28 +++++++++---------- .../IR/Operations/StandardGates/GPhaseOp.cpp | 6 ++-- .../IR/Operations/StandardGates/POp.cpp | 8 +++--- .../IR/Operations/StandardGates/ROp.cpp | 10 +++---- .../IR/Operations/StandardGates/RXOp.cpp | 7 ++--- .../IR/Operations/StandardGates/RXXOp.cpp | 6 ++-- .../IR/Operations/StandardGates/RYOp.cpp | 7 ++--- .../IR/Operations/StandardGates/RYYOp.cpp | 6 ++-- .../IR/Operations/StandardGates/RZOp.cpp | 7 ++--- .../IR/Operations/StandardGates/RZXOp.cpp | 6 ++-- .../IR/Operations/StandardGates/RZZOp.cpp | 6 ++-- .../IR/Operations/StandardGates/U2Op.cpp | 10 +++---- .../IR/Operations/StandardGates/UOp.cpp | 12 ++++---- .../Operations/StandardGates/XXMinusYYOp.cpp | 8 +++--- .../Operations/StandardGates/XXPlusYYOp.cpp | 8 +++--- 32 files changed, 139 insertions(+), 146 deletions(-) diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index fecf733bd6..3619a9fdc7 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -17,18 +17,17 @@ constexpr double TOLERANCE = 1e-12; /** * @brief Convert a variant parameter (double or Value) to a Value * - * @param odsBuilder The operation builder. - * @param odsState The operation state. + * @param builder The operation builder. + * @param state The operation state. * @param parameter The parameter as a variant (double or Value). * @return Value The parameter as a Value. */ -inline Value variantToValue(OpBuilder& odsBuilder, OperationState& odsState, +inline Value variantToValue(OpBuilder& builder, OperationState& state, const std::variant& parameter) { Value operand; if (std::holds_alternative(parameter)) { - operand = odsBuilder.create( - odsState.location, - odsBuilder.getF64FloatAttr(std::get(parameter))); + operand = builder.create( + state.location, builder.getF64FloatAttr(std::get(parameter))); } else { operand = std::get(parameter); } diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 19cf45589f..31f65c9c09 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -219,31 +219,31 @@ Value CtrlOp::getParameter(const size_t i) { return getBodyUnitary().getParameter(i); } -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void CtrlOp::build(OpBuilder& builder, OperationState& state, const ValueRange controls, const ValueRange targets, UnitaryOpInterface bodyUnitary) { - build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); + build(builder, state, controls, targets); + auto& block = state.regions.front()->emplaceBlock(); // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); - odsBuilder.create(odsState.location, op->getResults()); + const OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&block); + auto* op = builder.clone(*bodyUnitary.getOperation()); + builder.create(state.location, op->getResults()); } void CtrlOp::build( - OpBuilder& odsBuilder, OperationState& odsState, const ValueRange controls, + OpBuilder& builder, OperationState& state, const ValueRange controls, const ValueRange targets, const std::function& bodyBuilder) { - build(odsBuilder, odsState, controls, targets); - auto& block = odsState.regions.front()->emplaceBlock(); + build(builder, state, controls, targets); + auto& block = state.regions.front()->emplaceBlock(); // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(odsBuilder); - odsBuilder.setInsertionPointToStart(&block); - auto targetsOut = bodyBuilder(odsBuilder, targets); - odsBuilder.create(odsState.location, targetsOut); + const OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&block); + auto targetsOut = bodyBuilder(builder, targets); + builder.create(state.location, targetsOut); } LogicalResult CtrlOp::verify() { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index 1fdef88a7d..06f4c26291 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -145,14 +145,14 @@ Value BarrierOp::getParameter(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp has no parameters"); } -void BarrierOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void BarrierOp::build(OpBuilder& builder, OperationState& state, const ValueRange qubits) { SmallVector resultTypes; resultTypes.reserve(qubits.size()); for (const Value qubit : qubits) { resultTypes.push_back(qubit.getType()); } - build(odsBuilder, odsState, resultTypes, qubits); + build(builder, state, resultTypes, qubits); } void BarrierOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index 52bb0f5dc4..d13f3e04b0 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -52,10 +52,10 @@ struct RemoveTrivialGPhase final : OpRewritePattern { } // namespace -void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void GPhaseOp::build(OpBuilder& builder, OperationState& state, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, thetaOperand); } void GPhaseOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index 8aca29a781..8ec60b6c08 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -54,10 +54,10 @@ struct RemoveTrivialP final : OpRewritePattern { } // namespace -void POp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); +void POp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& theta) { + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } void POp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 5fa3e36d34..001f9552bf 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -81,12 +81,12 @@ struct ReplaceRWithRY final : OpRewritePattern { } // namespace -void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& theta, +void ROp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& theta, const std::variant& phi) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); - build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& phiOperand = variantToValue(builder, state, phi); + build(builder, state, qubitIn, thetaOperand, phiOperand); } void ROp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index 0871eb6c5f..77b06faa31 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -54,11 +54,10 @@ struct RemoveTrivialRX final : OpRewritePattern { } // namespace -void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, +void RXOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } void RXOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index 83482649f0..ba18e81d48 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -54,11 +54,11 @@ struct RemoveTrivialRXX final : OpRewritePattern { } // namespace -void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RXXOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } void RXXOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index df7c69d6bb..6fa31ba416 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -54,11 +54,10 @@ struct RemoveTrivialRY final : OpRewritePattern { } // namespace -void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, +void RYOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } void RYOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index afab2fb4c6..5e4edae47e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -54,11 +54,11 @@ struct RemoveTrivialRYY final : OpRewritePattern { } // namespace -void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RYYOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } void RYYOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index 76c9b17d4d..2447f1ea10 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -54,11 +54,10 @@ struct RemoveTrivialRZ final : OpRewritePattern { } // namespace -void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, +void RZOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } void RZOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index 85155441d9..1193eb3f22 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -54,11 +54,11 @@ struct RemoveTrivialRZX final : OpRewritePattern { } // namespace -void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RZXOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } void RZXOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index 781f524131..b6b951c0a2 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -54,11 +54,11 @@ struct RemoveTrivialRZZ final : OpRewritePattern { } // namespace -void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RZZOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } void RZZOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 0058fcba5b..869f8c874a 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -114,12 +114,12 @@ struct ReplaceU2WithRY final : OpRewritePattern { } // namespace -void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& phi, +void U2Op::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& phi, const std::variant& lambda) { - const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); - const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); - build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); + const auto& phiOperand = variantToValue(builder, state, phi); + const auto& lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, phiOperand, lambdaOperand); } void U2Op::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index 54b5492dd2..e8c6b33c6e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -114,14 +114,14 @@ struct ReplaceUWithRY final : OpRewritePattern { } // namespace -void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& theta, +void UOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); - const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); - build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& phiOperand = variantToValue(builder, state, phi); + const auto& lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, thetaOperand, phiOperand, lambdaOperand); } void UOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index 7cda2d7336..224dd13836 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -72,13 +72,13 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { } // namespace -void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } void XXMinusYYOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 0c2bef2032..cd897d1d6c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -72,13 +72,13 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { } // namespace -void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } void XXPlusYYOp::getCanonicalizationPatterns(RewritePatternSet& results, diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 2fff7a2ead..2b2ab1e954 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -141,29 +141,29 @@ Value CtrlOp::getParameter(const size_t i) { return getBodyUnitary().getParameter(i); } -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void CtrlOp::build(OpBuilder& builder, OperationState& state, const ValueRange controls, UnitaryOpInterface bodyUnitary) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); + const OpBuilder::InsertionGuard guard(builder); + state.addOperands(controls); + auto* region = state.addRegion(); auto& block = region->emplaceBlock(); // Move the unitary op into the block - odsBuilder.setInsertionPointToStart(&block); - odsBuilder.clone(*bodyUnitary.getOperation()); - odsBuilder.create(odsState.location); + builder.setInsertionPointToStart(&block); + builder.clone(*bodyUnitary.getOperation()); + builder.create(state.location); } -void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void CtrlOp::build(OpBuilder& builder, OperationState& state, const ValueRange controls, const std::function& bodyBuilder) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); + const OpBuilder::InsertionGuard guard(builder); + state.addOperands(controls); + auto* region = state.addRegion(); auto& block = region->emplaceBlock(); - odsBuilder.setInsertionPointToStart(&block); - bodyBuilder(odsBuilder); - odsBuilder.create(odsState.location); + builder.setInsertionPointToStart(&block); + bodyBuilder(builder); + builder.create(state.location); } LogicalResult CtrlOp::verify() { diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp index a050bdb32e..8297e436db 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -21,8 +21,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void GPhaseOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void GPhaseOp::build(OpBuilder& builder, OperationState& state, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp index f0dbb07119..093356471a 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp @@ -21,8 +21,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void POp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); +void POp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& theta) { + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index b1e22bea29..f106da5dc8 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -21,10 +21,10 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void ROp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& theta, +void ROp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& theta, const std::variant& phi) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); - build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& phiOperand = variantToValue(builder, state, phi); + build(builder, state, qubitIn, thetaOperand, phiOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index 14cd4bd932..2887cf5815 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -21,9 +21,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RXOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, +void RXOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index 206de67cd7..ec03490785 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -21,9 +21,9 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RXXOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RXXOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index 494898bb76..5962325dd8 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -21,9 +21,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RYOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, +void RYOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index 187fa4be76..03ebb265b6 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -21,9 +21,9 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RYYOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index 1266d1677c..f38ed5c714 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -21,9 +21,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RZOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, +void RZOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubitIn, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 163263b054..1385d3f9e4 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -21,9 +21,9 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RZXOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RZXOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index b50bd9879c..177e3a527c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -21,9 +21,9 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RZZOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void RZZOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index c30b4d5a7c..a50ac3ccd2 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -21,10 +21,10 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void U2Op::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& phi, +void U2Op::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& phi, const std::variant& lambda) { - const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); - const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); - build(odsBuilder, odsState, qubitIn, phiOperand, lambdaOperand); + const auto& phiOperand = variantToValue(builder, state, phi); + const auto& lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index 8199d0718a..0b6074a9e3 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -21,12 +21,12 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void UOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const Value qubitIn, const std::variant& theta, +void UOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, + const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& phiOperand = variantToValue(odsBuilder, odsState, phi); - const auto& lambdaOperand = variantToValue(odsBuilder, odsState, lambda); - build(odsBuilder, odsState, qubitIn, thetaOperand, phiOperand, lambdaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& phiOperand = variantToValue(builder, state, phi); + const auto& lambdaOperand = variantToValue(builder, state, lambda); + build(builder, state, qubitIn, thetaOperand, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index 878f6297af..8994754612 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -21,11 +21,11 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void XXMinusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index c395a81abe..ea7e64d3d3 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -21,11 +21,11 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void XXPlusYYOp::build(OpBuilder& odsBuilder, OperationState& odsState, +void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, const Value qubit0In, const Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(odsBuilder, odsState, theta); - const auto& betaOperand = variantToValue(odsBuilder, odsState, beta); - build(odsBuilder, odsState, qubit0In, qubit1In, thetaOperand, betaOperand); + const auto& thetaOperand = variantToValue(builder, state, theta); + const auto& betaOperand = variantToValue(builder, state, beta); + build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } From 97950e0144001be7dbd5f7ab951d32c06044c317 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:56:15 +0100 Subject: [PATCH 315/419] Include variant --- mlir/include/mlir/Dialect/Utils/Utils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 3619a9fdc7..70dc6fbb90 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -10,6 +10,8 @@ #pragma once +#include + namespace mlir::utils { constexpr double TOLERANCE = 1e-12; From 2669b30515ed12321d7cc29e2a2d841004824608 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:29:52 +0100 Subject: [PATCH 316/419] Make use of interface methods in canonicalization patterns --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 46 ++++++++++--------- .../Flux/IR/Operations/StandardGates/ROp.cpp | 4 +- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 6 +-- .../Flux/IR/Operations/StandardGates/UOp.cpp | 6 +-- .../Operations/StandardGates/XXMinusYYOp.cpp | 10 ++-- .../Operations/StandardGates/XXPlusYYOp.cpp | 9 ++-- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index d2f75e672e..cf2ef80bc6 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -30,14 +30,14 @@ inline mlir::LogicalResult removeInversePairOneTargetZeroParameter(OpType op, mlir::PatternRewriter& rewriter) { // Check if the predecessor is the inverse operation - auto prevOp = op.getQubitIn().template getDefiningOp(); + auto prevOp = op.getInputQubit(0).template getDefiningOp(); if (!prevOp) { return failure(); } // Remove both operations - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - rewriter.replaceOp(op, op.getQubitIn()); + rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); + rewriter.replaceOp(op, op.getInputQubit(0)); return success(); } @@ -56,19 +56,20 @@ inline mlir::LogicalResult removeInversePairTwoTargetZeroParameter(OpType op, mlir::PatternRewriter& rewriter) { // Check if the predecessor is the inverse operation - auto prevOp = op.getQubit0In().template getDefiningOp(); + auto prevOp = op.getInputQubit(0).template getDefiningOp(); if (!prevOp) { return failure(); } // Confirm operations act on same qubits - if (op.getQubit1In() != prevOp.getQubit1Out()) { + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { return failure(); } // Remove both operations - rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); - rewriter.replaceOp(op, {op.getQubit0In(), op.getQubit1In()}); + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); + rewriter.replaceOp(op, {op.getInputQubit(0), op.getInputQubit(1)}); return success(); } @@ -90,18 +91,18 @@ template inline mlir::LogicalResult mergeOneTargetZeroParameter(OpType op, mlir::PatternRewriter& rewriter) { // Check if the predecessor is the same operation - auto prevOp = op.getQubitIn().template getDefiningOp(); + auto prevOp = op.getInputQubit(0).template getDefiningOp(); if (!prevOp) { return failure(); } // Replace operation with square operation - auto squareOp = rewriter.create(op.getLoc(), op.getQubitIn()); - rewriter.replaceOp(op, squareOp.getQubitOut()); + auto squareOp = + rewriter.create(op.getLoc(), op.getInputQubit(0)); + rewriter.replaceOp(op, squareOp.getOutputQubit(0)); // Trivialize predecessor - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); - + rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); return success(); } @@ -120,18 +121,19 @@ template inline mlir::LogicalResult mergeOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { // Check if the predecessor is the same operation - auto prevOp = op.getQubitIn().template getDefiningOp(); + auto prevOp = op.getInputQubit(0).template getDefiningOp(); if (!prevOp) { return failure(); } - // Compute and set new angle + // Compute and set new parameter + // For OneTargetOneParameter operations, the parameter has index 1 auto newParameter = rewriter.create( op.getLoc(), op.getOperand(1), prevOp.getOperand(1)); op->setOperand(1, newParameter.getResult()); // Trivialize predecessor - rewriter.replaceOp(prevOp, prevOp.getQubitIn()); + rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); return success(); } @@ -151,23 +153,25 @@ template inline mlir::LogicalResult mergeTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { // Check if the predecessor is the same operation - auto prevOp = op.getQubit0In().template getDefiningOp(); + auto prevOp = op.getInputQubit(0).template getDefiningOp(); if (!prevOp) { return failure(); } // Confirm operations act on same qubits - if (op.getQubit1In() != prevOp.getQubit1Out()) { + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { return failure(); } - // Compute and set new angle + // Compute and set new parameter + // For TwoTargetOneParameter operations, the parameter has index 2 auto newParameter = rewriter.create( op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); op->setOperand(2, newParameter.getResult()); // Trivialize predecessor - rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); return success(); } @@ -193,7 +197,7 @@ removeTrivialOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return failure(); } - rewriter.replaceOp(op, op.getQubitIn()); + rewriter.replaceOp(op, op.getInputQubit(0)); return success(); } @@ -219,7 +223,7 @@ removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return failure(); } - rewriter.replaceOp(op, {op.getQubit0In(), op.getQubit1In()}); + rewriter.replaceOp(op, {op.getInputQubit(0), op.getInputQubit(1)}); return success(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 001f9552bf..885d3aaa2e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -46,7 +46,7 @@ struct ReplaceRWithRX final : OpRewritePattern { } auto rxOp = - rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); rewriter.replaceOp(op, rxOp.getResult()); return success(); @@ -72,7 +72,7 @@ struct ReplaceRWithRY final : OpRewritePattern { } auto ryOp = - rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); rewriter.replaceOp(op, ryOp.getResult()); return success(); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 869f8c874a..02f175f6d7 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -48,7 +48,7 @@ struct ReplaceU2WithH final : OpRewritePattern { return failure(); } - auto hOp = rewriter.create(op.getLoc(), op.getQubitIn()); + auto hOp = rewriter.create(op.getLoc(), op.getInputQubit(0)); rewriter.replaceOp(op, hOp.getResult()); return success(); @@ -76,7 +76,7 @@ struct ReplaceU2WithRX final : OpRewritePattern { return failure(); } - auto rxOp = rewriter.create(op.getLoc(), op.getQubitIn(), + auto rxOp = rewriter.create(op.getLoc(), op.getInputQubit(0), std::numbers::pi / 2.0); rewriter.replaceOp(op, rxOp.getResult()); @@ -104,7 +104,7 @@ struct ReplaceU2WithRY final : OpRewritePattern { return failure(); } - auto ryOp = rewriter.create(op.getLoc(), op.getQubitIn(), + auto ryOp = rewriter.create(op.getLoc(), op.getInputQubit(0), std::numbers::pi / 2.0); rewriter.replaceOp(op, ryOp.getResult()); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index e8c6b33c6e..ade33b66a6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -48,7 +48,7 @@ struct ReplaceUWithP final : OpRewritePattern { } auto pOp = - rewriter.create(op.getLoc(), op.getQubitIn(), op.getLambda()); + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getLambda()); rewriter.replaceOp(op, pOp.getResult()); return success(); @@ -77,7 +77,7 @@ struct ReplaceUWithRX final : OpRewritePattern { } auto rxOp = - rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); rewriter.replaceOp(op, rxOp.getResult()); return success(); @@ -105,7 +105,7 @@ struct ReplaceUWithRY final : OpRewritePattern { } auto ryOp = - rewriter.create(op.getLoc(), op.getQubitIn(), op.getTheta()); + rewriter.create(op.getLoc(), op.getInputQubit(0), op.getTheta()); rewriter.replaceOp(op, ryOp.getResult()); return success(); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index 224dd13836..c1f09eafc8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -36,13 +36,13 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { LogicalResult matchAndRewrite(XXMinusYYOp op, PatternRewriter& rewriter) const override { - auto prevOp = op.getQubit0In().getDefiningOp(); + auto prevOp = op.getInputQubit(0).getDefiningOp(); if (!prevOp) { return failure(); } // Confirm operations act on same qubits - if (op.getQubit1In() != prevOp.getQubit1Out()) { + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { return failure(); } @@ -58,14 +58,14 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { return failure(); } - // Compute and set new theta + // Compute and set new theta, which has index 2 auto newParameter = rewriter.create( op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); op->setOperand(2, newParameter.getResult()); // Trivialize predecessor - rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); - + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); return success(); } }; diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index cd897d1d6c..b9407e529f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -36,13 +36,13 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { LogicalResult matchAndRewrite(XXPlusYYOp op, PatternRewriter& rewriter) const override { - auto prevOp = op.getQubit0In().getDefiningOp(); + auto prevOp = op.getInputQubit(0).getDefiningOp(); if (!prevOp) { return failure(); } // Confirm operations act on same qubits - if (op.getQubit1In() != prevOp.getQubit1Out()) { + if (op.getInputQubit(1) != prevOp.getOutputQubit(1)) { return failure(); } @@ -58,13 +58,14 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { return failure(); } - // Compute and set new theta + // Compute and set new theta, which has index 2 auto newParameter = rewriter.create( op.getLoc(), op.getOperand(2), prevOp.getOperand(2)); op->setOperand(2, newParameter.getResult()); // Trivialize predecessor - rewriter.replaceOp(prevOp, {prevOp.getQubit0In(), prevOp.getQubit1In()}); + rewriter.replaceOp(prevOp, + {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); return success(); } From 7b75a94cf407f2ad6b1726a94ae464ee09969aeb Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:34:37 +0100 Subject: [PATCH 317/419] Remove unnecessary header --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index d6a444203c..20756f7cc9 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -20,7 +20,6 @@ #include #include #include -#include namespace mlir::flux { From 0537ffbf8e2783ce497708c06d51a6708a3c395d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:46:10 +0100 Subject: [PATCH 318/419] Fix module initialization --- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index e104d76d6e..8ddc89ff1e 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -117,12 +117,11 @@ LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, const OpBuilder::InsertionGuard insertGuard(builder); // Create the declaration at the start of the module - if (auto module = dyn_cast(op)) { - builder.setInsertionPointToStart(module.getBody()); - } else { + auto module = dyn_cast(op); + if (!module) { module = op->getParentOfType(); - builder.setInsertionPointToStart(module.getBody()); } + builder.setInsertionPointToStart(module.getBody()); const auto symbolName = builder.getStringAttr((symbolPrefix + "_" + label).str()); From f5529be5d16f4e94a797108b2c31575eab88676d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:55:06 +0100 Subject: [PATCH 319/419] Fix linter errors --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 1 + mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 1 - .../Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp | 1 - .../Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp | 3 +-- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp | 4 ++-- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp | 2 -- .../lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp | 2 -- .../lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp | 2 -- .../lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp | 2 -- .../lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp | 2 -- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp | 7 +++---- mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp | 6 +++--- .../Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp | 4 +--- .../Quartz/IR/Operations/StandardGates/GPhaseOp.cpp | 2 -- .../lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp | 2 -- .../lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp | 2 -- .../Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp | 2 -- .../lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp | 2 -- .../Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp | 2 -- .../Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp | 2 -- 30 files changed, 12 insertions(+), 59 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 670f7e36a3..7bf30be9c5 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 31f65c9c09..8beb0561ac 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -8,7 +8,6 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index 06f4c26291..dfde2fef31 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -8,7 +8,6 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index d13f3e04b0..f39418708a 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -8,11 +8,10 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index 8ec60b6c08..92bd0f0f13 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 885d3aaa2e..a5cc3d6609 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -11,7 +11,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include +#include #include #include #include @@ -67,7 +67,7 @@ struct ReplaceRWithRY final : OpRewritePattern { } const auto phiValue = phi.getValueAsDouble(); - if (std::abs(phiValue - std::numbers::pi / 2.0) > TOLERANCE) { + if (std::abs(phiValue - (std::numbers::pi / 2.0)) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index 77b06faa31..fbe3eb12c6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index ba18e81d48..56a50e8b44 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index 6fa31ba416..de8be4ad73 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index 5e4edae47e..6c9d43f2b4 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index 2447f1ea10..60cddbba6f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index 1193eb3f22..2c669c5408 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index b6b951c0a2..e71a1b2c6c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -12,9 +12,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 02f175f6d7..9f17e9358c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -11,9 +11,8 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include +#include #include -#include #include #include #include @@ -71,8 +70,8 @@ struct ReplaceU2WithRX final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (std::abs(phiValue + std::numbers::pi / 2.0) > TOLERANCE || - std::abs(lambdaValue - std::numbers::pi / 2.0) > TOLERANCE) { + if (std::abs(phiValue + (std::numbers::pi / 2.0)) > TOLERANCE || + std::abs(lambdaValue - (std::numbers::pi / 2.0)) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index ade33b66a6..ed4c12b5e4 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -11,7 +11,7 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include +#include #include #include #include @@ -71,8 +71,8 @@ struct ReplaceUWithRX final : OpRewritePattern { const auto phiValue = phi.getValueAsDouble(); const auto lambdaValue = lambda.getValueAsDouble(); - if (std::abs(phiValue + std::numbers::pi / 2.0) > TOLERANCE || - std::abs(lambdaValue - std::numbers::pi / 2.0) > TOLERANCE) { + if (std::abs(phiValue + (std::numbers::pi / 2.0)) > TOLERANCE || + std::abs(lambdaValue - (std::numbers::pi / 2.0)) > TOLERANCE) { return failure(); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index c1f09eafc8..b7c46ff8c3 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -8,10 +8,10 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" +#include #include #include #include diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index b9407e529f..562f520fe7 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -8,13 +8,11 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include +#include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp index 8297e436db..a03ec043d4 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp index 093356471a..db6905c378 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index f106da5dc8..9ce00b7a1e 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index 2887cf5815..1177e69760 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index ec03490785..01d189bf29 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index 5962325dd8..e441950fd1 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index 03ebb265b6..c2aa61bfee 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index f38ed5c714..f72b03d2d2 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 1385d3f9e4..29a9ed31a7 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index 177e3a527c..de53cd37dd 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index 0b6074a9e3..b23ae626ad 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index 8994754612..813cbebf0c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index ea7e64d3d3..a847633bc1 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include From 5a2842cfcfd15b52cd3adc7ca678993b16c1b39e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:18:08 +0100 Subject: [PATCH 320/419] Fix remaining linter errors --- .../lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp | 1 + mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 562f520fe7..9f66536ab8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/Utils/Utils.h" #include +#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index a50ac3ccd2..4ac1e343ae 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -11,9 +11,7 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include -#include #include #include From 9c8242f66d7902b2d890f6b63ebb205a95417b09 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:21:12 +0100 Subject: [PATCH 321/419] Improve error handling --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 +++- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 7bf30be9c5..8800849bfe 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -248,7 +248,8 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { // The pointer was created by the step below const auto globalIndex = it->second + registerIndex; if (!ptrMap.contains(globalIndex)) { - llvm::report_fatal_error("Pointer not found"); + llvm::errs() << "Pointer not found.\n"; + return failure(); } rewriter.replaceOp(op, ptrMap.at(globalIndex)); return success(); @@ -513,6 +514,7 @@ struct ConvertQuartzGPhaseOpQIR final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); if (state.inCtrlOp != 0) { + llvm::errs() << "Controlled GPhaseOps cannot be converted to QIR.\n"; return failure(); } return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), state, diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 8f467712b5..0a61c35a52 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -108,9 +108,9 @@ FluxProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { void FluxProgramBuilder::validateQubitValue(const Value qubit) const { if (!validQubits.contains(qubit)) { - llvm::errs() << "Error: Attempting to use an invalid qubit SSA value. " + llvm::errs() << "Attempting to use an invalid qubit SSA value. " << "The value may have been consumed by a previous operation " - << "or was never created through this \n"; + << "or was never created through this builder.\n"; llvm::reportFatalUsageError( "Invalid qubit value used (either consumed or not tracked)"); } From c63763089616121dff1b3a8f39f7e58780b28e32 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:39:56 +0100 Subject: [PATCH 322/419] Improve null safety in QIRUtils.cpp --- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 +- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 0803453877..4be35b564d 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -201,7 +201,7 @@ QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, const auto registerIndex = bit.registerIndex; const auto key = std::make_pair(registerName, registerIndex); if (!registerResultMap.contains(key)) { - llvm::report_fatal_error("Result pointer not found"); + llvm::reportFatalInternalError("Result pointer not found"); } const auto resultValue = registerResultMap.at(key); diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index 8ddc89ff1e..cd6ee64105 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,9 @@ LLVM::LLVMFuncOp getMainFunction(Operation* op) { if (!module) { module = op->getParentOfType(); } + if (!module) { + return nullptr; + } // Search for function with entry_point attribute for (const auto funcOp : module.getOps()) { @@ -97,6 +101,9 @@ LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, if (!module) { module = op->getParentOfType(); } + if (!module) { + llvm::reportFatalInternalError("Module not found"); + } builder.setInsertionPointToEnd(module.getBody()); fnDecl = builder.create(op->getLoc(), fnName, fnType); @@ -135,9 +142,12 @@ LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, globalOp->setAttr("addr_space", builder.getI32IntegerAttr(0)); globalOp->setAttr("dso_local", builder.getUnitAttr()); - // Create addressOf operation + // Create AddressOfOp // Shall be added to the first block of the `main` function in the module auto main = getMainFunction(op); + if (!main) { + llvm::reportFatalInternalError("Main function not found"); + } auto& firstBlock = *(main.getBlocks().begin()); builder.setInsertionPointToStart(&firstBlock); From 7bdb289b86a4b09bd14024900e849c66b2936c38 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:42:25 +0100 Subject: [PATCH 323/419] Fix insertion point --- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 4be35b564d..3f9d7b2ded 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -246,7 +246,7 @@ void QIRProgramBuilder::createCallOp( const OpBuilder::InsertionGuard entryGuard(builder); // Insert constants in entry block - builder.setInsertionPointToEnd(entryBlock); + builder.setInsertionPoint(entryBlock->getTerminator()); SmallVector parameterOperands; parameterOperands.reserve(parameters.size()); From de23c3950ef8d5e9ad8cd8c7485f71cb7a483862 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:46:33 +0100 Subject: [PATCH 324/419] Add bounds checks to CtrlOp methods --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 24 ++++++++++++++++--- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 7 +++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 8beb0561ac..af796e3a5f 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -166,13 +166,31 @@ Value CtrlOp::getOutputQubit(const size_t i) { llvm::reportFatalUsageError("Invalid qubit index"); } -Value CtrlOp::getInputTarget(const size_t i) { return getTargetsIn()[i]; } +Value CtrlOp::getInputTarget(const size_t i) { + if (i >= getNumTargets()) { + llvm::reportFatalUsageError("Target index out of bounds"); + } + return getTargetsIn()[i]; +} -Value CtrlOp::getOutputTarget(const size_t i) { return getTargetsOut()[i]; } +Value CtrlOp::getOutputTarget(const size_t i) { + if (i >= getNumTargets()) { + llvm::reportFatalUsageError("Target index out of bounds"); + } + return getTargetsOut()[i]; +} -Value CtrlOp::getInputPosControl(const size_t i) { return getControlsIn()[i]; } +Value CtrlOp::getInputPosControl(const size_t i) { + if (i >= getNumPosControls()) { + llvm::reportFatalUsageError("Control index out of bounds"); + } + return getControlsIn()[i]; +} Value CtrlOp::getOutputPosControl(const size_t i) { + if (i >= getNumPosControls()) { + llvm::reportFatalUsageError("Control index out of bounds"); + } return getControlsOut()[i]; } diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 2b2ab1e954..fc99616101 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -129,7 +129,12 @@ Value CtrlOp::getTarget(const size_t i) { return getBodyUnitary().getTarget(i); } -Value CtrlOp::getPosControl(const size_t i) { return getControls()[i]; } +Value CtrlOp::getPosControl(const size_t i) { + if (i >= getNumPosControls()) { + llvm::reportFatalUsageError("Control index out of bounds"); + } + return getControls()[i]; +} Value CtrlOp::getNegControl(const size_t i) { return getBodyUnitary().getNegControl(i); From a0e54d5c9ed34f2176d3e516061bea5bec0a54c6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:22:26 +0100 Subject: [PATCH 325/419] Fix docstrings --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 5 ++--- mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 +- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 20756f7cc9..a88015a54b 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -248,7 +248,6 @@ class FluxProgramBuilder final : public OpBuilder { * @brief Apply a OP_CLASS \ * \ * @param PARAM Rotation angle in radians \ - * @return Reference to this builder for method chaining \ * \ * @par Example: \ * ```c++ \ @@ -972,11 +971,11 @@ class FluxProgramBuilder final : public OpBuilder { * @param controls Control qubits * @param targets Target qubits * @param body Function that builds the body containing the target operation - * @return Reference to this builder for method chaining + * @return Pair of (output_control_qubits, output_target_qubits) * * @par Example: * ```c++ - * controls_out, targets_out = builder.ctrl(q0_in, q1_in, [&](auto& b) { + * {controls_out, targets_out} = builder.ctrl(q0_in, q1_in, [&](auto& b) { * auto q1_res = b.x(q1_in); * return {q1_res}; * }); diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 8daaa579f2..bfddae9be0 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -167,7 +167,7 @@ LLVM::LLVMFuncOp getMainFunction(Operation* op); * - `qir_profiles`: base_profile * - `required_num_qubits`: Number of qubits used * - `required_num_results`: Number of measurement results - * - `qir_major_version`: 1 + * - `qir_major_version`: 2 * - `qir_minor_version`: 0 * - `dynamic_qubit_management`: true/false * - `dynamic_result_management`: true/false diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index e28744b59e..f91370f95d 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -312,7 +312,7 @@ class QuartzProgramBuilder final : public OpBuilder { * \ * @param control Control qubit \ * @param target Target qubit \ - * @return Reference to this builder for method chaining \ + * @return Reference to this builder for method chaining \ * \ * @par Example: \ * ```c++ \ From e7ac02034b2368e89b8261d4f01c78d5de6beac2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:30:43 +0100 Subject: [PATCH 326/419] Validate register size --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 8 ++++++++ mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 8 ++++++++ mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0a61c35a52..1c7d578307 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -80,6 +80,10 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + llvm::SmallVector qubits; qubits.reserve(static_cast(size)); @@ -99,6 +103,10 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, FluxProgramBuilder::ClassicalRegister& FluxProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + return allocatedClassicalRegisters.emplace_back(name, size); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 3f9d7b2ded..557ce4daab 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -114,6 +114,10 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + llvm::SmallVector qubits; qubits.reserve(size); @@ -127,6 +131,10 @@ QIRProgramBuilder::allocQubitRegister(const int64_t size) { QIRProgramBuilder::ClassicalRegister& QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, StringRef name) { + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + // Save current insertion point const OpBuilder::InsertionGuard insertGuard(builder); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 9615c630d4..a6ca8df096 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -73,6 +73,10 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + // Allocate a sequence of qubits with register metadata llvm::SmallVector qubits; qubits.reserve(size); @@ -93,6 +97,10 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, QuartzProgramBuilder::ClassicalRegister& QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { + if (size <= 0) { + llvm::reportFatalUsageError("Size must be positive"); + } + return allocatedClassicalRegisters.emplace_back(name, size); } From cccac6f0c1e492bde629b7e3703c28e65890393a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:33:13 +0100 Subject: [PATCH 327/419] Validate static index --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 4 ++++ mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 4 ++++ mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 1c7d578307..15b7356c69 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -67,6 +67,10 @@ Value FluxProgramBuilder::allocQubit() { } Value FluxProgramBuilder::staticQubit(const int64_t index) { + if (index < 0) { + llvm::reportFatalUsageError("Index must be non-negative"); + } + auto indexAttr = getI64IntegerAttr(index); auto staticOp = create(loc, indexAttr); const auto qubit = staticOp.getQubit(); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 557ce4daab..bfdf1d7c2e 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -94,6 +94,10 @@ void QIRProgramBuilder::initialize() { } Value QIRProgramBuilder::staticQubit(const int64_t index) { + if (index < 0) { + llvm::reportFatalUsageError("Index must be non-negative"); + } + // Check cache Value val{}; if (const auto it = ptrCache.find(index); it != ptrCache.end()) { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index a6ca8df096..414101964f 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -64,6 +64,10 @@ Value QuartzProgramBuilder::allocQubit() { } Value QuartzProgramBuilder::staticQubit(const int64_t index) { + if (index < 0) { + llvm::reportFatalUsageError("Index must be non-negative"); + } + // Create the StaticOp with the given index auto indexAttr = getI64IntegerAttr(index); auto staticOp = create(loc, indexAttr); From 25b8805dc0b2e073231c2177371d833998311e5b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:39:50 +0100 Subject: [PATCH 328/419] Use inline constexpr instead of static constexpr --- .../include/mlir/Dialect/QIR/Utils/QIRUtils.h | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index bfddae9be0..12e802963d 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -19,23 +19,23 @@ namespace mlir::qir { // QIR function names -static constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; -static constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; -static constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; -static constexpr auto QIR_ARRAY_RECORD_OUTPUT = +inline constexpr auto QIR_INITIALIZE = "__quantum__rt__initialize"; +inline constexpr auto QIR_MEASURE = "__quantum__qis__mz__body"; +inline constexpr auto QIR_RECORD_OUTPUT = "__quantum__rt__result_record_output"; +inline constexpr auto QIR_ARRAY_RECORD_OUTPUT = "__quantum__rt__array_record_output"; -static constexpr auto QIR_RESET = "__quantum__qis__reset__body"; +inline constexpr auto QIR_RESET = "__quantum__qis__reset__body"; -static constexpr auto QIR_GPHASE = "__quantum__qis__gphase__body"; +inline constexpr auto QIR_GPHASE = "__quantum__qis__gphase__body"; #define ADD_STANDARD_GATE(NAME_BIG, NAME_SMALL) \ - static constexpr auto QIR_##NAME_BIG = \ + inline constexpr auto QIR_##NAME_BIG = \ "__quantum__qis__" #NAME_SMALL "__body"; \ - static constexpr auto QIR_C##NAME_BIG = \ + inline constexpr auto QIR_C##NAME_BIG = \ "__quantum__qis__c" #NAME_SMALL "__body"; \ - static constexpr auto QIR_CC##NAME_BIG = \ + inline constexpr auto QIR_CC##NAME_BIG = \ "__quantum__qis__cc" #NAME_SMALL "__body"; \ - static constexpr auto QIR_CCC##NAME_BIG = \ + inline constexpr auto QIR_CCC##NAME_BIG = \ "__quantum__qis__ccc" #NAME_SMALL "__body"; ADD_STANDARD_GATE(I, i) From c2513ecabfd089fb92f2bde2a1edc45ab3a95c9a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:52:04 +0100 Subject: [PATCH 329/419] Include headers in header file --- mlir/include/mlir/Dialect/Utils/Utils.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 70dc6fbb90..e0ab6f9cba 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -10,6 +10,9 @@ #pragma once +#include +#include +#include #include namespace mlir::utils { From b53ccd697f96560db416dcd244c2bf2970eefbf2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:56:22 +0100 Subject: [PATCH 330/419] Streamline Flux helpers --- mlir/include/mlir/Dialect/Flux/FluxUtils.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/Flux/FluxUtils.h index cf2ef80bc6..15e7b45654 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/Flux/FluxUtils.h @@ -35,7 +35,7 @@ removeInversePairOneTargetZeroParameter(OpType op, return failure(); } - // Remove both operations + // Trivialize both operations rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); rewriter.replaceOp(op, op.getInputQubit(0)); @@ -66,7 +66,7 @@ removeInversePairTwoTargetZeroParameter(OpType op, return failure(); } - // Remove both operations + // Trivialize both operations rewriter.replaceOp(prevOp, {prevOp.getInputQubit(0), prevOp.getInputQubit(1)}); rewriter.replaceOp(op, {op.getInputQubit(0), op.getInputQubit(1)}); @@ -103,6 +103,7 @@ mergeOneTargetZeroParameter(OpType op, mlir::PatternRewriter& rewriter) { // Trivialize predecessor rewriter.replaceOp(prevOp, prevOp.getInputQubit(0)); + return success(); } @@ -197,6 +198,7 @@ removeTrivialOneTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return failure(); } + // Trivialize operation rewriter.replaceOp(op, op.getInputQubit(0)); return success(); @@ -223,6 +225,7 @@ removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return failure(); } + // Trivialize operation rewriter.replaceOp(op, {op.getInputQubit(0), op.getInputQubit(1)}); return success(); From b1d352ad63a7adf39b559eaa7157f8a4865ee22e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:01:19 +0100 Subject: [PATCH 331/419] Clarify insertion guard names --- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index bfdf1d7c2e..56ec4b6b0b 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -255,7 +255,7 @@ void QIRProgramBuilder::createCallOp( const ValueRange controls, const SmallVector& targets, StringRef fnName) { // Save current insertion point - const OpBuilder::InsertionGuard entryGuard(builder); + const OpBuilder::InsertionGuard insertGuard(builder); // Insert constants in entry block builder.setInsertionPoint(entryBlock->getTerminator()); @@ -277,7 +277,7 @@ void QIRProgramBuilder::createCallOp( } // Save current insertion point - const OpBuilder::InsertionGuard bodyGuard(builder); + const OpBuilder::InsertionGuard entryBlockGuard(builder); // Insert in body block (before branch) builder.setInsertionPoint(bodyBlock->getTerminator()); From dcb2b080d29bfa4f5395485ad4fecfd71caead1b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:20:37 +0100 Subject: [PATCH 332/419] Mark command-line options as static instead of const --- mlir/tools/mqt-cc/mqt-cc.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 72d8b5bfac..b3b530066d 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -37,30 +37,30 @@ using namespace mlir; namespace { // Command-line options -const cl::opt INPUT_FILENAME(cl::Positional, +static cl::opt InputFilename(cl::Positional, cl::desc(""), cl::init("-")); -const cl::opt OUTPUT_FILENAME("o", cl::desc("Output filename"), +static cl::opt OutputFilename("o", cl::desc("Output filename"), cl::value_desc("filename"), cl::init("-")); -const cl::opt CONVERT_TO_QIR("emit-qir", - cl::desc("Convert to QIR at the end"), - cl::init(false)); +static cl::opt ConvertToQIR("emit-qir", + cl::desc("Convert to QIR at the end"), + cl::init(false)); -const cl::opt ENABLE_TIMING("mlir-timing", +static cl::opt EnableTiming("mlir-timing", cl::desc("Enable pass timing statistics"), cl::init(false)); -const cl::opt ENABLE_STATISTICS("mlir-statistics", +static cl::opt EnableStatistics("mlir-statistics", cl::desc("Enable pass statistics"), cl::init(false)); -const cl::opt - PRINT_IR_AFTER_ALL_STAGES("mlir-print-ir-after-all-stages", - cl::desc("Print IR after each compiler stage"), - cl::init(false)); +static cl::opt + PrintIRAfterAllStages("mlir-print-ir-after-all-stages", + cl::desc("Print IR after each compiler stage"), + cl::init(false)); /** * @brief Load and parse a .mlir file From 4f2b5353e2ac804ec95bc8aef2f9dfebc91b2151 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:25:07 +0100 Subject: [PATCH 333/419] Add comment to explain handling of nested CtrlOps --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index 1071a7b961..b655b49220 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -1088,7 +1088,8 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { auto fluxOp = rewriter.create(op.getLoc(), fluxControls, fluxTargets); - // Update state map + // Update state map if this is a top-level CtrlOp + // Nested CtrlOps are managed via the targetsIn and targetsOut maps if (state.inCtrlOp == 0) { for (const auto& [quartzControl, fluxControl] : llvm::zip(quartzControls, fluxOp.getControlsOut())) { From cca99596d4872a57230bd004cc0cdd12ba3d1b2e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:32:36 +0100 Subject: [PATCH 334/419] Make use of getParameter() to improve readability of conversions --- .../Conversion/FluxToQuartz/FluxToQuartz.cpp | 16 ++++----- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 34 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp index 5bfad96257..97e5448ed2 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp @@ -47,7 +47,7 @@ template LogicalResult convertZeroTargetOneParameter(FluxOpType& op, ConversionPatternRewriter& rewriter) { - rewriter.create(op.getLoc(), op.getOperand()); + rewriter.create(op.getLoc(), op.getParameter(0)); rewriter.eraseOp(op); return success(); } @@ -100,7 +100,7 @@ convertOneTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, const auto& quartzQubit = adaptor.getQubitIn(); // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit, op.getOperand(1)); + rewriter.create(op.getLoc(), quartzQubit, op.getParameter(0)); // Replace the output qubit with the same Quartz reference rewriter.replaceOp(op, quartzQubit); @@ -128,8 +128,8 @@ convertOneTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, const auto& quartzQubit = adaptor.getQubitIn(); // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit, op.getOperand(1), - op.getOperand(2)); + rewriter.create(op.getLoc(), quartzQubit, op.getParameter(0), + op.getParameter(1)); // Replace the output qubit with the same Quartz reference rewriter.replaceOp(op, quartzQubit); @@ -157,8 +157,8 @@ convertOneTargetThreeParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, const auto& quartzQubit = adaptor.getQubitIn(); // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit, op.getOperand(1), - op.getOperand(2), op.getOperand(3)); + rewriter.create(op.getLoc(), quartzQubit, op.getParameter(0), + op.getParameter(1), op.getParameter(2)); // Replace the output qubit with the same Quartz reference rewriter.replaceOp(op, quartzQubit); @@ -217,7 +217,7 @@ convertTwoTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, // Create the Quartz operation (in-place, no result) rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1, - op.getOperand(2)); + op.getParameter(0)); // Replace the output qubits with the same Quartz references rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); @@ -247,7 +247,7 @@ convertTwoTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, // Create the Quartz operation (in-place, no result) rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1, - op.getOperand(2), op.getOperand(3)); + op.getParameter(0), op.getParameter(1)); // Replace the output qubits with the same Quartz references rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index b655b49220..d49ff00383 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -124,7 +124,7 @@ LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, LoweringState& state) { const auto inCtrlOp = state.inCtrlOp; - rewriter.create(op.getLoc(), op.getOperand()); + rewriter.create(op.getLoc(), op.getParameter(0)); // Update the state if (inCtrlOp != 0) { @@ -156,7 +156,7 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); + const auto quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -199,7 +199,7 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); + const auto quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -209,7 +209,7 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, // Create the Flux operation (consumes input, produces output) auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, op->getOperand(1)); + rewriter.create(op.getLoc(), fluxQubit, op.getParameter(0)); // Update the state map if (inCtrlOp == 0) { @@ -243,7 +243,7 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); + const auto quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -253,7 +253,7 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, // Create the Flux operation (consumes input, produces output) auto fluxOp = rewriter.create( - op.getLoc(), fluxQubit, op->getOperand(1), op->getOperand(2)); + op.getLoc(), fluxQubit, op.getParameter(0), op.getParameter(1)); // Update the state map if (inCtrlOp == 0) { @@ -288,7 +288,7 @@ convertOneTargetThreeParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op->getOperand(0); + const auto quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -298,8 +298,8 @@ convertOneTargetThreeParameter(QuartzOpType& op, // Create the Flux operation (consumes input, produces output) auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, op->getOperand(1), - op->getOperand(2), op->getOperand(3)); + rewriter.create(op.getLoc(), fluxQubit, op.getParameter(0), + op.getParameter(1), op.getParameter(2)); // Update the state map if (inCtrlOp == 0) { @@ -333,8 +333,8 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubits - const auto quartzQubit0 = op->getOperand(0); - const auto quartzQubit1 = op->getOperand(1); + const auto quartzQubit0 = op.getQubit0In(); + const auto quartzQubit1 = op.getQubit1In(); Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { @@ -384,8 +384,8 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubits - const auto quartzQubit0 = op->getOperand(0); - const auto quartzQubit1 = op->getOperand(1); + const auto quartzQubit0 = op.getQubit0In(); + const auto quartzQubit1 = op.getQubit1In(); Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { @@ -399,7 +399,7 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, // Create the Flux operation (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1, - op->getOperand(2)); + op.getParameter(0)); // Update state map if (inCtrlOp == 0) { @@ -435,8 +435,8 @@ LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubits - const auto quartzQubit0 = op->getOperand(0); - const auto quartzQubit1 = op->getOperand(1); + const auto quartzQubit0 = op.getQubit0In(); + const auto quartzQubit1 = op.getQubit1In(); Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { @@ -451,7 +451,7 @@ LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, // Create the Flux operation (consumes input, produces output) auto fluxOp = rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1, - op->getOperand(2), op->getOperand(3)); + op.getParameter(0), op.getParameter(1)); // Update state map if (inCtrlOp == 0) { From 998242c0adaba59bf2b1a756aabee9ba5a78343f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:05:13 +0100 Subject: [PATCH 335/419] Improve documentation of compiler pipeline --- mlir/lib/Compiler/CompilerPipeline.cpp | 51 +++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 6fad74f4c2..2af0f8c06d 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -89,24 +89,31 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, configurePassManager(pm); // Determine total number of stages for progress indication - auto totalStages = - 8; // Base stages: Initial + Flux + FluxCanon + Optimization + - // OptimizationCanon + QuartzBack + QuartzCanon + // 1. Quartz import + // 2. Quartz canonicalization + // 3. Quartz-to-Flux conversion + // 4. Flux canonicalization + // 5. Optimization passes + // 6. Flux canonicalization + // 7. Flux-to-Quartz conversion + // 8. Quartz canonicalization + // 9. Quartz-to-QIR conversion (optional) + // 10. QIR canonicalization (optional) + auto totalStages = 8; if (config_.convertToQIR) { - totalStages += 2; // QIR + QIRCanon + totalStages += 2; } auto currentStage = 0; - // Record initial state if requested + // Stage 1: Quartz import if (record != nullptr && config_.recordIntermediates) { record->afterQuartzImport = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Quartz Import (Initial)", ++currentStage, - totalStages); + prettyPrintStage(module, "Quartz Import", ++currentStage, totalStages); } } - // Stage 1: Initial canonicalization + // Stage 2: Quartz canonicalization addCleanupPasses(pm); if (pm.run(module).failed()) { return failure(); @@ -114,13 +121,13 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterInitialCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Initial Canonicalization", ++currentStage, - totalStages); + prettyPrintStage(module, "Initial Quartz Canonicalization", + ++currentStage, totalStages); } } pm.clear(); - // Stage 2: Convert to Flux + // Stage 3: Quartz-to-Flux conversion pm.addPass(createQuartzToFlux()); if (failed(pm.run(module))) { return failure(); @@ -134,7 +141,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } pm.clear(); - // Stage 3: Canonicalize Flux + // Stage 4: Flux canonicalization addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); @@ -142,13 +149,13 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterFluxCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Flux Canonicalization", ++currentStage, + prettyPrintStage(module, "Initial Flux Canonicalization", ++currentStage, totalStages); } } pm.clear(); - // Stage 4: Optimization passes + // Stage 5: Optimization passes // TODO: Add optimization passes addCleanupPasses(pm); if (failed(pm.run(module))) { @@ -163,7 +170,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } pm.clear(); - // Stage 5: Canonicalize after optimization + // Stage 6: Flux canonicalization addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); @@ -171,13 +178,13 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterOptimizationCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Post-Optimization Canonicalization", - ++currentStage, totalStages); + prettyPrintStage(module, "Final Flux Canonicalization", ++currentStage, + totalStages); } } pm.clear(); - // Stage 6: Convert back to Quartz + // Stage 7: Flux-to-Quartz conversion pm.addPass(createFluxToQuartz()); if (failed(pm.run(module))) { return failure(); @@ -191,7 +198,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } pm.clear(); - // Stage 7: Canonicalize Quartz + // Stage 8: Quartz canonicalization addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); @@ -205,7 +212,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } pm.clear(); - // Stage 8: Optional QIR conversion + // Stage 9: Quartz-to-QIR conversion (optional) if (config_.convertToQIR) { pm.addPass(createQuartzToQIR()); if (failed(pm.run(module))) { @@ -220,7 +227,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } pm.clear(); - // Final canonicalization + // Stage 10: QIR canonicalization (optional) addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); @@ -228,7 +235,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterQIRCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Final QIR Canonicalization", ++currentStage, + prettyPrintStage(module, "QIR Canonicalization", ++currentStage, totalStages); } } From 481bc80184fa65107ac10649273cd8066f55ebae Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:46:40 +0100 Subject: [PATCH 336/419] Revert "Mark command-line options as static instead of const" This reverts commit dcb2b080d29bfa4f5395485ad4fecfd71caead1b. --- mlir/tools/mqt-cc/mqt-cc.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index b3b530066d..72d8b5bfac 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -37,30 +37,30 @@ using namespace mlir; namespace { // Command-line options -static cl::opt InputFilename(cl::Positional, +const cl::opt INPUT_FILENAME(cl::Positional, cl::desc(""), cl::init("-")); -static cl::opt OutputFilename("o", cl::desc("Output filename"), +const cl::opt OUTPUT_FILENAME("o", cl::desc("Output filename"), cl::value_desc("filename"), cl::init("-")); -static cl::opt ConvertToQIR("emit-qir", - cl::desc("Convert to QIR at the end"), - cl::init(false)); +const cl::opt CONVERT_TO_QIR("emit-qir", + cl::desc("Convert to QIR at the end"), + cl::init(false)); -static cl::opt EnableTiming("mlir-timing", +const cl::opt ENABLE_TIMING("mlir-timing", cl::desc("Enable pass timing statistics"), cl::init(false)); -static cl::opt EnableStatistics("mlir-statistics", +const cl::opt ENABLE_STATISTICS("mlir-statistics", cl::desc("Enable pass statistics"), cl::init(false)); -static cl::opt - PrintIRAfterAllStages("mlir-print-ir-after-all-stages", - cl::desc("Print IR after each compiler stage"), - cl::init(false)); +const cl::opt + PRINT_IR_AFTER_ALL_STAGES("mlir-print-ir-after-all-stages", + cl::desc("Print IR after each compiler stage"), + cl::init(false)); /** * @brief Load and parse a .mlir file From 218cb98ae33fd2ec6b9b95d030509d91d0809c27 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:48:03 +0100 Subject: [PATCH 337/419] Further improve null safety in QIRUtils.cpp --- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index cd6ee64105..a6d99db62b 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -128,6 +128,9 @@ LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, if (!module) { module = op->getParentOfType(); } + if (!module) { + llvm::reportFatalInternalError("Module not found"); + } builder.setInsertionPointToStart(module.getBody()); const auto symbolName = From bebe71b641adee58439a12d365ef7734f32d7f07 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:51:06 +0100 Subject: [PATCH 338/419] =?UTF-8?q?Do=20not=20explicitly=C2=A0erase=20body?= =?UTF-8?q?=20CtrlOp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 1 - mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index af796e3a5f..1491cb910c 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -50,7 +50,6 @@ struct MergeNestedCtrl final : OpRewritePattern { rewriter.replaceOpWithNewOp(op, newControls, op.getTargetsIn(), bodyCtrlOp.getBodyUnitary()); - rewriter.eraseOp(bodyCtrlOp); return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index fc99616101..70675c8435 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -48,7 +48,6 @@ struct MergeNestedCtrl final : OpRewritePattern { rewriter.replaceOpWithNewOp(ctrlOp, newControls, bodyCtrlOp.getBodyUnitary()); - rewriter.eraseOp(bodyCtrlOp); return success(); } From 448feea4701823985960cfb5464ea02b6af80d86 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:36:26 +0100 Subject: [PATCH 339/419] Fix RemoveTrivialCtrl patterns --- .../Conversion/QuartzToQIR/QuartzToQIR.cpp | 2 +- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 8 +++++++- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 12 +++++------ mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 4 ++-- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 20 ++++++++++++------- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 8800849bfe..878d637d8e 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -989,7 +989,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { auto* fnDecl = SymbolTable::lookupNearestSymbolFrom( main, builder.getStringAttr(QIR_INITIALIZE)); if (fnDecl == nullptr) { - const PatternRewriter::InsertionGuard insertGuard(builder); + const PatternRewriter::InsertionGuard guard(builder); builder.setInsertionPointToEnd(moduleOp.getBody()); auto fnSignature = LLVM::LLVMFunctionType::get( LLVM::LLVMVoidType::get(ctx), LLVM::LLVMPointerType::get(ctx)); diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 1491cb910c..e0b3d4d79c 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -66,7 +66,13 @@ struct RemoveTrivialCtrl final : OpRewritePattern { if (op.getNumPosControls() > 0) { return failure(); } - rewriter.replaceOp(op, op.getBodyUnitary()); + + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(op); + + auto* clonedBody = rewriter.clone(*op.getBodyUnitary().getOperation()); + rewriter.replaceOp(op, clonedBody->getResults()); + return success(); } }; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 56ec4b6b0b..f0ba93a63f 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -140,7 +140,7 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, } // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); @@ -164,7 +164,7 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); @@ -203,7 +203,7 @@ Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, const Bit& bit) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); @@ -230,7 +230,7 @@ QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); @@ -255,7 +255,7 @@ void QIRProgramBuilder::createCallOp( const ValueRange controls, const SmallVector& targets, StringRef fnName) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Insert constants in entry block builder.setInsertionPoint(entryBlock->getTerminator()); @@ -552,7 +552,7 @@ void QIRProgramBuilder::generateOutputRecording() { } // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Insert in output block (before return) builder.setInsertionPoint(outputBlock->getTerminator()); diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index a6d99db62b..6e8a3a917b 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -94,7 +94,7 @@ LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, if (fnDecl == nullptr) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Create the declaration at the end of the module auto module = dyn_cast(op); @@ -121,7 +121,7 @@ LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, const StringRef label, const StringRef symbolPrefix) { // Save current insertion point - const OpBuilder::InsertionGuard insertGuard(builder); + const OpBuilder::InsertionGuard guard(builder); // Create the declaration at the start of the module auto module = dyn_cast(op); diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 70675c8435..0c379e38cd 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -33,20 +33,20 @@ namespace { */ struct MergeNestedCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, + LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto bodyUnitary = ctrlOp.getBodyUnitary(); + auto bodyUnitary = op.getBodyUnitary(); auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); if (!bodyCtrlOp) { return failure(); } - llvm::SmallVector newControls(ctrlOp.getControls()); + llvm::SmallVector newControls(op.getControls()); for (const auto control : bodyCtrlOp.getControls()) { newControls.push_back(control); } - rewriter.replaceOpWithNewOp(ctrlOp, newControls, + rewriter.replaceOpWithNewOp(op, newControls, bodyCtrlOp.getBodyUnitary()); return success(); @@ -58,12 +58,18 @@ struct MergeNestedCtrl final : OpRewritePattern { */ struct RemoveTrivialCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(CtrlOp ctrlOp, + LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - if (ctrlOp.getNumPosControls() > 0) { + if (op.getNumPosControls() > 0) { return failure(); } - rewriter.replaceOp(ctrlOp, ctrlOp.getBodyUnitary()); + + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(op); + + auto* clonedBody = rewriter.clone(*op.getBodyUnitary().getOperation()); + rewriter.replaceOp(op, clonedBody->getResults()); + return success(); } }; From bd42b36d1df9c85b087778c840045ec416ffad0e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:55:31 +0100 Subject: [PATCH 340/419] Use op.emitError() instead of llvm::errs() --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 878d637d8e..68304f528a 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -248,8 +248,7 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { // The pointer was created by the step below const auto globalIndex = it->second + registerIndex; if (!ptrMap.contains(globalIndex)) { - llvm::errs() << "Pointer not found.\n"; - return failure(); + return op.emitError("Pointer not found"); } rewriter.replaceOp(op, ptrMap.at(globalIndex)); return success(); @@ -514,8 +513,7 @@ struct ConvertQuartzGPhaseOpQIR final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); if (state.inCtrlOp != 0) { - llvm::errs() << "Controlled GPhaseOps cannot be converted to QIR.\n"; - return failure(); + return op.emitError("Controlled GPhaseOps cannot be converted to QIR"); } return convertUnitaryToCallOp(op, adaptor, rewriter, getContext(), state, QIR_GPHASE, 0, 1); From 282dca87e43482d1f1b6a075f7c670704e4782b0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:00:33 +0100 Subject: [PATCH 341/419] Do not using OpConversionPattern --- mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index d49ff00383..abf7e908e2 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -94,7 +94,6 @@ struct LoweringState { */ template class StatefulOpConversionPattern : public OpConversionPattern { - using OpConversionPattern::OpConversionPattern; public: StatefulOpConversionPattern(TypeConverter& typeConverter, From c28b7794440bdd78f731b642587f4cd7379a5cf5 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:05:38 +0100 Subject: [PATCH 342/419] Validate result index --- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index f0ba93a63f..244a1cf8d7 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -163,6 +163,10 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, } Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { + if (resultIndex < 0) { + llvm::reportFatalUsageError("Result index must be non-negative"); + } + // Save current insertion point const OpBuilder::InsertionGuard guard(builder); From af4ed3cff083c06621b41937528be950d73a167a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:38:03 +0100 Subject: [PATCH 343/419] Fix docstring --- mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 12e802963d..8552821447 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -163,7 +163,7 @@ LLVM::LLVMFuncOp getMainFunction(Operation* op); * @details * Adds the required metadata attributes for QIR base profile compliance: * - `entry_point`: Marks the main entry point function - * - `output_labeling_schema`: schema_id + * - `output_labeling_schema`: labeled * - `qir_profiles`: base_profile * - `required_num_qubits`: Number of qubits used * - `required_num_results`: Number of measurement results From 171367293ac3ce8846a154abef62f08069f80c60 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:57 +0100 Subject: [PATCH 344/419] Add unit test for nested CtrlOps --- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 1 + .../pipeline/test_compiler_pipeline.cpp | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 0c379e38cd..b3a1f0ff48 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -171,6 +171,7 @@ void CtrlOp::build(OpBuilder& builder, OperationState& state, state.addOperands(controls); auto* region = state.addRegion(); auto& block = region->emplaceBlock(); + builder.setInsertionPointToStart(&block); bodyBuilder(builder); builder.create(state.location); diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 51fe8bc36d..25bb9bf8cc 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1579,6 +1579,38 @@ TEST_F(CompilerPipelineTest, MCX) { }); } +TEST_F(CompilerPipelineTest, MCXNested) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.ctrl(reg[0], [&](OpBuilder& b) { + static_cast(b).cx(reg[1], reg[2]); + }); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(3, "q"); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(3); + b.mcx({reg[0], reg[1]}, reg[2]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, Y) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From fc1c4a4bc6e13f88b1127b61b1e85021805e7959 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:02:23 +0100 Subject: [PATCH 345/419] Include more headers in header files --- mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h | 1 + mlir/include/mlir/Dialect/Utils/Utils.h | 1 + 2 files changed, 2 insertions(+) diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 8552821447..02725f5a28 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -12,6 +12,7 @@ #include #include +#include #include #include diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index e0ab6f9cba..4b5f46ee23 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace mlir::utils { From 5bfe41a28b100a578c17799bd8419d08b3da641c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:59:30 +0100 Subject: [PATCH 346/419] Simply erase operation in RemoveTrivialCtrl pattern for Quartz --- mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index b3a1f0ff48..aea3490d64 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -67,8 +67,8 @@ struct RemoveTrivialCtrl final : OpRewritePattern { const OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(op); - auto* clonedBody = rewriter.clone(*op.getBodyUnitary().getOperation()); - rewriter.replaceOp(op, clonedBody->getResults()); + rewriter.clone(*op.getBodyUnitary().getOperation()); + rewriter.eraseOp(op); return success(); } From d342a2766b64ef967a5384f794dca16c95085b13 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:06:51 +0100 Subject: [PATCH 347/419] Remove const from Value and ValueRange --- .../mlir/Dialect/Flux/IR/FluxDialect.h | 2 +- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 4 +- .../mlir/Dialect/Quartz/IR/QuartzDialect.h | 2 +- .../Flux/Builder/FluxProgramBuilder.cpp | 150 +++++++++--------- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 10 +- .../IR/Operations/StandardGates/BarrierOp.cpp | 8 +- .../Flux/IR/Operations/StandardGates/POp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/ROp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/RXOp.cpp | 2 +- .../IR/Operations/StandardGates/RXXOp.cpp | 5 +- .../Flux/IR/Operations/StandardGates/RYOp.cpp | 2 +- .../IR/Operations/StandardGates/RYYOp.cpp | 5 +- .../Flux/IR/Operations/StandardGates/RZOp.cpp | 2 +- .../IR/Operations/StandardGates/RZXOp.cpp | 5 +- .../IR/Operations/StandardGates/RZZOp.cpp | 5 +- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 2 +- .../Flux/IR/Operations/StandardGates/UOp.cpp | 2 +- .../Operations/StandardGates/XXMinusYYOp.cpp | 2 +- .../Operations/StandardGates/XXPlusYYOp.cpp | 2 +- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 82 +++++----- .../Quartz/Builder/QuartzProgramBuilder.cpp | 4 +- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 4 +- .../IR/Operations/StandardGates/POp.cpp | 2 +- .../IR/Operations/StandardGates/ROp.cpp | 2 +- .../IR/Operations/StandardGates/RXOp.cpp | 2 +- .../IR/Operations/StandardGates/RXXOp.cpp | 5 +- .../IR/Operations/StandardGates/RYOp.cpp | 2 +- .../IR/Operations/StandardGates/RYYOp.cpp | 5 +- .../IR/Operations/StandardGates/RZOp.cpp | 2 +- .../IR/Operations/StandardGates/RZXOp.cpp | 5 +- .../IR/Operations/StandardGates/RZZOp.cpp | 5 +- .../IR/Operations/StandardGates/U2Op.cpp | 2 +- .../IR/Operations/StandardGates/UOp.cpp | 2 +- .../Operations/StandardGates/XXMinusYYOp.cpp | 2 +- .../Operations/StandardGates/XXPlusYYOp.cpp | 2 +- .../TranslateQuantumComputationToQuartz.cpp | 2 +- .../pipeline/test_compiler_pipeline.cpp | 4 +- 37 files changed, 166 insertions(+), 182 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h index debf233483..9d9db381e2 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h @@ -117,7 +117,7 @@ template class TargetAndParameterArityTrait { return this->getOperation()->getOperand(T + i); } - [[nodiscard]] static FloatAttr getStaticParameter(const Value param) { + [[nodiscard]] static FloatAttr getStaticParameter(Value param) { auto constantOp = param.getDefiningOp(); if (!constantOp) { return nullptr; diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 01166f9bac..a54b01ee67 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -837,8 +837,8 @@ class QIRProgramBuilder { * @param fnName Name of the QIR function to call */ void createCallOp(const SmallVector>& parameters, - const ValueRange controls, - const SmallVector& targets, StringRef fnName); + ValueRange controls, const SmallVector& targets, + StringRef fnName); /** * @brief Generate array-based output recording in the output block diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h index 78d07e6546..de2e0a3b55 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h @@ -108,7 +108,7 @@ template class TargetAndParameterArityTrait { return this->getOperation()->getOperand(T + i); } - [[nodiscard]] static FloatAttr getStaticParameter(const Value param) { + [[nodiscard]] static FloatAttr getStaticParameter(Value param) { auto constantOp = param.getDefiningOp(); if (!constantOp) { return nullptr; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 15b7356c69..e487aed6b2 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -118,7 +118,7 @@ FluxProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { // Linear Type Tracking Helpers //===----------------------------------------------------------------------===// -void FluxProgramBuilder::validateQubitValue(const Value qubit) const { +void FluxProgramBuilder::validateQubitValue(Value qubit) const { if (!validQubits.contains(qubit)) { llvm::errs() << "Attempting to use an invalid qubit SSA value. " << "The value may have been consumed by a previous operation " @@ -128,8 +128,8 @@ void FluxProgramBuilder::validateQubitValue(const Value qubit) const { } } -void FluxProgramBuilder::updateQubitTracking(const Value inputQubit, - const Value outputQubit) { +void FluxProgramBuilder::updateQubitTracking(Value inputQubit, + Value outputQubit) { // Validate the input qubit validateQubitValue(inputQubit); @@ -190,20 +190,19 @@ Value FluxProgramBuilder::reset(Value qubit) { create(loc, PARAM); \ } \ Value FluxProgramBuilder::c##OP_NAME( \ - const std::variant&(PARAM), const Value control) { \ - const auto [controlsOut, targetsOut] = \ - ctrl(control, {}, \ - [&](OpBuilder& b, const ValueRange /*targets*/) -> ValueRange { \ - b.create(loc, PARAM); \ - return {}; \ - }); \ + const std::variant&(PARAM), Value control) { \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, {}, [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ + b.create(loc, PARAM); \ + return {}; \ + }); \ return controlsOut[0]; \ } \ ValueRange FluxProgramBuilder::mc##OP_NAME( \ - const std::variant&(PARAM), const ValueRange controls) { \ + const std::variant&(PARAM), ValueRange controls) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {}, \ - [&](OpBuilder& b, const ValueRange /*targets*/) -> ValueRange { \ + [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ b.create(loc, PARAM); \ return {}; \ }); \ @@ -217,27 +216,26 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ - Value FluxProgramBuilder::OP_NAME(const Value qubit) { \ + Value FluxProgramBuilder::OP_NAME(Value qubit) { \ auto op = create(loc, qubit); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ } \ - std::pair FluxProgramBuilder::c##OP_NAME(const Value control, \ - const Value target) { \ - const auto [controlsOut, targetsOut] = \ - ctrl(control, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0]); \ - return op->getResults(); \ - }); \ + std::pair FluxProgramBuilder::c##OP_NAME(Value control, \ + Value target) { \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0]); \ + return op->getResults(); \ + }); \ return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ - const ValueRange controls, const Value target) { \ + ValueRange controls, Value target) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0]); \ return op->getResults(); \ }); \ @@ -262,29 +260,28 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ Value FluxProgramBuilder::OP_NAME(const std::variant&(PARAM), \ - const Value qubit) { \ + Value qubit) { \ auto op = create(loc, qubit, PARAM); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ } \ std::pair FluxProgramBuilder::c##OP_NAME( \ - const std::variant&(PARAM), const Value control, \ - const Value target) { \ - const auto [controlsOut, targetsOut] = \ - ctrl(control, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], PARAM); \ - return op->getResults(); \ - }); \ + const std::variant&(PARAM), Value control, \ + Value target) { \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], PARAM); \ + return op->getResults(); \ + }); \ return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ - const std::variant&(PARAM), const ValueRange controls, \ - const Value target) { \ + const std::variant&(PARAM), ValueRange controls, \ + Value target) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0], PARAM); \ return op->getResults(); \ }); \ @@ -303,7 +300,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ Value FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value qubit) { \ + const std::variant&(PARAM2), Value qubit) { \ auto op = create(loc, qubit, PARAM1, PARAM2); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ @@ -311,24 +308,22 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) } \ std::pair FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value control, \ - const Value target) { \ - const auto [controlsOut, targetsOut] = \ - ctrl(control, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ - const auto op = \ - b.create(loc, targets[0], PARAM1, PARAM2); \ - return op->getResults(); \ - }); \ + const std::variant&(PARAM2), Value control, \ + Value target) { \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = b.create(loc, targets[0], PARAM1, PARAM2); \ + return op->getResults(); \ + }); \ return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const ValueRange controls, \ - const Value target) { \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ b.create(loc, targets[0], PARAM1, PARAM2); \ return op->getResults(); \ @@ -348,7 +343,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) Value FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ - const std::variant&(PARAM3), const Value qubit) { \ + const std::variant&(PARAM3), Value qubit) { \ auto op = create(loc, qubit, PARAM1, PARAM2, PARAM3); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ @@ -357,25 +352,24 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) std::pair FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ - const std::variant&(PARAM3), const Value control, \ - const Value target) { \ - const auto [controlsOut, targetsOut] = \ - ctrl(control, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], PARAM1, \ - PARAM2, PARAM3); \ - return op->getResults(); \ - }); \ + const std::variant&(PARAM3), Value control, \ + Value target) { \ + const auto [controlsOut, targetsOut] = ctrl( \ + control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ + const auto op = \ + b.create(loc, targets[0], PARAM1, PARAM2, PARAM3); \ + return op->getResults(); \ + }); \ return {controlsOut[0], targetsOut[0]}; \ } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ - const std::variant&(PARAM3), const ValueRange controls, \ - const Value target) { \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0], PARAM1, \ PARAM2, PARAM3); \ return op->getResults(); \ @@ -400,10 +394,10 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) return {qubit0Out, qubit1Out}; \ } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ - const Value control, Value qubit0, Value qubit1) { \ + Value control, Value qubit0, Value qubit1) { \ const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ b.create(loc, targets[0], targets[1]); \ return op->getResults(); \ @@ -411,11 +405,11 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ - FluxProgramBuilder::mc##OP_NAME(const ValueRange controls, Value qubit0, \ + FluxProgramBuilder::mc##OP_NAME(ValueRange controls, Value qubit0, \ Value qubit1) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ b.create(loc, targets[0], targets[1]); \ return op->getResults(); \ @@ -443,11 +437,11 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) return {qubit0Out, qubit1Out}; \ } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ - const std::variant&(PARAM), const Value control, \ - Value qubit0, Value qubit1) { \ + const std::variant&(PARAM), Value control, Value qubit0, \ + Value qubit1) { \ const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ b.create(loc, targets[0], targets[1], PARAM); \ return op->getResults(); \ @@ -456,11 +450,11 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) } \ std::pair> \ FluxProgramBuilder::mc##OP_NAME( \ - const std::variant&(PARAM), \ - const ValueRange controls, Value qubit0, Value qubit1) { \ + const std::variant&(PARAM), ValueRange controls, \ + Value qubit0, Value qubit1) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ b.create(loc, targets[0], targets[1], PARAM); \ return op->getResults(); \ @@ -491,11 +485,11 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value control, \ - Value qubit0, Value qubit1) { \ + const std::variant&(PARAM2), Value control, Value qubit0, \ + Value qubit1) { \ const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0], targets[1], \ PARAM1, PARAM2); \ return op->getResults(); \ @@ -505,11 +499,11 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) std::pair> \ FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - const ValueRange controls, Value qubit0, Value qubit1) { \ + const std::variant&(PARAM2), ValueRange controls, \ + Value qubit0, Value qubit1) { \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ - [&](OpBuilder& b, const ValueRange targets) -> ValueRange { \ + [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0], targets[1], \ PARAM1, PARAM2); \ return op->getResults(); \ @@ -538,7 +532,7 @@ ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { //===----------------------------------------------------------------------===// std::pair FluxProgramBuilder::ctrl( - const ValueRange controls, const ValueRange targets, + ValueRange controls, ValueRange targets, const std::function& body) { auto ctrlOp = create(loc, controls, targets, body); diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index e0b3d4d79c..77e96c3d79 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -207,7 +207,7 @@ Value CtrlOp::getOutputNegControl(const size_t i) { return getBodyUnitary().getOutputNegControl(i); } -Value CtrlOp::getInputForOutput(const Value output) { +Value CtrlOp::getInputForOutput(Value output) { for (size_t i = 0; i < getNumPosControls(); ++i) { if (output == getControlsOut()[i]) { return getControlsIn()[i]; @@ -221,7 +221,7 @@ Value CtrlOp::getInputForOutput(const Value output) { llvm::reportFatalUsageError("Given qubit is not an output of the operation"); } -Value CtrlOp::getOutputForInput(const Value input) { +Value CtrlOp::getOutputForInput(Value input) { for (size_t i = 0; i < getNumPosControls(); ++i) { if (input == getControlsIn()[i]) { return getControlsOut()[i]; @@ -242,7 +242,7 @@ Value CtrlOp::getParameter(const size_t i) { } void CtrlOp::build(OpBuilder& builder, OperationState& state, - const ValueRange controls, const ValueRange targets, + ValueRange controls, ValueRange targets, UnitaryOpInterface bodyUnitary) { build(builder, state, controls, targets); auto& block = state.regions.front()->emplaceBlock(); @@ -255,8 +255,8 @@ void CtrlOp::build(OpBuilder& builder, OperationState& state, } void CtrlOp::build( - OpBuilder& builder, OperationState& state, const ValueRange controls, - const ValueRange targets, + OpBuilder& builder, OperationState& state, ValueRange controls, + ValueRange targets, const std::function& bodyBuilder) { build(builder, state, controls, targets); auto& block = state.regions.front()->emplaceBlock(); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index dfde2fef31..eb4712e430 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -120,7 +120,7 @@ Value BarrierOp::getOutputNegControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } -Value BarrierOp::getInputForOutput(const Value output) { +Value BarrierOp::getInputForOutput(Value output) { for (size_t i = 0; i < getNumTargets(); ++i) { if (output == getQubitsOut()[i]) { return getQubitsIn()[i]; @@ -129,7 +129,7 @@ Value BarrierOp::getInputForOutput(const Value output) { llvm::reportFatalUsageError("Given qubit is not an output of the operation"); } -Value BarrierOp::getOutputForInput(const Value input) { +Value BarrierOp::getOutputForInput(Value input) { for (size_t i = 0; i < getNumTargets(); ++i) { if (input == getQubitsIn()[i]) { return getQubitsOut()[i]; @@ -145,10 +145,10 @@ Value BarrierOp::getParameter(const size_t /*i*/) { } void BarrierOp::build(OpBuilder& builder, OperationState& state, - const ValueRange qubits) { + ValueRange qubits) { SmallVector resultTypes; resultTypes.reserve(qubits.size()); - for (const Value qubit : qubits) { + for (Value qubit : qubits) { resultTypes.push_back(qubit.getType()); } build(builder, state, resultTypes, qubits); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index 92bd0f0f13..3e62348fd8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -52,7 +52,7 @@ struct RemoveTrivialP final : OpRewritePattern { } // namespace -void POp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index a5cc3d6609..8c878b8c54 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -81,7 +81,7 @@ struct ReplaceRWithRY final : OpRewritePattern { } // namespace -void ROp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi) { const auto& thetaOperand = variantToValue(builder, state, theta); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index fbe3eb12c6..ec43aef6dc 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -52,7 +52,7 @@ struct RemoveTrivialRX final : OpRewritePattern { } // namespace -void RXOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index 56a50e8b44..a6cb7f92ed 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -52,9 +52,8 @@ struct RemoveTrivialRXX final : OpRewritePattern { } // namespace -void RXXOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index de8be4ad73..91c69fae73 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -52,7 +52,7 @@ struct RemoveTrivialRY final : OpRewritePattern { } // namespace -void RYOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index 6c9d43f2b4..1158064710 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -52,9 +52,8 @@ struct RemoveTrivialRYY final : OpRewritePattern { } // namespace -void RYYOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index 60cddbba6f..22de827d57 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -52,7 +52,7 @@ struct RemoveTrivialRZ final : OpRewritePattern { } // namespace -void RZOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index 2c669c5408..e47f694aa9 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -52,9 +52,8 @@ struct RemoveTrivialRZX final : OpRewritePattern { } // namespace -void RZXOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index e71a1b2c6c..ee30f11205 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -52,9 +52,8 @@ struct RemoveTrivialRZZ final : OpRewritePattern { } // namespace -void RZZOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index 9f17e9358c..e331e48d66 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -113,7 +113,7 @@ struct ReplaceU2WithRY final : OpRewritePattern { } // namespace -void U2Op::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& phi, const std::variant& lambda) { const auto& phiOperand = variantToValue(builder, state, phi); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index ed4c12b5e4..5e3093f1ea 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -114,7 +114,7 @@ struct ReplaceUWithRY final : OpRewritePattern { } // namespace -void UOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index b7c46ff8c3..9720e3ff8d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -73,7 +73,7 @@ struct MergeSubsequentXXMinusYY final : OpRewritePattern { } // namespace void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, + Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { const auto& thetaOperand = variantToValue(builder, state, theta); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 9f66536ab8..778e1e2c5f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -73,7 +73,7 @@ struct MergeSubsequentXXPlusYY final : OpRewritePattern { } // namespace void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, + Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { const auto& thetaOperand = variantToValue(builder, state, theta); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 244a1cf8d7..3cd8ff6dca 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -162,7 +162,7 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, return reg; } -Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { +Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { if (resultIndex < 0) { llvm::reportFatalUsageError("Result index must be non-negative"); } @@ -204,8 +204,7 @@ Value QIRProgramBuilder::measure(const Value qubit, const int64_t resultIndex) { return resultValue; } -QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, - const Bit& bit) { +QIRProgramBuilder& QIRProgramBuilder::measure(Value qubit, const Bit& bit) { // Save current insertion point const OpBuilder::InsertionGuard guard(builder); @@ -232,7 +231,7 @@ QIRProgramBuilder& QIRProgramBuilder::measure(const Value qubit, return *this; } -QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { +QIRProgramBuilder& QIRProgramBuilder::reset(Value qubit) { // Save current insertion point const OpBuilder::InsertionGuard guard(builder); @@ -256,8 +255,7 @@ QIRProgramBuilder& QIRProgramBuilder::reset(const Value qubit) { void QIRProgramBuilder::createCallOp( const SmallVector>& parameters, - const ValueRange controls, const SmallVector& targets, - StringRef fnName) { + ValueRange controls, const SmallVector& targets, StringRef fnName) { // Save current insertion point const OpBuilder::InsertionGuard guard(builder); @@ -332,17 +330,17 @@ QIRProgramBuilder::gphase(const std::variant& theta) { // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ - QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(const Value qubit) { \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(Value qubit) { \ createCallOp({}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ - QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL(const Value control, \ - const Value target) { \ + QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL(Value control, \ + Value target) { \ createCallOp({}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ - QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ - const ValueRange controls, const Value target) { \ + QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL(ValueRange controls, \ + Value target) { \ createCallOp({}, controls, {target}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ @@ -366,19 +364,19 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXDG, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ - const std::variant&(PARAM), const Value qubit) { \ + const std::variant&(PARAM), Value qubit) { \ createCallOp({PARAM}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ - const std::variant&(PARAM), const Value control, \ - const Value target) { \ + const std::variant&(PARAM), Value control, \ + Value target) { \ createCallOp({PARAM}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ - const std::variant&(PARAM), const ValueRange controls, \ - const Value target) { \ + const std::variant&(PARAM), ValueRange controls, \ + Value target) { \ createCallOp({PARAM}, controls, {target}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ @@ -397,21 +395,21 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p, theta) PARAM2) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value qubit) { \ + const std::variant&(PARAM2), Value qubit) { \ createCallOp({PARAM1, PARAM2}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value control, \ - const Value target) { \ + const std::variant&(PARAM2), Value control, \ + Value target) { \ createCallOp({PARAM1, PARAM2}, {control}, {target}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const ValueRange controls, \ - const Value target) { \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target) { \ createCallOp({PARAM1, PARAM2}, controls, {target}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ @@ -429,15 +427,15 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ - const std::variant&(PARAM3), const Value qubit) { \ + const std::variant&(PARAM3), Value qubit) { \ createCallOp({PARAM1, PARAM2, PARAM3}, {}, {qubit}, QIR_##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ - const std::variant&(PARAM3), const Value control, \ - const Value target) { \ + const std::variant&(PARAM3), Value control, \ + Value target) { \ createCallOp({PARAM1, PARAM2, PARAM3}, {control}, {target}, \ QIR_C##OP_NAME_BIG); \ return *this; \ @@ -445,8 +443,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2, phi, lambda) QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ - const std::variant&(PARAM3), const ValueRange controls, \ - const Value target) { \ + const std::variant&(PARAM3), ValueRange controls, \ + Value target) { \ createCallOp({PARAM1, PARAM2, PARAM3}, controls, {target}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ @@ -459,18 +457,18 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u, theta, phi, lambda) // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL) \ - QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(const Value target0, \ - const Value target1) { \ + QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL(Value target0, \ + Value target1) { \ createCallOp({}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ - const Value control, const Value target0, const Value target1) { \ + Value control, Value target0, Value target1) { \ createCallOp({}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ - const ValueRange controls, const Value target0, const Value target1) { \ + ValueRange controls, Value target0, Value target1) { \ createCallOp({}, controls, {target0, target1}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ @@ -487,20 +485,20 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_NAME_BIG, OP_NAME_SMALL, PARAM) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ - const std::variant&(PARAM), const Value target0, \ - const Value target1) { \ + const std::variant&(PARAM), Value target0, \ + Value target1) { \ createCallOp({PARAM}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ - const std::variant&(PARAM), const Value control, \ - const Value target0, const Value target1) { \ + const std::variant&(PARAM), Value control, Value target0, \ + Value target1) { \ createCallOp({PARAM}, {control}, {target0, target1}, QIR_C##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ - const std::variant&(PARAM), const ValueRange controls, \ - const Value target0, const Value target1) { \ + const std::variant&(PARAM), ValueRange controls, \ + Value target0, Value target1) { \ createCallOp({PARAM}, controls, {target0, target1}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ @@ -519,23 +517,23 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz, theta) PARAM2) \ QIRProgramBuilder& QIRProgramBuilder::OP_NAME_SMALL( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value target0, \ - const Value target1) { \ + const std::variant&(PARAM2), Value target0, \ + Value target1) { \ createCallOp({PARAM1, PARAM2}, {}, {target0, target1}, QIR_##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::c##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const Value control, \ - const Value target0, const Value target1) { \ + const std::variant&(PARAM2), Value control, \ + Value target0, Value target1) { \ createCallOp({PARAM1, PARAM2}, {control}, {target0, target1}, \ QIR_C##OP_NAME_BIG); \ return *this; \ } \ QIRProgramBuilder& QIRProgramBuilder::mc##OP_NAME_SMALL( \ const std::variant&(PARAM1), \ - const std::variant&(PARAM2), const ValueRange controls, \ - const Value target0, const Value target1) { \ + const std::variant&(PARAM2), ValueRange controls, \ + Value target0, Value target1) { \ createCallOp({PARAM1, PARAM2}, controls, {target0, target1}, \ getFnName##OP_NAME_BIG(controls.size())); \ return *this; \ diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 414101964f..d54cb6af71 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -291,7 +291,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ - Value control, const Value qubit0, const Value qubit1) { \ + Value control, Value qubit0, Value qubit1) { \ return mc##OP_NAME({control}, qubit0, qubit1); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ @@ -412,7 +412,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all remaining allocated qubits - for (const Value qubit : allocatedQubits) { + for (Value qubit : allocatedQubits) { create(loc, qubit); } diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index aea3490d64..ff875d3e50 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -152,7 +152,7 @@ Value CtrlOp::getParameter(const size_t i) { } void CtrlOp::build(OpBuilder& builder, OperationState& state, - const ValueRange controls, UnitaryOpInterface bodyUnitary) { + ValueRange controls, UnitaryOpInterface bodyUnitary) { const OpBuilder::InsertionGuard guard(builder); state.addOperands(controls); auto* region = state.addRegion(); @@ -165,7 +165,7 @@ void CtrlOp::build(OpBuilder& builder, OperationState& state, } void CtrlOp::build(OpBuilder& builder, OperationState& state, - const ValueRange controls, + ValueRange controls, const std::function& bodyBuilder) { const OpBuilder::InsertionGuard guard(builder); state.addOperands(controls); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp index db6905c378..4e42cd1c19 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void POp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index 9ce00b7a1e..fb368bc14d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void ROp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi) { const auto& thetaOperand = variantToValue(builder, state, theta); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index 1177e69760..2906ef48b6 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RXOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index 01d189bf29..f6ce795979 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -19,9 +19,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RXXOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index e441950fd1..f70a64a92b 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RYOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index c2aa61bfee..2b33e9c6d8 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -19,9 +19,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RYYOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index f72b03d2d2..ddd4a4c043 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RZOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 29a9ed31a7..9e783cf5e0 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -19,9 +19,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RZXOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index de53cd37dd..8201c492ad 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -19,9 +19,8 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void RZZOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, - const std::variant& theta) { +void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, + Value qubit1In, const std::variant& theta) { const auto& thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index 4ac1e343ae..b2c1931d19 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void U2Op::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& phi, const std::variant& lambda) { const auto& phiOperand = variantToValue(builder, state, phi); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index b23ae626ad..826f25cf73 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -19,7 +19,7 @@ using namespace mlir; using namespace mlir::quartz; using namespace mlir::utils; -void UOp::build(OpBuilder& builder, OperationState& state, const Value qubitIn, +void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index 813cbebf0c..a54f9cf511 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -20,7 +20,7 @@ using namespace mlir::quartz; using namespace mlir::utils; void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, + Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { const auto& thetaOperand = variantToValue(builder, state, theta); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index a847633bc1..39df76d87f 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -20,7 +20,7 @@ using namespace mlir::quartz; using namespace mlir::utils; void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, - const Value qubit0In, const Value qubit1In, + Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { const auto& thetaOperand = variantToValue(builder, state, theta); diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 5a003ed017..b7567dcd42 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -216,7 +216,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { for (const auto& target : operation.getTargets()) { - const Value qubit = qubits[target]; + Value qubit = qubits[target]; builder.reset(qubit); } } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 25bb9bf8cc..49ee2f45a9 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -78,7 +78,7 @@ struct OperationStructuralHash { } // Hash operand types (not values) - for (const Value operand : op->getOperands()) { + for (Value operand : op->getOperands()) { hash = llvm::hash_combine(hash, operand.getType().getAsOpaquePointer()); } @@ -264,7 +264,7 @@ buildDependenceGraph(ArrayRef ops) { } // Register this operation as the producer of its results - for (const Value result : op->getResults()) { + for (Value result : op->getResults()) { valueProducers[result] = op; } } From d7924240772e8e91afcc59d3ec21566922a2e823 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:47:49 +0100 Subject: [PATCH 348/419] Remove const from more Values --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 26 +++++++++---------- .../IR/Operations/StandardGates/GPhaseOp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/POp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/ROp.cpp | 4 +-- .../Flux/IR/Operations/StandardGates/RXOp.cpp | 2 +- .../IR/Operations/StandardGates/RXXOp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/RYOp.cpp | 2 +- .../IR/Operations/StandardGates/RYYOp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/RZOp.cpp | 2 +- .../IR/Operations/StandardGates/RZXOp.cpp | 2 +- .../IR/Operations/StandardGates/RZZOp.cpp | 2 +- .../Flux/IR/Operations/StandardGates/U2Op.cpp | 4 +-- .../Flux/IR/Operations/StandardGates/UOp.cpp | 6 ++--- .../Operations/StandardGates/XXMinusYYOp.cpp | 4 +-- .../Operations/StandardGates/XXPlusYYOp.cpp | 4 +-- .../IR/Operations/StandardGates/GPhaseOp.cpp | 2 +- .../IR/Operations/StandardGates/POp.cpp | 2 +- .../IR/Operations/StandardGates/ROp.cpp | 4 +-- .../IR/Operations/StandardGates/RXOp.cpp | 2 +- .../IR/Operations/StandardGates/RXXOp.cpp | 2 +- .../IR/Operations/StandardGates/RYOp.cpp | 2 +- .../IR/Operations/StandardGates/RYYOp.cpp | 2 +- .../IR/Operations/StandardGates/RZOp.cpp | 2 +- .../IR/Operations/StandardGates/RZXOp.cpp | 2 +- .../IR/Operations/StandardGates/RZZOp.cpp | 2 +- .../IR/Operations/StandardGates/U2Op.cpp | 4 +-- .../IR/Operations/StandardGates/UOp.cpp | 6 ++--- .../Operations/StandardGates/XXMinusYYOp.cpp | 4 +-- .../Operations/StandardGates/XXPlusYYOp.cpp | 4 +-- 29 files changed, 53 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index abf7e908e2..a7a4b93297 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -155,7 +155,7 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -198,7 +198,7 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -242,7 +242,7 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -287,7 +287,7 @@ convertOneTargetThreeParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubit - const auto quartzQubit = op.getQubitIn(); + const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { fluxQubit = qubitMap[quartzQubit]; @@ -332,8 +332,8 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubits - const auto quartzQubit0 = op.getQubit0In(); - const auto quartzQubit1 = op.getQubit1In(); + const auto& quartzQubit0 = op.getQubit0In(); + const auto& quartzQubit1 = op.getQubit1In(); Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { @@ -383,8 +383,8 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubits - const auto quartzQubit0 = op.getQubit0In(); - const auto quartzQubit1 = op.getQubit1In(); + const auto& quartzQubit0 = op.getQubit0In(); + const auto& quartzQubit1 = op.getQubit1In(); Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { @@ -434,8 +434,8 @@ LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, const auto inCtrlOp = state.inCtrlOp; // Get the latest Flux qubits - const auto quartzQubit0 = op.getQubit0In(); - const auto quartzQubit1 = op.getQubit1In(); + const auto& quartzQubit0 = op.getQubit0In(); + const auto& quartzQubit1 = op.getQubit1In(); Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { @@ -657,8 +657,8 @@ struct ConvertQuartzMeasureOp final op.getLoc(), fluxQubit, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - const auto outFluxQubit = fluxOp.getQubitOut(); - const auto newBit = fluxOp.getResult(); + const auto& outFluxQubit = fluxOp.getQubitOut(); + const auto& newBit = fluxOp.getResult(); // Update mapping: the Quartz qubit now corresponds to the output qubit qubitMap[quartzQubit] = outFluxQubit; @@ -1094,7 +1094,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { llvm::zip(quartzControls, fluxOp.getControlsOut())) { qubitMap[quartzControl] = fluxControl; } - const auto targetsOut = fluxOp.getTargetsOut(); + const auto& targetsOut = fluxOp.getTargetsOut(); for (size_t i = 0; i < numTargets; ++i) { const auto& quartzTarget = op.getTarget(i); qubitMap[quartzTarget] = targetsOut[i]; diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp index f39418708a..c57ad97ed3 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp @@ -53,7 +53,7 @@ struct RemoveTrivialGPhase final : OpRewritePattern { void GPhaseOp::build(OpBuilder& builder, OperationState& state, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp index 3e62348fd8..9fefc91e86 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialP final : OpRewritePattern { void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp index 8c878b8c54..63004e44e7 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp @@ -84,8 +84,8 @@ struct ReplaceRWithRY final : OpRewritePattern { void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& phiOperand = variantToValue(builder, state, phi); + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); build(builder, state, qubitIn, thetaOperand, phiOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp index ec43aef6dc..2cc700e73b 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRX final : OpRewritePattern { void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp index a6cb7f92ed..edacf6ed08 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRXX final : OpRewritePattern { void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp index 91c69fae73..60c760766d 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRY final : OpRewritePattern { void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp index 1158064710..0eb31330cc 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRYY final : OpRewritePattern { void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp index 22de827d57..c65c77ad89 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRZ final : OpRewritePattern { void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp index e47f694aa9..5bbd5cb7ac 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRZX final : OpRewritePattern { void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp index ee30f11205..8791f30d38 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp @@ -54,7 +54,7 @@ struct RemoveTrivialRZZ final : OpRewritePattern { void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp index e331e48d66..a34341fa3f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp @@ -116,8 +116,8 @@ struct ReplaceU2WithRY final : OpRewritePattern { void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& phi, const std::variant& lambda) { - const auto& phiOperand = variantToValue(builder, state, phi); - const auto& lambdaOperand = variantToValue(builder, state, lambda); + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); build(builder, state, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp index 5e3093f1ea..36f66fc4ae 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp @@ -118,9 +118,9 @@ void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& phiOperand = variantToValue(builder, state, phi); - const auto& lambdaOperand = variantToValue(builder, state, lambda); + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); build(builder, state, qubitIn, thetaOperand, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp index 9720e3ff8d..d362467458 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -76,8 +76,8 @@ void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& betaOperand = variantToValue(builder, state, beta); + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp index 778e1e2c5f..b58683122f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -76,8 +76,8 @@ void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& betaOperand = variantToValue(builder, state, beta); + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp index a03ec043d4..9511e85288 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void GPhaseOp::build(OpBuilder& builder, OperationState& state, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp index 4e42cd1c19..23860fff15 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp index fb368bc14d..a9342ebdab 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp @@ -22,7 +22,7 @@ using namespace mlir::utils; void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& phiOperand = variantToValue(builder, state, phi); + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); build(builder, state, qubitIn, thetaOperand, phiOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp index 2906ef48b6..cc50e6bf09 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp index f6ce795979..682e46461f 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp index f70a64a92b..304ef93866 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp index 2b33e9c6d8..5fbf16e278 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp index ddd4a4c043..6b51d56a96 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubitIn, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp index 9e783cf5e0..42068e181e 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp index 8201c492ad..cf993078ba 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp @@ -21,6 +21,6 @@ using namespace mlir::utils; void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta) { - const auto& thetaOperand = variantToValue(builder, state, theta); + auto thetaOperand = variantToValue(builder, state, theta); build(builder, state, qubit0In, qubit1In, thetaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp index b2c1931d19..b7f64d9a74 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp @@ -22,7 +22,7 @@ using namespace mlir::utils; void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& phi, const std::variant& lambda) { - const auto& phiOperand = variantToValue(builder, state, phi); - const auto& lambdaOperand = variantToValue(builder, state, lambda); + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); build(builder, state, qubitIn, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp index 826f25cf73..948813b33d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp @@ -23,8 +23,8 @@ void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, const std::variant& theta, const std::variant& phi, const std::variant& lambda) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& phiOperand = variantToValue(builder, state, phi); - const auto& lambdaOperand = variantToValue(builder, state, lambda); + auto thetaOperand = variantToValue(builder, state, theta); + auto phiOperand = variantToValue(builder, state, phi); + auto lambdaOperand = variantToValue(builder, state, lambda); build(builder, state, qubitIn, thetaOperand, phiOperand, lambdaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp index a54f9cf511..847f24a7be 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -23,7 +23,7 @@ void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& betaOperand = variantToValue(builder, state, beta); + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp index 39df76d87f..4050aa0461 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -23,7 +23,7 @@ void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, Value qubit1In, const std::variant& theta, const std::variant& beta) { - const auto& thetaOperand = variantToValue(builder, state, theta); - const auto& betaOperand = variantToValue(builder, state, beta); + auto thetaOperand = variantToValue(builder, state, theta); + auto betaOperand = variantToValue(builder, state, beta); build(builder, state, qubit0In, qubit1In, thetaOperand, betaOperand); } From 6eabd43d9332bcb095860f38c96d944931099e39 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:24:46 +0100 Subject: [PATCH 349/419] Use factor method to create ModuleOp --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 2 +- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index e487aed6b2..bdfec6702b 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -34,7 +34,7 @@ namespace mlir::flux { FluxProgramBuilder::FluxProgramBuilder(MLIRContext* context) : OpBuilder(context), ctx(context), loc(getUnknownLoc()), - module(create(loc)) {} + module(ModuleOp::create(loc)) {} void FluxProgramBuilder::initialize() { // Ensure the Flux dialect is loaded diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index d54cb6af71..645a24c306 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -30,7 +30,7 @@ namespace mlir::quartz { QuartzProgramBuilder::QuartzProgramBuilder(MLIRContext* context) : OpBuilder(context), ctx(context), loc(getUnknownLoc()), - module(create(loc)) {} + module(ModuleOp::create(loc)) {} void QuartzProgramBuilder::initialize() { // Ensure the Quartz dialect is loaded From 71ba049f6dcae313de32d4f43640c45ad030334a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:27:15 +0100 Subject: [PATCH 350/419] Add --record-intermediates flag --- mlir/tools/mqt-cc/mqt-cc.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 72d8b5bfac..2493d6b410 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -49,6 +49,11 @@ const cl::opt CONVERT_TO_QIR("emit-qir", cl::desc("Convert to QIR at the end"), cl::init(false)); +const cl::opt RECORD_INTERMEDIATES( + "record-intermediates", + cl::desc("Record intermediate IR after each compiler stage"), + cl::init(false)); + const cl::opt ENABLE_TIMING("mlir-timing", cl::desc("Enable pass timing statistics"), cl::init(false)); @@ -127,6 +132,7 @@ int main(int argc, char** argv) { // Configure the compiler pipeline QuantumCompilerConfig config; config.convertToQIR = CONVERT_TO_QIR; + config.recordIntermediates = RECORD_INTERMEDIATES; config.enableTiming = ENABLE_TIMING; config.enableStatistics = ENABLE_STATISTICS; config.printIRAfterAllStages = PRINT_IR_AFTER_ALL_STAGES; From 07760199bc657a9d19a1d773765847b34366006a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:32:22 +0100 Subject: [PATCH 351/419] Update docstrings in QIRUtils.h --- mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h index 02725f5a28..7ef8071360 100644 --- a/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h +++ b/mlir/include/mlir/Dialect/QIR/Utils/QIRUtils.h @@ -188,8 +188,8 @@ void setQIRAttributes(LLVM::LLVMFuncOp& main, const QIRMetadata& metadata); * Searches for an existing function declaration in the symbol table. If not * found, creates a new function declaration at the end of the module. * - * For QIR functions that are irreversible (measurement, reset, deallocation), - * the "irreversible" attribute is added automatically. + * For QIR functions that are irreversible (measurement and reset), the + * "irreversible" attribute is added automatically. * * @param builder The builder to use for creating operations * @param op The operation requesting the function (for context) @@ -205,9 +205,8 @@ LLVM::LLVMFuncOp getOrCreateFunctionDeclaration(OpBuilder& builder, * @brief Create a global string constant for result labeling * * @details - * Creates a global string constant at module level and returns an addressOf - * operation pointing to it. The global is created at the start of the module, - * and the addressOf is inserted at the builder's current insertion point. + * Creates a global string constant at the module level and inserts an + * AddressOfOp at the start of the main function's entry block. * * @param builder The builder to use for creating operations * @param op The operation requesting the label (for context/location) From 3999b4262d12bc47f609c014ff3500b6b961de09 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:40:31 +0100 Subject: [PATCH 352/419] Document current limitations of Quartz-to-QIR conversion --- mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td | 5 +++-- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td index 7a93ea0482..3229a5b99a 100644 --- a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td +++ b/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td @@ -21,6 +21,8 @@ def QuartzToQIR : Pass<"quartz-to-qir"> { Requirements: - Input is a valid module in the Quartz dialect. - The entry function must be marked with the `entry_point` attribute. + - The entry function must have a single block. + Multi-block functions are currently unsupported. - The program must have straight-line control flow (i.e., Base Profile QIR). Behavior: @@ -30,8 +32,7 @@ def QuartzToQIR : Pass<"quartz-to-qir"> { 0. Initialization block: Sets up the execution environment and performs required runtime initialization. 1. Reversible operations block: Contains only void-returning calls to reversible quantum operations. 2. Irreversible operations block: Contains only void-returning calls to operations marked irreversible, e.g.: - `__quantum__qis__mz__body`, `__quantum__rt__qubit_release_array`, - `__quantum__rt__qubit_release`, `__quantum__qis__reset__body`. + `__quantum__qis__mz__body`, `__quantum__qis__reset__body`. 3. Epilogue block: Records measurement results and returns from the entry function. - Blocks are connected via unconditional branches in the order listed above. - Non-quantum dialects are lowered via MLIR's built-in conversions. diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index 68304f528a..eba7264920 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -881,6 +881,10 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { * 4. Set QIR metadata attributes * 5. Convert arith and cf dialects to LLVM * 6. Reconcile unrealized casts + * + * @pre + * The entry function must have a single block. The pass will restructure it + * into a 4-block layout. Multi-block functions are currently unsupported. */ struct QuartzToQIR final : impl::QuartzToQIRBase { using QuartzToQIRBase::QuartzToQIRBase; From a382021b3d78d8f82e97bcb3a4926dc8f00e3bff Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:43:25 +0100 Subject: [PATCH 353/419] Remove dialect-name variables --- mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt | 3 --- mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt | 3 --- 2 files changed, 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt index d3ad9bfd11..d9cffd4ae0 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt @@ -6,9 +6,6 @@ # # Licensed under the MIT License -set(DIALECT_NAME "Flux") -set(DIALECT_NAME_UPPER "FLUX") - add_mlir_dialect(FluxOps flux) add_mlir_interface(FluxInterfaces) add_mlir_doc(FluxOps MLIRFluxDialect Dialects/ -gen-dialect-doc) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt index ee38d53a7f..e17fbf093f 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt @@ -6,9 +6,6 @@ # # Licensed under the MIT License -set(DIALECT_NAME "Quartz") -set(DIALECT_NAME_UPPER "QUARTZ") - add_mlir_dialect(QuartzOps quartz) add_mlir_interface(QuartzInterfaces) add_mlir_doc(QuartzOps MLIRQuartzDialect Dialects/ -gen-dialect-doc) From 4c655b39f157373259ac8d4182836c35d888c064 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:50:47 +0100 Subject: [PATCH 354/419] Let pipeline fail if config is not valid --- mlir/lib/Compiler/CompilerPipeline.cpp | 7 +++++++ mlir/unittests/pipeline/test_compiler_pipeline.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 2af0f8c06d..e79244b8a6 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -83,6 +83,13 @@ void QuantumCompilerPipeline::configurePassManager(PassManager& pm) const { LogicalResult QuantumCompilerPipeline::runPipeline(ModuleOp module, CompilationRecord* record) const { + // Ensure printIRAfterAllStages implies recordIntermediates + if (config_.printIRAfterAllStages && !config_.recordIntermediates) { + llvm::errs() << "Invalid configuration: printIRAfterAllStages requires " + "recordIntermediates to be enabled.\n"; + return failure(); + } + PassManager pm(module.getContext()); // Configure PassManager with diagnostic options diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 49ee2f45a9..2b7824b4e6 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -637,7 +637,7 @@ class CompilerPipelineTest : public testing::Test { pm.addPass(createCanonicalizerPass()); pm.addPass(createRemoveDeadValuesPass()); if (pm.run(module).failed()) { - llvm::errs() << "Failed to run canonicalization passes\n"; + llvm::errs() << "Failed to run canonicalization passes.\n"; } } From bbbe2b7202a00c29261f1b3b2af9b98caf0888b4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:53:21 +0100 Subject: [PATCH 355/419] Load dialects in constructors --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 7 +++---- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 7 +++---- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index bdfec6702b..dc94a35432 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -34,12 +34,11 @@ namespace mlir::flux { FluxProgramBuilder::FluxProgramBuilder(MLIRContext* context) : OpBuilder(context), ctx(context), loc(getUnknownLoc()), - module(ModuleOp::create(loc)) {} - -void FluxProgramBuilder::initialize() { - // Ensure the Flux dialect is loaded + module(ModuleOp::create(loc)) { ctx->loadDialect(); +} +void FluxProgramBuilder::initialize() { // Set insertion point to the module body setInsertionPointToStart(module.getBody()); diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 3cd8ff6dca..13ea4dbec6 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -37,12 +37,11 @@ namespace mlir::qir { QIRProgramBuilder::QIRProgramBuilder(MLIRContext* context) : builder(context), module(builder.create(UnknownLoc::get(context))), - loc(UnknownLoc::get(context)) {} - -void QIRProgramBuilder::initialize() { - // Ensure LLVM dialect is loaded + loc(UnknownLoc::get(context)) { builder.getContext()->loadDialect(); +} +void QIRProgramBuilder::initialize() { // Set insertion point to the module body builder.setInsertionPointToStart(module.getBody()); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 645a24c306..0cd3ee5432 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -30,12 +30,11 @@ namespace mlir::quartz { QuartzProgramBuilder::QuartzProgramBuilder(MLIRContext* context) : OpBuilder(context), ctx(context), loc(getUnknownLoc()), - module(ModuleOp::create(loc)) {} - -void QuartzProgramBuilder::initialize() { - // Ensure the Quartz dialect is loaded + module(ModuleOp::create(loc)) { ctx->loadDialect(); +} +void QuartzProgramBuilder::initialize() { // Set insertion point to the module body setInsertionPointToStart(module.getBody()); From 864dbf2f4eb5128fbf0f28c9e36deb8d15b7e5b8 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:11:56 +0100 Subject: [PATCH 356/419] Make default classical-register name more safe --- mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp | 9 +++++++-- mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 10 +++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp index eba7264920..534e416cce 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp @@ -429,14 +429,19 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { resultValue = registerResultMap.at(key); } } else { - // no register info, check if ptr has already been allocated (as a Qubit) + // Choose a safe default register name + StringRef defaultRegName = "c"; + if (state.registerStartIndexMap.contains("c")) { + defaultRegName = "__unnamed__"; + } + // No register info, check if ptr has already been allocated (as a Qubit) if (const auto it = ptrMap.find(numResults); it != ptrMap.end()) { resultValue = it->second; } else { resultValue = createPointerFromIndex(rewriter, op.getLoc(), numResults); ptrMap[numResults] = resultValue; } - registerResultMap.insert({{"c", numResults}, resultValue}); + registerResultMap.insert({{defaultRegName, numResults}, resultValue}); state.numResults++; } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 13ea4dbec6..e02df7d76a 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -166,13 +166,21 @@ Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { llvm::reportFatalUsageError("Result index must be non-negative"); } + // Choose a safe default register name + StringRef defaultRegName = "c"; + if (llvm::any_of(registerResultMap, [](const auto& entry) { + return entry.first.first == "c"; + })) { + defaultRegName = "__unnamed__"; + } + // Save current insertion point const OpBuilder::InsertionGuard guard(builder); // Insert in measurements block (before branch) builder.setInsertionPoint(measurementsBlock->getTerminator()); - const auto key = std::make_pair("c", resultIndex); + const auto key = std::make_pair(defaultRegName, resultIndex); if (const auto it = registerResultMap.find(key); it != registerResultMap.end()) { return it->second; From 94458a89e41c7b257243561e10f434b1419df84f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:24:16 +0100 Subject: [PATCH 357/419] Make createResultLabel more safe --- mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp index 6e8a3a917b..15ec2f2b3d 100644 --- a/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp +++ b/mlir/lib/Dialect/QIR/Utils/QIRUtils.cpp @@ -123,7 +123,6 @@ LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, // Save current insertion point const OpBuilder::InsertionGuard guard(builder); - // Create the declaration at the start of the module auto module = dyn_cast(op); if (!module) { module = op->getParentOfType(); @@ -131,19 +130,24 @@ LLVM::AddressOfOp createResultLabel(OpBuilder& builder, Operation* op, if (!module) { llvm::reportFatalInternalError("Module not found"); } - builder.setInsertionPointToStart(module.getBody()); const auto symbolName = builder.getStringAttr((symbolPrefix + "_" + label).str()); - const auto llvmArrayType = LLVM::LLVMArrayType::get( - builder.getIntegerType(8), static_cast(label.size() + 1)); - const auto stringInitializer = builder.getStringAttr(label.str() + '\0'); - - const auto globalOp = builder.create( - op->getLoc(), llvmArrayType, /*isConstant=*/true, LLVM::Linkage::Internal, - symbolName, stringInitializer); - globalOp->setAttr("addr_space", builder.getI32IntegerAttr(0)); - globalOp->setAttr("dso_local", builder.getUnitAttr()); + + if (!module.lookupSymbol(symbolName)) { + const auto llvmArrayType = LLVM::LLVMArrayType::get( + builder.getIntegerType(8), static_cast(label.size() + 1)); + const auto stringInitializer = builder.getStringAttr(label.str() + '\0'); + + // Create the declaration at the start of the module + builder.setInsertionPointToStart(module.getBody()); + + const auto globalOp = builder.create( + op->getLoc(), llvmArrayType, /*isConstant=*/true, + LLVM::Linkage::Internal, symbolName, stringInitializer); + globalOp->setAttr("addr_space", builder.getI32IntegerAttr(0)); + globalOp->setAttr("dso_local", builder.getUnitAttr()); + } // Create AddressOfOp // Shall be added to the first block of the `main` function in the module From c50594bed0f2d186f3d0e9cd913efd2304aa8ce9 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:47:43 +0100 Subject: [PATCH 358/419] Ensure that builders cannot be used after finalization --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 3 ++ .../Dialect/QIR/Builder/QIRProgramBuilder.h | 5 ++ .../Quartz/Builder/QuartzProgramBuilder.h | 3 ++ .../Flux/Builder/FluxProgramBuilder.cpp | 54 +++++++++++++++++++ .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 23 ++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 49 +++++++++++++++++ 6 files changed, 137 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index a88015a54b..023fa48f8b 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -1037,6 +1037,9 @@ class FluxProgramBuilder final : public OpBuilder { Location loc; ModuleOp module; + /// Check if the builder has been finalized + void checkFinalized() const; + //===--------------------------------------------------------------------===// // Linear Type Tracking Helpers //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index a54b01ee67..a48f735c34 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -850,6 +850,11 @@ class QIRProgramBuilder { * 2. result_record_output for each measurement in the register */ void generateOutputRecording(); + + bool isFinalized = false; + + /// Check if the builder has been finalized + void checkFinalized() const; }; } // namespace mlir::qir diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index f91370f95d..e1468f01ba 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -872,5 +872,8 @@ class QuartzProgramBuilder final : public OpBuilder { /// Track allocated classical Registers SmallVector allocatedClassicalRegisters; + + /// Check if the builder has been finalized + void checkFinalized() const; }; } // namespace mlir::quartz diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index dc94a35432..66cd1d0dff 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -56,6 +56,8 @@ void FluxProgramBuilder::initialize() { } Value FluxProgramBuilder::allocQubit() { + checkFinalized(); + auto allocOp = create(loc); const auto qubit = allocOp.getResult(); @@ -66,6 +68,8 @@ Value FluxProgramBuilder::allocQubit() { } Value FluxProgramBuilder::staticQubit(const int64_t index) { + checkFinalized(); + if (index < 0) { llvm::reportFatalUsageError("Index must be non-negative"); } @@ -83,6 +87,8 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { + checkFinalized(); + if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } @@ -106,6 +112,8 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, FluxProgramBuilder::ClassicalRegister& FluxProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { + checkFinalized(); + if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } @@ -144,6 +152,8 @@ void FluxProgramBuilder::updateQubitTracking(Value inputQubit, //===----------------------------------------------------------------------===// std::pair FluxProgramBuilder::measure(Value qubit) { + checkFinalized(); + auto measureOp = create(loc, qubit); auto qubitOut = measureOp.getQubitOut(); auto result = measureOp.getResult(); @@ -155,6 +165,8 @@ std::pair FluxProgramBuilder::measure(Value qubit) { } Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { + checkFinalized(); + auto nameAttr = getStringAttr(bit.registerName); auto sizeAttr = getI64IntegerAttr(bit.registerSize); auto indexAttr = getI64IntegerAttr(bit.registerIndex); @@ -168,6 +180,8 @@ Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { } Value FluxProgramBuilder::reset(Value qubit) { + checkFinalized(); + auto resetOp = create(loc, qubit); const auto qubitOut = resetOp.getQubitOut(); @@ -186,10 +200,12 @@ Value FluxProgramBuilder::reset(Value qubit) { #define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ void FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM)) { \ + checkFinalized(); \ create(loc, PARAM); \ } \ Value FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, {}, [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ b.create(loc, PARAM); \ @@ -199,6 +215,7 @@ Value FluxProgramBuilder::reset(Value qubit) { } \ ValueRange FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {}, \ [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ @@ -216,6 +233,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ Value FluxProgramBuilder::OP_NAME(Value qubit) { \ + checkFinalized(); \ auto op = create(loc, qubit); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ @@ -223,6 +241,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) } \ std::pair FluxProgramBuilder::c##OP_NAME(Value control, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0]); \ @@ -232,6 +251,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) } \ std::pair FluxProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -260,6 +280,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ Value FluxProgramBuilder::OP_NAME(const std::variant&(PARAM), \ Value qubit) { \ + checkFinalized(); \ auto op = create(loc, qubit, PARAM); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ @@ -268,6 +289,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) std::pair FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0], PARAM); \ @@ -278,6 +300,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) std::pair FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -300,6 +323,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) Value FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit) { \ + checkFinalized(); \ auto op = create(loc, qubit, PARAM1, PARAM2); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ @@ -309,6 +333,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = b.create(loc, targets[0], PARAM1, PARAM2); \ @@ -320,6 +345,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -343,6 +369,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value qubit) { \ + checkFinalized(); \ auto op = create(loc, qubit, PARAM1, PARAM2, PARAM3); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ @@ -353,6 +380,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value control, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ @@ -366,6 +394,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), ValueRange controls, \ Value target) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -385,6 +414,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ std::pair FluxProgramBuilder::OP_NAME(Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ auto op = create(loc, qubit0, qubit1); \ const auto& qubit0Out = op.getQubit0Out(); \ const auto& qubit1Out = op.getQubit1Out(); \ @@ -394,6 +424,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) } \ std::pair> FluxProgramBuilder::c##OP_NAME( \ Value control, Value qubit0, Value qubit1) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -406,6 +437,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) std::pair> \ FluxProgramBuilder::mc##OP_NAME(ValueRange controls, Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -428,6 +460,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ std::pair FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ + checkFinalized(); \ auto op = create(loc, qubit0, qubit1, PARAM); \ const auto& qubit0Out = op.getQubit0Out(); \ const auto& qubit1Out = op.getQubit1Out(); \ @@ -438,6 +471,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) std::pair> FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -451,6 +485,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value qubit0, Value qubit1) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -475,6 +510,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ auto op = create(loc, qubit0, qubit1, PARAM1, PARAM2); \ const auto& qubit0Out = op.getQubit0Out(); \ const auto& qubit1Out = op.getQubit1Out(); \ @@ -486,6 +522,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -500,6 +537,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value qubit0, Value qubit1) { \ + checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -518,6 +556,8 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) // BarrierOp ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { + checkFinalized(); + auto op = create(loc, qubits); const auto& qubitsOut = op.getQubitsOut(); for (const auto& [inputQubit, outputQubit] : llvm::zip(qubits, qubitsOut)) { @@ -533,6 +573,8 @@ ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { std::pair FluxProgramBuilder::ctrl( ValueRange controls, ValueRange targets, const std::function& body) { + checkFinalized(); + auto ctrlOp = create(loc, controls, targets, body); // Update tracking @@ -553,6 +595,8 @@ std::pair FluxProgramBuilder::ctrl( //===----------------------------------------------------------------------===// FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { + checkFinalized(); + validateQubitValue(qubit); validQubits.erase(qubit); @@ -565,6 +609,13 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { // Finalization //===----------------------------------------------------------------------===// +void FluxProgramBuilder::checkFinalized() const { + if (ctx == nullptr) { + llvm::reportFatalUsageError( + "FluxProgramBuilder instance has been finalized"); + } +} + OwningOpRef FluxProgramBuilder::finalize() { // Automatically deallocate all remaining valid qubits for (const auto qubit : validQubits) { @@ -579,6 +630,9 @@ OwningOpRef FluxProgramBuilder::finalize() { // Add return statement with exit code 0 to the main function create(loc, ValueRange{exitCode}); + // Invalidate context to prevent use-after-finalize + ctx = nullptr; + return module; } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index e02df7d76a..167850b495 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -93,6 +93,8 @@ void QIRProgramBuilder::initialize() { } Value QIRProgramBuilder::staticQubit(const int64_t index) { + checkFinalized(); + if (index < 0) { llvm::reportFatalUsageError("Index must be non-negative"); } @@ -117,6 +119,8 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { + checkFinalized(); + if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } @@ -134,6 +138,8 @@ QIRProgramBuilder::allocQubitRegister(const int64_t size) { QIRProgramBuilder::ClassicalRegister& QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, StringRef name) { + checkFinalized(); + if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } @@ -162,6 +168,8 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, } Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { + checkFinalized(); + if (resultIndex < 0) { llvm::reportFatalUsageError("Result index must be non-negative"); } @@ -212,6 +220,8 @@ Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { } QIRProgramBuilder& QIRProgramBuilder::measure(Value qubit, const Bit& bit) { + checkFinalized(); + // Save current insertion point const OpBuilder::InsertionGuard guard(builder); @@ -239,6 +249,8 @@ QIRProgramBuilder& QIRProgramBuilder::measure(Value qubit, const Bit& bit) { } QIRProgramBuilder& QIRProgramBuilder::reset(Value qubit) { + checkFinalized(); + // Save current insertion point const OpBuilder::InsertionGuard guard(builder); @@ -263,6 +275,8 @@ QIRProgramBuilder& QIRProgramBuilder::reset(Value qubit) { void QIRProgramBuilder::createCallOp( const SmallVector>& parameters, ValueRange controls, const SmallVector& targets, StringRef fnName) { + checkFinalized(); + // Save current insertion point const OpBuilder::InsertionGuard guard(builder); @@ -555,6 +569,13 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMINUSYY, xx_minus_yy, theta, beta) // Finalization //===----------------------------------------------------------------------===// +void QIRProgramBuilder::checkFinalized() const { + if (isFinalized) { + llvm::reportFatalUsageError( + "QIRProgramBuilder instance has been finalized"); + } +} + void QIRProgramBuilder::generateOutputRecording() { if (registerResultMap.empty()) { return; // No measurements to record @@ -632,6 +653,8 @@ OwningOpRef QIRProgramBuilder::finalize() { setQIRAttributes(mainFunc, metadata_); + isFinalized = true; + return module; } diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 0cd3ee5432..bb0857c35f 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -52,6 +52,8 @@ void QuartzProgramBuilder::initialize() { } Value QuartzProgramBuilder::allocQubit() { + checkFinalized(); + // Create the AllocOp without register metadata auto allocOp = create(loc); const auto qubit = allocOp.getResult(); @@ -63,6 +65,8 @@ Value QuartzProgramBuilder::allocQubit() { } Value QuartzProgramBuilder::staticQubit(const int64_t index) { + checkFinalized(); + if (index < 0) { llvm::reportFatalUsageError("Index must be non-negative"); } @@ -76,6 +80,8 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, const StringRef name) { + checkFinalized(); + if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } @@ -100,6 +106,8 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, QuartzProgramBuilder::ClassicalRegister& QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { + checkFinalized(); + if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } @@ -112,12 +120,14 @@ QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { //===----------------------------------------------------------------------===// Value QuartzProgramBuilder::measure(Value qubit) { + checkFinalized(); auto measureOp = create(loc, qubit); return measureOp.getResult(); } QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, const Bit& bit) { + checkFinalized(); auto nameAttr = getStringAttr(bit.registerName); auto sizeAttr = getI64IntegerAttr(bit.registerSize); auto indexAttr = getI64IntegerAttr(bit.registerIndex); @@ -126,6 +136,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, } QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { + checkFinalized(); create(loc, qubit); return *this; } @@ -139,15 +150,18 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { #define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM)) { \ + checkFinalized(); \ create(loc, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control) { \ + checkFinalized(); \ return mc##OP_NAME(PARAM, {control}); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls) { \ + checkFinalized(); \ create(loc, controls, \ [&](OpBuilder& b) { b.create(loc, PARAM); }); \ return *this; \ @@ -161,15 +175,18 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit) { \ + checkFinalized(); \ create(loc, qubit); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME(Value control, \ Value target) { \ + checkFinalized(); \ return mc##OP_NAME({control}, target); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME(ValueRange controls, \ Value target) { \ + checkFinalized(); \ create(loc, controls, \ [&](OpBuilder& b) { b.create(loc, target); }); \ return *this; \ @@ -194,17 +211,20 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit) { \ + checkFinalized(); \ create(loc, qubit, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, \ Value target) { \ + checkFinalized(); \ return mc##OP_NAME(PARAM, {control}, target); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value target) { \ + checkFinalized(); \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, target, PARAM); \ }); \ @@ -224,6 +244,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit) { \ + checkFinalized(); \ create(loc, qubit, PARAM1, PARAM2); \ return *this; \ } \ @@ -231,12 +252,14 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, \ Value target) { \ + checkFinalized(); \ return mc##OP_NAME(PARAM1, PARAM2, {control}, target); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value target) { \ + checkFinalized(); \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, target, PARAM1, PARAM2); \ }); \ @@ -256,6 +279,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value qubit) { \ + checkFinalized(); \ create(loc, qubit, PARAM1, PARAM2, PARAM3); \ return *this; \ } \ @@ -264,6 +288,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value control, \ Value target) { \ + checkFinalized(); \ return mc##OP_NAME(PARAM1, PARAM2, PARAM3, {control}, target); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ @@ -271,6 +296,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), ValueRange controls, \ Value target) { \ + checkFinalized(); \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, target, PARAM1, PARAM2, PARAM3); \ }); \ @@ -286,15 +312,18 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ create(loc, qubit0, qubit1); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ Value control, Value qubit0, Value qubit1) { \ + checkFinalized(); \ return mc##OP_NAME({control}, qubit0, qubit1); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value qubit0, Value qubit1) { \ + checkFinalized(); \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, qubit0, qubit1); \ }); \ @@ -313,17 +342,20 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ + checkFinalized(); \ create(loc, qubit0, qubit1, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ return mc##OP_NAME(PARAM, {control}, qubit0, qubit1); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value qubit0, Value qubit1) { \ + checkFinalized(); \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, qubit0, qubit1, PARAM); \ }); \ @@ -344,6 +376,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ create(loc, qubit0, qubit1, PARAM1, PARAM2); \ return *this; \ } \ @@ -351,12 +384,14 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, Value qubit0, \ Value qubit1) { \ + checkFinalized(); \ return mc##OP_NAME(PARAM1, PARAM2, {control}, qubit0, qubit1); \ } \ QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value qubit0, Value qubit1) { \ + checkFinalized(); \ create(loc, controls, [&](OpBuilder& b) { \ b.create(loc, qubit0, qubit1, PARAM1, PARAM2); \ }); \ @@ -371,6 +406,7 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) // BarrierOp QuartzProgramBuilder& QuartzProgramBuilder::barrier(ValueRange qubits) { + checkFinalized(); create(loc, qubits); return *this; } @@ -382,6 +418,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::barrier(ValueRange qubits) { QuartzProgramBuilder& QuartzProgramBuilder::ctrl(ValueRange controls, const std::function& body) { + checkFinalized(); create(loc, controls, body); return *this; } @@ -391,6 +428,8 @@ QuartzProgramBuilder::ctrl(ValueRange controls, //===----------------------------------------------------------------------===// QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { + checkFinalized(); + // Check if the qubit is in the tracking set if (!allocatedQubits.erase(qubit)) { // Qubit was not found in the set - either never allocated or already @@ -409,6 +448,13 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { // Finalization //===----------------------------------------------------------------------===// +void QuartzProgramBuilder::checkFinalized() const { + if (ctx == nullptr) { + llvm::reportFatalUsageError( + "QuartzProgramBuilder instance has been finalized"); + } +} + OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all remaining allocated qubits for (Value qubit : allocatedQubits) { @@ -424,6 +470,9 @@ OwningOpRef QuartzProgramBuilder::finalize() { // Add return statement with exit code 0 to the main function create(loc, ValueRange{exitCode}); + // Invalidate context to prevent use-after-finalize + ctx = nullptr; + // Transfer ownership to the caller return module; } From 8cc1959e7166faf855b80b9f2434820317e8273b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:53:57 +0100 Subject: [PATCH 359/419] Use auto instead of Value to maybe get rid of const linter errors --- .../Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp | 2 +- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 2 +- .../Translation/TranslateQuantumComputationToQuartz.cpp | 2 +- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp index eb4712e430..f5a1106c23 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp @@ -148,7 +148,7 @@ void BarrierOp::build(OpBuilder& builder, OperationState& state, ValueRange qubits) { SmallVector resultTypes; resultTypes.reserve(qubits.size()); - for (Value qubit : qubits) { + for (auto qubit : qubits) { resultTypes.push_back(qubit.getType()); } build(builder, state, resultTypes, qubits); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index bb0857c35f..91f7562991 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -457,7 +457,7 @@ void QuartzProgramBuilder::checkFinalized() const { OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all remaining allocated qubits - for (Value qubit : allocatedQubits) { + for (auto qubit : allocatedQubits) { create(loc, qubit); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index b7567dcd42..a50db820ee 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -216,7 +216,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const llvm::SmallVector& qubits) { for (const auto& target : operation.getTargets()) { - Value qubit = qubits[target]; + auto qubit = qubits[target]; builder.reset(qubit); } } diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 2b7824b4e6..ee32f5da79 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -73,12 +73,12 @@ struct OperationStructuralHash { size_t hash = llvm::hash_value(op->getName().getStringRef()); // Hash result types - for (const Type type : op->getResultTypes()) { + for (auto type : op->getResultTypes()) { hash = llvm::hash_combine(hash, type.getAsOpaquePointer()); } // Hash operand types (not values) - for (Value operand : op->getOperands()) { + for (auto operand : op->getOperands()) { hash = llvm::hash_combine(hash, operand.getType().getAsOpaquePointer()); } @@ -264,7 +264,7 @@ buildDependenceGraph(ArrayRef ops) { } // Register this operation as the producer of its results - for (Value result : op->getResults()) { + for (auto result : op->getResults()) { valueProducers[result] = op; } } From d574f847e58f5728b64f6041a50e4c53fc569ded Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:55:27 +0100 Subject: [PATCH 360/419] Ignore downcast linter error for now --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index ee32f5da79..7a4ab5fb43 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1583,6 +1583,7 @@ TEST_F(CompilerPipelineTest, MCXNested) { auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.ctrl(reg[0], [&](OpBuilder& b) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) static_cast(b).cx(reg[1], reg[2]); }); }); From e15acd9f00fd326d35d5662a37f30e4fa70dae3c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:56:51 +0100 Subject: [PATCH 361/419] Bump minimum MLIR version for clarity --- cmake/SetupMLIR.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/SetupMLIR.cmake b/cmake/SetupMLIR.cmake index 6ff928c446..096f48d739 100644 --- a/cmake/SetupMLIR.cmake +++ b/cmake/SetupMLIR.cmake @@ -10,7 +10,7 @@ set(MQT_MLIR_SOURCE_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/mlir/include") set(MQT_MLIR_BUILD_INCLUDE_DIR "${PROJECT_BINARY_DIR}/mlir/include") set(MQT_MLIR_MIN_VERSION - "21.0" + "21.1" CACHE STRING "Minimum required MLIR version") # MLIR must be installed on the system From b3545bf65873c61a3aee21be84058a2701801e67 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:59:38 +0100 Subject: [PATCH 362/419] Sort qubits before deallocating --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 9 +++++++-- .../Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 66cd1d0dff..7f5fcae41b 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -617,8 +617,13 @@ void FluxProgramBuilder::checkFinalized() const { } OwningOpRef FluxProgramBuilder::finalize() { - // Automatically deallocate all remaining valid qubits - for (const auto qubit : validQubits) { + // Automatically deallocate all still-allocated qubits + // Sort qubits for deterministic output + SmallVector sortedQubits(validQubits.begin(), validQubits.end()); + llvm::sort(sortedQubits, [](Value a, Value b) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + }); + for (auto qubit : sortedQubits) { create(loc, qubit); } diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 91f7562991..12b8599c31 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -456,8 +456,14 @@ void QuartzProgramBuilder::checkFinalized() const { } OwningOpRef QuartzProgramBuilder::finalize() { - // Automatically deallocate all remaining allocated qubits - for (auto qubit : allocatedQubits) { + // Automatically deallocate all still-allocated qubits + // Sort qubits for deterministic output + SmallVector sortedQubits(allocatedQubits.begin(), + allocatedQubits.end()); + llvm::sort(sortedQubits, [](Value a, Value b) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + }); + for (auto qubit : sortedQubits) { create(loc, qubit); } From 842c65332719d242dc98bec40febb24a1ec42f60 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 00:12:48 +0100 Subject: [PATCH 363/419] Add assertions to Quartz-to-Flux conversion --- .../Conversion/QuartzToFlux/QuartzToFlux.cpp | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp index a7a4b93297..f3345c53c0 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp @@ -158,8 +158,11 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); fluxQubit = qubitMap[quartzQubit]; } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); fluxQubit = state.targetsIn[inCtrlOp].front(); } @@ -201,8 +204,11 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); fluxQubit = qubitMap[quartzQubit]; } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); fluxQubit = state.targetsIn[inCtrlOp].front(); } @@ -245,8 +251,11 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); fluxQubit = qubitMap[quartzQubit]; } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); fluxQubit = state.targetsIn[inCtrlOp].front(); } @@ -290,8 +299,11 @@ convertOneTargetThreeParameter(QuartzOpType& op, const auto& quartzQubit = op.getQubitIn(); Value fluxQubit; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); fluxQubit = qubitMap[quartzQubit]; } else { + assert(state.targetsIn[inCtrlOp].size() == 1 && + "Invalid number of input targets"); fluxQubit = state.targetsIn[inCtrlOp].front(); } @@ -337,9 +349,13 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); + assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); const auto& targetsIn = state.targetsIn[inCtrlOp]; fluxQubit0 = targetsIn[0]; fluxQubit1 = targetsIn[1]; @@ -351,9 +367,13 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, // Update state map if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); + assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); state.targetsIn.erase(inCtrlOp); const SmallVector targetsOut( {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); @@ -388,9 +408,13 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); + assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); const auto& targetsIn = state.targetsIn[inCtrlOp]; fluxQubit0 = targetsIn[0]; fluxQubit1 = targetsIn[1]; @@ -439,9 +463,13 @@ LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, Value fluxQubit0; Value fluxQubit1; if (inCtrlOp == 0) { + assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); + assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); fluxQubit0 = qubitMap[quartzQubit0]; fluxQubit1 = qubitMap[quartzQubit1]; } else { + assert(state.targetsIn[inCtrlOp].size() == 2 && + "Invalid number of input targets"); const auto& targetsIn = state.targetsIn[inCtrlOp]; fluxQubit0 = targetsIn[0]; fluxQubit1 = targetsIn[1]; @@ -563,6 +591,7 @@ struct ConvertQuartzDeallocOp final const auto& quartzQubit = op.getQubit(); // Look up the latest Flux value for this Quartz qubit + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); const auto& fluxQubit = qubitMap[quartzQubit]; // Create the dealloc operation @@ -650,6 +679,7 @@ struct ConvertQuartzMeasureOp final const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); const auto& fluxQubit = qubitMap[quartzQubit]; // Create flux.measure (returns both output qubit and bit result) @@ -701,6 +731,7 @@ struct ConvertQuartzResetOp final const auto& quartzQubit = op.getQubit(); // Get the latest Flux qubit value from the state map + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); const auto& fluxQubit = qubitMap[quartzQubit]; // Create flux.reset (consumes input, produces output) @@ -1021,6 +1052,7 @@ struct ConvertQuartzBarrierOp final SmallVector fluxQubits; fluxQubits.reserve(quartzQubits.size()); for (const auto& quartzQubit : quartzQubits) { + assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); fluxQubits.push_back(qubitMap[quartzQubit]); } @@ -1070,6 +1102,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { SmallVector fluxControls; fluxControls.reserve(quartzControls.size()); for (const auto& quartzControl : quartzControls) { + assert(qubitMap.contains(quartzControl) && "Quartz qubit not found"); fluxControls.push_back(qubitMap[quartzControl]); } @@ -1079,6 +1112,7 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { fluxTargets.reserve(numTargets); for (size_t i = 0; i < numTargets; ++i) { const auto& quartzTarget = op.getTarget(i); + assert(qubitMap.contains(quartzTarget) && "Quartz qubit not found"); const auto& fluxTarget = qubitMap[quartzTarget]; fluxTargets.push_back(fluxTarget); } From 6b447a2bb267108f1980784fd9c608db9285ee0d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 01:03:13 +0100 Subject: [PATCH 364/419] Give ClassicalRegister ownership of name --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 17 +++++++---------- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 14 ++++++-------- .../Quartz/Builder/QuartzProgramBuilder.h | 17 +++++++---------- .../Dialect/Flux/Builder/FluxProgramBuilder.cpp | 8 ++++---- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 13 ++++++------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 9 +++++---- .../TranslateQuantumComputationToQuartz.cpp | 8 ++++---- 7 files changed, 39 insertions(+), 47 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 023fa48f8b..37155e1a5b 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include namespace mlir::flux { @@ -123,7 +123,7 @@ class FluxProgramBuilder final : public OpBuilder { * %q2 = flux.alloc("q", 3, 2) : !flux.qubit * ``` */ - SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); + SmallVector allocQubitRegister(int64_t size, std::string name = "q"); /** * @brief A small structure representing a single classical bit within a @@ -131,7 +131,7 @@ class FluxProgramBuilder final : public OpBuilder { */ struct Bit { /// Name of the register containing this bit - StringRef registerName; + std::string registerName; /// Size of the register containing this bit int64_t registerSize{}; /// Index of this bit within the register @@ -143,7 +143,7 @@ class FluxProgramBuilder final : public OpBuilder { */ struct ClassicalRegister { /// Name of the classical register - StringRef name; + std::string name; /// Size of the classical register int64_t size; @@ -165,15 +165,15 @@ class FluxProgramBuilder final : public OpBuilder { * @brief Allocate a classical bit register * @param size Number of bits * @param name Register name (default: "c") - * @return A reference to a ClassicalRegister structure + * @return A ClassicalRegister structure * * @par Example: * ```c++ * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` */ - ClassicalRegister& allocClassicalBitRegister(int64_t size, - StringRef name = "c"); + ClassicalRegister allocClassicalBitRegister(int64_t size, + std::string name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset @@ -1063,8 +1063,5 @@ class FluxProgramBuilder final : public OpBuilder { /// When an operation consumes a qubit and produces a new one, the old value /// is removed and the new output is added. llvm::DenseSet validQubits; - - /// Track allocated classical Registers - SmallVector allocatedClassicalRegisters; }; } // namespace mlir::flux diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index a48f735c34..d62af1129e 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace mlir::qir { @@ -128,7 +129,7 @@ class QIRProgramBuilder { */ struct Bit { /// Name of the register containing this bit - StringRef registerName; + std::string registerName; /// Size of the register containing this bit int64_t registerSize{}; /// Index of this bit within the register @@ -140,7 +141,7 @@ class QIRProgramBuilder { */ struct ClassicalRegister { /// Name of the classical register - StringRef name; + std::string name; /// Size of the classical register int64_t size; @@ -162,15 +163,15 @@ class QIRProgramBuilder { * @brief Allocate a classical bit register * @param size Number of bits * @param name Register name (default: "c") - * @return A reference to a ClassicalRegister structure + * @return A ClassicalRegister structure * * @par Example: * ```c++ * auto& c = builder.allocClassicalBitRegister(3, "c"); * ``` */ - ClassicalRegister& allocClassicalBitRegister(int64_t size, - StringRef name = "c"); + ClassicalRegister allocClassicalBitRegister(int64_t size, + std::string name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset @@ -802,9 +803,6 @@ class QIRProgramBuilder { ModuleOp module; Location loc; - /// Track allocated classical Registers - SmallVector allocatedClassicalRegisters; - LLVM::LLVMFuncOp mainFunc; /// Entry block: constants and initialization diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index e1468f01ba..dc09e7911a 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include namespace mlir::quartz { @@ -116,7 +116,7 @@ class QuartzProgramBuilder final : public OpBuilder { * %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit * ``` */ - SmallVector allocQubitRegister(int64_t size, StringRef name = "q"); + SmallVector allocQubitRegister(int64_t size, std::string name = "q"); /** * @brief A small structure representing a single classical bit within a @@ -124,7 +124,7 @@ class QuartzProgramBuilder final : public OpBuilder { */ struct Bit { /// Name of the register containing this bit - StringRef registerName; + std::string registerName; /// Size of the register containing this bit int64_t registerSize{}; /// Index of this bit within the register @@ -136,7 +136,7 @@ class QuartzProgramBuilder final : public OpBuilder { */ struct ClassicalRegister { /// Name of the classical register - StringRef name; + std::string name; /// Size of the classical register int64_t size; @@ -158,15 +158,15 @@ class QuartzProgramBuilder final : public OpBuilder { * @brief Allocate a classical bit register * @param size Number of bits * @param name Register name (default: "c") - * @return A reference to a ClassicalRegister structure + * @return A ClassicalRegister structure * * @par Example: * ```c++ * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` */ - ClassicalRegister& allocClassicalBitRegister(int64_t size, - StringRef name = "c"); + ClassicalRegister allocClassicalBitRegister(int64_t size, + std::string name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset @@ -870,9 +870,6 @@ class QuartzProgramBuilder final : public OpBuilder { /// Track allocated qubits for automatic deallocation llvm::DenseSet allocatedQubits; - /// Track allocated classical Registers - SmallVector allocatedClassicalRegisters; - /// Check if the builder has been finalized void checkFinalized() const; }; diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 7f5fcae41b..9f92c6a826 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -86,7 +86,7 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, - const StringRef name) { + const std::string name) { checkFinalized(); if (size <= 0) { @@ -110,15 +110,15 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, return qubits; } -FluxProgramBuilder::ClassicalRegister& -FluxProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { +FluxProgramBuilder::ClassicalRegister +FluxProgramBuilder::allocClassicalBitRegister(int64_t size, std::string name) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - return allocatedClassicalRegisters.emplace_back(name, size); + return {.name = name, .size = size}; } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 167850b495..d36beb51b1 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -135,9 +135,9 @@ QIRProgramBuilder::allocQubitRegister(const int64_t size) { return qubits; } -QIRProgramBuilder::ClassicalRegister& +QIRProgramBuilder::ClassicalRegister QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, - StringRef name) { + std::string name) { checkFinalized(); if (size <= 0) { @@ -151,7 +151,6 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, builder.setInsertionPoint(measurementsBlock->getTerminator()); const auto numResults = static_cast(metadata_.numResults); - auto& reg = allocatedClassicalRegisters.emplace_back(name, size); for (int64_t i = 0; i < size; ++i) { Value val{}; if (const auto it = ptrCache.find(numResults + i); it != ptrCache.end()) { @@ -164,7 +163,7 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, registerResultMap.insert({{name, i}, val}); } metadata_.numResults += size; - return reg; + return {.name = name, .size = size}; } Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { @@ -175,7 +174,7 @@ Value QIRProgramBuilder::measure(Value qubit, const int64_t resultIndex) { } // Choose a safe default register name - StringRef defaultRegName = "c"; + std::string defaultRegName = "c"; if (llvm::any_of(registerResultMap, [](const auto& entry) { return entry.first.first == "c"; })) { @@ -598,7 +597,7 @@ void QIRProgramBuilder::generateOutputRecording() { // Sort registers by name for deterministic output llvm::SmallVector< - std::pair>>> + std::pair>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); @@ -637,7 +636,7 @@ void QIRProgramBuilder::generateOutputRecording() { for (const auto [regIdx, resultPtr] : measurements) { // Create label for result: "{registerName}{regIdx}r" const std::string resultLabel = - registerName.str() + std::to_string(regIdx) + "r"; + registerName + std::to_string(regIdx) + "r"; auto resultLabelOp = createResultLabel(builder, module, resultLabel); builder.create( diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 12b8599c31..297c9b92ad 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -79,7 +79,7 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { llvm::SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, - const StringRef name) { + const std::string name) { checkFinalized(); if (size <= 0) { @@ -104,15 +104,16 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, return qubits; } -QuartzProgramBuilder::ClassicalRegister& -QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, StringRef name) { +QuartzProgramBuilder::ClassicalRegister +QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, + std::string name) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - return allocatedClassicalRegisters.emplace_back(name, size); + return {.name = name, .size = size}; } //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index a50db820ee..454a49f5d4 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -49,7 +49,7 @@ struct QregInfo { llvm::SmallVector qubits; }; -using BitMemInfo = std::pair; // (register ref, localIdx) using BitIndexVec = llvm::SmallVector; @@ -161,11 +161,11 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, BitIndexVec bitMap; bitMap.resize(quantumComputation.getNcbits()); for (const auto* reg : cregPtrs) { - auto& mem = builder.allocClassicalBitRegister( + const auto& mem = builder.allocClassicalBitRegister( static_cast(reg->getSize()), reg->getName()); for (size_t i = 0; i < reg->getSize(); ++i) { const auto globalIdx = static_cast(reg->getStartIndex() + i); - bitMap[globalIdx] = {&mem, i}; + bitMap[globalIdx] = {mem, i}; } } @@ -198,7 +198,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, const auto& [mem, localIdx] = bitMap[bitIdx]; // Use builder's measure method which keeps output record - builder.measure(qubit, (*mem)[static_cast(localIdx)]); + builder.measure(qubit, mem[static_cast(localIdx)]); } } From 14cab18c071c000c74e8fb6d8864b27956832efd Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 01:24:46 +0100 Subject: [PATCH 365/419] Fix trivial CtrlOps --- mlir/include/mlir/Dialect/Flux/IR/FluxOps.td | 3 +- .../mlir/Dialect/Quartz/IR/QuartzOps.td | 3 +- .../pipeline/test_compiler_pipeline.cpp | 30 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td index 5dc52bd8c0..9741ec0d38 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td @@ -1041,7 +1041,8 @@ def CtrlOp : FluxOp<"ctrl", traits = AttrSizedResultSegments, SameOperandsAndResultType, SameOperandsAndResultShape, - SingleBlock + SingleBlock, + RecursiveMemoryEffects ]> { let summary = "Add control qubits to a unitary operation"; let description = [{ diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 6be240e165..20325e06a2 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -931,7 +931,8 @@ def YieldOp : QuartzOp<"yield", traits = [Terminator]> { def CtrlOp : QuartzOp<"ctrl", traits = [ UnitaryOpInterface, - SingleBlockImplicitTerminator<"::mlir::quartz::YieldOp"> + SingleBlockImplicitTerminator<"::mlir::quartz::YieldOp">, + RecursiveMemoryEffects ]> { let summary = "Add control qubits to a unitary operation"; let description = [{ diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 7a4ab5fb43..71a3356814 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1612,6 +1612,36 @@ TEST_F(CompilerPipelineTest, MCXNested) { }); } +TEST_F(CompilerPipelineTest, MCXTrivial) { + auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.mcx({}, reg[0]); + }); + + ASSERT_TRUE(runPipeline(input.get()).succeeded()); + + const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.x(reg[0]); + }); + const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + auto reg = b.allocQubitRegister(1, "q"); + b.x(reg[0]); + }); + const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { + auto reg = b.allocQubitRegister(1); + b.x(reg[0]); + }); + + verifyAllStages({ + .quartzImport = quartz.get(), + .fluxConversion = flux.get(), + .optimization = flux.get(), + .quartzConversion = quartz.get(), + .qirConversion = qir.get(), + }); +} + TEST_F(CompilerPipelineTest, Y) { qc::QuantumComputation qc; qc.addQubitRegister(1, "q"); From 657d1c7b1e3ed44597f7e8b371700170a55dff16 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 01:54:42 +0100 Subject: [PATCH 366/419] Do not use const std::string --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 3 +-- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 9f92c6a826..33199163ce 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -85,8 +85,7 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { } llvm::SmallVector -FluxProgramBuilder::allocQubitRegister(const int64_t size, - const std::string name) { +FluxProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { checkFinalized(); if (size <= 0) { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 297c9b92ad..d4bc0230a4 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -78,8 +78,7 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { } llvm::SmallVector -QuartzProgramBuilder::allocQubitRegister(const int64_t size, - const std::string name) { +QuartzProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { checkFinalized(); if (size <= 0) { From ba33fa1de06a988fef7efe8de3deed9246387a30 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:02:15 +0100 Subject: [PATCH 367/419] Save name before inserting it into DenseMap --- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 5 +++++ mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index d62af1129e..f626fcc4a5 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -805,6 +806,10 @@ class QIRProgramBuilder { LLVM::LLVMFuncOp mainFunc; + /// Allocator and StringSaver for stable StringRefs + llvm::BumpPtrAllocator allocator; + llvm::StringSaver stringSaver{allocator}; + /// Entry block: constants and initialization Block* entryBlock{}; /// Body block: reversible operations (gates) diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index d36beb51b1..59cc23bd75 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -160,7 +160,7 @@ QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, // Cache for reuse ptrCache[numResults + i] = val; } - registerResultMap.insert({{name, i}, val}); + registerResultMap.insert({{stringSaver.save(name), i}, val}); } metadata_.numResults += size; return {.name = name, .size = size}; From 351bb366f2cfa16e41a848b96429e31b00b66ad0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:05:17 +0100 Subject: [PATCH 368/419] Fix docstrings --- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index dc09e7911a..4982d7cbef 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -257,10 +257,10 @@ class QuartzProgramBuilder final : public OpBuilder { * \ * @par Example: \ * ```c++ \ - * builder.c##OP_NAME(PARAM, {q0, q1}); \ + * builder.c##OP_NAME(PARAM, q); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ + * quartz.ctrl(%q) { \ * quartz.OP_NAME(%PARAM) \ * } \ */ \ @@ -415,9 +415,9 @@ class QuartzProgramBuilder final : public OpBuilder { QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls, Value target); - DECLARE_ONE_TARGET_ONE_PARAMETER(RzOp, rz, theta) - DECLARE_ONE_TARGET_ONE_PARAMETER(RxOp, rx, theta) - DECLARE_ONE_TARGET_ONE_PARAMETER(RyOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rz, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RZOp, ry, theta) DECLARE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) #undef DECLARE_ONE_TARGET_ONE_PARAMETER From 4374c9091a2c7412a1f2b8b79389f73abc39558b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:06:10 +0100 Subject: [PATCH 369/419] Update docs to reflect corrected required LLVM version --- docs/mlir/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mlir/index.md b/docs/mlir/index.md index b409318f93..8184041d5f 100644 --- a/docs/mlir/index.md +++ b/docs/mlir/index.md @@ -121,6 +121,6 @@ module { ## Development -Building the MLIR library requires LLVM version 21.0 or later. +Building the MLIR library requires LLVM version 21.1 or later. Our CI pipeline on GitHub continuously builds and tests the MLIR library on Linux, macOS, and Windows. To access the latest build logs, visit the [GitHub Actions page](https://github.com/munich-quantum-toolkit/core/actions/workflows/ci.yml). From 02a76117c5f66f711921296bf5308aadeafc8837 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:14:01 +0100 Subject: [PATCH 370/419] Add error handling to translation --- .../TranslateQuantumComputationToQuartz.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 454a49f5d4..7f3dfcbebf 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -564,9 +564,9 @@ translateOperations(QuartzProgramBuilder& builder, ADD_OP_CASE(XXminusYY) ADD_OP_CASE(Barrier) default: - // Unsupported operation - skip for now - // As the Quartz dialect is expanded, more operations will be supported - continue; + llvm::errs() << operation->getName() + << "cannot be translated to Quartz\n"; + return failure(); } } @@ -627,8 +627,8 @@ OwningOpRef translateQuantumComputationToQuartz( // Translate operations if (translateOperations(builder, quantumComputation, qubits, bitMap) .failed()) { - // Note: Currently all operations succeed or are skipped - // This check is here for future error handling + llvm::reportFatalInternalError( + "Failed to translate QuantumComputation to Quartz"); } // Finalize and return the module (adds return statement and transfers From adf603b544147c09b0d9951c8e19c2b5ea74dbca Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:22:35 +0100 Subject: [PATCH 371/419] Validate builder states before finalizing --- .../Dialect/Flux/Builder/FluxProgramBuilder.cpp | 17 +++++++++++++++++ .../Quartz/Builder/QuartzProgramBuilder.cpp | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 33199163ce..88dfa5e1f0 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -616,6 +616,23 @@ void FluxProgramBuilder::checkFinalized() const { } OwningOpRef FluxProgramBuilder::finalize() { + // Ensure that main function exists and insertion point is valid + auto* insertionBlock = getInsertionBlock(); + func::FuncOp mainFunc = nullptr; + for (auto op : module.getOps()) { + if (op.getName() == "main") { + mainFunc = op; + break; + } + } + if (!mainFunc) { + llvm::reportFatalUsageError("Could not find main function"); + } + if (!insertionBlock || insertionBlock != &mainFunc.getBody().front()) { + llvm::reportFatalUsageError( + "Insertion point is not in entry block of main function"); + } + // Automatically deallocate all still-allocated qubits // Sort qubits for deterministic output SmallVector sortedQubits(validQubits.begin(), validQubits.end()); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index d4bc0230a4..39a6da4d17 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -456,6 +456,23 @@ void QuartzProgramBuilder::checkFinalized() const { } OwningOpRef QuartzProgramBuilder::finalize() { + // Ensure that main function exists and insertion point is valid + auto* insertionBlock = getInsertionBlock(); + func::FuncOp mainFunc = nullptr; + for (auto op : module.getOps()) { + if (op.getName() == "main") { + mainFunc = op; + break; + } + } + if (!mainFunc) { + llvm::reportFatalUsageError("Could not find main function"); + } + if (!insertionBlock || insertionBlock != &mainFunc.getBody().front()) { + llvm::reportFatalUsageError( + "Insertion point is not in entry block of main function"); + } + // Automatically deallocate all still-allocated qubits // Sort qubits for deterministic output SmallVector sortedQubits(allocatedQubits.begin(), From 48f509720f466aa07d99ddc1b2ec19220753e107 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:25:39 +0100 Subject: [PATCH 372/419] Improve state validation in runPipeline() --- mlir/lib/Compiler/CompilerPipeline.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index e79244b8a6..2ddd3c4071 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -84,9 +84,10 @@ LogicalResult QuantumCompilerPipeline::runPipeline(ModuleOp module, CompilationRecord* record) const { // Ensure printIRAfterAllStages implies recordIntermediates - if (config_.printIRAfterAllStages && !config_.recordIntermediates) { - llvm::errs() << "Invalid configuration: printIRAfterAllStages requires " - "recordIntermediates to be enabled.\n"; + if (config_.printIRAfterAllStages && + (!config_.recordIntermediates || record == nullptr)) { + llvm::errs() << "printIRAfterAllStages requires recordIntermediates to be " + "enabled and the record pointer to be non-null.\n"; return failure(); } From b4b39a524278bf0592db8a3c97b9aa3cdf9c249c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:28:49 +0100 Subject: [PATCH 373/419] Include missing headers in header files --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 3 +++ .../include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 37155e1a5b..7ab595435f 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -13,11 +13,14 @@ #include "mlir/Dialect/Flux/IR/FluxDialect.h" #include +#include #include #include +#include #include #include #include +#include #include #include diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 4982d7cbef..ccacb6aeaa 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -13,11 +13,14 @@ #include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include +#include #include #include +#include #include #include #include +#include #include #include From 30f2fb6a893ff7d1d4bfbc570983ddbc93912b41 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:34:50 +0100 Subject: [PATCH 374/419] Remove unused variables --- .../Dialect/Flux/Builder/FluxProgramBuilder.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 88dfa5e1f0..2ca18c05cb 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -205,22 +205,25 @@ Value FluxProgramBuilder::reset(Value qubit) { Value FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control) { \ checkFinalized(); \ - const auto [controlsOut, targetsOut] = ctrl( \ - control, {}, [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ - b.create(loc, PARAM); \ - return {}; \ - }); \ + const auto controlsOut = \ + ctrl(control, {}, \ + [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ + b.create(loc, PARAM); \ + return {}; \ + }) \ + .first; \ return controlsOut[0]; \ } \ ValueRange FluxProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls) { \ checkFinalized(); \ - const auto [controlsOut, targetsOut] = \ + const auto controlsOut = \ ctrl(controls, {}, \ [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ b.create(loc, PARAM); \ return {}; \ - }); \ + }) \ + .first; \ return controlsOut; \ } From 14240358293c012579f91b8b7cae446aeddc3a6d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:36:18 +0100 Subject: [PATCH 375/419] Ensure that builders cannot be finalized twice --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 2 ++ mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 ++ mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 2ca18c05cb..aeb81dac60 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -619,6 +619,8 @@ void FluxProgramBuilder::checkFinalized() const { } OwningOpRef FluxProgramBuilder::finalize() { + checkFinalized(); + // Ensure that main function exists and insertion point is valid auto* insertionBlock = getInsertionBlock(); func::FuncOp mainFunc = nullptr; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 59cc23bd75..bb669b877e 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -647,6 +647,8 @@ void QIRProgramBuilder::generateOutputRecording() { } OwningOpRef QIRProgramBuilder::finalize() { + checkFinalized(); + // Generate output recording in the output block generateOutputRecording(); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 39a6da4d17..137af13e8b 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -456,6 +456,8 @@ void QuartzProgramBuilder::checkFinalized() const { } OwningOpRef QuartzProgramBuilder::finalize() { + checkFinalized(); + // Ensure that main function exists and insertion point is valid auto* insertionBlock = getInsertionBlock(); func::FuncOp mainFunc = nullptr; From ef121412824cc53ffa0838683b680eeee1e52925 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 02:41:15 +0100 Subject: [PATCH 376/419] Improve deallocation order --- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 7 ++++++- mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index aeb81dac60..9c71837aee 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -642,7 +642,12 @@ OwningOpRef FluxProgramBuilder::finalize() { // Sort qubits for deterministic output SmallVector sortedQubits(validQubits.begin(), validQubits.end()); llvm::sort(sortedQubits, [](Value a, Value b) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + } + return opA->isBeforeInBlock(opB); }); for (auto qubit : sortedQubits) { create(loc, qubit); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 137af13e8b..bdd32fe092 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -480,7 +480,12 @@ OwningOpRef QuartzProgramBuilder::finalize() { SmallVector sortedQubits(allocatedQubits.begin(), allocatedQubits.end()); llvm::sort(sortedQubits, [](Value a, Value b) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + } + return opA->isBeforeInBlock(opB); }); for (auto qubit : sortedQubits) { create(loc, qubit); From fd6e2f144881901b54590435e23dd9824ae609f4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:13:12 +0100 Subject: [PATCH 377/419] Include missing header --- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index f626fcc4a5..a8c7e97102 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include From c8fcc5a955c918d3457b752bd6e7c4ff1b27bdea Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:14:44 +0100 Subject: [PATCH 378/419] Improve docstrings --- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 6 +++--- .../Translation/TranslateQuantumComputationToQuartz.cpp | 7 +------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index a8c7e97102..ffe58a3111 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -169,7 +169,7 @@ class QIRProgramBuilder { * * @par Example: * ```c++ - * auto& c = builder.allocClassicalBitRegister(3, "c"); + * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index ccacb6aeaa..d2367bbef0 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -418,9 +418,9 @@ class QuartzProgramBuilder final : public OpBuilder { QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls, Value target); - DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rz, theta) - DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, rx, theta) - DECLARE_ONE_TARGET_ONE_PARAMETER(RZOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) + DECLARE_ONE_TARGET_ONE_PARAMETER(RZOp, rz, theta) DECLARE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) #undef DECLARE_ONE_TARGET_ONE_PARAMETER diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 7f3dfcbebf..d18eb5b098 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -507,12 +507,7 @@ void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * * @details * Iterates through all operations in the QuantumComputation and translates - * them to Quartz dialect operations. Currently supports: - * - Measurement operations - * - Reset operations - * - * Unary gates and other operations will be added as the Quartz dialect - * is expanded. + * them to Quartz dialect operations. * * @param builder The QuartzProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate From 157a4691a632df515fb8c070838d4339d222a5df Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:23:29 +0100 Subject: [PATCH 379/419] Improve more docstrings --- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- .../Quartz/Translation/TranslateQuantumComputationToQuartz.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index ffe58a3111..ef06035900 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -224,7 +224,7 @@ class QIRProgramBuilder { * * @par Example: * ```c++ - * auto& c = builder.allocClassicalBitRegister(2, "c"); + * auto c = builder.allocClassicalBitRegister(2, "c"); * builder.measure(q0, c[0]); * builder.measure(q1, c[1]); * ``` diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index d18eb5b098..c6d76d052c 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -560,7 +560,7 @@ translateOperations(QuartzProgramBuilder& builder, ADD_OP_CASE(Barrier) default: llvm::errs() << operation->getName() - << "cannot be translated to Quartz\n"; + << " cannot be translated to Quartz\n"; return failure(); } } From a76453613edc74256ce45dd0a0a66723dd93961e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:08:55 +0100 Subject: [PATCH 380/419] Fix assembly formats of some Quartz operations --- mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td index 20325e06a2..d046d172bb 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td @@ -666,7 +666,7 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParamet let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "swap"; } @@ -686,7 +686,7 @@ def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParam let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "iswap"; } @@ -706,7 +706,7 @@ def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "dcx"; } @@ -726,7 +726,7 @@ def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "ecr"; } From 04c8b04776a0c8118ca42e594138556cb0287d65 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:19:11 +0100 Subject: [PATCH 381/419] Improve docstrings one more time --- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 5 ++-- .../TranslateQuantumComputationToQuartz.cpp | 25 +++++++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index ef06035900..f62af0d5b5 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -57,8 +57,9 @@ namespace mlir::qir { * builder.h(q0).cx(q0, q1); * * // Measure with register info for proper output recording - * builder.measure(q0, "c", 0); - * builder.measure(q1, "c", 1); + * auto c = builder.allocClassicalBitRegister(2, "c"); + * builder.measure(q0, c[0]); + * builder.measure(q1, c[1]); * * auto module = builder.finalize(); * ``` diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index c6d76d052c..a3af24dc22 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -507,7 +507,7 @@ void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * * @details * Iterates through all operations in the QuantumComputation and translates - * them to Quartz dialect operations. + * them to Quartz operations. * * @param builder The QuartzProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate @@ -578,27 +578,20 @@ translateOperations(QuartzProgramBuilder& builder, * * @details * This function takes a quantum computation and translates it into an MLIR - * module containing Quartz dialect operations. It uses the QuartzProgramBuilder - * to handle module and function creation, resource allocation, and operation + * module containing Quartz operations. It uses the QuartzProgramBuilder to + * handle module and function creation, resource allocation, and operation * translation. * * The translation process: - * 1. Creates a QuartzProgramBuilder and initializes it (creates main function - * with signature () -> i64) - * 2. Allocates quantum registers using quartz.alloc with register metadata + * 1. Creates a QuartzProgramBuilder and initializes it (creates the main + * function) + * 2. Allocates quantum registers using quartz.alloc * 3. Tracks classical registers for measurement results - * 4. Translates operations (currently: measure, reset) + * 4. Translates operations * 5. Finalizes the module (adds return statement with exit code 0) * - * The generated main function returns exit code 0 to indicate successful - * execution of the quantum program. - * - * Currently supported operations: - * - Measurement (quartz.measure) - * - Reset (quartz.reset) - * - * Operations not yet supported are silently skipped. As the Quartz dialect - * is expanded with gate operations, this translation will be enhanced. + * If the translation fails due to an unsupported operation, a fatal error is + * reported. * * @param context The MLIR context in which the module will be created * @param quantumComputation The quantum computation to translate From 11949730757b61cfeea87f8949897b09dc839d11 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:23:15 +0100 Subject: [PATCH 382/419] Improve error message if bit index is out of bounds --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 5 ++++- mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 5 ++++- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 7ab595435f..f438d48b3d 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -157,7 +157,10 @@ class FluxProgramBuilder final : public OpBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - llvm::reportFatalUsageError("Bit index out of bounds"); + StringRef msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + "' of size " + + std::to_string(size); + llvm::reportFatalUsageError(msg); } return { .registerName = name, .registerSize = size, .registerIndex = index}; diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index f62af0d5b5..872b071227 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -155,7 +155,10 @@ class QIRProgramBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - llvm::reportFatalUsageError("Bit index out of bounds"); + StringRef msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + "' of size " + + std::to_string(size); + llvm::reportFatalUsageError(msg); } return { .registerName = name, .registerSize = size, .registerIndex = index}; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index d2367bbef0..db08634fcf 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -150,7 +150,10 @@ class QuartzProgramBuilder final : public OpBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - llvm::reportFatalUsageError("Bit index out of bounds"); + StringRef msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + "' of size " + + std::to_string(size); + llvm::reportFatalUsageError(msg); } return { .registerName = name, .registerSize = size, .registerIndex = index}; From ef698db0eff6621757dda1f53aecd049de032d12 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:34:55 +0100 Subject: [PATCH 383/419] Fix linter errors --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 2 +- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- .../Quartz/Builder/QuartzProgramBuilder.h | 2 +- .../Dialect/Flux/Builder/FluxProgramBuilder.cpp | 17 ++++++++++------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 13 ++++++------- .../Quartz/Builder/QuartzProgramBuilder.cpp | 17 ++++++++++------- .../TranslateQuantumComputationToQuartz.cpp | 1 + 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index f438d48b3d..6ecafe725d 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -179,7 +179,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c"); + const std::string& name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 872b071227..64e1e10bb6 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -177,7 +177,7 @@ class QIRProgramBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c"); + const std::string& name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index db08634fcf..54cf84fbed 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -172,7 +172,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c"); + const std::string& name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 9c71837aee..d265fe597d 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -27,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -84,15 +85,15 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { return qubit; } -llvm::SmallVector -FluxProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { +SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, + std::string name) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - llvm::SmallVector qubits; + SmallVector qubits; qubits.reserve(static_cast(size)); auto nameAttr = getStringAttr(name); @@ -110,14 +111,15 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { } FluxProgramBuilder::ClassicalRegister -FluxProgramBuilder::allocClassicalBitRegister(int64_t size, std::string name) { +FluxProgramBuilder::allocClassicalBitRegister(int64_t size, + const std::string& name) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - return {.name = name, .size = size}; + return {.name = std::move(name), .size = size}; } //===----------------------------------------------------------------------===// @@ -633,7 +635,8 @@ OwningOpRef FluxProgramBuilder::finalize() { if (!mainFunc) { llvm::reportFatalUsageError("Could not find main function"); } - if (!insertionBlock || insertionBlock != &mainFunc.getBody().front()) { + if ((insertionBlock == nullptr) || + insertionBlock != &mainFunc.getBody().front()) { llvm::reportFatalUsageError( "Insertion point is not in entry block of main function"); } diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index bb669b877e..9bd451f3e1 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -117,15 +118,14 @@ Value QIRProgramBuilder::staticQubit(const int64_t index) { return val; } -llvm::SmallVector -QIRProgramBuilder::allocQubitRegister(const int64_t size) { +SmallVector QIRProgramBuilder::allocQubitRegister(const int64_t size) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - llvm::SmallVector qubits; + SmallVector qubits; qubits.reserve(size); for (int64_t i = 0; i < size; ++i) { @@ -137,7 +137,7 @@ QIRProgramBuilder::allocQubitRegister(const int64_t size) { QIRProgramBuilder::ClassicalRegister QIRProgramBuilder::allocClassicalBitRegister(const int64_t size, - std::string name) { + const std::string& name) { checkFinalized(); if (size <= 0) { @@ -589,15 +589,14 @@ void QIRProgramBuilder::generateOutputRecording() { auto ptrType = LLVM::LLVMPointerType::get(builder.getContext()); // Group measurements by register - llvm::StringMap>> registerGroups; + llvm::StringMap>> registerGroups; for (const auto& [key, resultPtr] : registerResultMap) { const auto& [regName, regIdx] = key; registerGroups[regName].emplace_back(regIdx, resultPtr); } // Sort registers by name for deterministic output - llvm::SmallVector< - std::pair>>> + SmallVector>>> sortedRegisters; for (auto& [name, measurements] : registerGroups) { sortedRegisters.emplace_back(name, std::move(measurements)); diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index bdd32fe092..cb18fd6e3f 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include namespace mlir::quartz { @@ -77,8 +79,8 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { return staticOp.getQubit(); } -llvm::SmallVector -QuartzProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { +SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, + std::string name) { checkFinalized(); if (size <= 0) { @@ -86,7 +88,7 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { } // Allocate a sequence of qubits with register metadata - llvm::SmallVector qubits; + SmallVector qubits; qubits.reserve(size); auto nameAttr = getStringAttr(name); @@ -105,14 +107,14 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, std::string name) { QuartzProgramBuilder::ClassicalRegister QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, - std::string name) { + const std::string& name) { checkFinalized(); if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - return {.name = name, .size = size}; + return {.name = std::move(name), .size = size}; } //===----------------------------------------------------------------------===// @@ -470,7 +472,8 @@ OwningOpRef QuartzProgramBuilder::finalize() { if (!mainFunc) { llvm::reportFatalUsageError("Could not find main function"); } - if (!insertionBlock || insertionBlock != &mainFunc.getBody().front()) { + if ((insertionBlock == nullptr) || + insertionBlock != &mainFunc.getBody().front()) { llvm::reportFatalUsageError( "Insertion point is not in entry block of main function"); } diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index a3af24dc22..576e9b9e25 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From e5cd6d157e2d1d7517a982da2e0d8607f394ac2e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:49:40 +0100 Subject: [PATCH 384/419] Use llvm::cast to fix linter error --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 71a3356814..ec0755f040 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1583,8 +1583,7 @@ TEST_F(CompilerPipelineTest, MCXNested) { auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.ctrl(reg[0], [&](OpBuilder& b) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) - static_cast(b).cx(reg[1], reg[2]); + llvm::cast(b).cx(reg[1], reg[2]); }); }); From f02db72ff14cb4b796da509e8749c816cd39fe7c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:59:43 +0100 Subject: [PATCH 385/419] Remove redesign concept --- docs/mlir/flux-logo.svg | 106 - ...lation-infrastructure-technical-concept.md | 1745 ----------------- docs/mlir/quartz-logo.svg | 126 -- 3 files changed, 1977 deletions(-) delete mode 100644 docs/mlir/flux-logo.svg delete mode 100644 docs/mlir/mlir-compilation-infrastructure-technical-concept.md delete mode 100644 docs/mlir/quartz-logo.svg diff --git a/docs/mlir/flux-logo.svg b/docs/mlir/flux-logo.svg deleted file mode 100644 index 4ce4aa3ecb..0000000000 --- a/docs/mlir/flux-logo.svg +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FLUX - - - - - diff --git a/docs/mlir/mlir-compilation-infrastructure-technical-concept.md b/docs/mlir/mlir-compilation-infrastructure-technical-concept.md deleted file mode 100644 index d89330dfb0..0000000000 --- a/docs/mlir/mlir-compilation-infrastructure-technical-concept.md +++ /dev/null @@ -1,1745 +0,0 @@ -# MQT MLIR Compilation Infrastructure: Technical Concept - -## Executive Summary - -This document describes the technical design of the MQT's MLIR-based compilation infrastructure for quantum computing. -The infrastructure provides a unified, extensible framework for quantum circuit representation, optimization, and compilation through a multi-dialect architecture built on MLIR (Multi-Level Intermediate Representation). - -**Architecture Overview:** - -The MQT MLIR infrastructure consists of two complementary dialects that work together to enable flexible quantum program representation and optimization: - -- **Quartz Dialect** (`quartz`): Uses reference semantics with in-place qubit mutation, optimized for direct hardware mapping and straightforward translation to/from existing quantum programming languages. -- **Flux Dialect** (`flux`): Uses SSA value semantics with functional-style transformations, designed for powerful compiler optimizations and circuit transformations. - -Both dialects implement a unified unitary interface, composable gate modifiers, and comprehensive canonicalization frameworks that enable seamless interoperability and progressive optimization strategies. - -**Key Features:** - -- **Unified Interface:** All unitary operations expose consistent APIs for introspection and composition across dialects -- **Enhanced Expressiveness:** Support for arbitrary gate modifications, custom gate definitions, and symbolic parameters -- **Optimization-Ready:** Built-in canonicalization rules and transformation hooks throughout the representation -- **Dual Semantics:** Choose the appropriate dialect for each compilation stage—reference semantics for I/O boundaries, value semantics for optimization -- **Extensible Architecture:** Designed to accommodate additional dialects and abstraction levels as the quantum computing field evolves - -**Design Rationale:** - -The dual-dialect architecture reflects a fundamental insight: different stages of quantum compilation benefit from different representations. -Reference semantics (Quartz) provides a natural bridge to hardware and existing quantum programming ecosystems, while value semantics (Flux) unlocks the full power of compiler analysis and optimization. -Conversion passes between dialects enable compilation strategies that leverage the strengths of each representation at the appropriate stage. - -## 1. Overview and Design Philosophy - -The MQT MLIR compilation infrastructure represents a comprehensive approach to quantum program compilation, building on MLIR's proven multi-level intermediate representation framework. -This design enables quantum computing to benefit from decades of classical compiler research while addressing the unique challenges of quantum circuits. - -**Design Goals:** - -- **Unified Unitary Interface:** Provide a coherent interface for all operations that apply or produce a unitary transformation (base gates, user-defined gates, modified operations, compositions) -- **Multi-Level Representation:** Support both reference semantics (Quartz) for hardware-oriented representations and value semantics (Flux) for optimization-oriented transformations -- **Composable Modifiers:** Enable arbitrary combinations of inversion, powering, and positive/negative control modifications through a clean modifier system -- **Custom Gate Support:** Allow users to define gates via matrix representations or compositional sequences, enabling domain-specific abstractions -- **Consistent Parameterization:** Unify handling of static and dynamic parameters with consistent ordering and introspection capabilities -- **Systematic Canonicalization:** Embed optimization rules directly at operation definitions for predictable and composable transformations -- **Normalized Representations:** Establish canonical forms (e.g., modifier ordering: `negctrl → ctrl → pow → inv`) to simplify pattern matching and optimization -- **Matrix Extraction:** Enable static matrix computation where possible while supporting symbolic composition for dynamic cases - -**Architectural Principles:** - -1. **Separation of Concerns:** Base gates are modifier-free; extensions are applied via wrapper operations -2. **Progressive Refinement:** Compilation proceeds through multiple passes, each operating at appropriate abstraction levels -3. **Semantic Preservation:** All transformations maintain unitary equivalence (or explicitly document approximations) -4. **Extensibility:** The architecture accommodates future dialects for pulse-level control, error correction, or domain-specific optimizations - -## 2. Motivation and Context - -**The MLIR Opportunity:** - -MLIR provides a proven framework for building progressive compilation pipelines with multiple levels of abstraction. -By building quantum compilation infrastructure on MLIR, we gain: - -- Mature ecosystem of transformation passes and analysis frameworks -- Standard infrastructure for dialect definition, conversion, and optimization -- Established patterns for SSA-based transformations and rewrites -- Interoperability with classical compilation infrastructure for hybrid quantum-classical systems - -**Why Dual Dialects?** - -The Quartz/Flux dual-dialect architecture reflects a key insight: quantum compilation benefits from different semantic models at different stages: - -- **Quartz (Reference Semantics):** Provides an intuitive, hardware-like model where gates modify qubits in place. These semantics align naturally with: - - Physical quantum hardware models - - Existing quantum programming languages (OpenQASM, Qiskit, etc.) - - Direct circuit representations - - Backend code generation - -- **Flux (Value Semantics):** Provides a functional, SSA-based model where operations produce new quantum values. These semantics enable: - - Powerful dataflow analysis - - Safe parallelization and reordering - - Sophisticated optimization passes - - Clear dependency tracking - -Conversion passes between dialects allow compilation strategies to use the right representation at the right time. - -## 3. Dialect Architecture - -The MQT MLIR infrastructure consists of two parallel dialects with identical operation sets but different operational semantics. -This section describes the architectural design shared across both dialects. - -### 3.1 Dialect Overview - -**Quartz Dialect (`quartz`):** - -Quartz uses **reference semantics** where quantum operations modify qubits in place, similar to how hardware physically transforms quantum states. -This model provides: - -- Natural mapping to hardware execution models -- Intuitive representation for circuit descriptions -- Direct compatibility with imperative quantum programming languages -- Straightforward backend code generation - -The name "Quartz" reflects the crystalline, structured nature of hardware-oriented representations—operations have fixed positions and transform states in place, like atoms in a crystal lattice. - -**Example:** - -```mlir -quartz.h %q // Applies Hadamard to qubit %q in place -quartz.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets -``` - -**Flux Dialect (`flux`):** - -Flux uses **value semantics** where quantum operations consume input qubits and produce new output values, following the functional programming and SSA paradigm. -This model enables: - -- Powerful compiler optimizations through clear dataflow -- Safe reordering and parallelization analysis -- Advanced transformation passes -- Explicit dependency tracking - -The name "Flux" captures the flowing, transformative nature of value-based representations—quantum states flow through operations, each transformation producing new values like a river flowing through a landscape. - -**Example:** - -```mlir -%q_out = flux.h %q_in // Consumes %q_in, produces %q_out -%q0_out, %q1_out = flux.swap %q0_in, %q1_in // Consumes inputs, produces outputs -``` - -**Dialect Interoperability:** - -Both dialects share operation names and core semantics, differing only in their type systems and value threading models. Bidirectional conversion passes enable flexible compilation strategies: - -``` -Frontend (Qiskit, OpenQASM3, ...) → Quartz → Flux → Optimizations → Flux → Quartz → Backend (QIR, OpenQASM 3, ...) - ↑_____(conversion passes)_____↑ - ↑______________(translation passes)_____________↑ -``` - -This architecture allows: - -- Input from quantum programming languages via Quartz -- Optimization in Flux using SSA transformations -- Output to hardware backends via Quartz - -**Future Extensibility:** - -The architecture anticipates additional dialects for: - -- Pulse-level control representations -- Error-corrected logical circuits -- Tensor network representations (e.g., ZX calculus) -- Domain-specific optimizations - -Each dialect can target specific optimization goals while maintaining interoperability through conversion passes. - -### 3.2 Operation Categories - -All operations fall into three primary categories: - -1. **Resource Operations:** Manage qubit lifetime and allocation -2. **Measurement and Reset:** Non-unitary operations that collapse or reinitialize quantum states -3. **Unitary Operations:** All operations implementing the `UnitaryOpInterface` (base gates, modifiers, sequences, custom gates) - -### 3.3 Resource Operations - -**Purpose:** Manage qubit lifetime and references. - -**Qubit and Register Allocation:** - -The MQT MLIR dialects represent quantum and classical registers using MLIR-native `memref` operations rather than custom types. This design choice offers several advantages: - -- **MLIR Integration:** Seamless compatibility with existing MLIR infrastructure, enabling reuse of memory handling patterns and optimization passes -- **Implementation Efficiency:** No need to define and maintain custom register operations, significantly reducing implementation complexity -- **Enhanced Interoperability:** Easier integration with other MLIR dialects and passes, allowing for more flexible compilation pipelines -- **Sustainable Evolution:** Standard memory operations can handle transformations while allowing new features without changing the fundamental register model - -**Quantum Register Representation:** - -A quantum register is represented by a `memref` of type `!quartz.Qubit` or `!flux.Qubit`: - -```mlir -// A quantum register with 2 qubits -%qreg = memref.alloc() : memref<2x!quartz.Qubit> - -// Load qubits from the register -%q0 = memref.load %qreg[%i0] : memref<2x!quartz.Qubit> -%q1 = memref.load %qreg[%i1] : memref<2x!quartz.Qubit> -``` - -**Classical Register Representation:** - -Classical registers follow the same pattern but use the `i1` type for Boolean measurement results: - -```mlir -// A classical register with 1 bit -%creg = memref.alloc() : memref<1xi1> - -// Store measurement result -%c = quartz.measure %q -memref.store %c, %creg[%i0] : memref<1xi1> -``` - -**Quartz Dialect (Reference Semantics):** - -```mlir -%q = quartz.alloc : !quartz.qubit -quartz.dealloc %q : !quartz.qubit -%q0 = quartz.qubit 0 : !quartz.qubit // Static qubit reference -``` - -**Flux Dialect (Value Semantics):** - -```mlir -%q = flux.alloc : !flux.qubit -flux.dealloc %q : !flux.qubit -%q0 = flux.qubit 0 : !flux.qubit // Static qubit reference -``` - -**Canonicalization Patterns:** - -- Dead allocation elimination: Remove unused `alloc` operations (DCE) - -### 3.4 Measurement and Reset - -Non-unitary operations that do not implement the `UnitaryOpInterface`. - -**Measurement Basis:** All measurements are performed in the **computational basis** (Z-basis). Measurements in other bases must be implemented by applying appropriate basis-change gates before measurement. - -**Single-Qubit Measurements Only:** Multi-qubit measurements are explicitly **not supported**. Joint measurements must be decomposed into individual single-qubit measurements. - -**Quartz Dialect (Reference Semantics):** - -```mlir -%c = quartz.measure %q : !quartz.qubit -> i1 -quartz.reset %q : !quartz.qubit -``` - -**Flux Dialect (Value Semantics):** - -```mlir -%q_out, %c = flux.measure %q_in : !flux.qubit -> (!flux.qubit, i1) -%q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit -``` - -**Canonicalization Patterns:** - -- `reset` immediately after `alloc` → remove `reset` (already in ground state) -- Consecutive `reset` on same qubit (reference semantics) → single instance - -### 3.5 Unified Unitary Interface Design - -All unitary-applying operations implement a common `UnitaryOpInterface` that provides uniform introspection and composition capabilities. This applies to base gates, modifiers, sequences, and user-defined operations. - -**Interface Methods:** - -```c++ -// Qubit accessors -size_t getNumTargets(); -size_t getNumPosControls(); -size_t getNumNegControls(); -Value getTarget(size_t i); -Value getPosControl(size_t i); -Value getNegControl(size_t i); - -// Value semantics threading -Value getInput(size_t i); // Combined controls + targets -Value getOutput(size_t i); // Combined controls + targets -Value getOutputForInput(Value in); // Identity in reference semantics -Value getInputForOutput(Value out); // Identity in reference semantics - -// Parameter handling -size_t getNumParams(); -ParameterDescriptor getParameter(size_t i); - -// Parameter descriptor -struct ParameterDescriptor { - bool isStatic(); // True if attribute, false if operand - Optional getConstantValue(); // If static - Value getValueOperand(); // If dynamic -}; - -// Matrix extraction -bool hasStaticUnitary(); -Optional tryGetStaticMatrix(); // tensor<2^n x 2^n x complex> - -// Modifier state -bool isInverted(); -Optional getPower(); // Returns power exponent if applicable - -// Identification -std::string getBaseSymbol(); -CanonicalDescriptor getCanonicalDescriptor(); // For equivalence testing -``` - -**Canonical Descriptor Tuple:** - -Each unitary can be uniquely identified by the tuple: - -``` -(baseSymbol, orderedParams, posControls, negControls, powerExponent, invertedFlag) -``` - -This enables canonical equality tests and efficient deduplication. - -**Parameter Model:** - -- Parameters appear in parentheses immediately after the operation mnemonic -- Support for mixed static (attributes) and dynamic (SSA values) parameters in original order -- Enumeration returns a flattened ordered list where each parameter can be inspected for static/dynamic nature -- Example: `quartz.u(%theta, 1.5708, %lambda) %q` has three parameters: dynamic, static, dynamic - -**Static Matrix Extraction:** - -- Provided when the gate is analytic and all parameters are static -- For matrix-defined user gates, returns the defined matrix -- For sequences/composites of static subunits, composes matrices via multiplication -- Returns `std::nullopt_t` for symbolic or dynamic parameterizations - -**Modifier Interaction:** - -- `inv` modifier introduces `invertedFlag` in the canonical descriptor -- `pow` modifier stores exponent; negative exponents are canonicalized to `inv(pow(+exp))` then reordered -- Control modifiers (`ctrl`, `negctrl`) extend control sets; interface aggregates flattened sets across nested modifiers - -## 4. Base Gate Operations - -### 4.1 Philosophy and Design Principles - -**Core Principles:** - -- **Named Basis Gates:** Each base gate defines a unitary with fixed target arity and parameter arity (expressed via traits) -- **Static Matrix When Possible:** Provide static matrix representations when parameters are static or absent -- **Modifier-Free Core:** Avoid embedding modifier semantics directly—use wrapper operations instead -- **Consistent Signatures:** Maintain uniform syntax across Quartz and Flux dialects - -**Benefits:** - -- Simplifies gate definitions and verification -- Enables powerful pattern matching and rewriting -- Separates concerns between gate semantics and modifications - -### 4.2 Base Gate Specification Template - -For every named base gate operation `G`: - -**Specification Elements:** - -- **Purpose:** Brief description of the unitary operation -- **Traits:** Target arity (e.g., `OneTarget`, `TwoTarget`), parameter arity (e.g., `OneParameter`), special properties (e.g., `Hermitian`, `Diagonal`) -- **Signatures:** - - Quartz: `quartz.G(param_list?) %targets : (param_types..., qubit_types...)` - - Flux: `%out_targets = flux.G(param_list?) %in_targets : (param_types..., qubit_types...) -> (qubit_types...)` -- **Assembly Format:** `G(params?) targets` where params are in parentheses, qubits as trailing operands -- **Interface Implementation:** - - `getNumTargets()` fixed by target arity trait - - Parameters enumerated in declared order (static via attribute, dynamic via operand) - - `hasStaticUnitary()` returns true iff all parameters are static -- **Canonicalization:** List of simplification rules and rewrites -- **Matrix:** Mathematical matrix representation (static or symbolic) -- **Equivalences:** Relationships to other gates (e.g., decomposition in terms of `u` gate) - -**General Canonicalization Patterns Based on Traits:** - -The following canonicalization patterns apply automatically to all gates with the specified traits (not repeated for individual gates): - -- **Hermitian gates:** - - `inv(G) → G` (self-adjoint) - - `G %q; G %q → remove` (cancellation) - - `pow(n: even_integer) G → id` - - `pow(n: odd_integer) G → G` -- **Diagonal gates:** - - Commute with other diagonal gates on same qubits - - Can be merged when adjacent -- **Zero-parameter gates with Hermitian trait:** - - Consecutive applications cancel - -### 4.3 Gate Catalog - -**Gate Organization:** - -- **Zero-qubit gates:** Global phase -- **Single-qubit gates:** Pauli gates, rotations, phase gates, universal gates -- **Two-qubit gates:** Entangling gates, Ising-type interactions -- **Variable-qubit gates:** Barrier and utility operations - -#### 4.3.1 `gphase` Gate (Global Phase) - -- **Purpose:** Apply global phase `exp(iθ)` to the quantum state -- **Traits:** `NoTarget`, `OneParameter` -- **Signatures:** - - Quartz: `quartz.gphase(%theta)` - - Flux: `flux.gphase(%theta)` -- **Examples:** - - Static: `quartz.gphase(3.14159)` - - Dynamic: `quartz.gphase(%theta)` -- **Canonicalization:** - - `gphase(0) → remove` - - `inv(gphase(θ)) → gphase(-θ)` - - `gphase(a); gphase(b) → gphase(a + b)` (consecutive phases merge within same scope) - - `ctrl(%q) { gphase(θ) } → p(θ) %q` (controlled global phase becomes phase gate) - - `negctrl(%q) { gphase(θ) } → gphase(π); p(θ) %q` (negative control specialization) - - `pow(n) { gphase(θ) } → gphase(n*θ)` -- **Matrix:** `[exp(iθ)]` (1×1 scalar, static if θ constant) -- **Global Phase Preservation:** Global phase gates are **preserved by default** and should only be eliminated by dedicated optimization passes. They are bound to a scope (e.g., function, box) at which they are aggregated and canonicalized. Operations within the same scope may merge adjacent global phases, but global phases should not be arbitrarily removed during general canonicalization. - -#### 4.3.2 `id` Gate (Identity) - -- **Purpose:** Identity operation (does nothing) -- **Traits:** `OneTarget`, `NoParameter`, `Hermitian`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.id %q` - - Flux: `%q_out = flux.id %q_in` -- **Canonicalization:** - - `id → remove` (no effect) - - `pow(r) id → id` (any power is still id) - - `ctrl(...) { id } → id` (control with id is just id) - - `negctrl(...) { id } → id` -- **Matrix:** `[1, 0; 0, 1]` (2x2 identity matrix) -- **Definition in terms of `u`:** `u(0, 0, 0) %q` - -#### 4.3.3 `x` Gate (Pauli-X) - -- **Purpose:** Pauli-X gate (bit flip) -- **Traits:** `OneTarget`, `NoParameter`, `Hermitian` -- **Signatures:** - - Quartz: `quartz.x %q` - - Flux: `%q_out = flux.x %q_in` -- **Canonicalization:** - - `pow(1/2) x → gphase(-π/4); sx` (square root with global phase correction) - - `pow(-1/2) x → gphase(π/4); sxdg` - - `pow(r) x → gphase(-r*π/2); rx(r*π)` (general power translates to rotation with global phase) -- **Matrix:** `[0, 1; 1, 0]` (2x2 matrix) -- **Definition in terms of `u`:** `u(π, 0, π) %q` -- **Global Phase Relationship:** The Pauli-X gate and `rx(π)` differ by a global phase: `X = -i·exp(-iπ/2·X) = -i·rx(π)`. When raising X to fractional powers, the result is expressed as a rotation with an appropriate global phase factor. - -#### 4.3.4 `y` Gate (Pauli-Y) - -- **Purpose:** Pauli-Y gate (bit and phase flip) -- **Traits:** `OneTarget`, `NoParameter`, `Hermitian` -- **Signatures:** - - Quartz: `quartz.y %q` - - Flux: `%q_out = flux.y %q_in` -- **Canonicalization:** - - `pow(r) y → gphase(-r*π/2); ry(r*π)` (general power translates to rotation with global phase) -- **Matrix:** `[0, -i; i, 0]` (2x2 matrix) -- **Definition in terms of `u`:** `u(π, π/2, π/2) %q` -- **Global Phase Relationship:** The Pauli-Y gate and `ry(π)` differ by a global phase: `Y = -i·exp(-iπ/2·Y) = -i·ry(π)`. - -#### 4.3.5 `z` Gate (Pauli-Z) - -- **Purpose:** Pauli-Z gate (phase flip) -- **Traits:** `OneTarget`, `NoParameter`, `Hermitian`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.z %q` - - Flux: `%q_out = flux.z %q_in` -- **Canonicalization:** - - `pow(1/2) z → s` - - `pow(-1/2) z → sdg` - - `pow(1/4) z → t` - - `pow(-1/4) z → tdg` - - `pow(r) z → p(π * r)` for real r -- **Matrix:** `[1, 0; 0, -1]` (2x2 matrix) -- **Definition in terms of `u`:** `u(0, 0, π) %q` - -#### 4.3.6 `h` Gate (Hadamard) - -- **Purpose:** Hadamard gate (creates superposition) -- **Traits:** `OneTarget`, `NoParameter`, `Hermitian` -- **Signatures:** - - Quartz: `quartz.h %q` - - Flux: `%q_out = flux.h %q_in` -- **Matrix:** `1/sqrt(2) * [1, 1; 1, -1]` (2x2 matrix) -- **Definition in terms of `u`:** `u(π/2, 0, π) %q` - -#### 4.3.7 `s` Gate (S/Phase-90) - -- **Purpose:** S gate (applies a phase of π/2) -- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.s %q` - - Flux: `%q_out = flux.s %q_in` -- **Canonicalization:** - - `inv s → sdg` - - `s %q; s %q → z %q` - - `pow(n: int) s → if n % 4 == 0 then id else if n % 4 == 1 then s else if n % 4 == 2 then z else sdg` - - `pow(1/2) s → t` - - `pow(-1/2) s → tdg` - - `pow(+-2) s → z` - - `pow(r) s → p(π/2 * r)` for real r -- **Matrix:** `[1, 0; 0, i]` (2x2 matrix) -- **Definition in terms of `u`:** `u(0, 0, π/2) %q` - -#### 4.3.8 `sdg` Gate (S-Dagger) - -- **Purpose:** Sdg gate (applies a phase of -π/2) -- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.sdg %q` - - Flux: `%q_out = flux.sdg %q_in` -- **Canonicalization:** - - `inv sdg → s` - - `sdg %q; sdg %q → z %q` - - `pow(n: int) sdg → if n % 4 == 0 then id else if n % 4 == 1 then sdg else if n % 4 == 2 then z else s` - - `pow(1/2) sdg → tdg` - - `pow(-1/2) sdg → t` - - `pow(+-2) sdg → z` - - `pow(r) sdg → p(-π/2 * r)` for real r -- **Matrix:** `[1, 0; 0, -i]` (2x2 matrix) -- **Definition in terms of `u`:** `u(0, 0, -π/2) %q` - -#### 4.3.9 `t` Gate (T/π-8) - -- **Purpose:** T gate (applies a phase of π/4) -- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.t %q` - - Flux: `%q_out = flux.t %q_in` -- **Canonicalization:** - - `inv t → tdg` - - `t %q; t %q; → s %q` - - `pow(2) t → s` - - `pow(-2) t → sdg` - - `pow(+-4) t → z` - - `pow(r) t → p(π/4 * r)` for real r -- **Matrix:** `[1, 0; 0, exp(i π/4)]` (2x2 matrix) -- **Definition in terms of `u`:** `u(0, 0, π/4) %q` - -#### 4.3.10 `tdg` Gate (T-Dagger) - -- **Purpose:** Tdg gate (applies a phase of -π/4) -- **Traits:** `OneTarget`, `NoParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.tdg %q` - - Flux: `%q_out = flux.tdg %q_in` -- **Canonicalization:** - - `inv tdg → t` - - `tdg %q; tdg %q; → sdg %q` - - `pow(2) tdg → sdg` - - `pow(-2) tdg → s` - - `pow(+-4) tdg → z` - - `pow(r) tdg → p(-π/4 * r)` for real r -- **Matrix:** `[1, 0; 0, exp(-i π/4)]` (2x2 matrix) -- **Definition in terms of `u`:** `u(0, 0, -π/4) %q` - -#### 4.3.11 `sx` Gate (√X) - -- **Purpose:** Square root of X gate -- **Traits:** `OneTarget`, `NoParameter` -- **Signatures:** - - Quartz: `quartz.sx %q` - - Flux: `%q_out = flux.sx %q_in` -- **Canonicalization:** - - `inv sx → sxdg` - - `sx %q; sx %q → x %q` - - `pow(+-2) sx → x` - - `pow(r) sx → gphase(-r*π/4); rx(r*π/2)` (power translates to rotation with global phase) -- **Matrix:** `1/2 * [1 + i, 1 - i; 1 - i, 1 + i]` (2x2 matrix) -- **Global Phase Relationship:** `sx = exp(-iπ/4)·rx(π/2)`. Powers are handled by translating to the rotation form with appropriate global phase correction. - -#### 4.3.12 `sxdg` Gate (√X-Dagger) - -- **Purpose:** Square root of X-Dagger gate -- **Traits:** `OneTarget`, `NoParameter` -- **Signatures:** - - Quartz: `quartz.sxdg %q` - - Flux: `%q_out = flux.sxdg %q_in` -- **Canonicalization:** - - `inv sxdg → sx` - - `sxdg %q; sxdg %q → x %q` - - `pow(+-2) sxdg → x` - - `pow(r) sxdg → gphase(r*π/4); rx(-r*π/2)` (power translates to rotation with global phase) -- **Matrix:** `1/2 * [1 - i, 1 + i; 1 + i, 1 - i]` (2x2 matrix) -- **Global Phase Relationship:** `sxdg = exp(iπ/4)·rx(-π/2)`. Powers are handled by translating to the rotation form with appropriate global phase correction. - -#### 4.3.13 `rx` Gate (X-Rotation) - -- **Purpose:** Rotation around the X-axis by angle θ -- **Traits:** `OneTarget`, `OneParameter` -- **Signatures:** - - Quartz: `quartz.rx(%theta) %q` - - Flux: `%q_out = flux.rx(%theta) %q_in` -- **Static variant:** `quartz.rx(3.14159) %q` -- **Canonicalization:** - - `rx(a) %q; rx(b) %q → rx(a + b) %q` - - `inv rx(θ) → rx(-θ)` - - `pow(r) rx(θ) → rx(r * θ)` for real r -- **Matrix (dynamic):** `exp(-i θ/2 X) = [cos(θ/2), -i sin(θ/2); -i sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. -- **Definition in terms of `u`:** `u(θ, -π/2, π/2) %q` - -#### 4.3.14 `ry` Gate (Y-Rotation) - -- **Purpose:** Rotation around the Y-axis by angle θ -- **Traits:** `OneTarget`, `OneParameter` -- **Signatures:** - - Quartz: `quartz.ry(%theta) %q` - - Flux: `%q_out = flux.ry(%theta) %q_in` -- **Static variant:** `quartz.ry(3.14159) %q` -- **Canonicalization:** - - `ry(a) %q; ry(b) %q → ry(a + b) %q` - - `inv ry(θ) → ry(-θ)` - - `pow(r) ry(θ) → ry(r * θ)` for real r -- **Matrix (dynamic):** `exp(-i θ/2 Y) = [cos(θ/2), -sin(θ/2); sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ constant. -- **Definition in terms of `u`:** `u(θ, 0, 0) %q` - -#### 4.3.15 `rz` Gate (Z-Rotation) - -- **Purpose:** Rotation around the Z-axis by angle θ -- **Traits:** `OneTarget`, `OneParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.rz(%theta) %q` - - Flux: `%q_out = flux.rz(%theta) %q_in` -- **Static variant:** `quartz.rz(3.14159) %q` -- **Canonicalization:** - - `rz(a) %q; rz(b) %q → rz(a + b) %q` - - `inv rz(θ) → rz(-θ)` - - `pow(r) rz(θ) → rz(r * θ)` for real r -- **Matrix (dynamic):** `exp(-i θ/2 Z) = [exp(-i θ/2), 0; 0, exp(i θ/2)]` (2x2 matrix). Static if θ constant. -- **Relationship with `p` gate:** The `rz` and `p` gates differ by a global phase: `rz(θ) = exp(iθ/2)·p(θ)`. However, **`rz` and `p` should remain separate** and are **not canonicalized** into each other, as they represent different conventions that may be important for hardware backends or algorithm implementations. - -#### 4.3.16 `p` Gate (Phase) - -- **Purpose:** Phase gate (applies a phase of θ) -- **Traits:** `OneTarget`, `OneParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.p(%theta) %q` - - Flux: `%q_out = flux.p(%theta) %q_in` -- **Static variant:** `quartz.p(3.14159) %q` -- **Canonicalization:** - - `p(a) %q; p(b) %q → p(a + b) %q` - - `inv p(θ) → p(-θ)` - - `pow(r) p(θ) → p(r * θ)` for real r -- **Matrix (dynamic):** `[1, 0; 0, exp(i θ)]` (2x2 matrix). Static if θ constant. -- **Definition in terms of `u`:** `u(0, 0, θ) %q` - -#### 4.3.17 `r` Gate (Arbitrary Axis Rotation) - -- **Purpose:** Rotation around an arbitrary axis in the XY-plane by angles θ and φ -- **Traits:** `OneTarget`, `TwoParameter` -- **Signatures:** - - Quartz: `quartz.r(%theta, %phi) %q` - - Flux: `%q_out = flux.r(%theta, %phi) %q_in` -- **Static variant:** `quartz.r(3.14159, 1.5708) %q` -- **Mixed variant:** `quartz.r(%theta, 1.5708) %q` -- **Canonicalization:** - - `inv r(θ, φ) → r(-θ, φ)` - - `pow(real) r(θ, φ) → r(real * θ, φ)` for real `real` - - `r(θ, 0) → rx(θ)` - - `r(θ, π/2) → ry(θ)` -- **Matrix (dynamic):** `exp(-i θ (cos(φ) X + sin(φ) Y)) = [cos(θ/2), -i exp(-i φ) sin(θ/2); -i exp(i φ) sin(θ/2), cos(θ/2)]` (2x2 matrix). Static if θ and φ constant. -- **Definition in terms of `u`:** `u(θ, -π/2 + φ, π/2 - φ) %q` - -#### 4.3.18 `u` Gate (Universal Single-Qubit) - -- **Purpose:** Universal single-qubit gate (can implement any single-qubit operation) -- **Traits:** `OneTarget`, `ThreeParameter` -- **Signatures:** - - Quartz: `quartz.u(%theta, %phi, %lambda) %q` - - Flux: `%q_out = flux.u(%theta, %phi, %lambda) %q_in` -- **Static variant:** `quartz.u(3.14159, 1.5708, 0.785398) %q` -- **Mixed variant:** `quartz.u(%theta, 1.5708, 0.785398) %q` -- **Canonicalization:** - - `inv u(θ, φ, λ) → u(-θ, -φ, -λ)` - - `rx(θ) == u(θ, -π/2, π/2)` - - `ry(θ) == u(θ, 0, 0)` - - `p(λ) == u(0, 0, λ)` -- **Matrix (dynamic):** `p(φ) ry(θ) p(λ) = exp(i (φ + λ)/2) * rz(φ) ry(θ) rz(λ) = [cos(θ/2), -exp(i λ) sin(θ/2); exp(i φ) sin(θ/2), exp(i (φ + λ)) cos(θ/2)]` (2x2 matrix). Static if θ, φ, λ constant. - -#### 4.3.19 `u2` Gate (Simplified Universal) - -- **Purpose:** Simplified universal single-qubit gate (special case of `u` gate) -- **Traits:** `OneTarget`, `TwoParameter` -- \*\*Signatures - - Quartz: `quartz.u2(%phi, %lambda) %q` - - Flux: `%q_out = flux.u2(%phi, %lambda) %q_in` -- **Static variant:** `quartz.u2(1.5708, 0.785398) %q` -- **Mixed variant:** `quartz.u2(%phi, 0.785398) %q` -- **Canonicalization:** - - `inv u2(φ, λ) → u2(-λ - π, -φ + π)` - - `u2(0, π) → h` - - `u2(0, 0) → ry(π/2)` - - `u2(-π/2, π/2) → rx(π/2)` -- **Matrix (dynamic):** `1/sqrt(2) * [1, -exp(i λ); exp(i φ), exp(i (φ + λ))]` (2x2 matrix). Static if φ, λ constant. -- **Definition in terms of `u`:** `u2(φ, λ) == u(π/2, φ, λ)` - -#### 4.3.20 `swap` Gate - -- **Purpose:** Swap two qubits -- **Traits:** `TwoTarget`, `NoParameter`, `Hermitian` -- **Signatures:** - - Quartz: `quartz.swap %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.swap %q0_in, %q1_in` -- **Matrix:** `[1, 0, 0, 0; 0, 0, 1, 0; 0, 1, 0, 0; 0, 0, 0, 1]` (4x4 matrix) - -#### 4.3.21 `iswap` Gate - -- **Purpose:** Swap two qubits with imaginary coefficient -- **Traits:** `TwoTarget`, `NoParameter` -- **Signatures:** - - Quartz: `quartz.iswap %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.iswap %q0_in, %q1_in` -- **Canonicalization:** - - `pow(r) iswap → xx_plus_yy(-π * r)` -- **Matrix:** `[1, 0, 0, 0; 0, 0, 1j, 0; 0, 1j, 0, 0; 0, 0, 0, 1]` (4x4 matrix) - -#### 4.3.22 `dcx` Gate (Double CNOT) - -- **Purpose:** Double control-NOT gate -- **Traits:** `TwoTarget`, `NoParameter` -- **Signatures:** - - Quartz: `quartz.dcx %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.dcx %q0_in, %q1_in` -- **Canonicalization:** - - `inv dcx %q0, q1 => dcx %q1, %q0` -- **Matrix:** `[1, 0, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1; 0, 1, 0, 0]` (4x4 matrix) - -#### 4.3.23 `ecr` Gate (Echoed Cross-Resonance) - -- **Purpose:** Cross-resonance gate with echo -- **Traits:** `TwoTarget`, `NoParameter`, `Hermitian` -- **Signatures:** - - Quartz: `quartz.ecr %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.ecr %q0_in, %q1_in` -- **Matrix:** `1/sqrt(2) * [0, 0, 1, 1j; 0, 0, 1j, 1; 1, -1j, 0, 0; -1j, 1, 0, 0]` (4x4 matrix) - -#### 4.3.24 `rxx` Gate (XX-Rotation) - -- **Purpose:** General two-qubit rotation around XX. -- **Traits:** `TwoTarget`, `OneParameter` -- **Signatures:** - - Quartz: `quartz.rxx(%theta) %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.rxx(%theta) %q0_in, %q1_in` -- **Static variant:** `quartz.rxx(3.14159) %q0, %q1` -- **Canonicalization:** - - `inv rxx(%theta) => rxx(-%theta)` - - `pow(r) rxx(%theta) => rxx(r * %theta)` for real r - - `rxx(0) => remove` - - `rxx(a) %q0, %q1; rxx(b) %q0, %q1 => rxx(a + b) %q0, %q1` -- **Matrix (dynamic):** `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] - 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, 1, 0; 0, 1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. - -#### 4.3.25 `ryy` Gate (YY-Rotation) - -- **Purpose:** General two-qubit gate around YY. -- **Traits:** `TwoTarget`, `OneParameter` -- **Signatures:** - - Quartz: `quartz.ryy(%theta) %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.ryy(%theta) %q0_in, %q1_in` -- **Static variant:** `quartz.ryy(3.14159) %q0, %q1` -- **Canonicalization:** - - `inv ryy(%theta) => ryy(-%theta)` - - `pow(r) ryy(%theta) => ryy(r * %theta)` for real r - - `ryy(0) => remove` - - `ryy(a) %q0, %q1; ryy(b) %q0, %q1 => ryy(a + b) %q0, %q1` -- **Matrix (dynamic):** `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, 0, 0, 1; 0, 0, -1, 0; 0, -1, 0, 0; 1, 0, 0, 0]` (4x4 matrix). Static if θ constant. - -#### 4.3.26 `rzx` Gate (ZX-Rotation) - -- **Purpose:** General two-qubit gate around ZX. -- **Traits:** `TwoTarget`, `OneParameter` -- **Signatures:** - - Quartz: `quartz.rzx(%theta) %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.rzx(%theta) %q0_in, %q1_in` -- **Static variant:** `quartz.rzx(3.14159) %q0, %q1` -- **Canonicalization:** - - `inv rzx(%theta) => rzx(-%theta)` - - `pow(r) rzx(%theta) => rzx(r * %theta)` for real r - - `rzx(0) => remove` - - `rzx(a) %q0, %q1; rzx(b) %q0, %q1 => rzx(a + b) %q0, %q1` -- **Matrix (dynamic):** `cos(θ/2) * [1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1] + 1j * sin(θ/2) * [0, -1, 0, 0; -1, 0, 0, 0; 0, 0, 0, 1; 0, 0, 1, 0]` (4x4 matrix). Static if θ constant. - -#### 4.3.27 `rzz` Gate (ZZ-Rotation) - -- **Purpose:** General two-qubit gate around ZZ. -- **Traits:** `TwoTarget`, `OneParameter`, `Diagonal` -- **Signatures:** - - Quartz: `quartz.rzz(%theta) %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.rzz(%theta) %q0_in, %q1_in` -- **Static variant:** `quartz.rzz(3.14159) %q0, %q1` -- **Canonicalization:** - - `inv rzz(%theta) => rzz(-%theta)` - - `pow(r) rzz(%theta) => rzz(r * %theta)` for real r - - `rzz(0) => remove` - - `rzz(a) %q0, %q1; rzz(b) %q0, %q1 => rzz(a + b) %q0, %q1` -- **Matrix (dynamic):** `diag[exp(-i θ/2), exp(i θ/2), exp(i θ/2), exp(-i θ/2)]` (4x4 matrix). Static if θ constant. - -#### 4.3.28 `xx_plus_yy` Gate - -- **Purpose:** General two-qubit gate around XX+YY. -- **Traits:** `TwoTarget`, `TwoParameter` -- \*\*Signatures: - - Quartz: `quartz.xx_plus_yy(%theta, %beta) %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.xx_plus_yy(%theta, %beta) %q0_in, %q1_in` -- **Static variant:** `quartz.xx_plus_yy(3.14159, 1.5708) %q0, %q1` -- **Mixed variant:** `quartz.xx_plus_yy(%theta, 1.5708) %q0, %q1` -- **Canonicalization:** - - `inv xx_plus_yy(θ, β) => xx_plus_yy(-θ, β)` - - `pow(r) xx_plus_yy(θ, β) => xx_plus_yy(r * θ, β)` for real r - - `xx_plus_yy(θ1, β) %q0, %q1; xx_plus_yy(θ2, β) %q0, %q1 => xx_plus_yy(θ1 + θ2, β) %q0, %q1` -- **Matrix (dynamic):** `[1, 0, 0, 0; 0, cos(θ/2), sin(θ/2) * exp(-i β), 0; 0, -sin(θ/2) * exp(i β), cos(θ/2), 0; 0, 0, 0, 1]` (4x4 matrix). Static if θ and β constant. - -#### 4.3.29 `xx_minus_yy` Gate - -- **Purpose:** General two-qubit gate around XX-YY. -- **Traits:** `TwoTarget`, `TwoParameter` -- \*\*Signatures: - - Quartz: `quartz.xx_minus_yy(%theta, %beta) %q0, %q1` - - Flux: `%q0_out, %q1_out = flux.xx_minus_yy(%theta, %beta) %q0_in, %q1_in` -- **Static variant:** `quartz.xx_minus_yy(3.14159, 1.5708) %q0, %q1` -- **Mixed variant:** `quartz.xx_minus_yy(%theta, 1.5708) %q0, %q1` -- **Canonicalization:** - - `inv xx_minus_yy(θ, β) => xx_minus_yy(-θ, β)` - - `pow(r) xx_minus_yy(θ, β) => xx_minus_yy(r * θ, β)` for real r - - `xx_minus_yy(θ1, β) %q0, %q1; xx_minus_yy(θ2, β) %q0, %q1 => xx_minus_yy(θ1 + θ2, β) %q0, %q1` -- **Matrix (dynamic):** `[cos(θ/2), 0, 0, -sin(θ/2) * exp(i β); 0, 1, 0, 0; 0, 0, 1, 0; sin(θ/2) * exp(-i β), 0, 0, cos(θ/2)]` (4x4 matrix). Static if θ and β constant. - -#### 4.3.30 `barrier` Gate - -- **Purpose:** Prevents optimization passes from reordering operations across the barrier -- **Traits:** `OneTarget`, `TwoTarget`, `NoParameter` (overloaded for different qubit counts) -- **Signatures:** - - Quartz: `quartz.barrier %q0, %q1, ...` - - Flux: `%q0_out, %q1_out, ... = flux.barrier %q0_in, %q1_in, ...` -- **Semantics:** The `barrier` operation implements the `UnitaryOpInterface` and is treated similarly to the identity gate from a unitary perspective. However, it serves as a compiler directive that constrains optimization: operations cannot be moved across a barrier boundary. -- **Canonicalization:** - - Barriers with no qubits can be removed - - `barrier; barrier` on same qubits → single `barrier` (adjacent barriers merge) - - `inv { barrier } → barrier` (barrier is self-adjoint) - - `pow(r) { barrier } → barrier` (any power of barrier is still barrier) -- **Matrix:** Identity matrix of appropriate dimension (2^n × 2^n for n qubits) -- **UnitaryOpInterface Implementation:** Returns identity matrix, no parameters, no controls, targets are the specified qubits - -## 5. Modifier Operations - -### 5.1 Overview and Philosophy - -**What Are Modifiers?** - -Modifiers are wrapper operations that transform or extend unitary operations without modifying the base gate definitions. They provide a composable mechanism for: - -- Adding control qubits (positive or negative control) -- Inverting (taking the adjoint of) operations -- Raising operations to powers - -**Key Design Principles:** - -- **Single-Operation Regions:** Each modifier contains exactly one region with a single block whose only operation implements `UnitaryOpInterface` -- **Arbitrary Nesting:** Modifiers may be arbitrarily nested -- **Canonical Ordering:** Canonicalization rules flatten and reorder modifiers to a standard form: `negctrl → ctrl → pow → inv` -- **Dialect Consistency:** Both `quartz` and `flux` variants with corresponding semantics - -**Value vs. Reference Semantics:** - -- **Quartz (Reference):** Modifiers are statements without results; wrapped operation mutates qubits in-place -- **Flux (Value):** Modifiers thread SSA values through region arguments and yield results -- **Conversion:** `flux → quartz` is straightforward; `quartz → flux` requires adding SSA values to region arguments and yields - -### 5.2 Control Modifiers (`ctrl` and `negctrl`) - -**Purpose:** Add additional control qubits to an operation. Control qubits can be positive (1-state control via `ctrl`) or negative (0-state control via `negctrl`). - -**Signatures (shown for `ctrl`; `negctrl` is analogous):** - -- **Quartz (Reference Semantics):** - - ```mlir - quartz.ctrl(%ctrl0, %ctrl1, ...) { - quartz.unitaryOp %target0, %target1, ... - } - ``` - -- **Flux (Value Semantics):** - ```mlir - %ctrl_outs, %target_outs = flux.ctrl(%ctrl_ins, %target_ins) { - %new_targets = flux.unitaryOp %target_ins - flux.yield %new_targets - } - ``` - -**Interface Implementation:** - -- **Targets:** Aggregated from child unitary -- **Controls:** Control qubits from this modifier plus any controls from child unitary (flattened) -- **Parameters:** Aggregated from child unitary (none directly from modifier) -- **Static Unitary:** Available if child unitary is static - -**Canonicalization:** - -- Flatten nested control modifiers: `ctrl(%c1) { ctrl(%c2) { U } } → ctrl(%c1, %c2) { U }` -- Remove empty controls: `ctrl() { U } → U` -- Controlled global phase specialization: `ctrl(%c) { gphase(θ) } → p(θ) %c` -- Canonical ordering: `ctrl { negctrl { U } } → negctrl { ctrl { U } }` -- **TODO:** Define behavior when same qubit appears as both positive and negative control - -**Verifiers:** - -- Control and target qubits must be distinct (no qubit can be both control and target) -- All control qubits must be distinct from each other -- **Control Modifier Exclusivity:** A qubit must **not** appear in both positive (`ctrl`) and negative (`negctrl`) control modifiers. The sets of positive and negative controls must be **disjoint** with no overlap. If a qubit appears in both, the operation is invalid and must be rejected during verification. - -**Unitary Computation:** - -The unitary is computed by expanding the child operation's unitary to the larger Hilbert space defined by the additional control qubits: - -``` -U_controlled = |0⟩⟨0| ⊗ I_{target_space} + |1⟩⟨1| ⊗ U_{target_space} -``` - -For negative controls, `|1⟩⟨1|` is replaced with `|0⟩⟨0|`. - -### 5.3 Inverse Modifier (`inv`) - -**Purpose:** Take the adjoint (conjugate transpose) of a unitary operation. - -**Signatures:** - -- **Quartz (Reference Semantics):** - - ```mlir - quartz.inv { - quartz.unitaryOp %targets - } - ``` - -- **Flux (Value Semantics):** - ```mlir - %targets_out = flux.inv(%targets_in) { - %new_targets = flux.unitaryOp %targets_in - flux.yield %new_targets - } - ``` - -**Interface Implementation:** - -- **Targets:** Aggregated from child unitary -- **Controls:** Aggregated from child unitary -- **Parameters:** Aggregated from child unitary (none directly from modifier) -- **Static Unitary:** Available if child unitary is static -- **Is Inverted:** Returns `true` - -**Canonicalization:** - -- Double inverse cancellation: `inv { inv { U } } → U` -- Hermitian gate simplification: `inv { G } → G` (for Hermitian gates) -- Parametric rotation inversion: `inv { rx(θ) } → rx(-θ)` (and similar for ry, rz, p, etc.) -- Canonical ordering: `inv { ctrl { U } } → ctrl { inv { U } }` - -**Unitary Computation:** - -Given unitary matrix `U`, the inverse is computed as `U† = (U̅)ᵀ` (conjugate transpose). - -### 5.4 Power Modifier (`pow`) - -**Purpose:** Raise a unitary operation to a given power (exponent can be integer, rational, or real). - -**Signatures:** - -- **Quartz (Reference Semantics):** - - ```mlir - quartz.pow(%exponent) { - quartz.unitaryOp %targets - } - ``` - - Static variant: `quartz.pow {exponent = 2.0 : f64} { ... }` - -- **Flux (Value Semantics):** - ```mlir - %targets_out = flux.pow(%exponent, %targets_in) { - %new_targets = flux.unitaryOp %targets_in - flux.yield %new_targets - } - ``` - -**Interface Implementation:** - -- **Targets:** Aggregated from child unitary -- **Controls:** Aggregated from child unitary -- **Parameters:** Aggregated from child unitary plus the exponent parameter -- **Static Unitary:** Available if child unitary and exponent are both static -- **Get Power:** Returns the exponent value - -**Canonicalization:** - -- Nested power flattening: `pow(a) { pow(b) { U } } → pow(a*b) { U }` -- Identity power: `pow(1) { U } → U` -- Zero power: `pow(0) { U } → remove` (becomes identity, then eliminated) -- Negative power: `pow(-r) { U } → pow(r) { inv { U } }` -- Parametric gate power folding: `pow(r) { rx(θ) } → rx(r*θ)` (for rotation gates) -- Integer power specialization: For specific gates and integer powers (e.g., `pow(2) { sx } → x`) -- Canonical ordering: `pow { ctrl { U } } → ctrl { pow { U } }` - -**Unitary Computation:** - -- **Integer exponents:** Matrix multiplication `U^n = U · U · ... · U` (n times) -- **Real/rational exponents:** Matrix exponentiation via eigendecomposition: `U^r = V · D^r · V†` where `U = V · D · V†` is the eigendecomposition - -**Well-Definedness:** Since all quantum gates are unitary matrices, they are always diagonalizable (unitaries are normal operators). Therefore, **non-integer powers are always well-defined** through eigendecomposition, regardless of the specific gate. - -**Numerical Precision:** Matrix exponentiation should be computed to **machine precision** (typically double precision floating point, ~15-16 decimal digits). Implementations should use numerically stable algorithms for eigendecomposition. - -**Complex Exponents:** Complex exponents are **not supported** and will **not be supported** in the future, as they do not have meaningful physical interpretations for quantum gates. Only real-valued exponents are permitted. - -## 6. Box Operation (`box`) - -**Purpose:** Scoped composition of unitary operations with timing and optimization constraints. Inspired by OpenQASM 3.0's `box` statement, this operation encapsulates a sequence of operations while constraining how optimizations may interact with them. - -**Signatures:** - -- **Quartz (Reference Semantics):** - - ```mlir - quartz.box { - quartz.h %q0 - quartz.cx %q0, %q1 - quartz.rz(1.57) %q1 - } - ``` - -- **Flux (Value Semantics):** - ```mlir - %q0_out, %q1_out = flux.box(%q0_in, %q1_in) : (!flux.qubit, !flux.qubit) -> (!flux.qubit, !flux.qubit) { - %q0_1 = flux.h %q0_in - %q0_2, %q1_1 = flux.cx %q0_1, %q1_in - %q1_2 = flux.rz(1.57) %q1_1 - flux.yield %q0_2, %q1_2 - } - ``` - -**Interface Implementation:** - -- **Targets:** Aggregated from all child unitary operations (union of all targets) -- **Controls:** None (controls must be within individual operations or modifiers) -- **Parameters:** Aggregated from all child operations -- **Static Unitary:** Available if all child operations have static unitaries - -**Canonicalization:** - -- Empty sequence elimination: `box { } → remove` -- Single-operation inlining: `box { U } → U` -- Nested sequence flattening: `box { box { U; V }; W } → box { U; V; W }` - -**Verifiers:** - -- All operations in the block must implement `UnitaryOpInterface` -- Value semantics: All yielded values must be defined within the block - -**Unitary Computation:** - -The composite unitary is computed as the product of child unitaries in reverse order (right-to-left multiplication, since operations apply left-to-right): - -``` -U_box = U_n · U_{n-1} · ... · U_1 · U_0 -``` - -**Conversion Between Dialects:** - -- **`flux → quartz`:** Remove block arguments and results; replace argument uses with direct value references -- **`quartz → flux`:** Add block arguments for all used qubits; thread SSA values through operations; add yield with final values - -## 7. User-Defined Gates & Matrix/Composite Definitions - -**Purpose:** Enable users to define custom gates that can be referenced and instantiated throughout the program, similar to function definitions and calls. - -### 7.1 Overview - -User-defined gates follow a `func.func`-like design pattern with definitions and application sites: - -- **Gate definitions** create module-level symbols (similar to `func.func`) -- **Application operations** reference these symbols to instantiate gates (similar to `func.call`) -- All user-defined gate operations **implement the unitary interface**, enabling consistent introspection and composition - -Two definition mechanisms are provided to accommodate different use cases: - -1. **Matrix-based definitions (`define_matrix_gate`):** Define a gate via its unitary matrix representation - - Suitable for small qubit counts where the matrix is tractable (2^n × 2^n complexity) - - Provides exact representation and efficient unitary extraction - - Can be parameterized using symbolic expressions - -2. **Composite definitions (`define_composite_gate`):** Define a gate as a sequence of existing unitary operations - - Scalable to larger qubit counts - - Exposes internal structure for optimization and analysis - - Enables hierarchical abstractions - -**Separation Rationale:** The distinction between matrix and composite definitions is fundamental: - -- Matrix definitions provide a **semantic ground truth** via explicit unitary matrices -- Composite definitions provide **structural decomposition** that can be analyzed and optimized -- Gates may provide both representations, with the matrix taking precedence for unitary extraction - -### 7.2 Design Principles - -**Symbol Management:** - -- Gate definitions have module-level scope with unique symbol names -- Symbols are referenced by `apply` operations for instantiation -- Symbol resolution follows standard MLIR visibility rules - -**Parameterization:** - -- Both definition types support static and dynamic parameters -- Parameter binding follows standard calling conventions -- Parameters flow through to the unitary interface for matrix computation - -**Unitary Interface Implementation:** - -- Both `define_matrix_gate` and `define_composite_gate` implement the unitary interface -- Matrix definitions provide direct unitary extraction -- Composite definitions compute unitaries by composing constituent operations -- Application operations (`apply`) delegate to their referenced definitions - -**Consistency:** - -- When both matrix and composite representations are provided, they must represent the same unitary (within numerical tolerance) -- The matrix representation takes precedence as the semantic ground truth -- The composite representation serves as a preferred decomposition hint for optimization - -### 7.3 Integration with Modifier System - -User-defined gates integrate seamlessly with the modifier system described in Section 5: - -- `inv`, `pow`, `ctrl`, and `negctrl` can wrap `apply` operations -- Modifiers are applied to the resolved unitary from the definition -- This enables flexible composition: `inv(pow(apply(@my_gate), 2))` - -### 7.4 Future Work - -The detailed specification of user-defined gates—including concrete syntax, verification rules, inlining strategies, and builder API integration—is deferred to a subsequent design phase. Key open questions include: - -- Precise syntax for matrix attributes and symbolic parameterization -- Verification rules for matrix unitarity and consistency checking -- Inlining heuristics and thresholds for composite definitions -- Recursive definition policies -- Multi-module linking semantics -- Builder API convenience methods for gate definition - -This deferred specification allows the infrastructure to stabilize around core operations before committing to user-defined gate semantics. - -## 8. Builder API - -**Purpose:** Provide a programmatic API for constructing quantum programs within the MQT MLIR infrastructure. The builder APIs offer type-safe, ergonomic interfaces for both Quartz and Flux dialects. - -### 8.1 Design Goals - -- **Ergonomic:** Easy chaining and nesting of operations -- **Type-safe:** Leverage C++ type system to catch errors at compile time -- **Dialect-aware:** Separate builders for Quartz and Flux -- **Testable:** Enable structural comparison of circuits in unit tests - -### 8.2 Quartz Builder (Reference Semantics) - -```c++ -class QuartzProgramBuilder { -public: - QuartzProgramBuilder(mlir::MLIRContext *context); - - // Initialization - void initialize(); - - // Resource management - mlir::Value allocQubit(); // Dynamic allocation - mlir::Value qubit(size_t index); // Static qubit reference - mlir::Value allocQubits(size_t count); // Register allocation (returns memref) - mlir::Value allocBits(size_t count); // Classical register (returns memref) - - // Single-qubit gates (return *this for chaining) - QuartzProgramBuilder& h(mlir::Value q); - QuartzProgramBuilder& x(mlir::Value q); - QuartzProgramBuilder& y(mlir::Value q); - QuartzProgramBuilder& z(mlir::Value q); - QuartzProgramBuilder& s(mlir::Value q); - QuartzProgramBuilder& sdg(mlir::Value q); - QuartzProgramBuilder& t(mlir::Value q); - QuartzProgramBuilder& tdg(mlir::Value q); - QuartzProgramBuilder& sx(mlir::Value q); - QuartzProgramBuilder& sxdg(mlir::Value q); - - // Parametric single-qubit gates - QuartzProgramBuilder& rx(double theta, mlir::Value q); - QuartzProgramBuilder& rx(mlir::Value theta, mlir::Value q); // Dynamic - QuartzProgramBuilder& ry(double theta, mlir::Value q); - QuartzProgramBuilder& ry(mlir::Value theta, mlir::Value q); - QuartzProgramBuilder& rz(double theta, mlir::Value q); - QuartzProgramBuilder& rz(mlir::Value theta, mlir::Value q); - QuartzProgramBuilder& p(double lambda, mlir::Value q); - QuartzProgramBuilder& p(mlir::Value lambda, mlir::Value q); - - // Two-qubit gates - QuartzProgramBuilder& swap(mlir::Value q0, mlir::Value q1); - QuartzProgramBuilder& iswap(mlir::Value q0, mlir::Value q1); - QuartzProgramBuilder& dcx(mlir::Value q0, mlir::Value q1); - QuartzProgramBuilder& ecr(mlir::Value q0, mlir::Value q1); - // ... other two-qubit gates - - // Convenience gates (inspired by qc::QuantumComputation API) - // Standard controlled gates - QuartzProgramBuilder& cx(mlir::Value ctrl, mlir::Value target); // CNOT - QuartzProgramBuilder& cy(mlir::Value ctrl, mlir::Value target); - QuartzProgramBuilder& cz(mlir::Value ctrl, mlir::Value target); - QuartzProgramBuilder& ch(mlir::Value ctrl, mlir::Value target); - QuartzProgramBuilder& crx(double theta, mlir::Value ctrl, mlir::Value target); - QuartzProgramBuilder& cry(double theta, mlir::Value ctrl, mlir::Value target); - QuartzProgramBuilder& crz(double theta, mlir::Value ctrl, mlir::Value target); - - // Multi-controlled gates (arbitrary number of controls) - QuartzProgramBuilder& mcx(mlir::ValueRange ctrls, mlir::Value target); // Toffoli, etc. - QuartzProgramBuilder& mcy(mlir::ValueRange ctrls, mlir::Value target); - QuartzProgramBuilder& mcz(mlir::ValueRange ctrls, mlir::Value target); - QuartzProgramBuilder& mch(mlir::ValueRange ctrls, mlir::Value target); - - // Modifiers (take lambdas for body construction) - QuartzProgramBuilder& ctrl(mlir::ValueRange ctrls, - std::function body); - QuartzProgramBuilder& negctrl(mlir::ValueRange ctrls, - std::function body); - QuartzProgramBuilder& inv(std::function body); - QuartzProgramBuilder& pow(double exponent, - std::function body); - QuartzProgramBuilder& pow(mlir::Value exponent, - std::function body); - - // Box (scoped sequence with optimization constraints) - QuartzProgramBuilder& box(std::function body); - - // Gate definitions - void defineMatrixGate(mlir::StringRef name, size_t numQubits, - mlir::DenseElementsAttr matrix); - void defineCompositeGate(mlir::StringRef name, size_t numQubits, - mlir::ArrayRef paramTypes, - std::function body); - - // Apply custom gate - QuartzProgramBuilder& apply(mlir::StringRef gateName, - mlir::ValueRange targets, - mlir::ValueRange params = {}); - - // Measurement and reset - mlir::Value measure(mlir::Value q); - QuartzProgramBuilder& reset(mlir::Value q); - - // Finalization - mlir::ModuleOp finalize(); - mlir::ModuleOp getModule() const; - -private: - mlir::OpBuilder builder; - mlir::ModuleOp module; - mlir::Location loc; - // ... internal state -}; -``` - -### 8.3 Flux Builder (Value Semantics) - -The `FluxProgramBuilder` follows the same design principles as the Quartz builder but with SSA value threading: - -**Key Differences:** - -- **Return Values:** All gate operations return new SSA values representing the output qubits -- **Threading:** Operations consume input qubits and produce output qubits -- **Region Construction:** Modifiers and box operations handle proper argument threading and yield insertion -- **Type Signatures:** Uses `!flux.qubit` instead of `!quartz.qubit` - -**Example API Sketch:** - -```c++ -class FluxProgramBuilder { -public: - FluxProgramBuilder(mlir::MLIRContext *context); - - // Single-qubit gates return new qubit values - mlir::Value h(mlir::Value q_in); - mlir::Value x(mlir::Value q_in); - mlir::Value rx(double theta, mlir::Value q_in); - - // Two-qubit gates return tuple of output qubits - std::pair cx(mlir::Value ctrl_in, mlir::Value target_in); - std::pair swap(mlir::Value q0_in, mlir::Value q1_in); - - // Convenience multi-controlled gates - mlir::Value mcx(mlir::ValueRange ctrl_ins, mlir::Value target_in, - mlir::ValueRange& ctrl_outs); - - // Modifiers handle value threading automatically - mlir::ValueRange ctrl(mlir::ValueRange ctrl_ins, mlir::ValueRange target_ins, - std::function body); - - // ... similar methods to QuartzProgramBuilder -}; -``` - -### 8.4 Usage Examples - -```c++ -// Example: Build Bell state preparation with Quartz dialect -QuartzProgramBuilder builder(context); -builder.initialize(); - -auto q0 = builder.qubit(0); -auto q1 = builder.qubit(1); - -// Using convenience method -builder.h(q0).cx(q0, q1); - -// Equivalent using modifier -builder.h(q0) - .ctrl({q0}, [&](auto& b) { - b.x(q1); - }); - -auto module = builder.finalize(); -``` - -```c++ -// Example: Build Toffoli gate with multi-controlled convenience -QuartzProgramBuilder builder(context); -builder.initialize(); - -auto q0 = builder.qubit(0); -auto q1 = builder.qubit(1); -auto q2 = builder.qubit(2); - -// Toffoli using convenience method -builder.mcx({q0, q1}, q2); - -// Equivalent using modifier -builder.ctrl({q0, q1}, [&](auto& b) { - b.x(q2); -}); -``` - -## 9. Testing Strategy - -### 9.1 Philosophy - -**Priority:** Structural and semantic testing > textual pattern matching - -Move away from brittle FileCheck tests toward robust programmatic testing: - -- **Builder-based construction:** Use builder APIs to construct circuits -- **Structural equivalence:** Compare IR structure, not SSA names -- **Interface-based verification:** Use `UnitaryOpInterface` for semantic checks -- **Numerical validation:** Compare unitary matrices with appropriate tolerances - -### 9.2 Unit Testing Framework (GoogleTest) - -**Test Categories:** - -1. **Operation Construction:** Verify each operation constructs correctly -2. **Canonicalization:** Test each canonicalization pattern -3. **Interface Implementation:** Verify `UnitaryOpInterface` methods -4. **Matrix Extraction:** Validate static matrix computation -5. **Modifier Composition:** Test nested modifier behavior -6. **Conversion:** Test dialect conversion passes - -**Example Test Structure:** - -```c++ -TEST(QuantumDialectTest, InverseInverseCanonicalizes) { - MLIRContext context; - RefQuantumProgramBuilder builder(&context); - - auto q = builder.qubit(0); - builder.inv([&](auto& b) { - b.inv([&](auto& b2) { - b2.x(q); - }); - }); - - auto module = builder.finalize(); - - // Apply canonicalization - pm.addPass(createCanonicalizerPass()); - pm.run(module); - - // Verify structure: should just be single x gate - auto func = module.lookupSymbol("main"); - ASSERT_EQ(countOpsOfType(func), 1); - ASSERT_EQ(countOpsOfType(func), 0); -} -``` - -**Coverage Requirements:** - -- All base gates: construction, canonicalization, matrix extraction -- All modifiers: nesting, flattening, canonical ordering -- All canonicalization rules explicitly listed in this RFC -- Negative tests for verification failures - -### 9.3 Parser/Printer Round-Trip Tests (Minimal FileCheck) - -**Purpose:** Ensure textual representation is stable and parseable. - -**Scope:** Limited to basic round-trip validation, avoiding brittle SSA name checks. - -**Example:** - -```mlir -// RUN: mqt-opt %s | mqt-opt | FileCheck %s - -// CHECK-LABEL: func @nested_modifiers -func.func @nested_modifiers(%q: !quartz.qubit) { - // CHECK: quartz.ctrl - // CHECK-NEXT: quartz.pow - // CHECK-NEXT: quartz.inv - // CHECK-NEXT: quartz.x - quartz.ctrl %c { - quartz.pow {exponent = 2.0 : f64} { - quartz.inv { - quartz.x %q - } - } - } - return -} -``` - -**Principles:** - -- Check for presence/absence of operations, not exact formatting -- Avoid checking SSA value names (they may change) -- Focus on structural properties -- Keep tests small and focused - -### 9.4 Integration Tests - -Integration tests validate the complete MQT MLIR compilation infrastructure, covering end-to-end scenarios from frontend ingestion through optimization to backend emission. - -**Test Coverage:** - -1. **Frontend Translation:** - - **Qiskit → MLIR:** Convert Qiskit `QuantumCircuit` objects to Quartz/Flux dialects - - **OpenQASM 3.0 → MLIR:** Parse OpenQASM 3.0 source and lower to MLIR - - **`qc::QuantumComputation` → MLIR:** Translate from existing MQT Core IR to MLIR representation - -2. **Compiler Pipeline Integration:** - - Execute default optimization pass pipeline on translated circuits - - Verify correctness through unitary equivalence checks - - Test dialect conversions (Quartz ↔ Flux) within the pipeline - - Validate canonicalization and optimization passes - -3. **Backend Translation:** - - **MLIR → QIR:** Lower optimized MLIR to Quantum Intermediate Representation (QIR) - - Verify QIR output is valid and executable - - Round-trip testing where applicable - -4. **End-to-End Scenarios:** - - Common quantum algorithms (Bell state, GHZ, QPE, VQE ansätze) - - Parameterized circuits with classical optimization loops - - Circuits with measurements and classical control flow - - Large-scale circuits (100+ qubits, 1000+ gates) - -### 9.5 Default Compiler Passes - -**Purpose:** Define a standard set of compiler passes that should be applied by default to ensure consistent, optimized IR across the MQT MLIR infrastructure. - -**Rationale:** - -The MQT MLIR dialects define extensive canonicalization patterns for all operations (base gates, modifiers, boxes, and custom gates). -To ensure these canonicalization are consistently applied and to maintain clean IR for downstream passes, certain compiler passes should be run by default in most compilation scenarios. - -**Recommended Default Passes:** - -1. **Canonicalization Pass (`-canonicalize`):** - - **Purpose:** Apply all canonicalization patterns defined in the dialect operations - - **Benefits:** - - Simplifies IR by applying algebraic identities (e.g., `inv { inv { U } } → U`) - - Normalizes modifier ordering to canonical form (`negctrl → ctrl → pow → inv`) - - Merges adjacent compatible operations (e.g., `rx(a); rx(b) → rx(a+b)`) - - Eliminates identity operations and no-ops - - Hoists constants to the top of modules/functions - - Enables pattern matching for subsequent optimization passes - - **When to Apply:** After every major transformation pass, and as part of the default pipeline - - **Justification:** Given the rich set of canonicalization rules defined throughout this specification, this pass is essential to maintaining normalized IR - -2. **Remove Dead Values Pass (`-remove-dead-values`):** - - **Purpose:** Eliminate unused SSA values and operations with no side effects - - **Benefits:** - - Cleans up intermediate values from transformations - - Reduces IR size and complexity - - Improves readability of generated IR - - Essential for Flux dialect (SSA-based) to remove unused qubit values - - **When to Apply:** After canonicalization, as part of the default pipeline - - **Justification:** Particularly important in Flux dialect where value threading can create unused intermediate values - -**Default Pass Pipeline:** - -For most compilation scenarios, the following minimal pass pipeline should be applied: - -```bash -mqt-opt \ - --canonicalize \ - --remove-dead-values \ - input.mlir -``` - -For more aggressive optimization, this can be extended: - -```bash -mqt-opt \ - --canonicalize \ - --remove-dead-values \ - --canonicalize \ - input.mlir -``` - -**Testing Implications:** - -- **Consistent Test IR:** Running default passes ensures that constants are always defined at the top of modules in test files, improving readability and consistency -- **Normalized Forms:** Tests can rely on canonical forms (e.g., modifier ordering) rather than handling all possible equivalent representations -- **Reduced Brittleness:** Tests that check IR structure benefit from normalized representations with dead code eliminated - -**Integration with Custom Passes:** - -Custom optimization passes should be inserted into pipelines alongside the default passes: - -```bash -mqt-opt \ - --canonicalize \ - --remove-dead-values \ - --custom-optimization-pass \ - --canonicalize \ - --remove-dead-values \ - input.mlir -``` - -This pattern ensures that custom passes operate on normalized IR and that their outputs are subsequently normalized. - -**Builder API Integration:** - -The builder APIs may optionally apply these passes automatically when `finalize()` is called, controlled by a configuration flag: - -```c++ -QuartzProgramBuilder builder(context); -builder.setApplyDefaultPasses(true); // Enable automatic canonicalization -// ... build circuit ... -auto module = builder.finalize(); // Applies canonicalize + remove-dead-values -``` - -**Future Considerations:** - -As the dialect matures, additional passes may be added to the default pipeline: - -- Constant folding and propagation -- Common subexpression elimination (for parameterized gates) -- Gate fusion optimizations -- Circuit optimization passes (commutation-based reordering) - -However, the core default passes (`-canonicalize` and `-remove-dead-values`) should remain stable and universally applicable. - -## 10. Implementation Roadmap - -**Timeline:** 14-week implementation plan to complete the MQT MLIR compilation infrastructure. - -### Phase 1: Foundation (Weeks 1-2) - -**Goal:** Establish core infrastructure - -- **Week 1:** - - Define core type system (`quartz.qubit`, `flux.qubit`) - - Implement `UnitaryOpInterface` definition - - Set up basic CMake integration and build system - - Establish GoogleTest framework for unit testing - -- **Week 2:** - - Create basic builder infrastructure (QuartzProgramBuilder skeleton) - - Implement resource operations (alloc, dealloc, qubit reference) - - Add measurement and reset operations - - Basic parser/printer for qubit types - -**Deliverable:** Compiling infrastructure with basic qubit operations - -### Phase 2: Base Gates - Core Set (Weeks 2-3) - -**Goal:** Implement essential gates for basic circuits - -- **Week 2-3:** - - Implement Pauli gates (x, y, z, id) - - Implement Hadamard (h) - - Implement phase gates (s, sdg, t, tdg, p) - - Implement rotation gates (rx, ry, rz) - - Implement universal gates (u, u2) - - Implement convenience gates (sx, sxdg) - - Basic canonicalization patterns for each gate - - Unit tests for all implemented gates - -**Deliverable:** Core single-qubit gate set with tests - -### Phase 3: Base Gates - Two-Qubit Set (Week 4) - -**Goal:** Complete two-qubit gate library - -- Implement swap, iswap, dcx, ecr -- Implement parametric two-qubit gates (rxx, ryy, rzz, rzx, xx_plus_yy, xx_minus_yy) -- Implement barrier operation -- Implement global phase gate (gphase) -- Advanced canonicalization patterns -- Comprehensive unit tests - -**Deliverable:** Complete base gate set - -### Phase 4: Modifiers (Week 5) - -**Goal:** Implement composable modifier system - -- Implement `ctrl` modifier (positive controls) -- Implement `negctrl` modifier (negative controls) -- Implement `inv` modifier -- Implement `pow` modifier -- Nested modifier verification and canonicalization -- Canonical ordering implementation (negctrl → ctrl → pow → inv) -- Modifier flattening and optimization -- Extensive unit tests for modifier combinations - -**Deliverable:** Working modifier system with canonicalization - -### Phase 5: Composition & Box (Week 6) - -**Goal:** Enable circuit composition - -- Implement `box` operation (replaces `seq`) -- Add box-level canonicalization -- Implement optimization constraint enforcement -- Builder API for box operations -- Integration with modifiers -- Unit tests for box semantics - -**Deliverable:** Box operation with proper scoping - -### Phase 6: Custom Gates (Week 7) - -**Goal:** User-defined gate support - -- Design and implement gate definition operations (matrix-based) -- Design and implement gate definition operations (sequence-based) -- Implement `apply` operation -- Symbol table management and resolution -- Consistency verification for dual representations -- Inlining infrastructure -- Unit tests for custom gates - -**Deliverable:** Custom gate definition and application - -### Phase 7: Builder API & Convenience (Week 8) - -**Goal:** Ergonomic programmatic construction - -- Complete RefQuantumProgramBuilder implementation -- Complete OptQuantumProgramBuilder implementation -- Add convenience methods (cx, cz, ccx, mcx, mcy, mcz, mch, etc.) -- Builder unit tests -- Example programs using builders - -**Deliverable:** Production-ready builder APIs - -### Phase 8: Dialect Conversion (Week 9) - -**Goal:** Enable transformations between dialects - -- Implement MQTRef → MQTOpt conversion pass -- Implement MQTOpt → MQTRef conversion pass -- Handle modifier and box conversions -- SSA value threading logic -- Conversion unit tests - -**Deliverable:** Working dialect conversion infrastructure - -### Phase 9: Frontend Integration (Week 10) - -**Goal:** Connect to existing quantum software - -- Qiskit → MLIR translation -- OpenQASM 3.0 → MLIR translation -- `qc::QuantumComputation` → MLIR translation -- Parser infrastructure for OpenQASM -- Integration tests for each frontend - -**Deliverable:** Multiple input format support - -### Phase 10: Optimization Passes (Week 11) - -**Goal:** Implement optimization infrastructure - -- Advanced canonicalization patterns -- Gate fusion passes -- Circuit optimization passes -- Dead code elimination -- Constant folding and propagation -- Pass pipeline configuration -- Performance benchmarks - -**Deliverable:** Optimization pass suite - -### Phase 11: Backend Integration (Week 12) - -**Goal:** QIR lowering - -- MLIR → QIR lowering pass -- QIR emission and verification -- End-to-end integration tests (Qiskit → MLIR → QIR) -- Documentation for QIR mapping - -**Deliverable:** Complete compilation pipeline - -### Phase 12: Testing & Documentation (Week 13) - -**Goal:** Production readiness - -- Comprehensive integration test suite -- Parser/printer round-trip tests -- Performance regression tests -- API documentation -- Tutorial examples -- Migration guide for existing code - -**Deliverable:** Production-ready system with documentation - -### Phase 13: Polish & Release (Week 14) - -**Goal:** Release preparation - -- Bug fixes from testing -- Performance optimization -- Code review and cleanup -- Release notes -- Final documentation review -- Announce and deploy - -**Deliverable:** Released dialect system - -**Parallelization Strategy:** - -- Base gate implementation (Phases 2-3) can partially overlap -- Builder API (Phase 7) can be developed in parallel with custom gates (Phase 6) -- Frontend integration (Phase 9) can start once base operations are stable (after Phase 5) -- Testing and documentation (Phase 12) should be ongoing throughout - -**Risk Mitigation:** - -- **Critical Path:** Phases 1-4 are sequential and critical -- **Buffer Time:** Phase 13 provides 1-week buffer for unforeseen issues -- **Early Testing:** Integration tests should begin in Phase 9 to catch issues early -- **Incremental Delivery:** Each phase produces a working subset to enable testing diff --git a/docs/mlir/quartz-logo.svg b/docs/mlir/quartz-logo.svg deleted file mode 100644 index 7f10281550..0000000000 --- a/docs/mlir/quartz-logo.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - QUARTZ - - - - - From b171269a5e3de918c7bc3d97d6bd275ad9dd2508 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 15 Dec 2025 19:08:25 +0100 Subject: [PATCH 386/419] Revert "Use llvm::cast to fix linter error" This reverts commit e5cd6d157e2d1d7517a982da2e0d8607f394ac2e. --- mlir/unittests/pipeline/test_compiler_pipeline.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index ec0755f040..71a3356814 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1583,7 +1583,8 @@ TEST_F(CompilerPipelineTest, MCXNested) { auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.ctrl(reg[0], [&](OpBuilder& b) { - llvm::cast(b).cx(reg[1], reg[2]); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + static_cast(b).cx(reg[1], reg[2]); }); }); From 32a70921c77e8bcd95d623aed5274346f25da677 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 15 Dec 2025 22:31:18 +0100 Subject: [PATCH 387/419] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docstring=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 2 ++ .../Quartz/Builder/QuartzProgramBuilder.h | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 6ecafe725d..891e9604f2 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -280,6 +280,7 @@ class FluxProgramBuilder final : public OpBuilder { * flux.OP_NAME(%PARAM) \ * flux.yield \ * } : ({!flux.qubit}) -> ({!flux.qubit}) \ + * ``` \ */ \ Value c##OP_NAME(const std::variant&(PARAM), Value control); \ /** \ @@ -298,6 +299,7 @@ class FluxProgramBuilder final : public OpBuilder { * flux.OP_NAME(%PARAM) \ * flux.yield \ * } : ({!flux.qubit, !flux.qubit}) -> ({!flux.qubit, !flux.qubit}) \ + * ``` \ */ \ ValueRange mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls); diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 54cf84fbed..29090cb812 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -269,6 +269,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q) { \ * quartz.OP_NAME(%PARAM) \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ Value control); \ @@ -287,6 +288,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME(%PARAM) \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls); @@ -328,6 +330,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0) { \ * quartz.OP_NAME %q1 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(Value control, Value target); \ /** \ @@ -345,6 +348,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME %q2 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); @@ -398,6 +402,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0) { \ * quartz.OP_NAME(%PARAM) %q1 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ Value control, Value target); \ @@ -417,6 +422,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME(%PARAM) %q2 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls, Value target); @@ -467,6 +473,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0) { \ * quartz.OP_NAME(%PARAM1, %PARAM2) %q1 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ @@ -488,6 +495,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME(%PARAM1, %PARAM2) %q2 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME( \ const std::variant&(PARAM1), \ @@ -542,6 +550,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0) { \ * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ @@ -565,6 +574,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME( \ const std::variant&(PARAM1), \ @@ -611,6 +621,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0) { \ * quartz.OP_NAME %q1, %q2 : !quartz.qubit, !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ /** \ @@ -629,6 +640,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME %q2, %q3 : !quartz.qubit, !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ Value qubit1); @@ -678,6 +690,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0) { \ * quartz.OP_NAME(%PARAM) %q1, %q2 : !quartz.qubit, !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ Value control, Value qubit0, Value qubit1); \ @@ -698,6 +711,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME(%PARAM) %q2, %q3 : !quartz.qubit, !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ ValueRange controls, Value qubit0, \ @@ -752,6 +766,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !quartz.qubit, \ * !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ @@ -774,6 +789,7 @@ class QuartzProgramBuilder final : public OpBuilder { * quartz.ctrl(%q0, %q1) { \ * quartz.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !quartz.qubit, !quartz.qubit \ * } \ + * ``` \ */ \ QuartzProgramBuilder& mc##OP_NAME( \ const std::variant&(PARAM1), \ From d8a182ad420f788a10afae54769925b48fd9e844 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 15 Dec 2025 22:31:39 +0100 Subject: [PATCH 388/419] =?UTF-8?q?=F0=9F=9A=A8=20include=20fixes=20report?= =?UTF-8?q?ed=20by=20local=20clang-tidy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 2 ++ mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 3 ++- mlir/include/mlir/Dialect/Utils/Utils.h | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 891e9604f2..5885513d4a 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 64e1e10bb6..ae4ba5ddab 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -13,8 +13,8 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" #include -#include #include +#include #include #include #include @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace mlir::qir { diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 4b5f46ee23..6bd34ed837 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -28,7 +28,7 @@ constexpr double TOLERANCE = 1e-12; * @param parameter The parameter as a variant (double or Value). * @return Value The parameter as a Value. */ -inline Value variantToValue(OpBuilder& builder, OperationState& state, +inline Value variantToValue(OpBuilder& builder, const OperationState& state, const std::variant& parameter) { Value operand; if (std::holds_alternative(parameter)) { From c02310c8ea5e44982270e4232fdb1617aa709ca2 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 15 Dec 2025 22:31:54 +0100 Subject: [PATCH 389/419] =?UTF-8?q?=F0=9F=91=8C=20address=20code=20rabbit?= =?UTF-8?q?=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../mlir/Dialect/Flux/Builder/FluxProgramBuilder.h | 10 +++++----- .../mlir/Dialect/QIR/Builder/QIRProgramBuilder.h | 8 ++++---- .../mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h | 10 +++++----- mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp | 4 ++-- .../Dialect/Quartz/Builder/QuartzProgramBuilder.cpp | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 5885513d4a..6dd2d13db4 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -159,10 +159,10 @@ class FluxProgramBuilder final : public OpBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - StringRef msg = "Bit index " + std::to_string(index) + - " out of bounds for register '" + name + "' of size " + - std::to_string(size); - llvm::reportFatalUsageError(msg); + const std::string msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + + "' of size " + std::to_string(size); + llvm::reportFatalUsageError(msg.c_str()); } return { .registerName = name, .registerSize = size, .registerIndex = index}; @@ -181,7 +181,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - const std::string& name = "c"); + std::string name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index ae4ba5ddab..8066cd63fd 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -156,10 +156,10 @@ class QIRProgramBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - StringRef msg = "Bit index " + std::to_string(index) + - " out of bounds for register '" + name + "' of size " + - std::to_string(size); - llvm::reportFatalUsageError(msg); + const std::string msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + + "' of size " + std::to_string(size); + llvm::reportFatalUsageError(msg.c_str()); } return { .registerName = name, .registerSize = size, .registerIndex = index}; diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 29090cb812..2ea3bed8c9 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -150,10 +150,10 @@ class QuartzProgramBuilder final : public OpBuilder { */ Bit operator[](const int64_t index) const { if (index < 0 || index >= size) { - StringRef msg = "Bit index " + std::to_string(index) + - " out of bounds for register '" + name + "' of size " + - std::to_string(size); - llvm::reportFatalUsageError(msg); + const std::string msg = "Bit index " + std::to_string(index) + + " out of bounds for register '" + name + + "' of size " + std::to_string(size); + llvm::reportFatalUsageError(msg.c_str()); } return { .registerName = name, .registerSize = size, .registerIndex = index}; @@ -172,7 +172,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - const std::string& name = "c"); + std::string name = "c"); //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index d265fe597d..d221ae0845 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -111,8 +111,8 @@ SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, } FluxProgramBuilder::ClassicalRegister -FluxProgramBuilder::allocClassicalBitRegister(int64_t size, - const std::string& name) { +FluxProgramBuilder::allocClassicalBitRegister(const int64_t size, + std::string name) { checkFinalized(); if (size <= 0) { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index cb18fd6e3f..56247896bf 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -106,8 +106,8 @@ SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, } QuartzProgramBuilder::ClassicalRegister -QuartzProgramBuilder::allocClassicalBitRegister(int64_t size, - const std::string& name) { +QuartzProgramBuilder::allocClassicalBitRegister(const int64_t size, + std::string name) { checkFinalized(); if (size <= 0) { From 4bece21dd98decf07edd6333f8c359656da36309 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 15 Dec 2025 22:48:57 +0100 Subject: [PATCH 390/419] =?UTF-8?q?=F0=9F=94=A7=20lower=20tolerance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/Utils/Utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 6bd34ed837..adfb8fd3f3 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -18,7 +18,7 @@ namespace mlir::utils { -constexpr double TOLERANCE = 1e-12; +constexpr auto TOLERANCE = 1e-15; /** * @brief Convert a variant parameter (double or Value) to a Value From 5082913a36407aa73997e7afffed83cda48e7f57 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 00:31:53 +0100 Subject: [PATCH 391/419] =?UTF-8?q?=F0=9F=90=9B=20Fix=20canonicalization?= =?UTF-8?q?=20of=20controlled=20global=20phase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 21 +++++++++++-------- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 11 +++++----- .../pipeline/test_compiler_pipeline.cpp | 9 +++----- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 77e96c3d79..8e703d1bda 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -91,15 +91,18 @@ struct CtrlInlineGPhase final : OpRewritePattern { return failure(); } - SmallVector newOperands; - newOperands.reserve(op.getNumPosControls()); - for (size_t i = 0; i < op.getNumPosControls(); ++i) { - auto pOp = rewriter.create(op.getLoc(), op.getInputPosControl(i), - gPhaseOp.getTheta()); - newOperands.push_back(pOp.getQubitOut()); - } - - rewriter.replaceOp(op, newOperands); + SmallVector newControls(op.getControlsIn()); + const auto newTarget = newControls.back(); + newControls.pop_back(); + auto ctrlOp = CtrlOp::create(rewriter, op.getLoc(), newControls, newTarget, + [&](OpBuilder& b, ValueRange targets) { + auto pOp = + POp::create(b, op.getLoc(), targets[0], + gPhaseOp.getTheta()); + return pOp.getQubitOut(); + }); + + rewriter.replaceOp(op, ctrlOp.getResults()); return success(); } diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index ff875d3e50..a884b7818d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -88,11 +88,12 @@ struct CtrlInlineGPhase final : OpRewritePattern { return failure(); } - for (size_t i = 0; i < op.getNumPosControls(); ++i) { - rewriter.create(op.getLoc(), op.getPosControl(i), - gPhaseOp.getTheta()); - } - + SmallVector newControls(op.getControls()); + const auto newTarget = newControls.back(); + newControls.pop_back(); + CtrlOp::create(rewriter, op.getLoc(), newControls, [&](OpBuilder& b) { + POp::create(b, op.getLoc(), newTarget, gPhaseOp.getTheta()); + }); rewriter.eraseOp(op); return success(); diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 71a3356814..980f8b0043 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -1348,18 +1348,15 @@ TEST_F(CompilerPipelineTest, MCGPhase) { const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.p(1.0, reg[0]); - b.p(1.0, reg[1]); + b.cp(1.0, reg[0], reg[1]); }); const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); - b.p(1.0, reg[0]); - b.p(1.0, reg[1]); + b.cp(1.0, reg[0], reg[1]); }); const auto qir = buildQIR([](qir::QIRProgramBuilder& b) { auto reg = b.allocQubitRegister(2); - b.p(1.0, reg[0]); - b.p(1.0, reg[1]); + b.cp(1.0, reg[0], reg[1]); }); verifyAllStages({ From c333456e128a262cd4492e85e842a248030d7487 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 00:32:19 +0100 Subject: [PATCH 392/419] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20ensure=20consisten?= =?UTF-8?q?t=20parameter=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 28 +++++++++---------- .../Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 28 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 8e703d1bda..12ad3e1414 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -244,31 +244,31 @@ Value CtrlOp::getParameter(const size_t i) { return getBodyUnitary().getParameter(i); } -void CtrlOp::build(OpBuilder& builder, OperationState& state, +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, ValueRange targets, UnitaryOpInterface bodyUnitary) { - build(builder, state, controls, targets); - auto& block = state.regions.front()->emplaceBlock(); + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&block); - auto* op = builder.clone(*bodyUnitary.getOperation()); - builder.create(state.location, op->getResults()); + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto* op = odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location, op->getResults()); } void CtrlOp::build( - OpBuilder& builder, OperationState& state, ValueRange controls, + OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, ValueRange targets, const std::function& bodyBuilder) { - build(builder, state, controls, targets); - auto& block = state.regions.front()->emplaceBlock(); + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); // Move the unitary op into the block - const OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&block); - auto targetsOut = bodyBuilder(builder, targets); - builder.create(state.location, targetsOut); + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + auto targetsOut = bodyBuilder(odsBuilder, targets); + odsBuilder.create(odsState.location, targetsOut); } LogicalResult CtrlOp::verify() { diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index a884b7818d..47d5700906 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -152,30 +152,30 @@ Value CtrlOp::getParameter(const size_t i) { return getBodyUnitary().getParameter(i); } -void CtrlOp::build(OpBuilder& builder, OperationState& state, +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, UnitaryOpInterface bodyUnitary) { - const OpBuilder::InsertionGuard guard(builder); - state.addOperands(controls); - auto* region = state.addRegion(); + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); auto& block = region->emplaceBlock(); // Move the unitary op into the block - builder.setInsertionPointToStart(&block); - builder.clone(*bodyUnitary.getOperation()); - builder.create(state.location); + odsBuilder.setInsertionPointToStart(&block); + odsBuilder.clone(*bodyUnitary.getOperation()); + odsBuilder.create(odsState.location); } -void CtrlOp::build(OpBuilder& builder, OperationState& state, +void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, ValueRange controls, const std::function& bodyBuilder) { - const OpBuilder::InsertionGuard guard(builder); - state.addOperands(controls); - auto* region = state.addRegion(); + const OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands(controls); + auto* region = odsState.addRegion(); auto& block = region->emplaceBlock(); - builder.setInsertionPointToStart(&block); - bodyBuilder(builder); - builder.create(state.location); + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(odsBuilder); + odsBuilder.create(odsState.location); } LogicalResult CtrlOp::verify() { From 3ffb553406afc496868a5cdac944f63cf0900f4c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 11:23:36 +0100 Subject: [PATCH 393/419] =?UTF-8?q?=F0=9F=9A=A8=20add=20a=20dedicated=20cl?= =?UTF-8?q?ang-tidy=20file=20for=20MLIR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/.clang-tidy | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 mlir/.clang-tidy diff --git a/mlir/.clang-tidy b/mlir/.clang-tidy new file mode 100644 index 0000000000..22e04ffbb7 --- /dev/null +++ b/mlir/.clang-tidy @@ -0,0 +1,9 @@ +InheritParentConfig: true +Checks: | + llvm-include-order, + llvm-namespace-comment, + llvm-prefer-isa-or-dyn-cast-in-conditionals, + llvm-prefer-register-over-unsigned, + llvm-prefer-static-over-anonymous-namespace, + -misc-use-anonymous-namespace, + llvm-twine-local From 58cd84c6c78aff66830911e98da2de091e44efa5 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 11:25:41 +0100 Subject: [PATCH 394/419] =?UTF-8?q?=F0=9F=9A=A8=20some=20more=20clang-tidy?= =?UTF-8?q?=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 5 +- .../Quartz/Builder/QuartzProgramBuilder.h | 5 +- .../Flux/Builder/FluxProgramBuilder.cpp | 7 +-- .../Quartz/Builder/QuartzProgramBuilder.cpp | 8 +-- .../TranslateQuantumComputationToQuartz.cpp | 51 +++++++++---------- 5 files changed, 40 insertions(+), 36 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 6dd2d13db4..603b4d5c4c 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -128,7 +128,8 @@ class FluxProgramBuilder final : public OpBuilder { * %q2 = flux.alloc("q", 3, 2) : !flux.qubit * ``` */ - SmallVector allocQubitRegister(int64_t size, std::string name = "q"); + SmallVector allocQubitRegister(int64_t size, + const std::string& name = "q"); /** * @brief A small structure representing a single classical bit within a @@ -181,7 +182,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c"); + std::string name = "c") const; //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 2ea3bed8c9..0506980586 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -119,7 +119,8 @@ class QuartzProgramBuilder final : public OpBuilder { * %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit * ``` */ - SmallVector allocQubitRegister(int64_t size, std::string name = "q"); + SmallVector allocQubitRegister(int64_t size, + const std::string& name = "q"); /** * @brief A small structure representing a single classical bit within a @@ -172,7 +173,7 @@ class QuartzProgramBuilder final : public OpBuilder { * ``` */ ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c"); + std::string name = "c") const; //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index d221ae0845..0328bd8798 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -85,8 +85,9 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { return qubit; } -SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, - std::string name) { +SmallVector +FluxProgramBuilder::allocQubitRegister(const int64_t size, + const std::string& name) { checkFinalized(); if (size <= 0) { @@ -112,7 +113,7 @@ SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, FluxProgramBuilder::ClassicalRegister FluxProgramBuilder::allocClassicalBitRegister(const int64_t size, - std::string name) { + std::string name) const { checkFinalized(); if (size <= 0) { diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index 56247896bf..b0271dd8b0 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace mlir::quartz { @@ -79,8 +80,9 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { return staticOp.getQubit(); } -SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, - std::string name) { +SmallVector +QuartzProgramBuilder::allocQubitRegister(const int64_t size, + const std::string& name) { checkFinalized(); if (size <= 0) { @@ -107,7 +109,7 @@ SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, QuartzProgramBuilder::ClassicalRegister QuartzProgramBuilder::allocClassicalBitRegister(const int64_t size, - std::string name) { + std::string name) const { checkFinalized(); if (size <= 0) { diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp index 576e9b9e25..16e47b1fe6 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -47,12 +48,12 @@ namespace { */ struct QregInfo { const qc::QuantumRegister* qregPtr; - llvm::SmallVector qubits; + SmallVector qubits; }; using BitMemInfo = std::pair; // (register ref, localIdx) -using BitIndexVec = llvm::SmallVector; +using BitIndexVec = SmallVector; /** * @brief Allocates quantum registers using the QuartzProgramBuilder @@ -67,11 +68,11 @@ using BitIndexVec = llvm::SmallVector; * @param quantumComputation The quantum computation to translate * @return Vector containing information about all quantum registers */ -llvm::SmallVector +SmallVector allocateQregs(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - llvm::SmallVector qregPtrs; + SmallVector qregPtrs; qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + quantumComputation.getAncillaRegisters().size()); for (const auto& qreg : @@ -90,7 +91,7 @@ allocateQregs(QuartzProgramBuilder& builder, }); // Allocate quantum registers using the builder - llvm::SmallVector qregs; + SmallVector qregs; for (const auto* qregPtr : qregPtrs) { auto qubits = builder.allocQubitRegister( static_cast(qregPtr->getSize()), qregPtr->getName()); @@ -112,10 +113,10 @@ allocateQregs(QuartzProgramBuilder& builder, * @param qregs Vector containing information about all quantum registers * @return Flat vector of qubit values indexed by physical qubit index */ -llvm::SmallVector +SmallVector buildQubitMap(const qc::QuantumComputation& quantumComputation, - const llvm::SmallVector& qregs) { - llvm::SmallVector flatQubits; + const SmallVector& qregs) { + SmallVector flatQubits; const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); flatQubits.resize(static_cast(maxPhys) + 1); @@ -145,7 +146,7 @@ BitIndexVec allocateClassicalRegisters(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - llvm::SmallVector cregPtrs; + SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); for (const auto& reg : quantumComputation.getClassicalRegisters() | std::views::values) { @@ -186,8 +187,7 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, * @param bitMap Mapping from global bit index to (register, local_index) */ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { + const SmallVector& qubits, const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -215,7 +215,7 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index */ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { + const SmallVector& qubits) { for (const auto& target : operation.getTargets()) { auto qubit = qubits[target]; builder.reset(qubit); @@ -233,10 +233,9 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index * @return Vector of qubit values corresponding to positive controls */ -llvm::SmallVector -getPosControls(const qc::Operation& operation, - const llvm::SmallVector& qubits) { - llvm::SmallVector controls; +SmallVector getPosControls(const qc::Operation& operation, + const SmallVector& qubits) { + SmallVector controls; for (const auto& [control, type] : operation.getControls()) { if (type == qc::Control::Type::Neg) { continue; @@ -262,7 +261,7 @@ getPosControls(const qc::Operation& operation, */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ @@ -302,7 +301,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ @@ -334,7 +333,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target = qubits[operation.getTargets()[0]]; \ @@ -367,7 +366,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& param3 = operation.getParameter()[2]; \ @@ -400,7 +399,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ @@ -434,7 +433,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ @@ -469,7 +468,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) */ \ void add##OP_QC##Op(QuartzProgramBuilder& builder, \ const qc::Operation& operation, \ - const llvm::SmallVector& qubits) { \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ @@ -490,8 +489,8 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) // BarrierOp void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { - llvm::SmallVector targets; + const SmallVector& qubits) { + SmallVector targets; for (const auto& targetIdx : operation.getTargets()) { targets.push_back(qubits[targetIdx]); } @@ -519,7 +518,7 @@ void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, LogicalResult translateOperations(QuartzProgramBuilder& builder, const qc::QuantumComputation& quantumComputation, - const llvm::SmallVector& qubits, + const SmallVector& qubits, const BitIndexVec& bitMap) { if (quantumComputation.hasGlobalPhase()) { builder.gphase(quantumComputation.getGlobalPhase()); From 7eedc262f93130b9a8f3b864ea38ccb68c4be77b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 23:43:53 +0100 Subject: [PATCH 395/419] =?UTF-8?q?=F0=9F=9A=A8=20hide=20noisy=20clang-tid?= =?UTF-8?q?y=20warning=20for=20LLVM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/.clang-tidy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/.clang-tidy b/mlir/.clang-tidy index 22e04ffbb7..413a0f29ea 100644 --- a/mlir/.clang-tidy +++ b/mlir/.clang-tidy @@ -6,4 +6,5 @@ Checks: | llvm-prefer-register-over-unsigned, llvm-prefer-static-over-anonymous-namespace, -misc-use-anonymous-namespace, - llvm-twine-local + llvm-twine-local, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access From 9b41f857b48974c2fdc5d16019738cb83f75a30b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 23:44:51 +0100 Subject: [PATCH 396/419] =?UTF-8?q?=F0=9F=91=8C=20code=20rabbit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp | 10 ++++++++-- mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp index 12ad3e1414..c15fd8e37c 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp @@ -36,12 +36,18 @@ struct MergeNestedCtrl final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto bodyUnitary = op.getBodyUnitary(); - auto bodyCtrlOp = llvm::dyn_cast(bodyUnitary.getOperation()); + auto bodyCtrlOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); if (!bodyCtrlOp) { return failure(); } + // Require at least one positive control; otherwise let other patterns + // (e.g., RemoveTrivialCtrl) handle the trivial case. + if (op.getNumPosControls() == 0) { + return failure(); + } + // Merge controls SmallVector newControls(op.getControlsIn()); for (const auto control : bodyCtrlOp.getControlsIn()) { diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp index 47d5700906..b386aef369 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp @@ -88,6 +88,12 @@ struct CtrlInlineGPhase final : OpRewritePattern { return failure(); } + // Require at least one positive control; otherwise let other patterns + // (e.g., RemoveTrivialCtrl) handle the trivial case. + if (op.getNumPosControls() == 0) { + return failure(); + } + SmallVector newControls(op.getControls()); const auto newTarget = newControls.back(); newControls.pop_back(); From 0fd3566e0de56d6fdf938b469f65a72b8fe94f74 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 16 Dec 2025 23:47:06 +0100 Subject: [PATCH 397/419] =?UTF-8?q?=F0=9F=8E=A8=20miscellaneous=20code=20q?= =?UTF-8?q?uality=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../Dialect/Flux/Builder/FluxProgramBuilder.h | 27 +++--- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 18 ++-- .../Quartz/Builder/QuartzProgramBuilder.h | 10 +- .../Flux/Builder/FluxProgramBuilder.cpp | 91 ++++++++++--------- .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 2 - .../Quartz/Builder/QuartzProgramBuilder.cpp | 84 ++++++++--------- .../pipeline/test_compiler_pipeline.cpp | 8 +- 7 files changed, 124 insertions(+), 116 deletions(-) diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h index 603b4d5c4c..bd4096a605 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace mlir::flux { @@ -128,8 +129,8 @@ class FluxProgramBuilder final : public OpBuilder { * %q2 = flux.alloc("q", 3, 2) : !flux.qubit * ``` */ - SmallVector allocQubitRegister(int64_t size, - const std::string& name = "q"); + llvm::SmallVector allocQubitRegister(int64_t size, + const std::string& name = "q"); /** * @brief A small structure representing a single classical bit within a @@ -181,8 +182,8 @@ class FluxProgramBuilder final : public OpBuilder { * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` */ - ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c") const; + [[nodiscard]] ClassicalRegister + allocClassicalBitRegister(int64_t size, std::string name = "c") const; //===--------------------------------------------------------------------===// // Measurement and Reset @@ -417,7 +418,7 @@ class FluxProgramBuilder final : public OpBuilder { * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ * ``` \ */ \ - Value OP_NAME(const std::variant& PARAM, Value qubit); \ + Value OP_NAME(const std::variant&(PARAM), Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -503,8 +504,8 @@ class FluxProgramBuilder final : public OpBuilder { * !flux.qubit \ * ``` \ */ \ - Value OP_NAME(const std::variant& PARAM1, \ - const std::variant& PARAM2, Value qubit); \ + Value OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -597,9 +598,9 @@ class FluxProgramBuilder final : public OpBuilder { * !flux.qubit \ * ``` \ */ \ - Value OP_NAME(const std::variant& PARAM1, \ - const std::variant& PARAM2, \ - const std::variant& PARAM3, Value qubit); \ + Value OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -783,7 +784,7 @@ class FluxProgramBuilder final : public OpBuilder { * -> !flux.qubit, !flux.qubit \ * ``` \ */ \ - std::pair OP_NAME(const std::variant& PARAM, \ + std::pair OP_NAME(const std::variant&(PARAM), \ Value qubit0, Value qubit1); \ /** \ * @brief Apply a controlled OP_CLASS \ @@ -813,7 +814,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` \ */ \ std::pair> c##OP_NAME( \ - const std::variant& PARAM, Value control, Value qubit0, \ + const std::variant&(PARAM), Value control, Value qubit0, \ Value qubit1); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ @@ -844,7 +845,7 @@ class FluxProgramBuilder final : public OpBuilder { * ``` \ */ \ std::pair> mc##OP_NAME( \ - const std::variant& PARAM, ValueRange controls, \ + const std::variant&(PARAM), ValueRange controls, \ Value qubit0, Value qubit1); DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index 8066cd63fd..d6ea20e6d3 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -12,16 +12,21 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" +#include #include #include +#include #include #include #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -125,7 +130,7 @@ class QIRProgramBuilder { * %q2 = llvm.inttoptr %c2 : i64 to !llvm.ptr * ``` */ - SmallVector allocQubitRegister(int64_t size); + llvm::SmallVector allocQubitRegister(int64_t size); /** * @brief A small structure representing a single classical bit within a @@ -829,10 +834,10 @@ class QIRProgramBuilder { LLVM::ConstantOp exitCode; /// Cache static pointers for reuse - DenseMap ptrCache; + llvm::DenseMap ptrCache; /// Map from (register_name, register_index) to result pointer - DenseMap, Value> registerResultMap; + llvm::DenseMap, Value> registerResultMap; /// Track qubit and result counts for QIR metadata QIRMetadata metadata_; @@ -845,9 +850,10 @@ class QIRProgramBuilder { * @param targets Target qubits * @param fnName Name of the QIR function to call */ - void createCallOp(const SmallVector>& parameters, - ValueRange controls, const SmallVector& targets, - StringRef fnName); + void + createCallOp(const llvm::SmallVector>& parameters, + ValueRange controls, const SmallVector& targets, + llvm::StringRef fnName); /** * @brief Generate array-based output recording in the output block diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h index 0506980586..ba822b9eae 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -119,8 +121,8 @@ class QuartzProgramBuilder final : public OpBuilder { * %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit * ``` */ - SmallVector allocQubitRegister(int64_t size, - const std::string& name = "q"); + llvm::SmallVector allocQubitRegister(int64_t size, + const std::string& name = "q"); /** * @brief A small structure representing a single classical bit within a @@ -172,8 +174,8 @@ class QuartzProgramBuilder final : public OpBuilder { * auto c = builder.allocClassicalBitRegister(3, "c"); * ``` */ - ClassicalRegister allocClassicalBitRegister(int64_t size, - std::string name = "c") const; + [[nodiscard]] ClassicalRegister + allocClassicalBitRegister(int64_t size, std::string name = "c") const; //===--------------------------------------------------------------------===// // Measurement and Reset diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp index 0328bd8798..71c9977c89 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -45,7 +44,7 @@ void FluxProgramBuilder::initialize() { // Create main function as entry point auto funcType = getFunctionType({}, {getI64Type()}); - auto mainFunc = create(loc, "main", funcType); + auto mainFunc = func::FuncOp::create(*this, loc, "main", funcType); // Add entry_point attribute to identify the main function auto entryPointAttr = getStringAttr("entry_point"); @@ -59,7 +58,7 @@ void FluxProgramBuilder::initialize() { Value FluxProgramBuilder::allocQubit() { checkFinalized(); - auto allocOp = create(loc); + auto allocOp = AllocOp::create(*this, loc); const auto qubit = allocOp.getResult(); // Track the allocated qubit as valid @@ -76,7 +75,7 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { } auto indexAttr = getI64IntegerAttr(index); - auto staticOp = create(loc, indexAttr); + auto staticOp = StaticOp::create(*this, loc, indexAttr); const auto qubit = staticOp.getQubit(); // Track the static qubit as valid @@ -85,7 +84,7 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { return qubit; } -SmallVector +llvm::SmallVector FluxProgramBuilder::allocQubitRegister(const int64_t size, const std::string& name) { checkFinalized(); @@ -94,15 +93,15 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, llvm::reportFatalUsageError("Size must be positive"); } - SmallVector qubits; + llvm::SmallVector qubits; qubits.reserve(static_cast(size)); auto nameAttr = getStringAttr(name); auto sizeAttr = getI64IntegerAttr(size); for (int64_t i = 0; i < size; ++i) { - auto indexAttr = getI64IntegerAttr(i); - auto allocOp = create(loc, nameAttr, sizeAttr, indexAttr); + const auto indexAttr = getI64IntegerAttr(i); + auto allocOp = AllocOp::create(*this, loc, nameAttr, sizeAttr, indexAttr); const auto& qubit = qubits.emplace_back(allocOp.getResult()); // Track the allocated qubit as valid validQubits.insert(qubit); @@ -156,7 +155,7 @@ void FluxProgramBuilder::updateQubitTracking(Value inputQubit, std::pair FluxProgramBuilder::measure(Value qubit) { checkFinalized(); - auto measureOp = create(loc, qubit); + auto measureOp = MeasureOp::create(*this, loc, qubit); auto qubitOut = measureOp.getQubitOut(); auto result = measureOp.getResult(); @@ -172,7 +171,8 @@ Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { auto nameAttr = getStringAttr(bit.registerName); auto sizeAttr = getI64IntegerAttr(bit.registerSize); auto indexAttr = getI64IntegerAttr(bit.registerIndex); - auto measureOp = create(loc, qubit, nameAttr, sizeAttr, indexAttr); + auto measureOp = + MeasureOp::create(*this, loc, qubit, nameAttr, sizeAttr, indexAttr); const auto qubitOut = measureOp.getQubitOut(); // Update tracking @@ -184,7 +184,7 @@ Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { Value FluxProgramBuilder::reset(Value qubit) { checkFinalized(); - auto resetOp = create(loc, qubit); + auto resetOp = ResetOp::create(*this, loc, qubit); const auto qubitOut = resetOp.getQubitOut(); // Update tracking @@ -203,7 +203,7 @@ Value FluxProgramBuilder::reset(Value qubit) { void FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM)) { \ checkFinalized(); \ - create(loc, PARAM); \ + OP_CLASS::create(*this, loc, PARAM); \ } \ Value FluxProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control) { \ @@ -211,7 +211,7 @@ Value FluxProgramBuilder::reset(Value qubit) { const auto controlsOut = \ ctrl(control, {}, \ [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ - b.create(loc, PARAM); \ + OP_CLASS::create(b, loc, PARAM); \ return {}; \ }) \ .first; \ @@ -223,7 +223,7 @@ Value FluxProgramBuilder::reset(Value qubit) { const auto controlsOut = \ ctrl(controls, {}, \ [&](OpBuilder& b, ValueRange /*targets*/) -> ValueRange { \ - b.create(loc, PARAM); \ + OP_CLASS::create(b, loc, PARAM); \ return {}; \ }) \ .first; \ @@ -239,7 +239,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ Value FluxProgramBuilder::OP_NAME(Value qubit) { \ checkFinalized(); \ - auto op = create(loc, qubit); \ + auto op = OP_CLASS::create(*this, loc, qubit); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ @@ -249,7 +249,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0]); \ + const auto op = OP_CLASS::create(b, loc, targets[0]); \ return op->getResults(); \ }); \ return {controlsOut[0], targetsOut[0]}; \ @@ -260,7 +260,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0]); \ + const auto op = OP_CLASS::create(b, loc, targets[0]); \ return op->getResults(); \ }); \ return {controlsOut, targetsOut[0]}; \ @@ -286,7 +286,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) Value FluxProgramBuilder::OP_NAME(const std::variant&(PARAM), \ Value qubit) { \ checkFinalized(); \ - auto op = create(loc, qubit, PARAM); \ + auto op = OP_CLASS::create(*this, loc, qubit, PARAM); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ @@ -297,7 +297,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], PARAM); \ + const auto op = OP_CLASS::create(b, loc, targets[0], PARAM); \ return op->getResults(); \ }); \ return {controlsOut[0], targetsOut[0]}; \ @@ -309,7 +309,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], PARAM); \ + const auto op = OP_CLASS::create(b, loc, targets[0], PARAM); \ return op->getResults(); \ }); \ return {controlsOut, targetsOut[0]}; \ @@ -329,7 +329,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit) { \ checkFinalized(); \ - auto op = create(loc, qubit, PARAM1, PARAM2); \ + auto op = OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ @@ -341,7 +341,8 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], PARAM1, PARAM2); \ + const auto op = \ + OP_CLASS::create(b, loc, targets[0], PARAM1, PARAM2); \ return op->getResults(); \ }); \ return {controlsOut[0], targetsOut[0]}; \ @@ -355,7 +356,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ - b.create(loc, targets[0], PARAM1, PARAM2); \ + OP_CLASS::create(b, loc, targets[0], PARAM1, PARAM2); \ return op->getResults(); \ }); \ return {controlsOut, targetsOut[0]}; \ @@ -375,7 +376,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value qubit) { \ checkFinalized(); \ - auto op = create(loc, qubit, PARAM1, PARAM2, PARAM3); \ + auto op = OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2, PARAM3); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ @@ -389,7 +390,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ - b.create(loc, targets[0], PARAM1, PARAM2, PARAM3); \ + OP_CLASS::create(b, loc, targets[0], PARAM1, PARAM2, PARAM3); \ return op->getResults(); \ }); \ return {controlsOut[0], targetsOut[0]}; \ @@ -403,8 +404,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const auto [controlsOut, targetsOut] = \ ctrl(controls, target, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], PARAM1, \ - PARAM2, PARAM3); \ + const auto op = OP_CLASS::create(b, loc, targets[0], PARAM1, \ + PARAM2, PARAM3); \ return op->getResults(); \ }); \ return {controlsOut, targetsOut[0]}; \ @@ -420,7 +421,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) std::pair FluxProgramBuilder::OP_NAME(Value qubit0, \ Value qubit1) { \ checkFinalized(); \ - auto op = create(loc, qubit0, qubit1); \ + auto op = OP_CLASS::create(*this, loc, qubit0, qubit1); \ const auto& qubit0Out = op.getQubit0Out(); \ const auto& qubit1Out = op.getQubit1Out(); \ updateQubitTracking(qubit0, qubit0Out); \ @@ -434,7 +435,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) ctrl(control, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ - b.create(loc, targets[0], targets[1]); \ + OP_CLASS::create(b, loc, targets[0], targets[1]); \ return op->getResults(); \ }); \ return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ @@ -447,7 +448,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) ctrl(controls, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ - b.create(loc, targets[0], targets[1]); \ + OP_CLASS::create(b, loc, targets[0], targets[1]); \ return op->getResults(); \ }); \ return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ @@ -466,7 +467,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) std::pair FluxProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ checkFinalized(); \ - auto op = create(loc, qubit0, qubit1, PARAM); \ + auto op = OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM); \ const auto& qubit0Out = op.getQubit0Out(); \ const auto& qubit1Out = op.getQubit1Out(); \ updateQubitTracking(qubit0, qubit0Out); \ @@ -481,7 +482,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) ctrl(control, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ - b.create(loc, targets[0], targets[1], PARAM); \ + OP_CLASS::create(b, loc, targets[0], targets[1], PARAM); \ return op->getResults(); \ }); \ return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ @@ -495,7 +496,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) ctrl(controls, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ const auto op = \ - b.create(loc, targets[0], targets[1], PARAM); \ + OP_CLASS::create(b, loc, targets[0], targets[1], PARAM); \ return op->getResults(); \ }); \ return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ @@ -516,7 +517,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ checkFinalized(); \ - auto op = create(loc, qubit0, qubit1, PARAM1, PARAM2); \ + auto op = OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM1, PARAM2); \ const auto& qubit0Out = op.getQubit0Out(); \ const auto& qubit1Out = op.getQubit1Out(); \ updateQubitTracking(qubit0, qubit0Out); \ @@ -531,8 +532,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const auto [controlsOut, targetsOut] = \ ctrl(control, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], targets[1], \ - PARAM1, PARAM2); \ + const auto op = OP_CLASS::create(b, loc, targets[0], \ + targets[1], PARAM1, PARAM2); \ return op->getResults(); \ }); \ return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ @@ -546,8 +547,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ - const auto op = b.create(loc, targets[0], targets[1], \ - PARAM1, PARAM2); \ + const auto op = OP_CLASS::create(b, loc, targets[0], \ + targets[1], PARAM1, PARAM2); \ return op->getResults(); \ }); \ return {controlsOut, {targetsOut[0], targetsOut[1]}}; \ @@ -563,7 +564,7 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { checkFinalized(); - auto op = create(loc, qubits); + auto op = BarrierOp::create(*this, loc, qubits); const auto& qubitsOut = op.getQubitsOut(); for (const auto& [inputQubit, outputQubit] : llvm::zip(qubits, qubitsOut)) { updateQubitTracking(inputQubit, outputQubit); @@ -580,7 +581,7 @@ std::pair FluxProgramBuilder::ctrl( const std::function& body) { checkFinalized(); - auto ctrlOp = create(loc, controls, targets, body); + auto ctrlOp = CtrlOp::create(*this, loc, controls, targets, body); // Update tracking const auto& controlsOut = ctrlOp.getControlsOut(); @@ -605,7 +606,7 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { validateQubitValue(qubit); validQubits.erase(qubit); - create(loc, qubit); + DeallocOp::create(*this, loc, qubit); return *this; } @@ -644,7 +645,7 @@ OwningOpRef FluxProgramBuilder::finalize() { // Automatically deallocate all still-allocated qubits // Sort qubits for deterministic output - SmallVector sortedQubits(validQubits.begin(), validQubits.end()); + llvm::SmallVector sortedQubits(validQubits.begin(), validQubits.end()); llvm::sort(sortedQubits, [](Value a, Value b) { auto* opA = a.getDefiningOp(); auto* opB = b.getDefiningOp(); @@ -654,16 +655,16 @@ OwningOpRef FluxProgramBuilder::finalize() { return opA->isBeforeInBlock(opB); }); for (auto qubit : sortedQubits) { - create(loc, qubit); + DeallocOp::create(*this, loc, qubit); } validQubits.clear(); // Create constant 0 for successful exit code - auto exitCode = create(loc, getI64IntegerAttr(0)); + auto exitCode = arith::ConstantOp::create(*this, loc, getI64IntegerAttr(0)); // Add return statement with exit code 0 to the main function - create(loc, ValueRange{exitCode}); + func::ReturnOp::create(*this, loc, ValueRange{exitCode}); // Invalidate context to prevent use-after-finalize ctx = nullptr; diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 9bd451f3e1..2dc82bf9aa 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -12,7 +12,6 @@ #include "mlir/Dialect/QIR/Utils/QIRUtils.h" -#include #include #include #include @@ -28,7 +27,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp index b0271dd8b0..1e6a4b4358 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -43,7 +43,7 @@ void QuartzProgramBuilder::initialize() { // Create main function as entry point auto funcType = getFunctionType({}, {getI64Type()}); - auto mainFunc = create(loc, "main", funcType); + auto mainFunc = func::FuncOp::create(*this, loc, "main", funcType); // Add entry_point attribute to identify the main function auto entryPointAttr = getStringAttr("entry_point"); @@ -58,7 +58,7 @@ Value QuartzProgramBuilder::allocQubit() { checkFinalized(); // Create the AllocOp without register metadata - auto allocOp = create(loc); + auto allocOp = AllocOp::create(*this, loc); const auto qubit = allocOp.getResult(); // Track the allocated qubit for automatic deallocation @@ -76,11 +76,11 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { // Create the StaticOp with the given index auto indexAttr = getI64IntegerAttr(index); - auto staticOp = create(loc, indexAttr); + auto staticOp = StaticOp::create(*this, loc, indexAttr); return staticOp.getQubit(); } -SmallVector +llvm::SmallVector QuartzProgramBuilder::allocQubitRegister(const int64_t size, const std::string& name) { checkFinalized(); @@ -90,7 +90,7 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, } // Allocate a sequence of qubits with register metadata - SmallVector qubits; + llvm::SmallVector qubits; qubits.reserve(size); auto nameAttr = getStringAttr(name); @@ -98,7 +98,7 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, for (int64_t i = 0; i < size; ++i) { auto indexAttr = getI64IntegerAttr(i); - auto allocOp = create(loc, nameAttr, sizeAttr, indexAttr); + auto allocOp = AllocOp::create(*this, loc, nameAttr, sizeAttr, indexAttr); const auto& qubit = qubits.emplace_back(allocOp.getResult()); // Track the allocated qubit for automatic deallocation allocatedQubits.insert(qubit); @@ -125,7 +125,7 @@ QuartzProgramBuilder::allocClassicalBitRegister(const int64_t size, Value QuartzProgramBuilder::measure(Value qubit) { checkFinalized(); - auto measureOp = create(loc, qubit); + auto measureOp = MeasureOp::create(*this, loc, qubit); return measureOp.getResult(); } @@ -135,13 +135,13 @@ QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, auto nameAttr = getStringAttr(bit.registerName); auto sizeAttr = getI64IntegerAttr(bit.registerSize); auto indexAttr = getI64IntegerAttr(bit.registerIndex); - create(loc, qubit, nameAttr, sizeAttr, indexAttr); + MeasureOp::create(*this, loc, qubit, nameAttr, sizeAttr, indexAttr); return *this; } QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { checkFinalized(); - create(loc, qubit); + ResetOp::create(*this, loc, qubit); return *this; } @@ -155,7 +155,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM)) { \ checkFinalized(); \ - create(loc, PARAM); \ + OP_CLASS::create(*this, loc, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -166,8 +166,8 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls) { \ checkFinalized(); \ - create(loc, controls, \ - [&](OpBuilder& b) { b.create(loc, PARAM); }); \ + CtrlOp::create(*this, loc, controls, \ + [&](OpBuilder& b) { OP_CLASS::create(b, loc, PARAM); }); \ return *this; \ } @@ -180,7 +180,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit) { \ checkFinalized(); \ - create(loc, qubit); \ + OP_CLASS::create(*this, loc, qubit); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME(Value control, \ @@ -191,8 +191,8 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME(ValueRange controls, \ Value target) { \ checkFinalized(); \ - create(loc, controls, \ - [&](OpBuilder& b) { b.create(loc, target); }); \ + CtrlOp::create(*this, loc, controls, \ + [&](OpBuilder& b) { OP_CLASS::create(b, loc, target); }); \ return *this; \ } @@ -216,7 +216,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit) { \ checkFinalized(); \ - create(loc, qubit, PARAM); \ + OP_CLASS::create(*this, loc, qubit, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -229,8 +229,8 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) const std::variant&(PARAM), ValueRange controls, \ Value target) { \ checkFinalized(); \ - create(loc, controls, [&](OpBuilder& b) { \ - b.create(loc, target, PARAM); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, target, PARAM); \ }); \ return *this; \ } @@ -249,7 +249,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit) { \ checkFinalized(); \ - create(loc, qubit, PARAM1, PARAM2); \ + OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -264,8 +264,8 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) const std::variant&(PARAM2), ValueRange controls, \ Value target) { \ checkFinalized(); \ - create(loc, controls, [&](OpBuilder& b) { \ - b.create(loc, target, PARAM1, PARAM2); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, target, PARAM1, PARAM2); \ }); \ return *this; \ } @@ -284,7 +284,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value qubit) { \ checkFinalized(); \ - create(loc, qubit, PARAM1, PARAM2, PARAM3); \ + OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2, PARAM3); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -301,8 +301,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) const std::variant&(PARAM3), ValueRange controls, \ Value target) { \ checkFinalized(); \ - create(loc, controls, [&](OpBuilder& b) { \ - b.create(loc, target, PARAM1, PARAM2, PARAM3); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, target, PARAM1, PARAM2, PARAM3); \ }); \ return *this; \ } @@ -317,7 +317,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit0, \ Value qubit1) { \ checkFinalized(); \ - create(loc, qubit0, qubit1); \ + OP_CLASS::create(*this, loc, qubit0, qubit1); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -328,8 +328,8 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value qubit0, Value qubit1) { \ checkFinalized(); \ - create(loc, controls, [&](OpBuilder& b) { \ - b.create(loc, qubit0, qubit1); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, qubit0, qubit1); \ }); \ return *this; \ } @@ -347,7 +347,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ checkFinalized(); \ - create(loc, qubit0, qubit1, PARAM); \ + OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -360,8 +360,8 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) const std::variant&(PARAM), ValueRange controls, \ Value qubit0, Value qubit1) { \ checkFinalized(); \ - create(loc, controls, [&](OpBuilder& b) { \ - b.create(loc, qubit0, qubit1, PARAM); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, qubit0, qubit1, PARAM); \ }); \ return *this; \ } @@ -381,7 +381,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ checkFinalized(); \ - create(loc, qubit0, qubit1, PARAM1, PARAM2); \ + OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM1, PARAM2); \ return *this; \ } \ QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ @@ -396,8 +396,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) const std::variant&(PARAM2), ValueRange controls, \ Value qubit0, Value qubit1) { \ checkFinalized(); \ - create(loc, controls, [&](OpBuilder& b) { \ - b.create(loc, qubit0, qubit1, PARAM1, PARAM2); \ + CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ + OP_CLASS::create(b, loc, qubit0, qubit1, PARAM1, PARAM2); \ }); \ return *this; \ } @@ -411,7 +411,7 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) QuartzProgramBuilder& QuartzProgramBuilder::barrier(ValueRange qubits) { checkFinalized(); - create(loc, qubits); + BarrierOp::create(*this, loc, qubits); return *this; } @@ -423,7 +423,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::ctrl(ValueRange controls, const std::function& body) { checkFinalized(); - create(loc, controls, body); + CtrlOp::create(*this, loc, controls, body); return *this; } @@ -443,7 +443,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { } // Create the DeallocOp - create(loc, qubit); + DeallocOp::create(*this, loc, qubit); return *this; } @@ -482,8 +482,8 @@ OwningOpRef QuartzProgramBuilder::finalize() { // Automatically deallocate all still-allocated qubits // Sort qubits for deterministic output - SmallVector sortedQubits(allocatedQubits.begin(), - allocatedQubits.end()); + llvm::SmallVector sortedQubits(allocatedQubits.begin(), + allocatedQubits.end()); llvm::sort(sortedQubits, [](Value a, Value b) { auto* opA = a.getDefiningOp(); auto* opB = b.getDefiningOp(); @@ -493,17 +493,17 @@ OwningOpRef QuartzProgramBuilder::finalize() { return opA->isBeforeInBlock(opB); }); for (auto qubit : sortedQubits) { - create(loc, qubit); + DeallocOp::create(*this, loc, qubit); } // Clear the tracking set allocatedQubits.clear(); // Create constant 0 for successful exit code - auto exitCode = create(loc, getI64IntegerAttr(0)); + auto exitCode = arith::ConstantOp::create(*this, loc, getI64IntegerAttr(0)); // Add return statement with exit code 0 to the main function - create(loc, ValueRange{exitCode}); + func::ReturnOp::create(*this, loc, ValueRange{exitCode}); // Invalidate context to prevent use-after-finalize ctx = nullptr; diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 980f8b0043..f01ba03b58 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -903,7 +903,7 @@ TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocClassicalBitRegister(1, "c"); + std::ignore = b.allocClassicalBitRegister(1, "c"); }); verifyAllStages({ @@ -930,7 +930,7 @@ TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocClassicalBitRegister(5, "c"); + std::ignore = b.allocClassicalBitRegister(5, "c"); }); verifyAllStages({ @@ -959,8 +959,8 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { ASSERT_TRUE(runPipeline(module.get()).succeeded()); const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocClassicalBitRegister(3, "c"); - b.allocClassicalBitRegister(2, "d"); + std::ignore = b.allocClassicalBitRegister(3, "c"); + std::ignore = b.allocClassicalBitRegister(2, "d"); }); verifyAllStages({ From a1f39d9e3d139d593b56f0c1fad90826ca4cbbf4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 01:42:07 +0100 Subject: [PATCH 398/419] Rename Quartz to QC and Flux to QCO --- mlir/include/mlir/Compiler/CompilerPipeline.h | 16 +- mlir/include/mlir/Conversion/CMakeLists.txt | 6 +- .../Conversion/FluxToQuartz/CMakeLists.txt | 13 - .../mlir/Conversion/QCOToQC/CMakeLists.txt | 13 + .../QuartzToQIR.h => QCOToQC/QCOToQC.h} | 6 +- .../FluxToQuartz.td => QCOToQC/QCOToQC.td} | 14 +- .../mlir/Conversion/QCToQCO/CMakeLists.txt | 13 + .../QuartzToFlux.h => QCToQCO/QCToQCO.h} | 6 +- .../QuartzToFlux.td => QCToQCO/QCToQCO.td} | 14 +- .../mlir/Conversion/QCToQIR/CMakeLists.txt | 13 + .../FluxToQuartz.h => QCToQIR/QCToQIR.h} | 6 +- .../QuartzToQIR.td => QCToQIR/QCToQIR.td} | 14 +- .../Conversion/QuartzToFlux/CMakeLists.txt | 13 - .../Conversion/QuartzToQIR/CMakeLists.txt | 13 - mlir/include/mlir/Dialect/CMakeLists.txt | 4 +- .../mlir/Dialect/Flux/IR/CMakeLists.txt | 12 - .../Builder/QCProgramBuilder.h} | 250 ++- .../mlir/Dialect/{Flux => QC}/CMakeLists.txt | 0 .../include/mlir/Dialect/QC/IR/CMakeLists.txt | 12 + .../IR/QuartzDialect.h => QC/IR/QCDialect.h} | 14 +- .../IR/QCInterfaces.td} | 10 +- .../IR/QuartzOps.td => QC/IR/QCOps.td} | 206 +- .../TranslateQuantumComputationToQC.h} | 4 +- .../Builder/QCOProgramBuilder.h} | 246 +-- .../Dialect/{Quartz => QCO}/CMakeLists.txt | 0 .../mlir/Dialect/QCO/IR/CMakeLists.txt | 12 + .../IR/FluxDialect.h => QCO/IR/QCODialect.h} | 14 +- .../IR/QCOInterfaces.td} | 10 +- .../{Flux/IR/FluxOps.td => QCO/IR/QCOOps.td} | 212 +- .../{Flux/FluxUtils.h => QCO/QCOUtils.h} | 4 +- .../Dialect/QIR/Builder/QIRProgramBuilder.h | 2 +- .../mlir/Dialect/Quartz/IR/CMakeLists.txt | 12 - mlir/lib/Compiler/CMakeLists.txt | 6 +- mlir/lib/Compiler/CompilerPipeline.cpp | 72 +- mlir/lib/Conversion/CMakeLists.txt | 6 +- .../{FluxToQuartz => QCOToQC}/CMakeLists.txt | 8 +- .../FluxToQuartz.cpp => QCOToQC/QCOToQC.cpp} | 618 +++--- .../{QuartzToFlux => QCToQCO}/CMakeLists.txt | 8 +- .../QuartzToFlux.cpp => QCToQCO/QCToQCO.cpp} | 843 ++++---- .../{QuartzToQIR => QCToQIR}/CMakeLists.txt | 6 +- .../QuartzToQIR.cpp => QCToQIR/QCToQIR.cpp} | 248 ++- mlir/lib/Dialect/CMakeLists.txt | 4 +- .../{Quartz => QC}/Builder/CMakeLists.txt | 13 +- .../Builder/QCProgramBuilder.cpp} | 107 +- .../lib/Dialect/{Quartz => QC}/CMakeLists.txt | 0 .../Dialect/{Flux => QC}/IR/CMakeLists.txt | 16 +- .../{Quartz => QC}/IR/Modifiers/CtrlOp.cpp | 4 +- .../{Flux => QC}/IR/Operations/MeasureOp.cpp | 4 +- .../IR/Operations/StandardGates/BarrierOp.cpp | 4 +- .../IR/Operations/StandardGates/GPhaseOp.cpp | 4 +- .../IR/Operations/StandardGates/POp.cpp | 4 +- .../IR/Operations/StandardGates/ROp.cpp | 4 +- .../IR/Operations/StandardGates/RXOp.cpp | 4 +- .../IR/Operations/StandardGates/RXXOp.cpp | 4 +- .../IR/Operations/StandardGates/RYOp.cpp | 4 +- .../IR/Operations/StandardGates/RYYOp.cpp | 4 +- .../IR/Operations/StandardGates/RZOp.cpp | 4 +- .../IR/Operations/StandardGates/RZXOp.cpp | 4 +- .../IR/Operations/StandardGates/RZZOp.cpp | 4 +- .../IR/Operations/StandardGates/U2Op.cpp | 4 +- .../IR/Operations/StandardGates/UOp.cpp | 4 +- .../Operations/StandardGates/XXMinusYYOp.cpp | 4 +- .../Operations/StandardGates/XXPlusYYOp.cpp | 4 +- .../IR/QuartzOps.cpp => QC/IR/QCOps.cpp} | 18 +- .../IR/QubitManagement/AllocOp.cpp | 4 +- .../IR/QubitManagement/DeallocOp.cpp | 4 +- .../{Quartz => QC}/Translation/CMakeLists.txt | 13 +- .../TranslateQuantumComputationToQC.cpp} | 259 ++- .../{Flux => QCO}/Builder/CMakeLists.txt | 12 +- .../Builder/QCOProgramBuilder.cpp} | 119 +- mlir/lib/Dialect/{Flux => QCO}/CMakeLists.txt | 0 .../Dialect/{Quartz => QCO}/IR/CMakeLists.txt | 16 +- .../{Flux => QCO}/IR/Modifiers/CtrlOp.cpp | 4 +- .../IR/Operations/MeasureOp.cpp | 4 +- .../{Flux => QCO}/IR/Operations/ResetOp.cpp | 4 +- .../IR/Operations/StandardGates/BarrierOp.cpp | 4 +- .../IR/Operations/StandardGates/ECROp.cpp | 6 +- .../IR/Operations/StandardGates/GPhaseOp.cpp | 4 +- .../IR/Operations/StandardGates/HOp.cpp | 6 +- .../IR/Operations/StandardGates/IdOp.cpp | 4 +- .../IR/Operations/StandardGates/POp.cpp | 6 +- .../IR/Operations/StandardGates/ROp.cpp | 4 +- .../IR/Operations/StandardGates/RXOp.cpp | 6 +- .../IR/Operations/StandardGates/RXXOp.cpp | 6 +- .../IR/Operations/StandardGates/RYOp.cpp | 6 +- .../IR/Operations/StandardGates/RYYOp.cpp | 6 +- .../IR/Operations/StandardGates/RZOp.cpp | 6 +- .../IR/Operations/StandardGates/RZXOp.cpp | 6 +- .../IR/Operations/StandardGates/RZZOp.cpp | 6 +- .../IR/Operations/StandardGates/SOp.cpp | 6 +- .../IR/Operations/StandardGates/SWAPOp.cpp | 6 +- .../IR/Operations/StandardGates/SXOp.cpp | 6 +- .../IR/Operations/StandardGates/SXdgOp.cpp | 6 +- .../IR/Operations/StandardGates/SdgOp.cpp | 6 +- .../IR/Operations/StandardGates/TOp.cpp | 6 +- .../IR/Operations/StandardGates/TdgOp.cpp | 6 +- .../IR/Operations/StandardGates/U2Op.cpp | 4 +- .../IR/Operations/StandardGates/UOp.cpp | 4 +- .../IR/Operations/StandardGates/XOp.cpp | 6 +- .../Operations/StandardGates/XXMinusYYOp.cpp | 4 +- .../Operations/StandardGates/XXPlusYYOp.cpp | 4 +- .../IR/Operations/StandardGates/YOp.cpp | 6 +- .../IR/Operations/StandardGates/ZOp.cpp | 6 +- .../IR/FluxOps.cpp => QCO/IR/QCOOps.cpp} | 18 +- .../IR/QubitManagement/AllocOp.cpp | 4 +- .../IR/QubitManagement/DeallocOp.cpp | 4 +- mlir/tools/mqt-cc/mqt-cc.cpp | 8 +- mlir/unittests/pipeline/CMakeLists.txt | 6 +- .../pipeline/test_compiler_pipeline.cpp | 1789 ++++++++--------- 109 files changed, 2806 insertions(+), 2839 deletions(-) delete mode 100644 mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt create mode 100644 mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt rename mlir/include/mlir/Conversion/{QuartzToQIR/QuartzToQIR.h => QCOToQC/QCOToQC.h} (70%) rename mlir/include/mlir/Conversion/{FluxToQuartz/FluxToQuartz.td => QCOToQC/QCOToQC.td} (50%) create mode 100644 mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt rename mlir/include/mlir/Conversion/{QuartzToFlux/QuartzToFlux.h => QCToQCO/QCToQCO.h} (70%) rename mlir/include/mlir/Conversion/{QuartzToFlux/QuartzToFlux.td => QCToQCO/QCToQCO.td} (50%) create mode 100644 mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt rename mlir/include/mlir/Conversion/{FluxToQuartz/FluxToQuartz.h => QCToQIR/QCToQIR.h} (70%) rename mlir/include/mlir/Conversion/{QuartzToQIR/QuartzToQIR.td => QCToQIR/QCToQIR.td} (79%) delete mode 100644 mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt delete mode 100644 mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt delete mode 100644 mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt rename mlir/include/mlir/Dialect/{Quartz/Builder/QuartzProgramBuilder.h => QC/Builder/QCProgramBuilder.h} (84%) rename mlir/include/mlir/Dialect/{Flux => QC}/CMakeLists.txt (100%) create mode 100644 mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt rename mlir/include/mlir/Dialect/{Quartz/IR/QuartzDialect.h => QC/IR/QCDialect.h} (91%) rename mlir/include/mlir/Dialect/{Quartz/IR/QuartzInterfaces.td => QC/IR/QCInterfaces.td} (94%) rename mlir/include/mlir/Dialect/{Quartz/IR/QuartzOps.td => QC/IR/QCOps.td} (82%) rename mlir/include/mlir/Dialect/{Quartz/Translation/TranslateQuantumComputationToQuartz.h => QC/Translation/TranslateQuantumComputationToQC.h} (75%) rename mlir/include/mlir/Dialect/{Flux/Builder/FluxProgramBuilder.h => QCO/Builder/QCOProgramBuilder.h} (87%) rename mlir/include/mlir/Dialect/{Quartz => QCO}/CMakeLists.txt (100%) create mode 100644 mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt rename mlir/include/mlir/Dialect/{Flux/IR/FluxDialect.h => QCO/IR/QCODialect.h} (93%) rename mlir/include/mlir/Dialect/{Flux/IR/FluxInterfaces.td => QCO/IR/QCOInterfaces.td} (95%) rename mlir/include/mlir/Dialect/{Flux/IR/FluxOps.td => QCO/IR/QCOOps.td} (82%) rename mlir/include/mlir/Dialect/{Flux/FluxUtils.h => QCO/QCOUtils.h} (99%) delete mode 100644 mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt rename mlir/lib/Conversion/{FluxToQuartz => QCOToQC}/CMakeLists.txt (82%) rename mlir/lib/Conversion/{FluxToQuartz/FluxToQuartz.cpp => QCOToQC/QCOToQC.cpp} (53%) rename mlir/lib/Conversion/{QuartzToFlux => QCToQCO}/CMakeLists.txt (82%) rename mlir/lib/Conversion/{QuartzToFlux/QuartzToFlux.cpp => QCToQCO/QCToQCO.cpp} (54%) rename mlir/lib/Conversion/{QuartzToQIR => QCToQIR}/CMakeLists.txt (89%) rename mlir/lib/Conversion/{QuartzToQIR/QuartzToQIR.cpp => QCToQIR/QCToQIR.cpp} (85%) rename mlir/lib/Dialect/{Quartz => QC}/Builder/CMakeLists.txt (52%) rename mlir/lib/Dialect/{Quartz/Builder/QuartzProgramBuilder.cpp => QC/Builder/QCProgramBuilder.cpp} (85%) rename mlir/lib/Dialect/{Quartz => QC}/CMakeLists.txt (100%) rename mlir/lib/Dialect/{Flux => QC}/IR/CMakeLists.txt (83%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Modifiers/CtrlOp.cpp (98%) rename mlir/lib/Dialect/{Flux => QC}/IR/Operations/MeasureOp.cpp (93%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/BarrierOp.cpp (94%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/GPhaseOp.cpp (88%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/POp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/ROp.cpp (90%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RXOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RXXOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RYOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RYYOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RZOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RZXOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/RZZOp.cpp (89%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/U2Op.cpp (90%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/UOp.cpp (91%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/XXMinusYYOp.cpp (91%) rename mlir/lib/Dialect/{Quartz => QC}/IR/Operations/StandardGates/XXPlusYYOp.cpp (91%) rename mlir/lib/Dialect/{Quartz/IR/QuartzOps.cpp => QC/IR/QCOps.cpp} (74%) rename mlir/lib/Dialect/{Flux => QC}/IR/QubitManagement/AllocOp.cpp (93%) rename mlir/lib/Dialect/{Quartz => QC}/IR/QubitManagement/DeallocOp.cpp (94%) rename mlir/lib/Dialect/{Quartz => QC}/Translation/CMakeLists.txt (57%) rename mlir/lib/Dialect/{Quartz/Translation/TranslateQuantumComputationToQuartz.cpp => QC/Translation/TranslateQuantumComputationToQC.cpp} (70%) rename mlir/lib/Dialect/{Flux => QCO}/Builder/CMakeLists.txt (59%) rename mlir/lib/Dialect/{Flux/Builder/FluxProgramBuilder.cpp => QCO/Builder/QCOProgramBuilder.cpp} (88%) rename mlir/lib/Dialect/{Flux => QCO}/CMakeLists.txt (100%) rename mlir/lib/Dialect/{Quartz => QCO}/IR/CMakeLists.txt (82%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Modifiers/CtrlOp.cpp (99%) rename mlir/lib/Dialect/{Quartz => QCO}/IR/Operations/MeasureOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/ResetOp.cpp (94%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/BarrierOp.cpp (98%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/ECROp.cpp (89%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/GPhaseOp.cpp (95%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/HOp.cpp (89%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/IdOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/POp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/ROp.cpp (97%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RXOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RXXOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RYOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RYYOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RZOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RZXOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/RZZOp.cpp (93%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/SOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/SWAPOp.cpp (89%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/SXOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/SXdgOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/SdgOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/TOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/TdgOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/U2Op.cpp (98%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/UOp.cpp (98%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/XOp.cpp (89%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/XXMinusYYOp.cpp (97%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/XXPlusYYOp.cpp (97%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/YOp.cpp (89%) rename mlir/lib/Dialect/{Flux => QCO}/IR/Operations/StandardGates/ZOp.cpp (89%) rename mlir/lib/Dialect/{Flux/IR/FluxOps.cpp => QCO/IR/QCOOps.cpp} (76%) rename mlir/lib/Dialect/{Quartz => QCO}/IR/QubitManagement/AllocOp.cpp (92%) rename mlir/lib/Dialect/{Flux => QCO}/IR/QubitManagement/DeallocOp.cpp (94%) diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index fb28411ea4..8e8ae639d9 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -50,14 +50,14 @@ struct QuantumCompilerConfig { * All stages are recorded when recordIntermediates is enabled. */ struct CompilationRecord { - std::string afterQuartzImport; + std::string afterQCImport; std::string afterInitialCanon; - std::string afterFluxConversion; - std::string afterFluxCanon; + std::string afterQCOConversion; + std::string afterQCOCanon; std::string afterOptimization; std::string afterOptimizationCanon; - std::string afterQuartzConversion; - std::string afterQuartzCanon; + std::string afterQCConversion; + std::string afterQCCanon; std::string afterQIRConversion; std::string afterQIRCanon; }; @@ -69,14 +69,14 @@ struct CompilationRecord { * Provides a high-level interface for compiling quantum programs through * the MQT compiler infrastructure. The pipeline stages are: * - * 1. Quartz dialect (reference semantics) - imported from + * 1. QC dialect (reference semantics) - imported from * qc::QuantumComputation * 2. Canonicalization + cleanup - * 3. Flux dialect (value semantics) - enables SSA-based optimizations + * 3. QCO dialect (value semantics) - enables SSA-based optimizations * 4. Canonicalization + cleanup * 5. Quantum optimization passes * 6. Canonicalization + cleanup - * 7. Quartz dialect - converted back for backend lowering + * 7. QC dialect - converted back for backend lowering * 8. Canonicalization + cleanup * 9. QIR (Quantum Intermediate Representation) - optional final lowering * 10. Canonicalization + cleanup diff --git a/mlir/include/mlir/Conversion/CMakeLists.txt b/mlir/include/mlir/Conversion/CMakeLists.txt index a4a8ae82ec..f164a5e33d 100644 --- a/mlir/include/mlir/Conversion/CMakeLists.txt +++ b/mlir/include/mlir/Conversion/CMakeLists.txt @@ -10,6 +10,6 @@ add_subdirectory(MQTRefToMQTOpt) add_subdirectory(MQTOptToMQTRef) add_subdirectory(MQTRefToQIR) add_subdirectory(QIRToMQTRef) -add_subdirectory(FluxToQuartz) -add_subdirectory(QuartzToFlux) -add_subdirectory(QuartzToQIR) +add_subdirectory(QCOToQC) +add_subdirectory(QCToQCO) +add_subdirectory(QCToQIR) diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt b/mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt deleted file mode 100644 index c6136ad39e..0000000000 --- a/mlir/include/mlir/Conversion/FluxToQuartz/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -set(LLVM_TARGET_DEFINITIONS FluxToQuartz.td) -mlir_tablegen(FluxToQuartz.h.inc -gen-pass-decls -name FluxToQuartz) -add_public_tablegen_target(FluxToQuartzIncGen) - -add_mlir_doc(FluxToQuartz MLIRFluxToQuartz Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt b/mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt new file mode 100644 index 0000000000..7beea05328 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCOToQC/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QCOToQC.td) +mlir_tablegen(QCOToQC.h.inc -gen-pass-decls -name QCOToQC) +add_public_tablegen_target(QCOToQCIncGen) + +add_mlir_doc(QCOToQC MLIRQCOToQC Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.h similarity index 70% rename from mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h rename to mlir/include/mlir/Conversion/QCOToQC/QCOToQC.h index 06ed278374..b2fa86826c 100644 --- a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.h +++ b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.h @@ -13,9 +13,9 @@ #include // from @llvm-project namespace mlir { -#define GEN_PASS_DECL_QUARTZTOQIR -#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" +#define GEN_PASS_DECL_QCOTOQC +#include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" #define GEN_PASS_REGISTRATION -#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" +#include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" } // namespace mlir diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td similarity index 50% rename from mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td rename to mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td index 0225698a71..1f610d859a 100644 --- a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.td +++ b/mlir/include/mlir/Conversion/QCOToQC/QCOToQC.td @@ -8,19 +8,19 @@ include "mlir/Pass/PassBase.td" -def FluxToQuartz : Pass<"flux-to-quartz"> { - let summary = "Convert Flux dialect to Quartz dialect."; +def QCOToQC : Pass<"qco-to-qc"> { + let summary = "Convert QCO dialect to QC dialect."; let description = [{ - This pass converts all operations from the Flux dialect to their equivalent - operations in the Quartz dialect. It handles the transformation of qubit - values in Flux to qubit references in Quartz, ensuring that the semantics + This pass converts all operations from the QCO dialect to their equivalent + operations in the QC dialect. It handles the transformation of qubit + values in QCO to qubit references in QC, ensuring that the semantics of quantum operations are preserved during the conversion process. }]; // Define dependent dialects let dependentDialects = [ - "mlir::flux::FluxDialect", - "mlir::quartz::QuartzDialect" + "mlir::qco::QCODialect", + "mlir::qc::QCDialect" ]; } diff --git a/mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt b/mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt new file mode 100644 index 0000000000..1cc13df3ff --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQCO/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QCToQCO.td) +mlir_tablegen(QCToQCO.h.inc -gen-pass-decls -name QCToQCO) +add_public_tablegen_target(QCToQCOIncGen) + +add_mlir_doc(QCToQCO MLIRQCToQCO Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.h similarity index 70% rename from mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h rename to mlir/include/mlir/Conversion/QCToQCO/QCToQCO.h index 4602f5c801..d5d50d7880 100644 --- a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.h +++ b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.h @@ -13,9 +13,9 @@ #include // from @llvm-project namespace mlir { -#define GEN_PASS_DECL_QUARTZTOFLUX -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" +#define GEN_PASS_DECL_QCTOQCO +#include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" #define GEN_PASS_REGISTRATION -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" +#include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" } // namespace mlir diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td similarity index 50% rename from mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td rename to mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td index ff311bc0a4..06a225cee1 100644 --- a/mlir/include/mlir/Conversion/QuartzToFlux/QuartzToFlux.td +++ b/mlir/include/mlir/Conversion/QCToQCO/QCToQCO.td @@ -8,19 +8,19 @@ include "mlir/Pass/PassBase.td" -def QuartzToFlux : Pass<"quartz-to-flux"> { - let summary = "Convert Quartz dialect to Flux dialect."; +def QCToQCO : Pass<"qc-to-qco"> { + let summary = "Convert QC dialect to QCO dialect."; let description = [{ - This pass converts all operations from the Quartz dialect to their equivalent - operations in the Flux dialect. It handles the transformation of qubit - references in Quartz to qubit values in Flux, ensuring that the semantics + This pass converts all operations from the QC dialect to their equivalent + operations in the QCO dialect. It handles the transformation of qubit + references in QC to qubit values in QCO, ensuring that the semantics of quantum operations are preserved during the conversion process. }]; // Define dependent dialects let dependentDialects = [ - "mlir::quartz::QuartzDialect", - "mlir::flux::FluxDialect" + "mlir::qc::QCDialect", + "mlir::qco::QCODialect" ]; } diff --git a/mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt b/mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt new file mode 100644 index 0000000000..475145cb25 --- /dev/null +++ b/mlir/include/mlir/Conversion/QCToQIR/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS QCToQIR.td) +mlir_tablegen(QCToQIR.h.inc -gen-pass-decls -name QCToQIR) +add_public_tablegen_target(QCToQIRIncGen) + +add_mlir_doc(QCToQIR MLIRQCToQIR Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.h similarity index 70% rename from mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h rename to mlir/include/mlir/Conversion/QCToQIR/QCToQIR.h index 98a205924d..0da943bcc5 100644 --- a/mlir/include/mlir/Conversion/FluxToQuartz/FluxToQuartz.h +++ b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.h @@ -13,9 +13,9 @@ #include // from @llvm-project namespace mlir { -#define GEN_PASS_DECL_FLUXTOQUARTZ -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" +#define GEN_PASS_DECL_QCTOQIR +#include "mlir/Conversion/QCToQIR/QCToQIR.h.inc" #define GEN_PASS_REGISTRATION -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" +#include "mlir/Conversion/QCToQIR/QCToQIR.h.inc" } // namespace mlir diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td similarity index 79% rename from mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td rename to mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td index 3229a5b99a..34dad74d75 100644 --- a/mlir/include/mlir/Conversion/QuartzToQIR/QuartzToQIR.td +++ b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td @@ -8,25 +8,25 @@ include "mlir/Pass/PassBase.td" -def QuartzToQIR : Pass<"quartz-to-qir"> { - let summary = "Lower the Quartz dialect to the LLVM dialect compliant with QIR 2.0"; +def QCToQIR : Pass<"qc-to-qir"> { + let summary = "Lower the QC dialect to the LLVM dialect compliant with QIR 2.0"; let description = [{ - This pass lowers all operations from the Quartz dialect to their equivalent + This pass lowers all operations from the QC dialect to their equivalent operations in the LLVM dialect, ensuring compliance with the QIR 2.0 standard. - It translates quantum operations and types from Quartz to their corresponding + It translates quantum operations and types from QC to their corresponding representations in QIR, facilitating interoperability with quantum computing frameworks that utilize the QIR standard. Requirements: - - Input is a valid module in the Quartz dialect. + - Input is a valid module in the QC dialect. - The entry function must be marked with the `entry_point` attribute. - The entry function must have a single block. Multi-block functions are currently unsupported. - The program must have straight-line control flow (i.e., Base Profile QIR). Behavior: - - Each Quartz quantum operation is replaced by a call to the corresponding QIR function in the LLVM dialect. + - Each QC quantum operation is replaced by a call to the corresponding QIR function in the LLVM dialect. - Required QIR module flags are attached as attributes to the entry function. - The entry function is split into four blocks to satisfy QIR Base Profile constraints: 0. Initialization block: Sets up the execution environment and performs required runtime initialization. @@ -46,6 +46,6 @@ def QuartzToQIR : Pass<"quartz-to-qir"> { // Define dependent dialects let dependentDialects = [ "mlir::LLVM::LLVMDialect", - "mlir::QuartzDialect", + "mlir::QCDialect", ]; } diff --git a/mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt b/mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt deleted file mode 100644 index 234b18fbdf..0000000000 --- a/mlir/include/mlir/Conversion/QuartzToFlux/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -set(LLVM_TARGET_DEFINITIONS QuartzToFlux.td) -mlir_tablegen(QuartzToFlux.h.inc -gen-pass-decls -name QuartzToFlux) -add_public_tablegen_target(QuartzToFluxIncGen) - -add_mlir_doc(QuartzToFlux MLIRQuartzToFlux Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt b/mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt deleted file mode 100644 index 94f91181e9..0000000000 --- a/mlir/include/mlir/Conversion/QuartzToQIR/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -set(LLVM_TARGET_DEFINITIONS QuartzToQIR.td) -mlir_tablegen(QuartzToQIR.h.inc -gen-pass-decls -name QuartzToQIR) -add_public_tablegen_target(QuartzToQIRIncGen) - -add_mlir_doc(QuartzToQIR MLIRQuartzToQIR Conversions/ -gen-pass-doc) diff --git a/mlir/include/mlir/Dialect/CMakeLists.txt b/mlir/include/mlir/Dialect/CMakeLists.txt index 5cf34f8170..1586a5aead 100644 --- a/mlir/include/mlir/Dialect/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/CMakeLists.txt @@ -8,5 +8,5 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) -add_subdirectory(Quartz) -add_subdirectory(Flux) +add_subdirectory(QC) +add_subdirectory(QCO) diff --git a/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt deleted file mode 100644 index d9cffd4ae0..0000000000 --- a/mlir/include/mlir/Dialect/Flux/IR/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -add_mlir_dialect(FluxOps flux) -add_mlir_interface(FluxInterfaces) -add_mlir_doc(FluxOps MLIRFluxDialect Dialects/ -gen-dialect-doc) -add_mlir_doc(FluxInterfaces MLIRFluxInterfaces Dialects/ -gen-op-interface-docs -dialect=flux) diff --git a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h similarity index 84% rename from mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h rename to mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index ba822b9eae..80aff6bca5 100644 --- a/mlir/include/mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -10,7 +10,7 @@ #pragma once -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include #include @@ -26,20 +26,20 @@ #include #include -namespace mlir::quartz { +namespace mlir::qc { /** - * @brief Builder API for constructing quantum programs in the Quartz dialect + * @brief Builder API for constructing quantum programs in the QC dialect * * @details - * The QuartzProgramBuilder provides a type-safe interface for constructing + * The QCProgramBuilder provides a type-safe interface for constructing * quantum circuits using reference semantics. Operations modify qubits in * place without producing new SSA values, providing a natural mapping to * hardware execution models. * * @par Example Usage: * ```c++ - * QuartzProgramBuilder builder(context); + * QCProgramBuilder builder(context); * builder.initialize(); * * auto q0 = builder.staticQubit(0); @@ -51,13 +51,13 @@ namespace mlir::quartz { * auto module = builder.finalize(); * ``` */ -class QuartzProgramBuilder final : public OpBuilder { +class QCProgramBuilder final : public OpBuilder { public: /** - * @brief Construct a new QuartzProgramBuilder + * @brief Construct a new QCProgramBuilder * @param context The MLIR context to use for building operations */ - explicit QuartzProgramBuilder(MLIRContext* context); + explicit QCProgramBuilder(MLIRContext* context); //===--------------------------------------------------------------------===// // Initialization @@ -85,7 +85,7 @@ class QuartzProgramBuilder final : public OpBuilder { * auto q = builder.allocQubit(); * ``` * ```mlir - * %q = quartz.alloc : !quartz.qubit + * %q = qc.alloc : !qc.qubit * ``` */ Value allocQubit(); @@ -100,7 +100,7 @@ class QuartzProgramBuilder final : public OpBuilder { * auto q0 = builder.staticQubit(0); * ``` * ```mlir - * %q0 = quartz.static 0 : !quartz.qubit + * %q0 = qc.static 0 : !qc.qubit * ``` */ Value staticQubit(int64_t index); @@ -116,9 +116,9 @@ class QuartzProgramBuilder final : public OpBuilder { * auto q = builder.allocQubitRegister(3, "q"); * ``` * ```mlir - * %q0 = quartz.alloc("q", 3, 0) : !quartz.qubit - * %q1 = quartz.alloc("q", 3, 1) : !quartz.qubit - * %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit + * %q0 = qc.alloc("q", 3, 0) : !qc.qubit + * %q1 = qc.alloc("q", 3, 1) : !qc.qubit + * %q2 = qc.alloc("q", 3, 2) : !qc.qubit * ``` */ llvm::SmallVector allocQubitRegister(int64_t size, @@ -195,7 +195,7 @@ class QuartzProgramBuilder final : public OpBuilder { * auto result = builder.measure(q); * ``` * ```mlir - * %result = quartz.measure %q : !quartz.qubit -> i1 + * %result = qc.measure %q : !qc.qubit -> i1 * ``` */ Value measure(Value qubit); @@ -211,10 +211,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.measure(q0, c[0]); * ``` * ```mlir - * %r0 = quartz.measure("c", 3, 0) %q0 : !quartz.qubit -> i1 + * %r0 = qc.measure("c", 3, 0) %q0 : !qc.qubit -> i1 * ``` */ - QuartzProgramBuilder& measure(Value qubit, const Bit& bit); + QCProgramBuilder& measure(Value qubit, const Bit& bit); /** * @brief Reset a qubit to |0⟩ state @@ -230,10 +230,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.reset(q); * ``` * ```mlir - * quartz.reset %q : !quartz.qubit + * qc.reset %q : !qc.qubit * ``` */ - QuartzProgramBuilder& reset(Value qubit); + QCProgramBuilder& reset(Value qubit); //===--------------------------------------------------------------------===// // Unitary Operations @@ -253,10 +253,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM) \ + * qc.OP_NAME(%PARAM) \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM)); \ /** \ * Apply a controlled OP_CLASS \ * \ @@ -269,13 +269,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(PARAM, q); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q) { \ - * quartz.OP_NAME(%PARAM) \ + * qc.ctrl(%q) { \ + * qc.OP_NAME(%PARAM) \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ - Value control); \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -288,13 +288,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME(PARAM, {q0, q1}); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM) \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM) \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ - ValueRange controls); + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls); DECLARE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) @@ -314,10 +314,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(q); \ * ``` \ * ```mlir \ - * quartz.OP_NAME %q : !quartz.qubit \ + * qc.OP_NAME %q : !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(Value qubit); \ + QCProgramBuilder& OP_NAME(Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -330,12 +330,12 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(q0, q1); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME %q1 : !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME %q1 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(Value control, Value target); \ + QCProgramBuilder& c##OP_NAME(Value control, Value target); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -348,12 +348,12 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME({q0, q1}, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME %q2 : !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME %q2 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); + QCProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); DECLARE_ONE_TARGET_ZERO_PARAMETER(IdOp, id) DECLARE_ONE_TARGET_ZERO_PARAMETER(XOp, x) @@ -384,11 +384,11 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM, q); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM) %q : !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM), \ - Value qubit); \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -402,13 +402,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(PARAM, q0, q1); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME(%PARAM) %q1 : !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM) %q1 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ - Value control, Value target); \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value target); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -422,13 +422,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME(PARAM, {q0, q1}, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM) %q2 : !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM) %q2 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ - ValueRange controls, Value target); + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value target); DECLARE_ONE_TARGET_ONE_PARAMETER(RXOp, rx, theta) DECLARE_ONE_TARGET_ONE_PARAMETER(RYOp, ry, theta) @@ -453,12 +453,12 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM1, PARAM2, q); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q : !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - Value qubit); \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -473,14 +473,14 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q1 : !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q1 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - Value control, Value target); \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value target); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -495,15 +495,14 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q2 : !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q2 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME( \ - const std::variant&(PARAM1), \ - const std::variant&(PARAM2), ValueRange controls, \ - Value target); + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value target); DECLARE_ONE_TARGET_TWO_PARAMETER(ROp, r, theta, phi) DECLARE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) @@ -528,13 +527,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM1, PARAM2, PARAM3, q); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - const std::variant&(PARAM3), \ - Value qubit); \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -550,15 +549,15 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0, q1); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - const std::variant&(PARAM3), \ - Value control, Value target); \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value control, Value target); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -574,16 +573,15 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, {q0, q1}, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME( \ - const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - const std::variant&(PARAM3), ValueRange controls, \ - Value target); + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + ValueRange controls, Value target); DECLARE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) @@ -604,10 +602,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(q0, q1); \ * ``` \ * ```mlir \ - * quartz.OP_NAME %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(Value qubit0, Value qubit1); \ + QCProgramBuilder& OP_NAME(Value qubit0, Value qubit1); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -621,12 +619,12 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(q0, q1, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME %q1, %q2 : !qc.qubit, !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ + QCProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -640,13 +638,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME({q0, q1}, q2, q3); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME %q2, %q3 : !quartz.qubit, !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME %q2, %q3 : !qc.qubit, !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ - Value qubit1); + QCProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ + Value qubit1); DECLARE_TWO_TARGET_ZERO_PARAMETER(SWAPOp, swap) DECLARE_TWO_TARGET_ZERO_PARAMETER(iSWAPOp, iswap) @@ -671,11 +669,11 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM, q0, q1); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME(%PARAM) %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM), \ - Value qubit0, Value qubit1); \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM), \ + Value qubit0, Value qubit1); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -690,13 +688,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(PARAM, q0, q1, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME(%PARAM) %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM) %q1, %q2 : !qc.qubit, !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ - Value control, Value qubit0, Value qubit1); \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ + Value control, Value qubit0, Value qubit1); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -711,14 +709,14 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME(PARAM, {q0, q1}, q2, q3); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM) %q2, %q3 : !quartz.qubit, !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM) %q2, %q3 : !qc.qubit, !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ - ValueRange controls, Value qubit0, \ - Value qubit1); + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ + ValueRange controls, Value qubit0, \ + Value qubit1); DECLARE_TWO_TARGET_ONE_PARAMETER(RXXOp, rxx, theta) DECLARE_TWO_TARGET_ONE_PARAMETER(RYYOp, ryy, theta) @@ -744,12 +742,12 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM1, PARAM2, q0, q1); \ * ``` \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ */ \ - QuartzProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - Value qubit0, Value qubit1); \ + QCProgramBuilder& OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit0, Value qubit1); \ /** \ * @brief Apply a controlled OP_CLASS \ * \ @@ -765,15 +763,15 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1, q2); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0) { \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !quartz.qubit, \ - * !quartz.qubit \ + * qc.ctrl(%q0) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !qc.qubit, \ + * !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - Value control, Value qubit0, Value qubit1); \ + QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value control, Value qubit0, Value qubit1); \ /** \ * @brief Apply a multi-controlled OP_CLASS \ * \ @@ -789,15 +787,15 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2, q3); \ * ``` \ * ```mlir \ - * quartz.ctrl(%q0, %q1) { \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !quartz.qubit, !quartz.qubit \ + * qc.ctrl(%q0, %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !qc.qubit, !qc.qubit \ * } \ * ``` \ */ \ - QuartzProgramBuilder& mc##OP_NAME( \ - const std::variant&(PARAM1), \ - const std::variant&(PARAM2), ValueRange controls, \ - Value qubit0, Value qubit1); + QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + ValueRange controls, Value qubit0, \ + Value qubit1); DECLARE_TWO_TARGET_TWO_PARAMETER(XXPlusYYOp, xx_plus_yy, theta, beta) DECLARE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) @@ -817,10 +815,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.barrier({q0, q1}); * ``` * ```mlir - * quartz.barrier %q0, %q1 : !quartz.qubit, !quartz.qubit + * qc.barrier %q0, %q1 : !qc.qubit, !qc.qubit * ``` */ - QuartzProgramBuilder& barrier(ValueRange qubits); + QCProgramBuilder& barrier(ValueRange qubits); //===--------------------------------------------------------------------===// // Modifiers @@ -838,13 +836,13 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.ctrl(q0, [&](auto& b) { b.x(q1); }); * ``` * ```mlir - * quartz.ctrl(%q0) { - * quartz.x %q1 : !quartz.qubit + * qc.ctrl(%q0) { + * qc.x %q1 : !qc.qubit * } * ``` */ - QuartzProgramBuilder& ctrl(ValueRange controls, - const std::function& body); + QCProgramBuilder& ctrl(ValueRange controls, + const std::function& body); //===--------------------------------------------------------------------===// // Deallocation @@ -865,10 +863,10 @@ class QuartzProgramBuilder final : public OpBuilder { * builder.dealloc(q); * ``` * ```mlir - * quartz.dealloc %q : !quartz.qubit + * qc.dealloc %q : !qc.qubit * ``` */ - QuartzProgramBuilder& dealloc(Value qubit); + QCProgramBuilder& dealloc(Value qubit); //===--------------------------------------------------------------------===// // Finalization @@ -898,4 +896,4 @@ class QuartzProgramBuilder final : public OpBuilder { /// Check if the builder has been finalized void checkFinalized() const; }; -} // namespace mlir::quartz +} // namespace mlir::qc diff --git a/mlir/include/mlir/Dialect/Flux/CMakeLists.txt b/mlir/include/mlir/Dialect/QC/CMakeLists.txt similarity index 100% rename from mlir/include/mlir/Dialect/Flux/CMakeLists.txt rename to mlir/include/mlir/Dialect/QC/CMakeLists.txt diff --git a/mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt new file mode 100644 index 0000000000..74da153fac --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/IR/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_dialect(QCOps qc) +add_mlir_interface(QCInterfaces) +add_mlir_doc(QCOps MLIRQCDialect Dialects/ -gen-dialect-doc) +add_mlir_doc(QCInterfaces MLIRQCInterfaces Dialects/ -gen-op-interface-docs -dialect=qc) diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h similarity index 91% rename from mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h rename to mlir/include/mlir/Dialect/QC/IR/QCDialect.h index de2e0a3b55..67da004902 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzDialect.h +++ b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h @@ -30,26 +30,26 @@ #include #include -#define DIALECT_NAME_QUARTZ "quartz" +#define DIALECT_NAME_QC "qc" //===----------------------------------------------------------------------===// // Dialect //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.h.inc" +#include "mlir/Dialect/QC/IR/QCOpsDialect.h.inc" //===----------------------------------------------------------------------===// // Types //===----------------------------------------------------------------------===// #define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.h.inc" +#include "mlir/Dialect/QC/IR/QCOpsTypes.h.inc" //===----------------------------------------------------------------------===// // Interfaces //===----------------------------------------------------------------------===// -namespace mlir::quartz { +namespace mlir::qc { /** * @brief Trait for operations with a fixed number of target qubits and @@ -118,13 +118,13 @@ template class TargetAndParameterArityTrait { }; }; -} // namespace mlir::quartz +} // namespace mlir::qc -#include "mlir/Dialect/Quartz/IR/QuartzInterfaces.h.inc" // IWYU pragma: export +#include "mlir/Dialect/QC/IR/QCInterfaces.h.inc" // IWYU pragma: export //===----------------------------------------------------------------------===// // Operations //===----------------------------------------------------------------------===// #define GET_OP_CLASSES -#include "mlir/Dialect/Quartz/IR/QuartzOps.h.inc" // IWYU pragma: export +#include "mlir/Dialect/QC/IR/QCOps.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td similarity index 94% rename from mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td rename to mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td index 298a3d0fab..9f5bc5d030 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzInterfaces.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td @@ -6,8 +6,8 @@ // // Licensed under the MIT License -#ifndef QUARTZ_INTERFACES -#define QUARTZ_INTERFACES +#ifndef QC_INTERFACES +#define QC_INTERFACES include "mlir/IR/OpBase.td" @@ -22,10 +22,10 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { gates, modifier operations (control, inverse, power), and sequences. The interface enables uniform introspection and composition capabilities - across all unitary operations in the Quartz dialect. + across all unitary operations in the QC dialect. }]; - let cppNamespace = "::mlir::quartz"; + let cppNamespace = "::mlir::qc"; let methods = [ // Qubit accessors @@ -101,4 +101,4 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { ]; } -#endif // QUARTZ_INTERFACES +#endif // QC_INTERFACES diff --git a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td similarity index 82% rename from mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td rename to mlir/include/mlir/Dialect/QC/IR/QCOps.td index d046d172bb..69c2036c57 100644 --- a/mlir/include/mlir/Dialect/Quartz/IR/QuartzOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -6,10 +6,10 @@ // // Licensed under the MIT License -#ifndef QUARTZ_OPS -#define QUARTZ_OPS +#ifndef QC_OPS +#define QC_OPS -include "mlir/Dialect/Quartz/IR/QuartzInterfaces.td" +include "mlir/Dialect/QC/IR/QCInterfaces.td" include "mlir/IR/BuiltinTypeInterfaces.td" include "mlir/IR/DialectBase.td" include "mlir/IR/EnumAttr.td" @@ -19,16 +19,16 @@ include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" //===----------------------------------------------------------------------===// -// Quartz Dialect Definition +// QC Dialect Definition //===----------------------------------------------------------------------===// -def QuartzDialect : Dialect { - let name = "quartz"; +def QCDialect : Dialect { + let name = "qc"; - let summary = "The Quartz (reference semantics) dialect for quantum computing."; + let summary = "The QC (reference semantics) dialect for quantum computing."; let description = [{ - The Quartz dialect uses **reference semantics** where quantum operations + The QC dialect uses **reference semantics** where quantum operations modify qubits in place, similar to how hardware physically transforms quantum states. This model provides: @@ -37,36 +37,36 @@ def QuartzDialect : Dialect { - Direct compatibility with imperative quantum programming languages - Straightforward backend code generation - The name "Quartz" reflects the crystalline, structured nature of + The name "QC" reflects the crystalline, structured nature of hardware-oriented representations—operations have fixed positions and transform states in place, like atoms in a crystal lattice. Example: ```mlir - quartz.h %q // Applies Hadamard to qubit %q in place - quartz.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets + qc.h %q // Applies Hadamard to qubit %q in place + qc.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets ``` }]; - let cppNamespace = "::mlir::quartz"; + let cppNamespace = "::mlir::qc"; let useDefaultTypePrinterParser = 1; } //===----------------------------------------------------------------------===// -// Quartz Type Definitions +// QC Type Definitions //===----------------------------------------------------------------------===// -class QuartzType traits = []> - : TypeDef { +class QCType traits = []> + : TypeDef { let mnemonic = typeMnemonic; } -def QubitType : QuartzType<"Qubit", "qubit"> { - let summary = "Quartz qubit reference type"; +def QubitType : QCType<"Qubit", "qubit"> { + let summary = "QC qubit reference type"; let description = [{ - The `!quartz.qubit` type represents a reference to a quantum bit in the - Quartz dialect. Operations using this type modify qubits in place using + The `!qc.qubit` type represents a reference to a quantum bit in the + QC dialect. Operations using this type modify qubits in place using reference semantics, similar to how classical imperative languages handle mutable references. }]; @@ -76,14 +76,14 @@ def QubitType : QuartzType<"Qubit", "qubit"> { // Base Operation Classes //===----------------------------------------------------------------------===// -class QuartzOp traits = []> : - Op; +class QCOp traits = []> : + Op; //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// -def AllocOp : QuartzOp<"alloc", [MemoryEffects<[MemAlloc]>]> { +def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns a reference to it. @@ -96,14 +96,14 @@ def AllocOp : QuartzOp<"alloc", [MemoryEffects<[MemAlloc]>]> { Example (single qubit): ```mlir - %q = quartz.alloc : !quartz.qubit + %q = qc.alloc : !qc.qubit ``` Example (qubits in a register): ```mlir - %q0 = quartz.alloc("q", 3, 0) : !quartz.qubit - %q1 = quartz.alloc("q", 3, 1) : !quartz.qubit - %q2 = quartz.alloc("q", 3, 2) : !quartz.qubit + %q0 = qc.alloc("q", 3, 0) : !qc.qubit + %q1 = qc.alloc("q", 3, 1) : !qc.qubit + %q2 = qc.alloc("q", 3, 2) : !qc.qubit ``` }]; @@ -131,14 +131,14 @@ def AllocOp : QuartzOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let hasVerifier = 1; } -def DeallocOp : QuartzOp<"dealloc", [MemoryEffects<[MemFree]>]> { +def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. Example: ```mlir - quartz.dealloc %q : !quartz.qubit + qc.dealloc %q : !qc.qubit ``` }]; @@ -148,16 +148,16 @@ def DeallocOp : QuartzOp<"dealloc", [MemoryEffects<[MemFree]>]> { let hasCanonicalizer = 1; } -def StaticOp : QuartzOp<"static", [Pure]> { +def StaticOp : QCOp<"static", [Pure]> { let summary = "Retrieve a static qubit by index"; let description = [{ - The `quartz.static` operation produces an SSA value representing a qubit + The `qc.static` operation produces an SSA value representing a qubit identified by a static index. This is useful for referring to fixed qubits in a quantum program or to hardware-mapped qubits. Example: ```mlir - %q = quartz.static 0 : !quartz.qubit + %q = qc.static 0 : !qc.qubit ``` }]; @@ -170,7 +170,7 @@ def StaticOp : QuartzOp<"static", [Pure]> { // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp : QuartzOp<"measure"> { +def MeasureOp : QCOp<"measure"> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -184,12 +184,12 @@ def MeasureOp : QuartzOp<"measure"> { Example (simple measurement): ```mlir - %result = quartz.measure %q : !quartz.qubit -> i1 + %result = qc.measure %q : !qc.qubit -> i1 ``` Example (measurement with output recording): ```mlir - %result = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 + %result = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 ``` }]; @@ -212,14 +212,14 @@ def MeasureOp : QuartzOp<"measure"> { let hasVerifier = 1; } -def ResetOp : QuartzOp<"reset"> { +def ResetOp : QCOp<"reset"> { let summary = "Reset a qubit to |0⟩ state"; let description = [{ Resets a qubit to the |0⟩ state, regardless of its current state. Example: ```mlir - quartz.reset %q : !quartz.qubit + qc.reset %q : !qc.qubit ``` }]; @@ -233,7 +233,7 @@ def ResetOp : QuartzOp<"reset"> { class TargetAndParameterArityTrait : ParamNativeOpTrait<"TargetAndParameterArityTrait", !strconcat(!cast(T), ",", !cast(P))> { - let cppNamespace = "::mlir::quartz"; + let cppNamespace = "::mlir::qc"; } def ZeroTargetOneParameter : TargetAndParameterArityTrait<0, 1>; @@ -249,14 +249,14 @@ def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; // Unitary Operations //===----------------------------------------------------------------------===// -def GPhaseOp : QuartzOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { +def GPhaseOp : QCOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { let summary = "Apply a global phase to the state"; let description = [{ Applies a global phase to the state. Example: ```mlir - quartz.gphase(%theta) : () + qc.gphase(%theta) : () ``` }]; @@ -272,14 +272,14 @@ def GPhaseOp : QuartzOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOnePar ]; } -def IdOp : QuartzOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def IdOp : QCOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Id gate to a qubit"; let description = [{ Applies an Id gate to a qubit, modifying it in place. Example: ```mlir - quartz.id %q : !quartz.qubit + qc.id %q : !qc.qubit ``` }]; @@ -291,14 +291,14 @@ def IdOp : QuartzOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> }]; } -def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def XOp : QCOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit, modifying it in place. Example: ```mlir - quartz.x %q : !quartz.qubit + qc.x %q : !qc.qubit ``` }]; @@ -310,14 +310,14 @@ def XOp : QuartzOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } -def YOp : QuartzOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def YOp : QCOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Y gate to a qubit"; let description = [{ Applies a Y gate to a qubit, modifying it in place. Example: ```mlir - quartz.y %q : !quartz.qubit + qc.y %q : !qc.qubit ``` }]; @@ -329,14 +329,14 @@ def YOp : QuartzOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } -def ZOp : QuartzOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def ZOp : QCOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Z gate to a qubit"; let description = [{ Applies a Z gate to a qubit, modifying it in place. Example: ```mlir - quartz.z %q : !quartz.qubit + qc.z %q : !qc.qubit ``` }]; @@ -348,14 +348,14 @@ def ZOp : QuartzOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } -def HOp : QuartzOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def HOp : QCOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an H gate to a qubit"; let description = [{ Applies an H gate to a qubit, modifying it in place. Example: ```mlir - quartz.h %q : !quartz.qubit + qc.h %q : !qc.qubit ``` }]; @@ -367,14 +367,14 @@ def HOp : QuartzOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } -def SOp : QuartzOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SOp : QCOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an S gate to a qubit"; let description = [{ Applies an S gate to a qubit, modifying it in place. Example: ```mlir - quartz.s %q : !quartz.qubit + qc.s %q : !qc.qubit ``` }]; @@ -386,14 +386,14 @@ def SOp : QuartzOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } -def SdgOp : QuartzOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SdgOp : QCOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Sdg gate to a qubit"; let description = [{ Applies an Sdg gate to a qubit, modifying it in place. Example: ```mlir - quartz.sdg %q : !quartz.qubit + qc.sdg %q : !qc.qubit ``` }]; @@ -405,14 +405,14 @@ def SdgOp : QuartzOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter }]; } -def TOp : QuartzOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TOp : QCOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a T gate to a qubit"; let description = [{ Applies a T gate to a qubit, modifying it in place. Example: ```mlir - quartz.t %q : !quartz.qubit + qc.t %q : !qc.qubit ``` }]; @@ -424,14 +424,14 @@ def TOp : QuartzOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; } -def TdgOp : QuartzOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TdgOp : QCOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Tdg gate to a qubit"; let description = [{ Applies a Tdg gate to a qubit, modifying it in place. Example: ```mlir - quartz.tdg %q : !quartz.qubit + qc.tdg %q : !qc.qubit ``` }]; @@ -443,14 +443,14 @@ def TdgOp : QuartzOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter }]; } -def SXOp : QuartzOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXOp : QCOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an SX gate to a qubit"; let description = [{ Applies an SX gate to a qubit, modifying it in place. Example: ```mlir - quartz.sx %q : !quartz.qubit + qc.sx %q : !qc.qubit ``` }]; @@ -462,14 +462,14 @@ def SXOp : QuartzOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> }]; } -def SXdgOp : QuartzOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXdgOp : QCOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an SXdg gate to a qubit"; let description = [{ Applies an SXdg gate to a qubit, modifying it in place. Example: ```mlir - quartz.sxdg %q : !quartz.qubit + qc.sxdg %q : !qc.qubit ``` }]; @@ -481,14 +481,14 @@ def SXdgOp : QuartzOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParamet }]; } -def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RXOp : QCOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit, modifying it in place. Example: ```mlir - quartz.rx(%theta) %q : !quartz.qubit + qc.rx(%theta) %q : !qc.qubit ``` }]; @@ -505,14 +505,14 @@ def RXOp : QuartzOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> ]; } -def RYOp : QuartzOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RYOp : QCOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RY gate to a qubit"; let description = [{ Applies an RY gate to a qubit, modifying it in place. Example: ```mlir - quartz.ry(%theta) %q : !quartz.qubit + qc.ry(%theta) %q : !qc.qubit ``` }]; @@ -529,14 +529,14 @@ def RYOp : QuartzOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> ]; } -def RZOp : QuartzOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RZOp : QCOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RZ gate to a qubit"; let description = [{ Applies an RZ gate to a qubit, modifying it in place. Example: ```mlir - quartz.rz(%theta) %q : !quartz.qubit + qc.rz(%theta) %q : !qc.qubit ``` }]; @@ -553,14 +553,14 @@ def RZOp : QuartzOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> ]; } -def POp : QuartzOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def POp : QCOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply a P gate to a qubit"; let description = [{ Applies a P gate to a qubit, modifying it in place. Example: ```mlir - quartz.p(%theta) %q : !quartz.qubit + qc.p(%theta) %q : !qc.qubit ``` }]; @@ -577,14 +577,14 @@ def POp : QuartzOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { ]; } -def ROp : QuartzOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def ROp : QCOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply an R gate to a qubit"; let description = [{ Applies an R gate to a qubit, modifying it in place. Example: ```mlir - quartz.r(%theta, %phi) %q : !quartz.qubit + qc.r(%theta, %phi) %q : !qc.qubit ``` }]; @@ -602,14 +602,14 @@ def ROp : QuartzOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { ]; } -def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def U2Op : QCOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit, modifying it in place. Example: ```mlir - quartz.u2(%phi, %lambda) %q : !quartz.qubit + qc.u2(%phi, %lambda) %q : !qc.qubit ``` }]; @@ -627,14 +627,14 @@ def U2Op : QuartzOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> ]; } -def UOp : QuartzOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { +def UOp : QCOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let summary = "Apply a U gate to a qubit"; let description = [{ Applies a U gate to a qubit, modifying it in place. Example: ```mlir - quartz.u(%theta, %phi, %lambda) %q : !quartz.qubit + qc.u(%theta, %phi, %lambda) %q : !qc.qubit ``` }]; @@ -653,14 +653,14 @@ def UOp : QuartzOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> ]; } -def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def SWAPOp : QCOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits, modifying them in place. Example: ```mlir - quartz.swap %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.swap %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -673,14 +673,14 @@ def SWAPOp : QuartzOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParamet }]; } -def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def iSWAPOp : QCOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a iSWAP gate to two qubits"; let description = [{ Applies a iSWAP gate to two qubits, modifying them in place. Example: ```mlir - quartz.iswap %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.iswap %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -693,14 +693,14 @@ def iSWAPOp : QuartzOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParam }]; } -def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def DCXOp : QCOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a DCX gate to two qubits"; let description = [{ Applies a DCX gate to two qubits, modifying them in place. Example: ```mlir - quartz.dcx %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.dcx %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -713,14 +713,14 @@ def DCXOp : QuartzOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter }]; } -def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def ECROp : QCOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply an ECR gate to two qubits"; let description = [{ Applies an ECR gate to two qubits, modifying them in place. Example: ```mlir - quartz.ecr %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.ecr %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -733,14 +733,14 @@ def ECROp : QuartzOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter }]; } -def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RXXOp : QCOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RXX gate to two qubits"; let description = [{ Applies an RXX gate to two qubits, modifying them in place. Example: ```mlir - quartz.rxx(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.rxx(%theta) %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -758,14 +758,14 @@ def RXXOp : QuartzOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter] ]; } -def RYYOp : QuartzOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RYYOp : QCOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RYY gate to two qubits"; let description = [{ Applies an RYY gate to two qubits, modifying them in place. Example: ```mlir - quartz.ryy(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.ryy(%theta) %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -783,14 +783,14 @@ def RYYOp : QuartzOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter] ]; } -def RZXOp : QuartzOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZXOp : QCOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RZX gate to two qubits"; let description = [{ Applies an RZX gate to two qubits, modifying them in place. Example: ```mlir - quartz.rzx(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.rzx(%theta) %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -808,14 +808,14 @@ def RZXOp : QuartzOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter] ]; } -def RZZOp : QuartzOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZZOp : QCOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RZZ gate to two qubits"; let description = [{ Applies an RZZ gate to two qubits, modifying them in place. Example: ```mlir - quartz.rzz(%theta) %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.rzz(%theta) %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -833,14 +833,14 @@ def RZZOp : QuartzOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter] ]; } -def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXPlusYYOp : QCOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ Applies an XX+YY gate to two qubits, modifying them in place. Example: ```mlir - quartz.xx_plus_yy(%theta, %beta) %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.xx_plus_yy(%theta, %beta) %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -859,14 +859,14 @@ def XXPlusYYOp : QuartzOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetT ]; } -def XXMinusYYOp : QuartzOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXMinusYYOp : QCOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX-YY gate to two qubits"; let description = [{ Applies an XX-YY gate to two qubits, modifying them in place. Example: ```mlir - quartz.xx_minus_yy(%theta, %beta) %q0, %q1 : !quartz.qubit, !quartz.qubit + qc.xx_minus_yy(%theta, %beta) %q0, %q1 : !qc.qubit, !qc.qubit ``` }]; @@ -885,14 +885,14 @@ def XXMinusYYOp : QuartzOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTarge ]; } -def BarrierOp : QuartzOp<"barrier", traits = [UnitaryOpInterface]> { +def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { let summary = "Apply a barrier gate to a set of qubits"; let description = [{ Applies a barrier gate to a set of qubits, modifying them in place. Example: ```mlir - quartz.barrier %q : !quartz.qubit + qc.barrier %q : !qc.qubit ``` }]; @@ -919,7 +919,7 @@ def BarrierOp : QuartzOp<"barrier", traits = [UnitaryOpInterface]> { // Modifiers //===----------------------------------------------------------------------===// -def YieldOp : QuartzOp<"yield", traits = [Terminator]> { +def YieldOp : QCOp<"yield", traits = [Terminator]> { let summary = "Yield from a modifier region"; let description = [{ Terminates a modifier region, yielding control back to the enclosing operation. @@ -928,10 +928,10 @@ def YieldOp : QuartzOp<"yield", traits = [Terminator]> { let assemblyFormat = "attr-dict"; } -def CtrlOp : QuartzOp<"ctrl", +def CtrlOp : QCOp<"ctrl", traits = [ UnitaryOpInterface, - SingleBlockImplicitTerminator<"::mlir::quartz::YieldOp">, + SingleBlockImplicitTerminator<"::mlir::qc::YieldOp">, RecursiveMemoryEffects ]> { let summary = "Add control qubits to a unitary operation"; @@ -943,8 +943,8 @@ def CtrlOp : QuartzOp<"ctrl", Example: ```mlir - quartz.ctrl(%q0) { - quartz.x %q1 : !quartz.qubit + qc.ctrl(%q0) { + qc.x %q1 : !qc.qubit } ``` }]; @@ -978,4 +978,4 @@ def CtrlOp : QuartzOp<"ctrl", let hasVerifier = 1; } -#endif // QUARTZ_OPS +#endif // QC_OPS diff --git a/mlir/include/mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h b/mlir/include/mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h similarity index 75% rename from mlir/include/mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h rename to mlir/include/mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h index 3cabf71ff9..ed0c850c51 100644 --- a/mlir/include/mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h +++ b/mlir/include/mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h @@ -20,6 +20,6 @@ class QuantumComputation; namespace mlir { OwningOpRef -translateQuantumComputationToQuartz(MLIRContext* context, - const qc::QuantumComputation& qc); +translateQuantumComputationToQC(MLIRContext* context, + const ::qc::QuantumComputation& qc); } diff --git a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h similarity index 87% rename from mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h rename to mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index bd4096a605..cd5f4ae897 100644 --- a/mlir/include/mlir/Dialect/Flux/Builder/FluxProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -10,7 +10,7 @@ #pragma once -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -27,13 +27,13 @@ #include #include -namespace mlir::flux { +namespace mlir::qco { /** - * @brief Builder API for constructing quantum programs in the Flux dialect + * @brief Builder API for constructing quantum programs in the QCO dialect * * @details - * The FluxProgramBuilder provides a type-safe interface for constructing + * The QCOProgramBuilder provides a type-safe interface for constructing * quantum circuits using value semantics. Operations consume input qubit * SSA values and produce new output values, following the functional * programming paradigm. @@ -46,7 +46,7 @@ namespace mlir::flux { * * @par Example Usage: * ```c++ - * FluxProgramBuilder builder(context); + * QCOProgramBuilder builder(context); * builder.initialize(); * * auto q0 = builder.staticQubit(0); @@ -59,13 +59,13 @@ namespace mlir::flux { * auto module = builder.finalize(); * ``` */ -class FluxProgramBuilder final : public OpBuilder { +class QCOProgramBuilder final : public OpBuilder { public: /** - * @brief Construct a new FluxProgramBuilder + * @brief Construct a new QCOProgramBuilder * @param context The MLIR context to use for building operations */ - explicit FluxProgramBuilder(MLIRContext* context); + explicit QCOProgramBuilder(MLIRContext* context); //===--------------------------------------------------------------------===// // Initialization @@ -93,7 +93,7 @@ class FluxProgramBuilder final : public OpBuilder { * auto q = builder.allocQubit(); * ``` * ```mlir - * %q = flux.alloc : !flux.qubit + * %q = qco.alloc : !qco.qubit * ``` */ Value allocQubit(); @@ -108,7 +108,7 @@ class FluxProgramBuilder final : public OpBuilder { * auto q0 = builder.staticQubit(0); * ``` * ```mlir - * %q0 = flux.static 0 : !flux.qubit + * %q0 = qco.static 0 : !qco.qubit * ``` */ Value staticQubit(int64_t index); @@ -124,9 +124,9 @@ class FluxProgramBuilder final : public OpBuilder { * auto q = builder.allocQubitRegister(3, "q"); * ``` * ```mlir - * %q0 = flux.alloc("q", 3, 0) : !flux.qubit - * %q1 = flux.alloc("q", 3, 1) : !flux.qubit - * %q2 = flux.alloc("q", 3, 2) : !flux.qubit + * %q0 = qco.alloc("q", 3, 0) : !qco.qubit + * %q1 = qco.alloc("q", 3, 1) : !qco.qubit + * %q2 = qco.alloc("q", 3, 2) : !qco.qubit * ``` */ llvm::SmallVector allocQubitRegister(int64_t size, @@ -205,7 +205,7 @@ class FluxProgramBuilder final : public OpBuilder { * auto [q_out, result] = builder.measure(q); * ``` * ```mlir - * %q_out, %result = flux.measure %q : !flux.qubit + * %q_out, %result = qco.measure %q : !qco.qubit * ``` */ std::pair measure(Value qubit); @@ -222,7 +222,7 @@ class FluxProgramBuilder final : public OpBuilder { * q0 = builder.measure(q0, c[0]); * ``` * ```mlir - * %q0_out, %r0 = flux.measure("c", 3, 0) %q0 : !flux.qubit + * %q0_out, %r0 = qco.measure("c", 3, 0) %q0 : !qco.qubit * ``` */ Value measure(Value qubit, const Bit& bit); @@ -242,7 +242,7 @@ class FluxProgramBuilder final : public OpBuilder { * q = builder.reset(q); * ``` * ```mlir - * %q_out = flux.reset %q : !flux.qubit -> !flux.qubit + * %q_out = qco.reset %q : !qco.qubit -> !qco.qubit * ``` */ Value reset(Value qubit); @@ -264,7 +264,7 @@ class FluxProgramBuilder final : public OpBuilder { * builder.OP_NAME(PARAM); \ * ``` \ * ```mlir \ - * flux.OP_NAME(%PARAM) \ + * qco.OP_NAME(%PARAM) \ * ``` \ */ \ void OP_NAME(const std::variant&(PARAM)); \ @@ -280,10 +280,10 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.c##OP_NAME(PARAM, q_in); \ * ``` \ * ```mlir \ - * %q_out = flux.ctrl(%q_in) { \ - * flux.OP_NAME(%PARAM) \ - * flux.yield \ - * } : ({!flux.qubit}) -> ({!flux.qubit}) \ + * %q_out = qco.ctrl(%q_in) { \ + * qco.OP_NAME(%PARAM) \ + * qco.yield \ + * } : ({!qco.qubit}) -> ({!qco.qubit}) \ * ``` \ */ \ Value c##OP_NAME(const std::variant&(PARAM), Value control); \ @@ -299,10 +299,10 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.mc##OP_NAME(PARAM, {q0_in, q1_in}); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.ctrl(%q0_in, %q1_in) { \ - * flux.OP_NAME(%PARAM) \ - * flux.yield \ - * } : ({!flux.qubit, !flux.qubit}) -> ({!flux.qubit, !flux.qubit}) \ + * %q0_out, %q1_out = qco.ctrl(%q0_in, %q1_in) { \ + * qco.OP_NAME(%PARAM) \ + * qco.yield \ + * } : ({!qco.qubit, !qco.qubit}) -> ({!qco.qubit, !qco.qubit}) \ * ``` \ */ \ ValueRange mc##OP_NAME(const std::variant&(PARAM), \ @@ -330,7 +330,7 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.OP_NAME(q_in); \ * ``` \ * ```mlir \ - * %q_out = flux.OP_NAME %q_in : !flux.qubit -> !flux.qubit \ + * %q_out = qco.OP_NAME %q_in : !qco.qubit -> !qco.qubit \ * ``` \ */ \ Value OP_NAME(Value qubit); \ @@ -350,10 +350,10 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.c##OP_NAME(q0_in, q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ - * %q1_res = flux.OP_NAME %q1_in : !flux.qubit -> !flux.qubit \ - * flux.yield %q1_res \ - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME %q1_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair c##OP_NAME(Value control, Value target); \ @@ -373,11 +373,11 @@ class FluxProgramBuilder final : public OpBuilder { * {controls_out, target_out} = builder.mc##OP_NAME({q0_in, q1_in}, q2_in); \ * ``` \ * ```mlir \ - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ - * %q2_res = flux.OP_NAME %q2_in : !flux.qubit -> !flux.qubit \ - * flux.yield %q2_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ - * !flux.qubit}, {!flux.qubit}) \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME %q2_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair mc##OP_NAME(ValueRange controls, Value target); @@ -415,7 +415,7 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.OP_NAME(PARAM, q_in); \ * ``` \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM) %q_in : !qco.qubit -> !qco.qubit \ * ``` \ */ \ Value OP_NAME(const std::variant&(PARAM), Value qubit); \ @@ -436,10 +436,10 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.c##OP_NAME(PARAM, q0_in, q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ - * %q1_res = flux.OP_NAME(%PARAM) %q1_in : !flux.qubit -> !flux.qubit \ - * flux.yield %q1_res \ - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME(%PARAM) %q1_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair c##OP_NAME( \ @@ -462,11 +462,11 @@ class FluxProgramBuilder final : public OpBuilder { * q2_in); \ * ``` \ * ```mlir \ - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ - * %q2_res = flux.OP_NAME(%PARAM) %q2_in : !flux.qubit -> !flux.qubit \ - * flux.yield %q2_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ - * !flux.qubit}, {!flux.qubit}) \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME(%PARAM) %q2_in : !qco.qubit -> !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair mc##OP_NAME( \ @@ -500,8 +500,8 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.OP_NAME(PARAM1, PARAM2, q_in); \ * ``` \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM1, %PARAM2) %q_in : !flux.qubit -> \ - * !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2) %q_in : !qco.qubit -> \ + * !qco.qubit \ * ``` \ */ \ Value OP_NAME(const std::variant&(PARAM1), \ @@ -524,11 +524,11 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.c##OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ - * %q1_res = flux.OP_NAME(%PARAM1, %PARAM2) %q1_in : !flux.qubit -> \ - * !flux.qubit \ - * flux.yield %q1_res \ - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME(%PARAM1, %PARAM2) %q1_in : !qco.qubit -> \ + * !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair c##OP_NAME( \ @@ -554,12 +554,12 @@ class FluxProgramBuilder final : public OpBuilder { * q1_in}, q2_in); \ * ``` \ * ```mlir \ - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ - * %q2_res = flux.OP_NAME(%PARAM1, %PARAM2) %q2_in : !flux.qubit -> \ - * !flux.qubit \ - * flux.yield %q2_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ - * !flux.qubit}, {!flux.qubit}) \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME(%PARAM1, %PARAM2) %q2_in : !qco.qubit -> \ + * !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair mc##OP_NAME( \ @@ -594,8 +594,8 @@ class FluxProgramBuilder final : public OpBuilder { * q_out = builder.OP_NAME(PARAM1, PARAM2, PARAM3, q_in); \ * ``` \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !flux.qubit -> \ - * !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !qco.qubit -> \ + * !qco.qubit \ * ``` \ */ \ Value OP_NAME(const std::variant&(PARAM1), \ @@ -621,11 +621,11 @@ class FluxProgramBuilder final : public OpBuilder { * q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.ctrl(%q0_in) %q1_in { \ - * %q1_res = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1_in : !flux.qubit \ - * -> !flux.qubit \ - * flux.yield %q1_res \ - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) \ + * %q0_out, %q1_out = qco.ctrl(%q0_in) %q1_in { \ + * %q1_res = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1_in : !qco.qubit \ + * -> !qco.qubit \ + * qco.yield %q1_res \ + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair c##OP_NAME( \ @@ -653,12 +653,12 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_in, q1_in}, q2_in); \ * ``` \ * ```mlir \ - * %controls_out, %target_out = flux.ctrl(%q0_in, %q1_in) %q2_in { \ - * %q2_res = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2_in : !flux.qubit \ - * -> !flux.qubit \ - * flux.yield %q2_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit}) -> ({!flux.qubit, \ - * !flux.qubit}, {!flux.qubit}) \ + * %controls_out, %target_out = qco.ctrl(%q0_in, %q1_in) %q2_in { \ + * %q2_res = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2_in : !qco.qubit \ + * -> !qco.qubit \ + * qco.yield %q2_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit}) -> ({!qco.qubit, \ + * !qco.qubit}, {!qco.qubit}) \ * ``` \ */ \ std::pair mc##OP_NAME( \ @@ -690,8 +690,8 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.OP_NAME(q0_in, q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME %q0_in, %q1_in : !flux.qubit, !flux.qubit \ - * -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ * ``` \ */ \ std::pair OP_NAME(Value qubit0, Value qubit1); \ @@ -712,12 +712,12 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, {q1_out, q2_out}} = builder.c##OP_NAME(q0_in, q1_in, q2_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { \ - * %q1_res, %q2_res = flux.OP_NAME %q1_in, %q2_in : !flux.qubit, \ - * !flux.qubit -> !flux.qubit, !flux.qubit \ - * flux.yield %q1_res, %q2_res \ - * } : ({!flux.qubit}, {!flux.qubit, !flux.qubit}) -> ({!flux.qubit}, \ - * {!flux.qubit, !flux.qubit}) \ + * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = qco.OP_NAME %q1_in, %q2_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q1_res, %q2_res \ + * } : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> ({!qco.qubit}, \ + * {!qco.qubit, !qco.qubit}) \ * ``` \ */ \ std::pair> c##OP_NAME( \ @@ -740,13 +740,13 @@ class FluxProgramBuilder final : public OpBuilder { * q2_in, q3_in); \ * ``` \ * ```mlir \ - * %controls_out, %q1_out, %q2_out = flux.ctrl(%q0_in, %q1_in) %q2_in, \ + * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ * %q3_in { \ - * %q2_res, %q3_res = flux.OP_NAME %q2_in, %q3_in : !flux.qubit, \ - * !flux.qubit -> !flux.qubit, !flux.qubit \ - * flux.yield %q2_res, %q3_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ - * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * %q2_res, %q3_res = qco.OP_NAME %q2_in, %q3_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q2_res, %q3_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) \ * ``` \ */ \ std::pair> mc##OP_NAME( \ @@ -779,9 +779,9 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.OP_NAME(PARAM, q0_in, q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME(%PARAM) %q0_in, %q1_in : !flux.qubit, \ - * !flux.qubit \ - * -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ + * !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ * ``` \ */ \ std::pair OP_NAME(const std::variant&(PARAM), \ @@ -805,12 +805,12 @@ class FluxProgramBuilder final : public OpBuilder { * q2_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { \ - * %q1_res, %q2_res = flux.OP_NAME(%PARAM) %q1_in, %q2_in : !flux.qubit, \ - * !flux.qubit -> !flux.qubit, !flux.qubit \ - * flux.yield %q1_res, %q2_res \ - * } : ({!flux.qubit}, {!flux.qubit, !flux.qubit}) -> ({!flux.qubit}, \ - * {!flux.qubit, !flux.qubit}) \ + * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = qco.OP_NAME(%PARAM) %q1_in, %q2_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q1_res, %q2_res \ + * } : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> ({!qco.qubit}, \ + * {!qco.qubit, !qco.qubit}) \ * ``` \ */ \ std::pair> c##OP_NAME( \ @@ -835,13 +835,13 @@ class FluxProgramBuilder final : public OpBuilder { * q1_in}, q2_in, q3_in); \ * ``` \ * ```mlir \ - * %controls_out, %q1_out, %q2_out = flux.ctrl(%q0_in, %q1_in) %q2_in, \ + * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ * %q3_in { \ - * %q2_res, %q3_res = flux.OP_NAME(%PARAM) %q2_in, %q3_in : !flux.qubit, \ - * !flux.qubit -> !flux.qubit, !flux.qubit \ - * flux.yield %q2_res, %q3_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ - * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * %q2_res, %q3_res = qco.OP_NAME(%PARAM) %q2_in, %q3_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q2_res, %q3_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) \ * ``` \ */ \ std::pair> mc##OP_NAME( \ @@ -876,8 +876,8 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_out, q1_out} = builder.OP_NAME(PARAM1, PARAM2, q0_in, q1_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ - * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ * ``` \ */ \ std::pair OP_NAME(const std::variant&(PARAM1), \ @@ -903,12 +903,12 @@ class FluxProgramBuilder final : public OpBuilder { * q1_in, q2_in); \ * ``` \ * ```mlir \ - * %q0_out, %q1_out, %q2_out = flux.ctrl(%q0_in) %q1_in, %q2_in { \ - * %q1_res, %q2_res = flux.OP_NAME(%PARAM1, %PARAM2) %q1_in, %q2_in : \ - * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ - * flux.yield %q1_res, %q2_res \ - * } : ({!flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ - * ({!flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * %q0_out, %q1_out, %q2_out = qco.ctrl(%q0_in) %q1_in, %q2_in { \ + * %q1_res, %q2_res = qco.OP_NAME(%PARAM1, %PARAM2) %q1_in, %q2_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q1_res, %q2_res \ + * } : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit}, {!qco.qubit, !qco.qubit}) \ * ``` \ */ \ std::pair> c##OP_NAME( \ @@ -935,13 +935,13 @@ class FluxProgramBuilder final : public OpBuilder { * {q0_in, q1_in}, q2_in, q3_in); \ * ``` \ * ```mlir \ - * %controls_out, %q1_out, %q2_out = flux.ctrl(%q0_in, %q1_in) %q2_in, \ + * %controls_out, %q1_out, %q2_out = qco.ctrl(%q0_in, %q1_in) %q2_in, \ * %q3_in { \ - * %q2_res, %q3_res = flux.OP_NAME(%PARAM1, %PARAM2) %q2_in, %q3_in : \ - * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ - * flux.yield %q2_res, %q3_res \ - * } : ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) -> \ - * ({!flux.qubit, !flux.qubit}, {!flux.qubit, !flux.qubit}) \ + * %q2_res, %q3_res = qco.OP_NAME(%PARAM1, %PARAM2) %q2_in, %q3_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ + * qco.yield %q2_res, %q3_res \ + * } : ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) -> \ + * ({!qco.qubit, !qco.qubit}, {!qco.qubit, !qco.qubit}) \ * ``` \ */ \ std::pair> mc##OP_NAME( \ @@ -967,8 +967,8 @@ class FluxProgramBuilder final : public OpBuilder { * builder.barrier({q0, q1}); * ``` * ```mlir - * flux.barrier %q0, %q1 : !flux.qubit, !flux.qubit -> !flux.qubit, - * !flux.qubit + * qco.barrier %q0, %q1 : !qco.qubit, !qco.qubit -> !qco.qubit, + * !qco.qubit * ``` */ ValueRange barrier(ValueRange qubits); @@ -993,10 +993,10 @@ class FluxProgramBuilder final : public OpBuilder { * }); * ``` * ```mlir - * %controls_out, %targets_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * %controls_out, %targets_out = qco.ctrl(%q0_in) %q1_in { + * %q1_res = qco.x %q1_in : !qco.qubit -> !qco.qubit + * qco.yield %q1_res + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) * ``` */ std::pair @@ -1022,10 +1022,10 @@ class FluxProgramBuilder final : public OpBuilder { * builder.dealloc(q); * ``` * ```mlir - * flux.dealloc %q : !flux.qubit + * qco.dealloc %q : !qco.qubit * ``` */ - FluxProgramBuilder& dealloc(Value qubit); + QCOProgramBuilder& dealloc(Value qubit); //===--------------------------------------------------------------------===// // Finalization @@ -1076,4 +1076,4 @@ class FluxProgramBuilder final : public OpBuilder { /// is removed and the new output is added. llvm::DenseSet validQubits; }; -} // namespace mlir::flux +} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/Quartz/CMakeLists.txt b/mlir/include/mlir/Dialect/QCO/CMakeLists.txt similarity index 100% rename from mlir/include/mlir/Dialect/Quartz/CMakeLists.txt rename to mlir/include/mlir/Dialect/QCO/CMakeLists.txt diff --git a/mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt new file mode 100644 index 0000000000..bddaba3d7a --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_mlir_dialect(QCOOps qco) +add_mlir_interface(QCOInterfaces) +add_mlir_doc(QCOOps MLIRQCODialect Dialects/ -gen-dialect-doc) +add_mlir_doc(QCOInterfaces MLIRQCOInterfaces Dialects/ -gen-op-interface-docs -dialect=qco) diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h similarity index 93% rename from mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h rename to mlir/include/mlir/Dialect/QCO/IR/QCODialect.h index 9d9db381e2..97d2f76543 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxDialect.h +++ b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h @@ -30,26 +30,26 @@ #include #include -#define DIALECT_NAME_FLUX "flux" +#define DIALECT_NAME_QCO "qco" //===----------------------------------------------------------------------===// // Dialect //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Flux/IR/FluxOpsDialect.h.inc" +#include "mlir/Dialect/QCO/IR/QCOOpsDialect.h.inc" //===----------------------------------------------------------------------===// // Types //===----------------------------------------------------------------------===// #define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOpsTypes.h.inc" +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.h.inc" //===----------------------------------------------------------------------===// // Interfaces //===----------------------------------------------------------------------===// -namespace mlir::flux { +namespace mlir::qco { /** * @brief Trait for operations with a fixed number of target qubits and @@ -148,13 +148,13 @@ template class TargetAndParameterArityTrait { }; }; -} // namespace mlir::flux +} // namespace mlir::qco -#include "mlir/Dialect/Flux/IR/FluxInterfaces.h.inc" // IWYU pragma: export +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h.inc" // IWYU pragma: export //===----------------------------------------------------------------------===// // Operations //===----------------------------------------------------------------------===// #define GET_OP_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOps.h.inc" // IWYU pragma: export +#include "mlir/Dialect/QCO/IR/QCOOps.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td b/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td similarity index 95% rename from mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td rename to mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td index 2559ca41d6..3079a40003 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxInterfaces.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td @@ -6,8 +6,8 @@ // // Licensed under the MIT License -#ifndef FLUX_INTERFACES -#define FLUX_INTERFACES +#ifndef QCO_INTERFACES +#define QCO_INTERFACES include "mlir/IR/OpBase.td" @@ -18,7 +18,7 @@ include "mlir/IR/OpBase.td" def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { let description = [{ This interface provides a unified API for all operations that apply or - produce a unitary transformation in the Flux dialect. This includes base + produce a unitary transformation in the QCO dialect. This includes base gates, user-defined gates, modifier operations (control, inverse, power), and sequences. @@ -26,7 +26,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { across all unitary operations with value semantics. }]; - let cppNamespace = "::mlir::flux"; + let cppNamespace = "::mlir::qco"; let methods = [ // Qubit accessors @@ -126,4 +126,4 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { ]; } -#endif // FLUX_INTERFACES +#endif // QCO_INTERFACES diff --git a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td similarity index 82% rename from mlir/include/mlir/Dialect/Flux/IR/FluxOps.td rename to mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 9741ec0d38..1454d91cd3 100644 --- a/mlir/include/mlir/Dialect/Flux/IR/FluxOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -6,10 +6,10 @@ // // Licensed under the MIT License -#ifndef FluxOPS -#define FluxOPS +#ifndef QCOOPS +#define QCOOPS -include "mlir/Dialect/Flux/IR/FluxInterfaces.td" +include "mlir/Dialect/QCO/IR/QCOInterfaces.td" include "mlir/IR/BuiltinTypeInterfaces.td" include "mlir/IR/DialectBase.td" include "mlir/IR/EnumAttr.td" @@ -21,13 +21,13 @@ include "mlir/Interfaces/SideEffectInterfaces.td" // Dialect //===----------------------------------------------------------------------===// -def FluxDialect : Dialect { - let name = "flux"; +def QCODialect : Dialect { + let name = "qco"; - let summary = "The Flux (value semantics) dialect for quantum computing."; + let summary = "The QCO (value semantics) dialect for quantum computing."; let description = [{ - The Flux dialect uses **value semantics** where quantum operations + The QCO dialect uses **value semantics** where quantum operations consume input qubits and produce new output values, following the functional programming and SSA paradigm. This model enables: @@ -36,45 +36,45 @@ def FluxDialect : Dialect { - Advanced transformation passes - Explicit dependency tracking - The name "Flux" captures the flowing, transformative nature of + The name "QCO" captures the flowing, transformative nature of value-based representations—quantum states flow through operations, each transformation producing new values like a river flowing through a landscape. Example: ```mlir - %q_out = flux.h %q_in // Consumes %q_in, produces %q_out - %q0_out, %q1_out = flux.swap %q0_in, %q1_in // Consumes inputs, produces outputs + %q_out = qco.h %q_in // Consumes %q_in, produces %q_out + %q0_out, %q1_out = qco.swap %q0_in, %q1_in // Consumes inputs, produces outputs ``` }]; - let cppNamespace = "::mlir::flux"; + let cppNamespace = "::mlir::qco"; let useDefaultTypePrinterParser = 1; } //===----------------------------------------------------------------------===// -// Flux Type Definitions +// QCO Type Definitions //===----------------------------------------------------------------------===// -class FluxType traits = []> - : TypeDef { +class QCOType traits = []> + : TypeDef { let mnemonic = typeMnemonic; } -def QubitType : FluxType<"Qubit", "qubit"> { - let summary = "Flux qubit value type"; +def QubitType : QCOType<"Qubit", "qubit"> { + let summary = "QCO qubit value type"; let description = [{ - The `!flux.qubit` type represents an SSA value holding a quantum bit - in the Flux dialect. Operations using this type consume input qubits + The `!qco.qubit` type represents an SSA value holding a quantum bit + in the QCO dialect. Operations using this type consume input qubits and produce new output qubits following value semantics and the SSA paradigm, enabling powerful dataflow analysis and optimization. Example: ```mlir - %q0 = flux.alloc : !flux.qubit - %q1 = flux.h %q0 : !flux.qubit -> !flux.qubit - %q2 = flux.x %q1 : !flux.qubit -> !flux.qubit + %q0 = qco.alloc : !qco.qubit + %q1 = qco.h %q0 : !qco.qubit -> !qco.qubit + %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit ``` }]; } @@ -83,14 +83,14 @@ def QubitType : FluxType<"Qubit", "qubit"> { // Base Operation Classes //===----------------------------------------------------------------------===// -class FluxOp traits = []> : - Op; +class QCOOp traits = []> : + Op; //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// -def AllocOp : FluxOp<"alloc", [MemoryEffects<[MemAlloc]>]> { +def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let summary = "Allocate a qubit dynamically"; let description = [{ Allocates a new qubit dynamically and returns an SSA value representing it. @@ -103,14 +103,14 @@ def AllocOp : FluxOp<"alloc", [MemoryEffects<[MemAlloc]>]> { Example (single qubit): ```mlir - %q = flux.alloc : !flux.qubit + %q = qco.alloc : !qco.qubit ``` Example (qubits in a register): ```mlir - %q0 = flux.alloc("q", 3, 0) : !flux.qubit - %q1 = flux.alloc("q", 3, 1) : !flux.qubit - %q2 = flux.alloc("q", 3, 2) : !flux.qubit + %q0 = qco.alloc("q", 3, 0) : !qco.qubit + %q1 = qco.alloc("q", 3, 1) : !qco.qubit + %q2 = qco.alloc("q", 3, 2) : !qco.qubit ``` }]; @@ -138,14 +138,14 @@ def AllocOp : FluxOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let hasVerifier = 1; } -def DeallocOp : FluxOp<"dealloc", [MemoryEffects<[MemFree]>]> { +def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a qubit"; let description = [{ Deallocates a qubit, releasing its resources. Example: ```mlir - flux.dealloc %q : !flux.qubit + qco.dealloc %q : !qco.qubit ``` }]; @@ -154,16 +154,16 @@ def DeallocOp : FluxOp<"dealloc", [MemoryEffects<[MemFree]>]> { let hasCanonicalizer = 1; } -def StaticOp : FluxOp<"static", [Pure]> { +def StaticOp : QCOOp<"static", [Pure]> { let summary = "Retrieve a static qubit by index"; let description = [{ - The `flux.static` operation produces an SSA value representing a qubit + The `qco.static` operation produces an SSA value representing a qubit identified by a static index. This is useful for referring to fixed qubits in a quantum program or to hardware-mapped qubits. Example: ```mlir - %q = flux.static 0 : !flux.qubit + %q = qco.static 0 : !qco.qubit ``` }]; @@ -176,7 +176,7 @@ def StaticOp : FluxOp<"static", [Pure]> { // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp : FluxOp<"measure"> { +def MeasureOp : QCOOp<"measure"> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -190,12 +190,12 @@ def MeasureOp : FluxOp<"measure"> { Example (simple measurement): ```mlir - %q_out, %result = flux.measure %q_in : !flux.qubit + %q_out, %result = qco.measure %q_in : !qco.qubit ``` Example (measurement with output recording): ```mlir - %q_out, %result = flux.measure("c", 2, 0) %q_in : !flux.qubit + %q_out, %result = qco.measure("c", 2, 0) %q_in : !qco.qubit ``` }]; @@ -219,7 +219,7 @@ def MeasureOp : FluxOp<"measure"> { let hasVerifier = 1; } -def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { +def ResetOp : QCOOp<"reset", [Idempotent, SameOperandsAndResultType]> { let summary = "Reset a qubit to |0⟩ state"; let description = [{ Resets a qubit to the |0⟩ state, regardless of its current state, @@ -227,7 +227,7 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { Example: ```mlir - %q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.reset %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -243,7 +243,7 @@ def ResetOp : FluxOp<"reset", [Idempotent, SameOperandsAndResultType]> { class TargetAndParameterArityTrait : ParamNativeOpTrait<"TargetAndParameterArityTrait", !strconcat(!cast(T), ",", !cast(P))> { - let cppNamespace = "::mlir::flux"; + let cppNamespace = "::mlir::qco"; } def ZeroTargetOneParameter : TargetAndParameterArityTrait<0, 1>; @@ -259,14 +259,14 @@ def TwoTargetTwoParameter : TargetAndParameterArityTrait<2, 2>; // Unitary Operations //===----------------------------------------------------------------------===// -def GPhaseOp : FluxOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { +def GPhaseOp : QCOOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParameter, MemoryEffects<[MemWrite]>]> { let summary = "Apply a global phase to the state"; let description = [{ Applies a global phase to the state. Example: ```mlir - flux.gphase(%theta) : () + qco.gphase(%theta) : () ``` }]; @@ -284,14 +284,14 @@ def GPhaseOp : FluxOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParam let hasCanonicalizer = 1; } -def IdOp : FluxOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Id gate to a qubit"; let description = [{ Applies an Id gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.id %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.id %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -306,14 +306,14 @@ def IdOp : FluxOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.x %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.x %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -328,14 +328,14 @@ def XOp : FluxOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def YOp : FluxOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Y gate to a qubit"; let description = [{ Applies a Y gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.y %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.y %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -350,14 +350,14 @@ def YOp : FluxOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def ZOp : FluxOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Z gate to a qubit"; let description = [{ Applies a Z gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.z %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.z %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -372,14 +372,14 @@ def ZOp : FluxOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def HOp : FluxOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a H gate to a qubit"; let description = [{ Applies a H gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.h %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.h %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -394,14 +394,14 @@ def HOp : FluxOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SOp : FluxOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an S gate to a qubit"; let description = [{ Applies an S gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.s %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.s %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -416,14 +416,14 @@ def SOp : FluxOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SdgOp : FluxOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Sdg gate to a qubit"; let description = [{ Applies an Sdg gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.sdg %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.sdg %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -438,14 +438,14 @@ def SdgOp : FluxOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let hasCanonicalizer = 1; } -def TOp : FluxOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a T gate to a qubit"; let description = [{ Applies a T gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.t %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.t %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -460,14 +460,14 @@ def TOp : FluxOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def TdgOp : FluxOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Tdg gate to a qubit"; let description = [{ Applies a Tdg gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.tdg %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.tdg %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -482,14 +482,14 @@ def TdgOp : FluxOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let hasCanonicalizer = 1; } -def SXOp : FluxOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an SX gate to a qubit"; let description = [{ Applies an SX gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.sx %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.sx %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -504,14 +504,14 @@ def SXOp : FluxOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SXdgOp : FluxOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an SXdg gate to a qubit"; let description = [{ Applies an SXdg gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.sxdg %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.sxdg %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -526,14 +526,14 @@ def SXdgOp : FluxOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter let hasCanonicalizer = 1; } -def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.rx(%theta) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.rx(%theta) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -553,14 +553,14 @@ def RXOp : FluxOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def RYOp : FluxOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RY gate to a qubit"; let description = [{ Applies an RY gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.ry(%theta) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.ry(%theta) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -580,14 +580,14 @@ def RYOp : FluxOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZOp : FluxOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RZ gate to a qubit"; let description = [{ Applies an RZ gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.rz(%theta) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.rz(%theta) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -607,14 +607,14 @@ def RZOp : FluxOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def POp : FluxOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply a P gate to a qubit"; let description = [{ Applies a P gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.p(%theta) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.p(%theta) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -634,14 +634,14 @@ def POp : FluxOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def ROp : FluxOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply an R gate to a qubit"; let description = [{ Applies an R gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.r(%theta, %phi) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.r(%theta, %phi) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -662,14 +662,14 @@ def ROp : FluxOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } -def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.u2(%phi, %lambda) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.u2(%phi, %lambda) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -690,14 +690,14 @@ def U2Op : FluxOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } -def UOp : FluxOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { +def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let summary = "Apply a U gate to a qubit"; let description = [{ Applies a U gate to a qubit and returns the transformed qubit. Example: ```mlir - %q_out = flux.u(%theta, %phi, %lambda) %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.u(%theta, %phi, %lambda) %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -719,14 +719,14 @@ def UOp : FluxOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let hasCanonicalizer = 1; } -def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.swap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.swap %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -742,14 +742,14 @@ def SWAPOp : FluxOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter let hasCanonicalizer = 1; } -def iSWAPOp : FluxOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a iSWAP gate to two qubits"; let description = [{ Applies a iSWAP gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.iswap %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.iswap %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -763,14 +763,14 @@ def iSWAPOp : FluxOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParamet }]; } -def DCXOp : FluxOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a DCX gate to two qubits"; let description = [{ Applies a DCX gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.dcx %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.dcx %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -784,14 +784,14 @@ def DCXOp : FluxOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> }]; } -def ECROp : FluxOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply an ECR gate to two qubits"; let description = [{ Applies an ECR gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.ecr %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.ecr %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -807,14 +807,14 @@ def ECROp : FluxOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> let hasCanonicalizer = 1; } -def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RXX gate to two qubits"; let description = [{ Applies an RXX gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.rxx(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.rxx(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -835,14 +835,14 @@ def RXXOp : FluxOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let hasCanonicalizer = 1; } -def RYYOp : FluxOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RYY gate to two qubits"; let description = [{ Applies an RYY gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.ryy(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.ryy(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -863,14 +863,14 @@ def RYYOp : FluxOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let hasCanonicalizer = 1; } -def RZXOp : FluxOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RZX gate to two qubits"; let description = [{ Applies an RZX gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.rzx(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.rzx(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -891,14 +891,14 @@ def RZXOp : FluxOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let hasCanonicalizer = 1; } -def RZZOp : FluxOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RZZ gate to two qubits"; let description = [{ Applies an RZZ gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.rzz(%theta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.rzz(%theta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -919,14 +919,14 @@ def RZZOp : FluxOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> let hasCanonicalizer = 1; } -def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ Applies an XX+YY gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.xx_plus_yy(%theta, %beta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.xx_plus_yy(%theta, %beta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -948,14 +948,14 @@ def XXPlusYYOp : FluxOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwo let hasCanonicalizer = 1; } -def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX-YY gate to two qubits"; let description = [{ Applies an XX-YY gate to two qubits and returns the transformed qubits. Example: ```mlir - %q0_out, %q1_out = flux.xx_minus_yy(%theta, %beta) %q0_in, %q1_in : !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit + %q0_out, %q1_out = qco.xx_minus_yy(%theta, %beta) %q0_in, %q1_in : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit ``` }]; @@ -977,14 +977,14 @@ def XXMinusYYOp : FluxOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetT let hasCanonicalizer = 1; } -def BarrierOp : FluxOp<"barrier", traits = [UnitaryOpInterface]> { +def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { let summary = "Apply a barrier gate to a set of qubits"; let description = [{ Applies a barrier gate to a set of qubits and returns the transformed qubits. Example: ```mlir - %q_out = flux.barrier %q_in : !flux.qubit -> !flux.qubit + %q_out = qco.barrier %q_in : !qco.qubit -> !qco.qubit ``` }]; @@ -1024,7 +1024,7 @@ def BarrierOp : FluxOp<"barrier", traits = [UnitaryOpInterface]> { // Modifiers //===----------------------------------------------------------------------===// -def YieldOp : FluxOp<"yield", traits = [Terminator]> { +def YieldOp : QCOOp<"yield", traits = [Terminator]> { let summary = "Yield from a modifier region"; let description = [{ Terminates a modifier region, yielding control back to the enclosing operation. @@ -1034,7 +1034,7 @@ def YieldOp : FluxOp<"yield", traits = [Terminator]> { let assemblyFormat = "$targets attr-dict"; } -def CtrlOp : FluxOp<"ctrl", traits = +def CtrlOp : QCOOp<"ctrl", traits = [ UnitaryOpInterface, AttrSizedOperandSegments, @@ -1055,10 +1055,10 @@ def CtrlOp : FluxOp<"ctrl", traits = Example: ```mlir - %ctrl_q_out, %tgt_q_out = flux.ctrl(%ctrl_q_in) %tgt_q_in { - %tgt_q_out = flux.h %tgt_q_in : !flux.qubit -> !flux.qubit - flux.yield %tgt_q_out : !flux.qubit - } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + %ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in { + %tgt_q_out = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit + qco.yield %tgt_q_out : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) ``` }]; @@ -1108,4 +1108,4 @@ def CtrlOp : FluxOp<"ctrl", traits = let hasVerifier = 1; } -#endif // FluxOPS +#endif // QCOOPS diff --git a/mlir/include/mlir/Dialect/Flux/FluxUtils.h b/mlir/include/mlir/Dialect/QCO/QCOUtils.h similarity index 99% rename from mlir/include/mlir/Dialect/Flux/FluxUtils.h rename to mlir/include/mlir/Dialect/QCO/QCOUtils.h index 15e7b45654..f03b2818b0 100644 --- a/mlir/include/mlir/Dialect/Flux/FluxUtils.h +++ b/mlir/include/mlir/Dialect/QCO/QCOUtils.h @@ -14,7 +14,7 @@ #include #include -namespace mlir::flux { +namespace mlir::qco { /** * @brief Remove a pair of inverse one-target, zero-parameter operations @@ -231,4 +231,4 @@ removeTrivialTwoTargetOneParameter(OpType op, mlir::PatternRewriter& rewriter) { return success(); } -} // namespace mlir::flux +} // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h index d6ea20e6d3..753d359a6e 100644 --- a/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h @@ -39,7 +39,7 @@ namespace mlir::qir { * * @details * The QIRProgramBuilder provides a type-safe interface for constructing - * quantum programs in QIR format. Like Quartz, QIR uses reference semantics + * quantum programs in QIR format. Like QC, QIR uses reference semantics * where operations modify qubits in place, but QIR programs require specific * boilerplate structure including proper block organization and metadata * attributes. diff --git a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt deleted file mode 100644 index e17fbf093f..0000000000 --- a/mlir/include/mlir/Dialect/Quartz/IR/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -add_mlir_dialect(QuartzOps quartz) -add_mlir_interface(QuartzInterfaces) -add_mlir_doc(QuartzOps MLIRQuartzDialect Dialects/ -gen-dialect-doc) -add_mlir_doc(QuartzInterfaces MLIRQuartzInterfaces Dialects/ -gen-op-interface-docs -dialect=quartz) diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index cefa0e7d43..1e4483754d 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -17,9 +17,9 @@ add_mlir_library( MLIRPass MLIRTransforms MLIRTransformUtils - QuartzToFlux - FluxToQuartz - QuartzToQIR + QCToQCO + QCOToQC + QCToQIR MQT::MLIRSupport MQT::ProjectOptions) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 2ddd3c4071..d595ac7ead 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -10,9 +10,9 @@ #include "mlir/Compiler/CompilerPipeline.h" -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" -#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" +#include "mlir/Conversion/QCOToQC/QCOToQC.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Conversion/QCToQIR/QCToQIR.h" #include "mlir/Support/PrettyPrinting.h" #include @@ -97,15 +97,15 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, configurePassManager(pm); // Determine total number of stages for progress indication - // 1. Quartz import - // 2. Quartz canonicalization - // 3. Quartz-to-Flux conversion - // 4. Flux canonicalization + // 1. QC import + // 2. QC canonicalization + // 3. QC-to-QCO conversion + // 4. QCO canonicalization // 5. Optimization passes - // 6. Flux canonicalization - // 7. Flux-to-Quartz conversion - // 8. Quartz canonicalization - // 9. Quartz-to-QIR conversion (optional) + // 6. QCO canonicalization + // 7. QCO-to-QC conversion + // 8. QC canonicalization + // 9. QC-to-QIR conversion (optional) // 10. QIR canonicalization (optional) auto totalStages = 8; if (config_.convertToQIR) { @@ -113,15 +113,15 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } auto currentStage = 0; - // Stage 1: Quartz import + // Stage 1: QC import if (record != nullptr && config_.recordIntermediates) { - record->afterQuartzImport = captureIR(module); + record->afterQCImport = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Quartz Import", ++currentStage, totalStages); + prettyPrintStage(module, "QC Import", ++currentStage, totalStages); } } - // Stage 2: Quartz canonicalization + // Stage 2: QC canonicalization addCleanupPasses(pm); if (pm.run(module).failed()) { return failure(); @@ -129,35 +129,35 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterInitialCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Initial Quartz Canonicalization", - ++currentStage, totalStages); + prettyPrintStage(module, "Initial QC Canonicalization", ++currentStage, + totalStages); } } pm.clear(); - // Stage 3: Quartz-to-Flux conversion - pm.addPass(createQuartzToFlux()); + // Stage 3: QC-to-QCO conversion + pm.addPass(createQCToQCO()); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { - record->afterFluxConversion = captureIR(module); + record->afterQCOConversion = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Quartz → Flux Conversion", ++currentStage, + prettyPrintStage(module, "QC → QCO Conversion", ++currentStage, totalStages); } } pm.clear(); - // Stage 4: Flux canonicalization + // Stage 4: QCO canonicalization addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { - record->afterFluxCanon = captureIR(module); + record->afterQCOCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Initial Flux Canonicalization", ++currentStage, + prettyPrintStage(module, "Initial QCO Canonicalization", ++currentStage, totalStages); } } @@ -178,7 +178,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } pm.clear(); - // Stage 6: Flux canonicalization + // Stage 6: QCO canonicalization addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); @@ -186,50 +186,50 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterOptimizationCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Final Flux Canonicalization", ++currentStage, + prettyPrintStage(module, "Final QCO Canonicalization", ++currentStage, totalStages); } } pm.clear(); - // Stage 7: Flux-to-Quartz conversion - pm.addPass(createFluxToQuartz()); + // Stage 7: QCO-to-QC conversion + pm.addPass(createQCOToQC()); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { - record->afterQuartzConversion = captureIR(module); + record->afterQCConversion = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Flux → Quartz Conversion", ++currentStage, + prettyPrintStage(module, "QCO → QC Conversion", ++currentStage, totalStages); } } pm.clear(); - // Stage 8: Quartz canonicalization + // Stage 8: QC canonicalization addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { - record->afterQuartzCanon = captureIR(module); + record->afterQCCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Final Quartz Canonicalization", ++currentStage, + prettyPrintStage(module, "Final QC Canonicalization", ++currentStage, totalStages); } } pm.clear(); - // Stage 9: Quartz-to-QIR conversion (optional) + // Stage 9: QC-to-QIR conversion (optional) if (config_.convertToQIR) { - pm.addPass(createQuartzToQIR()); + pm.addPass(createQCToQIR()); if (failed(pm.run(module))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { record->afterQIRConversion = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Quartz → QIR Conversion", ++currentStage, + prettyPrintStage(module, "QC → QIR Conversion", ++currentStage, totalStages); } } diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt index 87d8545c64..7f095140a1 100644 --- a/mlir/lib/Conversion/CMakeLists.txt +++ b/mlir/lib/Conversion/CMakeLists.txt @@ -10,6 +10,6 @@ add_subdirectory(MQTOptToMQTRef) add_subdirectory(MQTRefToMQTOpt) add_subdirectory(MQTRefToQIR) add_subdirectory(QIRToMQTRef) -add_subdirectory(FluxToQuartz) -add_subdirectory(QuartzToFlux) -add_subdirectory(QuartzToQIR) +add_subdirectory(QCOToQC) +add_subdirectory(QCToQCO) +add_subdirectory(QCToQIR) diff --git a/mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt b/mlir/lib/Conversion/QCOToQC/CMakeLists.txt similarity index 82% rename from mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt rename to mlir/lib/Conversion/QCOToQC/CMakeLists.txt index e2c17340c1..1c4a8d1f0a 100644 --- a/mlir/lib/Conversion/FluxToQuartz/CMakeLists.txt +++ b/mlir/lib/Conversion/QCOToQC/CMakeLists.txt @@ -9,13 +9,13 @@ file(GLOB CONVERSION_SOURCES *.cpp) add_mlir_library( - FluxToQuartz + QCOToQC ${CONVERSION_SOURCES} DEPENDS - FluxToQuartzIncGen + QCOToQCIncGen LINK_LIBS PUBLIC - MLIRQuartzDialect - MLIRFluxDialect + MLIRQCDialect + MLIRQCODialect MLIRArithDialect MLIRFuncDialect) diff --git a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp similarity index 53% rename from mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp rename to mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 97e5448ed2..1dad4af715 100644 --- a/mlir/lib/Conversion/FluxToQuartz/FluxToQuartz.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -8,10 +8,10 @@ * Licensed under the MIT License */ -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h" +#include "mlir/Conversion/QCOToQC/QCOToQC.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -26,231 +26,224 @@ #include namespace mlir { -using namespace flux; -using namespace quartz; +using namespace qco; +using namespace qc; -#define GEN_PASS_DEF_FLUXTOQUARTZ -#include "mlir/Conversion/FluxToQuartz/FluxToQuartz.h.inc" +#define GEN_PASS_DEF_QCOTOQC +#include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" namespace { /** - * @brief Converts a zero-target, one-parameter Flux operation to Quartz + * @brief Converts a zero-target, one-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @param op The Flux operation instance to convert + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @param op The QCO operation instance to convert * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertZeroTargetOneParameter(FluxOpType& op, +convertZeroTargetOneParameter(QCOOpType& op, ConversionPatternRewriter& rewriter) { - rewriter.create(op.getLoc(), op.getParameter(0)); + rewriter.create(op.getLoc(), op.getParameter(0)); rewriter.eraseOp(op); return success(); } /** - * @brief Converts a one-target, zero-parameter Flux operation to Quartz + * @brief Converts a one-target, zero-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertOneTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertOneTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); + const auto& qcQubit = adaptor.getQubitIn(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit); - // Replace the output qubit with the same Quartz reference - rewriter.replaceOp(op, quartzQubit); + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); return success(); } /** - * @brief Converts a one-target, one-parameter Flux operation to Quartz + * @brief Converts a one-target, one-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertOneTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertOneTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); + const auto& qcQubit = adaptor.getQubitIn(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit, op.getParameter(0)); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit, op.getParameter(0)); - // Replace the output qubit with the same Quartz reference - rewriter.replaceOp(op, quartzQubit); + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); return success(); } /** - * @brief Converts a one-target, two-parameter Flux operation to Quartz + * @brief Converts a one-target, two-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertOneTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertOneTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); + const auto& qcQubit = adaptor.getQubitIn(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit, op.getParameter(0), - op.getParameter(1)); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit, op.getParameter(0), + op.getParameter(1)); - // Replace the output qubit with the same Quartz reference - rewriter.replaceOp(op, quartzQubit); + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); return success(); } /** - * @brief Converts a one-target, three-parameter Flux operation to Quartz + * @brief Converts a one-target, three-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertOneTargetThreeParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertOneTargetThreeParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); + const auto& qcQubit = adaptor.getQubitIn(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit, op.getParameter(0), - op.getParameter(1), op.getParameter(2)); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit, op.getParameter(0), + op.getParameter(1), op.getParameter(2)); - // Replace the output qubit with the same Quartz reference - rewriter.replaceOp(op, quartzQubit); + // Replace the output qubit with the same QC reference + rewriter.replaceOp(op, qcQubit); return success(); } /** - * @brief Converts a two-target, zero-parameter Flux operation to Quartz + * @brief Converts a two-target, zero-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertTwoTargetZeroParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertTwoTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubits - const auto& quartzQubit0 = adaptor.getQubit0In(); - const auto& quartzQubit1 = adaptor.getQubit1In(); + const auto& qcQubit0 = adaptor.getQubit0In(); + const auto& qcQubit1 = adaptor.getQubit1In(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit0, qcQubit1); - // Replace the output qubits with the same Quartz references - rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, {qcQubit0, qcQubit1}); return success(); } /** - * @brief Converts a two-target, one-parameter Flux operation to Quartz + * @brief Converts a two-target, one-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertTwoTargetOneParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertTwoTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubits - const auto& quartzQubit0 = adaptor.getQubit0In(); - const auto& quartzQubit1 = adaptor.getQubit1In(); + const auto& qcQubit0 = adaptor.getQubit0In(); + const auto& qcQubit1 = adaptor.getQubit1In(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1, - op.getParameter(0)); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit0, qcQubit1, + op.getParameter(0)); - // Replace the output qubits with the same Quartz references - rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, {qcQubit0, qcQubit1}); return success(); } /** - * @brief Converts a two-target, two-parameter Flux operation to Quartz + * @brief Converts a two-target, two-parameter QCO operation to QC * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam FluxOpType The operation type of the Flux operation - * @tparam FluxOpAdaptorType The OpAdaptor type of the Flux operation - * @param op The Flux operation instance to convert - * @param adaptor The OpAdaptor of the Flux operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOOpAdaptorType The OpAdaptor type of the QCO operation + * @param op The QCO operation instance to convert + * @param adaptor The OpAdaptor of the QCO operation * @param rewriter The pattern rewriter * @return LogicalResult Success or failure of the conversion */ -template +template LogicalResult -convertTwoTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, +convertTwoTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubits - const auto& quartzQubit0 = adaptor.getQubit0In(); - const auto& quartzQubit1 = adaptor.getQubit1In(); + const auto& qcQubit0 = adaptor.getQubit0In(); + const auto& qcQubit1 = adaptor.getQubit1In(); - // Create the Quartz operation (in-place, no result) - rewriter.create(op.getLoc(), quartzQubit0, quartzQubit1, - op.getParameter(0), op.getParameter(1)); + // Create the QC operation (in-place, no result) + rewriter.create(op.getLoc(), qcQubit0, qcQubit1, op.getParameter(0), + op.getParameter(1)); - // Replace the output qubits with the same Quartz references - rewriter.replaceOp(op, {quartzQubit0, quartzQubit1}); + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, {qcQubit0, qcQubit1}); return success(); } @@ -258,201 +251,201 @@ convertTwoTargetTwoParameter(FluxOpType& op, FluxOpAdaptorType& adaptor, } // namespace /** - * @brief Type converter for Flux-to-Quartz conversion + * @brief Type converter for QCO-to-QC conversion * * @details - * Handles type conversion between the Flux and Quartz dialects. - * The primary conversion is from !flux.qubit to !quartz.qubit, which + * Handles type conversion between the QCO and QC dialects. + * The primary conversion is from !qco.qubit to !qc.qubit, which * represents the semantic shift from value types to reference types. * * Other types (integers, booleans, etc.) pass through unchanged via * the identity conversion. */ -class FluxToQuartzTypeConverter final : public TypeConverter { +class QCOToQCTypeConverter final : public TypeConverter { public: - explicit FluxToQuartzTypeConverter(MLIRContext* ctx) { + explicit QCOToQCTypeConverter(MLIRContext* ctx) { // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // Convert Flux qubit values to Quartz qubit references - addConversion([ctx](flux::QubitType /*type*/) -> Type { - return quartz::QubitType::get(ctx); + // Convert QCO qubit values to QC qubit references + addConversion([ctx](qco::QubitType /*type*/) -> Type { + return qc::QubitType::get(ctx); }); } }; /** - * @brief Converts flux.alloc to quartz.alloc + * @brief Converts qco.alloc to qc.alloc * * @details * Allocates a new qubit initialized to the |0⟩ state. Register metadata * (name, size, index) is preserved during conversion. * - * The conversion is straightforward: the Flux allocation produces an SSA - * value, while the Quartz allocation produces a reference. MLIR's type + * The conversion is straightforward: the QCO allocation produces an SSA + * value, while the QC allocation produces a reference. MLIR's type * conversion system automatically handles the semantic shift. * * Example transformation: * ```mlir - * %q0 = flux.alloc("q", 3, 0) : !flux.qubit + * %q0 = qco.alloc("q", 3, 0) : !qco.qubit * // becomes: - * %q = quartz.alloc("q", 3, 0) : !quartz.qubit + * %q = qc.alloc("q", 3, 0) : !qc.qubit * ``` */ -struct ConvertFluxAllocOp final : OpConversionPattern { +struct ConvertQCOAllocOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::AllocOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qco::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - // Create quartz.alloc with preserved register metadata - rewriter.replaceOpWithNewOp(op, op.getRegisterNameAttr(), - op.getRegisterSizeAttr(), - op.getRegisterIndexAttr()); + // Create qc.alloc with preserved register metadata + rewriter.replaceOpWithNewOp(op, op.getRegisterNameAttr(), + op.getRegisterSizeAttr(), + op.getRegisterIndexAttr()); return success(); } }; /** - * @brief Converts flux.dealloc to quartz.dealloc + * @brief Converts qco.dealloc to qc.dealloc * * @details * Deallocates a qubit, releasing its resources. The OpAdaptor automatically - * provides the type-converted qubit operand (!quartz.qubit instead of - * !flux.qubit), so we simply pass it through to the new operation. + * provides the type-converted qubit operand (!qc.qubit instead of + * !qco.qubit), so we simply pass it through to the new operation. * * Example transformation: * ```mlir - * flux.dealloc %q_flux : !flux.qubit + * qco.dealloc %q_qco : !qco.qubit * // becomes: - * quartz.dealloc %q_quartz : !quartz.qubit + * qc.dealloc %q_qc : !qc.qubit * ``` */ -struct ConvertFluxDeallocOp final : OpConversionPattern { +struct ConvertQCODeallocOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::DeallocOp op, OpAdaptor adaptor, + matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { // OpAdaptor provides the already type-converted qubit - rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); + rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); } }; /** - * @brief Converts flux.static to quartz.static + * @brief Converts qco.static to qc.static * * @details * Static qubits represent references to hardware-mapped or fixed-position * qubits identified by an index. The conversion preserves the index attribute - * and creates the corresponding quartz.static operation. + * and creates the corresponding qc.static operation. * * Example transformation: * ```mlir - * %q0 = flux.static 0 : !flux.qubit + * %q0 = qco.static 0 : !qco.qubit * // becomes: - * %q = quartz.static 0 : !quartz.qubit + * %q = qc.static 0 : !qc.qubit * ``` */ -struct ConvertFluxStaticOp final : OpConversionPattern { +struct ConvertQCOStaticOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::StaticOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - // Create quartz.static with the same index - rewriter.replaceOpWithNewOp(op, op.getIndex()); + // Create qc.static with the same index + rewriter.replaceOpWithNewOp(op, op.getIndex()); return success(); } }; /** - * @brief Converts flux.measure to quartz.measure + * @brief Converts qco.measure to qc.measure * * @details * Measurement demonstrates the key semantic difference between the dialects: - * - Flux (value semantics): Consumes input qubit, returns both output qubit + * - QCO (value semantics): Consumes input qubit, returns both output qubit * and classical bit result - * - Quartz (reference semantics): Measures qubit in-place, returns only the + * - QC (reference semantics): Measures qubit in-place, returns only the * classical bit result * - * The OpAdaptor provides the input qubit already converted to !quartz.qubit. - * Since Quartz operations are in-place, we return the same qubit reference + * The OpAdaptor provides the input qubit already converted to !qc.qubit. + * Since QC operations are in-place, we return the same qubit reference * alongside the measurement bit. MLIR's conversion infrastructure automatically - * routes subsequent uses of the Flux output qubit to this Quartz reference. + * routes subsequent uses of the QCO output qubit to this QC reference. * * Register metadata (name, size, index) for output recording is preserved * during conversion. * * Example transformation: * ```mlir - * %q_out, %c = flux.measure("c", 2, 0) %q_in : !flux.qubit + * %q_out, %c = qco.measure("c", 2, 0) %q_in : !qco.qubit * // becomes: - * %c = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 + * %c = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 * // %q_out uses are replaced with %q (the adaptor-converted input) * ``` */ -struct ConvertFluxMeasureOp final : OpConversionPattern { +struct ConvertQCOMeasureOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::MeasureOp op, OpAdaptor adaptor, + matchAndRewrite(qco::MeasureOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); + const auto& qcQubit = adaptor.getQubitIn(); - // Create quartz.measure (in-place operation, returns only bit) + // Create qc.measure (in-place operation, returns only bit) // Preserve register metadata for output recording - auto quartzOp = rewriter.create( - op.getLoc(), quartzQubit, op.getRegisterNameAttr(), + auto qcOp = rewriter.create( + op.getLoc(), qcQubit, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - auto measureBit = quartzOp.getResult(); + auto measureBit = qcOp.getResult(); - // Replace both results: qubit output → same quartz reference, bit → new bit - rewriter.replaceOp(op, {quartzQubit, measureBit}); + // Replace both results: qubit output → same qc reference, bit → new bit + rewriter.replaceOp(op, {qcQubit, measureBit}); return success(); } }; /** - * @brief Converts flux.reset to quartz.reset + * @brief Converts qco.reset to qc.reset * * @details * Reset operations force a qubit to the |0⟩ state: - * - Flux (value semantics): Consumes input qubit, returns reset output qubit - * - Quartz (reference semantics): Resets qubit in-place, no result value + * - QCO (value semantics): Consumes input qubit, returns reset output qubit + * - QC (reference semantics): Resets qubit in-place, no result value * - * The OpAdaptor provides the input qubit already converted to !quartz.qubit. - * Since Quartz's reset is in-place, we return the same qubit reference. + * The OpAdaptor provides the input qubit already converted to !qc.qubit. + * Since QC's reset is in-place, we return the same qubit reference. * MLIR's conversion infrastructure automatically routes subsequent uses of - * the Flux output qubit to this Quartz reference. + * the QCO output qubit to this QC reference. * * Example transformation: * ```mlir - * %q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit + * %q_out = qco.reset %q_in : !qco.qubit -> !qco.qubit * // becomes: - * quartz.reset %q : !quartz.qubit + * qc.reset %q : !qc.qubit * // %q_out uses are replaced with %q (the adaptor-converted input) * ``` */ -struct ConvertFluxResetOp final : OpConversionPattern { +struct ConvertQCOResetOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::ResetOp op, OpAdaptor adaptor, + matchAndRewrite(qco::ResetOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { // OpAdaptor provides the already type-converted input qubit - const auto& quartzQubit = adaptor.getQubitIn(); + const auto& qcQubit = adaptor.getQubitIn(); - // Create quartz.reset (in-place operation, no result) - rewriter.create(op.getLoc(), quartzQubit); + // Create qc.reset (in-place operation, no result) + rewriter.create(op.getLoc(), qcQubit); - // Replace the output qubit with the same quartz reference - rewriter.replaceOp(op, quartzQubit); + // Replace the output qubit with the same qc reference + rewriter.replaceOp(op, qcQubit); return success(); } @@ -462,24 +455,24 @@ struct ConvertFluxResetOp final : OpConversionPattern { #define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * flux.OP_NAME(%PARAM) \ + * qco.OP_NAME(%PARAM) \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM) \ + * qc.OP_NAME(%PARAM) \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertZeroTargetOneParameter(op, rewriter); \ + return convertZeroTargetOneParameter(op, rewriter); \ } \ }; @@ -491,25 +484,25 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q_out = flux.OP_NAME %q_in : !flux.qubit -> !flux.qubit \ + * %q_out = qco.OP_NAME %q_in : !qco.qubit -> !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME %q : !quartz.qubit \ + * qc.OP_NAME %q : !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetZeroParameter(op, adaptor, \ - rewriter); \ + return convertOneTargetZeroParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -531,25 +524,25 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM) %q_in : !qco.qubit -> !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM) %q : !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetOneParameter(op, adaptor, \ - rewriter); \ + return convertOneTargetOneParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -564,26 +557,26 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM1, %PARAM2) %q_in : !flux.qubit -> \ - * !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2) %q_in : !qco.qubit -> \ + * !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q : !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetTwoParameter(op, adaptor, \ - rewriter); \ + return convertOneTargetTwoParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -597,26 +590,26 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ PARAM3) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !flux.qubit \ - * -> !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !qco.qubit \ + * -> !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetThreeParameter(op, adaptor, \ - rewriter); \ + return convertOneTargetThreeParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -628,26 +621,26 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME %q0_in, %q1_in : !flux.qubit, !flux.qubit \ - * -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertTwoTargetZeroParameter(op, adaptor, \ - rewriter); \ + return convertTwoTargetZeroParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -662,26 +655,26 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME(%PARAM) %q0_in, %q1_in : !flux.qubit, \ - * !flux.qubit -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME(%PARAM) %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertTwoTargetOneParameter(op, adaptor, \ - rewriter); \ + return convertTwoTargetOneParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -696,26 +689,26 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ /** \ - * @brief Converts flux.OP_NAME to quartz.OP_NAME \ + * @brief Converts qco.OP_NAME to qc.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ - * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ * ``` \ * is converted to \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ */ \ - struct ConvertFlux##OP_CLASS final : OpConversionPattern { \ + struct ConvertQCO##OP_CLASS final : OpConversionPattern { \ using OpConversionPattern::OpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(flux::OP_CLASS op, OpAdaptor adaptor, \ + matchAndRewrite(qco::OP_CLASS op, OpAdaptor adaptor, \ ConversionPatternRewriter& rewriter) const override { \ - return convertTwoTargetTwoParameter(op, adaptor, \ - rewriter); \ + return convertTwoTargetTwoParameter(op, adaptor, \ + rewriter); \ } \ }; @@ -727,71 +720,71 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) // BarrierOp /** - * @brief Converts flux.barrier to quartz.barrier + * @brief Converts qco.barrier to qc.barrier * * @par Example: * ```mlir - * %q0_out, %q1_out = flux.barrier %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit + * %q0_out, %q1_out = qco.barrier %q0_in, %q1_in : !qco.qubit, !qco.qubit -> + * !qco.qubit, !qco.qubit * ``` * is converted to * ```mlir - * quartz.barrier %q0, %q1 : !quartz.qubit, !quartz.qubit + * qc.barrier %q0, %q1 : !qc.qubit, !qc.qubit * ``` */ -struct ConvertFluxBarrierOp final : OpConversionPattern { +struct ConvertQCOBarrierOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::BarrierOp op, OpAdaptor adaptor, + matchAndRewrite(qco::BarrierOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { // OpAdaptor provides the already type-converted qubits - const auto& quartzQubits = adaptor.getQubitsIn(); + const auto& qcQubits = adaptor.getQubitsIn(); - // Create quartz.barrier operation - rewriter.create(op.getLoc(), quartzQubits); + // Create qc.barrier operation + rewriter.create(op.getLoc(), qcQubits); - // Replace the output qubits with the same quartz references - rewriter.replaceOp(op, quartzQubits); + // Replace the output qubits with the same qc references + rewriter.replaceOp(op, qcQubits); return success(); } }; /** - * @brief Converts flux.ctrl to quartz.ctrl + * @brief Converts qco.ctrl to qc.ctrl * * @par Example: * ```mlir - * %controls_out, %targets_out = flux.ctrl({%q0_in}, {%q1_in}) { - * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * %controls_out, %targets_out = qco.ctrl({%q0_in}, {%q1_in}) { + * %q1_res = qco.x %q1_in : !qco.qubit -> !qco.qubit + * qco.yield %q1_res + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) * ``` * is converted to * ```mlir - * quartz.ctrl(%q0) { - * quartz.x %q1 : !quartz.qubit + * qc.ctrl(%q0) { + * qc.x %q1 : !qc.qubit * } * ``` */ -struct ConvertFluxCtrlOp final : OpConversionPattern { +struct ConvertQCOCtrlOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::CtrlOp op, OpAdaptor adaptor, + matchAndRewrite(qco::CtrlOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // Get Quartz controls - const auto& quartzControls = adaptor.getControlsIn(); + // Get QC controls + const auto& qcControls = adaptor.getControlsIn(); - // Create quartz.ctrl operation - auto fluxOp = rewriter.create(op.getLoc(), quartzControls); + // Create qc.ctrl operation + auto qcoOp = rewriter.create(op.getLoc(), qcControls); - // Clone body region from Flux to Quartz - auto& dstRegion = fluxOp.getBody(); + // Clone body region from QCO to QC + auto& dstRegion = qcoOp.getBody(); rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); - // Replace the output qubits with the same Quartz references + // Replace the output qubits with the same QC references rewriter.replaceOp(op, adaptor.getOperands()); return success(); @@ -799,57 +792,57 @@ struct ConvertFluxCtrlOp final : OpConversionPattern { }; /** - * @brief Converts flux.yield to quartz.yield + * @brief Converts qco.yield to qc.yield * * @par Example: * ```mlir - * flux.yield %targets + * qco.yield %targets * ``` * is converted to * ```mlir - * quartz.yield + * qc.yield * ``` */ -struct ConvertFluxYieldOp final : OpConversionPattern { +struct ConvertQCOYieldOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(flux::YieldOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qco::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - rewriter.replaceOpWithNewOp(op); + rewriter.replaceOpWithNewOp(op); return success(); } }; /** - * @brief Pass implementation for Flux-to-Quartz conversion + * @brief Pass implementation for QCO-to-QC conversion * * @details - * This pass converts Flux dialect operations (value semantics) to - * Quartz dialect operations (reference semantics). The conversion is useful + * This pass converts QCO dialect operations (value semantics) to + * QC dialect operations (reference semantics). The conversion is useful * for lowering optimized SSA-form code back to a hardware-oriented * representation suitable for backend code generation. * * The conversion leverages MLIR's built-in type conversion infrastructure: - * The TypeConverter handles !flux.qubit → !quartz.qubit transformations, + * The TypeConverter handles !qco.qubit → !qc.qubit transformations, * and the OpAdaptor automatically provides type-converted operands to each * conversion pattern. This eliminates the need for manual state tracking. * * Key semantic transformation: - * - Flux operations form explicit SSA chains where each operation consumes + * - QCO operations form explicit SSA chains where each operation consumes * inputs and produces new outputs - * - Quartz operations modify qubits in-place using references - * - The conversion maps each Flux SSA chain to a single Quartz reference, + * - QC operations modify qubits in-place using references + * - The conversion maps each QCO SSA chain to a single QC reference, * with MLIR's conversion framework automatically handling the plumbing * * The pass operates through: - * 1. Type conversion: !flux.qubit → !quartz.qubit - * 2. Operation conversion: Each Flux op converted to its Quartz equivalent + * 1. Type conversion: !qco.qubit → !qc.qubit + * 2. Operation conversion: Each QCO op converted to its QC equivalent * 3. Automatic operand mapping: OpAdaptors provide converted operands - * 4. Function/control-flow adaptation: Signatures updated to use Quartz types + * 4. Function/control-flow adaptation: Signatures updated to use QC types */ -struct FluxToQuartz final : impl::FluxToQuartzBase { - using FluxToQuartzBase::FluxToQuartzBase; +struct QCOToQC final : impl::QCOToQCBase { + using QCOToQCBase::QCOToQCBase; void runOnOperation() override { MLIRContext* context = &getContext(); @@ -857,29 +850,28 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { ConversionTarget target(*context); RewritePatternSet patterns(context); - FluxToQuartzTypeConverter typeConverter(context); + QCOToQCTypeConverter typeConverter(context); - // Configure conversion target: Flux illegal, Quartz legal - target.addIllegalDialect(); - target.addLegalDialect(); + // Configure conversion target: QCO illegal, QC legal + target.addIllegalDialect(); + target.addLegalDialect(); // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion - patterns - .add( - typeConverter, context); - - // Conversion of flux types in func.func signatures + patterns.add( + typeConverter, context); + + // Conversion of qco types in func.func signatures // Note: This currently has limitations with signature changes populateFunctionOpInterfaceTypeConversionPattern( patterns, typeConverter); @@ -888,17 +880,17 @@ struct FluxToQuartz final : impl::FluxToQuartzBase { typeConverter.isLegal(&op.getBody()); }); - // Conversion of flux types in func.return + // Conversion of qco types in func.return populateReturnOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); - // Conversion of flux types in func.call + // Conversion of qco types in func.call populateCallOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::CallOp op) { return typeConverter.isLegal(op); }); - // Conversion of flux types in control-flow ops (e.g., cf.br, cf.cond_br) + // Conversion of qco types in control-flow ops (e.g., cf.br, cf.cond_br) populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); // Apply the conversion diff --git a/mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt b/mlir/lib/Conversion/QCToQCO/CMakeLists.txt similarity index 82% rename from mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt rename to mlir/lib/Conversion/QCToQCO/CMakeLists.txt index 53fee2501a..a4ffcfebbe 100644 --- a/mlir/lib/Conversion/QuartzToFlux/CMakeLists.txt +++ b/mlir/lib/Conversion/QCToQCO/CMakeLists.txt @@ -9,13 +9,13 @@ file(GLOB CONVERSION_SOURCES *.cpp) add_mlir_library( - QuartzToFlux + QCToQCO ${CONVERSION_SOURCES} DEPENDS - QuartzToFluxIncGen + QCToQCOIncGen LINK_LIBS PUBLIC - MLIRQuartzDialect - MLIRFluxDialect + MLIRQCDialect + MLIRQCODialect MLIRArithDialect MLIRFuncDialect) diff --git a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp similarity index 54% rename from mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp rename to mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index f3345c53c0..b2d395710a 100644 --- a/mlir/lib/Conversion/QuartzToFlux/QuartzToFlux.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -8,10 +8,10 @@ * Licensed under the MIT License */ -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -31,11 +31,11 @@ #include namespace mlir { -using namespace flux; -using namespace quartz; +using namespace qco; +using namespace qc; -#define GEN_PASS_DEF_QUARTZTOFLUX -#include "mlir/Conversion/QuartzToFlux/QuartzToFlux.h.inc" +#define GEN_PASS_DEF_QCTOQCO +#include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" namespace { @@ -43,32 +43,32 @@ namespace { * @brief State object for tracking qubit value flow during conversion * * @details - * This struct maintains the mapping between Quartz dialect qubits (which use - * reference semantics) and their corresponding Flux dialect qubit values - * (which use value semantics). As the conversion progresses, each Quartz - * qubit reference is mapped to its latest Flux SSA value. + * This struct maintains the mapping between QC dialect qubits (which use + * reference semantics) and their corresponding QCO dialect qubit values + * (which use value semantics). As the conversion progresses, each QC + * qubit reference is mapped to its latest QCO SSA value. * - * The key insight is that Quartz operations modify qubits in-place: + * The key insight is that QC operations modify qubits in-place: * ```mlir - * %q = quartz.alloc : !quartz.qubit - * quartz.h %q : !quartz.qubit // modifies %q in-place - * quartz.x %q : !quartz.qubit // modifies %q in-place + * %q = qc.alloc : !qc.qubit + * qc.h %q : !qc.qubit // modifies %q in-place + * qc.x %q : !qc.qubit // modifies %q in-place * ``` * - * While Flux operations consume inputs and produce new outputs: + * While QCO operations consume inputs and produce new outputs: * ```mlir - * %q0 = flux.alloc : !flux.qubit - * %q1 = flux.h %q0 : !flux.qubit -> !flux.qubit // %q0 consumed, %q1 produced - * %q2 = flux.x %q1 : !flux.qubit -> !flux.qubit // %q1 consumed, %q2 produced + * %q0 = qco.alloc : !qco.qubit + * %q1 = qco.h %q0 : !qco.qubit -> !qco.qubit // %q0 consumed, %q1 produced + * %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit // %q1 consumed, %q2 produced * ``` * - * The qubitMap tracks that the Quartz qubit %q corresponds to: + * The qubitMap tracks that the QC qubit %q corresponds to: * - %q0 after allocation * - %q1 after the H gate * - %q2 after the X gate */ struct LoweringState { - /// Map from original Quartz qubit references to their latest Flux SSA values + /// Map from original QC qubit references to their latest QCO SSA values llvm::DenseMap qubitMap; /// Modifier information @@ -82,15 +82,15 @@ struct LoweringState { * * @details * Extends OpConversionPattern to provide access to a shared LoweringState - * object, which tracks the mapping from reference-semantics Quartz qubits - * to value-semantics Flux qubits across multiple pattern applications. + * object, which tracks the mapping from reference-semantics QC qubits + * to value-semantics QCO qubits across multiple pattern applications. * * This stateful approach is necessary because the conversion needs to: - * 1. Track which Flux value corresponds to each Quartz qubit reference + * 1. Track which QCO value corresponds to each QC qubit reference * 2. Update these mappings as operations transform qubits * 3. Share this information across different conversion patterns * - * @tparam OpType The Quartz operation type to convert + * @tparam OpType The QC operation type to convert */ template class StatefulOpConversionPattern : public OpConversionPattern { @@ -108,22 +108,22 @@ class StatefulOpConversionPattern : public OpConversionPattern { }; /** - * @brief Converts a zero-target, one-parameter Quartz operation to Flux + * @brief Converts a zero-target, one-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, +template +LogicalResult convertZeroTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { const auto inCtrlOp = state.inCtrlOp; - rewriter.create(op.getLoc(), op.getParameter(0)); + rewriter.create(op.getLoc(), op.getParameter(0)); // Update the state if (inCtrlOp != 0) { @@ -138,43 +138,43 @@ LogicalResult convertZeroTargetOneParameter(QuartzOpType& op, } /** - * @brief Converts a one-target, zero-parameter Quartz operation to Flux + * @brief Converts a one-target, zero-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, +template +LogicalResult convertOneTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubit - const auto& quartzQubit = op.getQubitIn(); - Value fluxQubit; + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - fluxQubit = qubitMap[quartzQubit]; + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; } else { assert(state.targetsIn[inCtrlOp].size() == 1 && "Invalid number of input targets"); - fluxQubit = state.targetsIn[inCtrlOp].front(); + qcoQubit = state.targetsIn[inCtrlOp].front(); } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit); // Update the state map if (inCtrlOp == 0) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[qcQubit] = qcoOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({qcoOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -184,44 +184,44 @@ LogicalResult convertOneTargetZeroParameter(QuartzOpType& op, } /** - * @brief Converts a one-target, one-parameter Quartz operation to Flux + * @brief Converts a one-target, one-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertOneTargetOneParameter(QuartzOpType& op, +template +LogicalResult convertOneTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubit - const auto& quartzQubit = op.getQubitIn(); - Value fluxQubit; + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - fluxQubit = qubitMap[quartzQubit]; + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; } else { assert(state.targetsIn[inCtrlOp].size() == 1 && "Invalid number of input targets"); - fluxQubit = state.targetsIn[inCtrlOp].front(); + qcoQubit = state.targetsIn[inCtrlOp].front(); } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, op.getParameter(0)); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = + rewriter.create(op.getLoc(), qcoQubit, op.getParameter(0)); // Update the state map if (inCtrlOp == 0) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[qcQubit] = qcoOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({qcoOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -231,44 +231,44 @@ LogicalResult convertOneTargetOneParameter(QuartzOpType& op, } /** - * @brief Converts a one-target, two-parameter Quartz operation to Flux + * @brief Converts a one-target, two-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, +template +LogicalResult convertOneTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubit - const auto& quartzQubit = op.getQubitIn(); - Value fluxQubit; + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - fluxQubit = qubitMap[quartzQubit]; + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; } else { assert(state.targetsIn[inCtrlOp].size() == 1 && "Invalid number of input targets"); - fluxQubit = state.targetsIn[inCtrlOp].front(); + qcoQubit = state.targetsIn[inCtrlOp].front(); } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = rewriter.create( - op.getLoc(), fluxQubit, op.getParameter(0), op.getParameter(1)); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create( + op.getLoc(), qcoQubit, op.getParameter(0), op.getParameter(1)); // Update the state map if (inCtrlOp == 0) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[qcQubit] = qcoOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({qcoOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -278,46 +278,44 @@ LogicalResult convertOneTargetTwoParameter(QuartzOpType& op, } /** - * @brief Converts a one-target, three-parameter Quartz operation to Flux + * @brief Converts a one-target, three-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult -convertOneTargetThreeParameter(QuartzOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +template +LogicalResult convertOneTargetThreeParameter( + QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubit - const auto& quartzQubit = op.getQubitIn(); - Value fluxQubit; + // Get the latest QCO qubit + const auto& qcQubit = op.getQubitIn(); + Value qcoQubit; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - fluxQubit = qubitMap[quartzQubit]; + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubit = qubitMap[qcQubit]; } else { assert(state.targetsIn[inCtrlOp].size() == 1 && "Invalid number of input targets"); - fluxQubit = state.targetsIn[inCtrlOp].front(); + qcoQubit = state.targetsIn[inCtrlOp].front(); } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit, op.getParameter(0), - op.getParameter(1), op.getParameter(2)); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = + rewriter.create(op.getLoc(), qcoQubit, op.getParameter(0), + op.getParameter(1), op.getParameter(2)); // Update the state map if (inCtrlOp == 0) { - qubitMap[quartzQubit] = fluxOp.getQubitOut(); + qubitMap[qcQubit] = qcoOp.getQubitOut(); } else { state.targetsIn.erase(inCtrlOp); - const SmallVector targetsOut({fluxOp.getQubitOut()}); + const SmallVector targetsOut({qcoOp.getQubitOut()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -327,56 +325,55 @@ convertOneTargetThreeParameter(QuartzOpType& op, } /** - * @brief Converts a two-target, zero-parameter Quartz operation to Flux + * @brief Converts a two-target, zero-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, +template +LogicalResult convertTwoTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubits - const auto& quartzQubit0 = op.getQubit0In(); - const auto& quartzQubit1 = op.getQubit1In(); - Value fluxQubit0; - Value fluxQubit1; + // Get the latest QCO qubits + const auto& qcQubit0 = op.getQubit0In(); + const auto& qcQubit1 = op.getQubit1In(); + Value qcoQubit0; + Value qcoQubit1; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); - assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); - fluxQubit0 = qubitMap[quartzQubit0]; - fluxQubit1 = qubitMap[quartzQubit1]; + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qcoQubit0 = qubitMap[qcQubit0]; + qcoQubit1 = qubitMap[qcQubit1]; } else { assert(state.targetsIn[inCtrlOp].size() == 2 && "Invalid number of input targets"); const auto& targetsIn = state.targetsIn[inCtrlOp]; - fluxQubit0 = targetsIn[0]; - fluxQubit1 = targetsIn[1]; + qcoQubit0 = targetsIn[0]; + qcoQubit1 = targetsIn[1]; } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1); // Update state map if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); - assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); - qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qubitMap[qcQubit0] = qcoOp.getQubit0Out(); + qubitMap[qcQubit1] = qcoOp.getQubit1Out(); } else { assert(state.targetsIn[inCtrlOp].size() == 2 && "Invalid number of input targets"); state.targetsIn.erase(inCtrlOp); const SmallVector targetsOut( - {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -386,52 +383,52 @@ LogicalResult convertTwoTargetZeroParameter(QuartzOpType& op, } /** - * @brief Converts a two-target, one-parameter Quartz operation to Flux + * @brief Converts a two-target, one-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, +template +LogicalResult convertTwoTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubits - const auto& quartzQubit0 = op.getQubit0In(); - const auto& quartzQubit1 = op.getQubit1In(); - Value fluxQubit0; - Value fluxQubit1; + // Get the latest QCO qubits + const auto& qcQubit0 = op.getQubit0In(); + const auto& qcQubit1 = op.getQubit1In(); + Value qcoQubit0; + Value qcoQubit1; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); - assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); - fluxQubit0 = qubitMap[quartzQubit0]; - fluxQubit1 = qubitMap[quartzQubit1]; + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qcoQubit0 = qubitMap[qcQubit0]; + qcoQubit1 = qubitMap[qcQubit1]; } else { assert(state.targetsIn[inCtrlOp].size() == 2 && "Invalid number of input targets"); const auto& targetsIn = state.targetsIn[inCtrlOp]; - fluxQubit0 = targetsIn[0]; - fluxQubit1 = targetsIn[1]; + qcoQubit0 = targetsIn[0]; + qcoQubit1 = targetsIn[1]; } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1, - op.getParameter(0)); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1, + op.getParameter(0)); // Update state map if (inCtrlOp == 0) { - qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + qubitMap[qcQubit0] = qcoOp.getQubit0Out(); + qubitMap[qcQubit1] = qcoOp.getQubit1Out(); } else { state.targetsIn.erase(inCtrlOp); const SmallVector targetsOut( - {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -441,53 +438,53 @@ LogicalResult convertTwoTargetOneParameter(QuartzOpType& op, } /** - * @brief Converts a two-target, two-parameter Quartz operation to Flux + * @brief Converts a two-target, two-parameter QC operation to QCO * - * @tparam FluxOpType The operation type of the Flux operation - * @tparam QuartzOpType The operation type of the Quartz operation - * @param op The Quartz operation instance to convert + * @tparam QCOOpType The operation type of the QCO operation + * @tparam QCOpType The operation type of the QC operation + * @param op The QC operation instance to convert * @param rewriter The pattern rewriter * @param state The lowering state * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, +template +LogicalResult convertTwoTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; - // Get the latest Flux qubits - const auto& quartzQubit0 = op.getQubit0In(); - const auto& quartzQubit1 = op.getQubit1In(); - Value fluxQubit0; - Value fluxQubit1; + // Get the latest QCO qubits + const auto& qcQubit0 = op.getQubit0In(); + const auto& qcQubit1 = op.getQubit1In(); + Value qcoQubit0; + Value qcoQubit1; if (inCtrlOp == 0) { - assert(qubitMap.contains(quartzQubit0) && "Quartz qubit not found"); - assert(qubitMap.contains(quartzQubit1) && "Quartz qubit not found"); - fluxQubit0 = qubitMap[quartzQubit0]; - fluxQubit1 = qubitMap[quartzQubit1]; + assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); + assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); + qcoQubit0 = qubitMap[qcQubit0]; + qcoQubit1 = qubitMap[qcQubit1]; } else { assert(state.targetsIn[inCtrlOp].size() == 2 && "Invalid number of input targets"); const auto& targetsIn = state.targetsIn[inCtrlOp]; - fluxQubit0 = targetsIn[0]; - fluxQubit1 = targetsIn[1]; + qcoQubit0 = targetsIn[0]; + qcoQubit1 = targetsIn[1]; } - // Create the Flux operation (consumes input, produces output) - auto fluxOp = - rewriter.create(op.getLoc(), fluxQubit0, fluxQubit1, - op.getParameter(0), op.getParameter(1)); + // Create the QCO operation (consumes input, produces output) + auto qcoOp = + rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1, + op.getParameter(0), op.getParameter(1)); // Update state map if (inCtrlOp == 0) { - qubitMap[quartzQubit0] = fluxOp.getQubit0Out(); - qubitMap[quartzQubit1] = fluxOp.getQubit1Out(); + qubitMap[qcQubit0] = qcoOp.getQubit0Out(); + qubitMap[qcQubit1] = qcoOp.getQubit1Out(); } else { state.targetsIn.erase(inCtrlOp); const SmallVector targetsOut( - {fluxOp.getQubit0Out(), fluxOp.getQubit1Out()}); + {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); state.targetsOut.try_emplace(inCtrlOp, targetsOut); } @@ -499,161 +496,158 @@ LogicalResult convertTwoTargetTwoParameter(QuartzOpType& op, } // namespace /** - * @brief Type converter for Quartz-to-Flux conversion + * @brief Type converter for QC-to-QCO conversion * * @details - * Handles type conversion between the Quartz and Flux dialects. - * The primary conversion is from !quartz.qubit to !flux.qubit, which + * Handles type conversion between the QC and QCO dialects. + * The primary conversion is from !qc.qubit to !qco.qubit, which * represents the semantic shift from reference types to value types. * * Other types (integers, booleans, etc.) pass through unchanged via * the identity conversion. */ -class QuartzToFluxTypeConverter final : public TypeConverter { +class QCToQCOTypeConverter final : public TypeConverter { public: - explicit QuartzToFluxTypeConverter(MLIRContext* ctx) { + explicit QCToQCOTypeConverter(MLIRContext* ctx) { // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // Convert Quartz qubit references to Flux qubit values - addConversion([ctx](quartz::QubitType /*type*/) -> Type { - return flux::QubitType::get(ctx); + // Convert QC qubit references to QCO qubit values + addConversion([ctx](qc::QubitType /*type*/) -> Type { + return qco::QubitType::get(ctx); }); } }; /** - * @brief Converts quartz.alloc to flux.alloc + * @brief Converts qc.alloc to qco.alloc * * @details * Allocates a new qubit and establishes the initial mapping in the state. * Both dialects initialize qubits to the |0⟩ state. * * Register metadata (name, size, index) is preserved during conversion, - * allowing the Flux representation to maintain register information for + * allowing the QCO representation to maintain register information for * debugging and visualization. * * Example transformation: * ```mlir - * %q = quartz.alloc("q", 3, 0) : !quartz.qubit + * %q = qc.alloc("q", 3, 0) : !qc.qubit * // becomes: - * %q0 = flux.alloc("q", 3, 0) : !flux.qubit + * %q0 = qco.alloc("q", 3, 0) : !qco.qubit * ``` */ -struct ConvertQuartzAllocOp final - : StatefulOpConversionPattern { +struct ConvertQCAllocOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::AllocOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap; - const auto& quartzQubit = op.getResult(); + const auto& qcQubit = op.getResult(); - // Create the flux.alloc operation with preserved register metadata - auto fluxOp = rewriter.replaceOpWithNewOp( + // Create the qco.alloc operation with preserved register metadata + auto qcoOp = rewriter.replaceOpWithNewOp( op, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - const auto& fluxQubit = fluxOp.getResult(); + const auto& qcoQubit = qcoOp.getResult(); - // Establish initial mapping: this Quartz qubit reference now corresponds - // to this Flux SSA value - qubitMap.try_emplace(quartzQubit, fluxQubit); + // Establish initial mapping: this QC qubit reference now corresponds + // to this QCO SSA value + qubitMap.try_emplace(qcQubit, qcoQubit); return success(); } }; /** - * @brief Converts quartz.dealloc to flux.dealloc + * @brief Converts qc.dealloc to qco.dealloc * * @details - * Deallocates a qubit by looking up its latest Flux value and creating - * a corresponding flux.dealloc operation. The mapping is removed from + * Deallocates a qubit by looking up its latest QCO value and creating + * a corresponding qco.dealloc operation. The mapping is removed from * the state as the qubit is no longer in use. * * Example transformation: * ```mlir - * quartz.dealloc %q : !quartz.qubit + * qc.dealloc %q : !qc.qubit * // becomes (where %q maps to %q_final): - * flux.dealloc %q_final : !flux.qubit + * qco.dealloc %q_final : !qco.qubit * ``` */ -struct ConvertQuartzDeallocOp final - : StatefulOpConversionPattern { +struct ConvertQCDeallocOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::DeallocOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap; - const auto& quartzQubit = op.getQubit(); + const auto& qcQubit = op.getQubit(); - // Look up the latest Flux value for this Quartz qubit - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - const auto& fluxQubit = qubitMap[quartzQubit]; + // Look up the latest QCO value for this QC qubit + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + const auto& qcoQubit = qubitMap[qcQubit]; // Create the dealloc operation - rewriter.replaceOpWithNewOp(op, fluxQubit); + rewriter.replaceOpWithNewOp(op, qcoQubit); // Remove from state as qubit is no longer in use - qubitMap.erase(quartzQubit); + qubitMap.erase(qcQubit); return success(); } }; /** - * @brief Converts quartz.static to flux.static + * @brief Converts qc.static to qco.static * * @details * Static qubits represent references to hardware-mapped or fixed-position * qubits identified by an index. This conversion creates the corresponding - * flux.static operation and establishes the mapping. + * qco.static operation and establishes the mapping. * * Example transformation: * ```mlir - * %q = quartz.static 0 : !quartz.qubit + * %q = qc.static 0 : !qc.qubit * // becomes: - * %q0 = flux.static 0 : !flux.qubit + * %q0 = qco.static 0 : !qco.qubit * ``` */ -struct ConvertQuartzStaticOp final - : StatefulOpConversionPattern { +struct ConvertQCStaticOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::StaticOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap; - const auto& quartzQubit = op.getQubit(); + const auto& qcQubit = op.getQubit(); - // Create new flux.static operation with the same index - auto fluxOp = rewriter.create(op.getLoc(), op.getIndex()); + // Create new qco.static operation with the same index + auto qcoOp = rewriter.create(op.getLoc(), op.getIndex()); - // Collect Flux qubit SSA value - const auto& fluxQubit = fluxOp.getQubit(); + // Collect QCO qubit SSA value + const auto& qcoQubit = qcoOp.getQubit(); - // Establish mapping from Quartz reference to Flux value - qubitMap[quartzQubit] = fluxQubit; + // Establish mapping from QC reference to QCO value + qubitMap[qcQubit] = qcoQubit; // Replace the old operation result with the new result - rewriter.replaceOp(op, fluxQubit); + rewriter.replaceOp(op, qcoQubit); return success(); } }; /** - * @brief Converts quartz.measure to flux.measure + * @brief Converts qc.measure to qco.measure * * @details * Measurement is a key operation where the semantic difference is visible: - * - Quartz: Measures in-place, returning only the classical bit - * - Flux: Consumes input qubit, returns both output qubit and classical bit + * - QC: Measures in-place, returning only the classical bit + * - QCO: Consumes input qubit, returns both output qubit and classical bit * - * The conversion looks up the latest Flux value for the Quartz qubit, + * The conversion looks up the latest QCO value for the QC qubit, * performs the measurement, updates the mapping with the output qubit, * and returns the classical bit result. * @@ -662,38 +656,37 @@ struct ConvertQuartzStaticOp final * * Example transformation: * ```mlir - * %c = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 + * %c = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 * // becomes (where %q maps to %q_in): - * %q_out, %c = flux.measure("c", 2, 0) %q_in : !flux.qubit + * %q_out, %c = qco.measure("c", 2, 0) %q_in : !qco.qubit * // state updated: %q now maps to %q_out * ``` */ -struct ConvertQuartzMeasureOp final - : StatefulOpConversionPattern { +struct ConvertQCMeasureOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::MeasureOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap; - const auto& quartzQubit = op.getQubit(); + const auto& qcQubit = op.getQubit(); - // Get the latest Flux qubit value from the state map - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - const auto& fluxQubit = qubitMap[quartzQubit]; + // Get the latest QCO qubit value from the state map + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + const auto& qcoQubit = qubitMap[qcQubit]; - // Create flux.measure (returns both output qubit and bit result) - auto fluxOp = rewriter.create( - op.getLoc(), fluxQubit, op.getRegisterNameAttr(), + // Create qco.measure (returns both output qubit and bit result) + auto qcoOp = rewriter.create( + op.getLoc(), qcoQubit, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - const auto& outFluxQubit = fluxOp.getQubitOut(); - const auto& newBit = fluxOp.getResult(); + const auto& outQCOQubit = qcoOp.getQubitOut(); + const auto& newBit = qcoOp.getResult(); - // Update mapping: the Quartz qubit now corresponds to the output qubit - qubitMap[quartzQubit] = outFluxQubit; + // Update mapping: the QC qubit now corresponds to the output qubit + qubitMap[qcQubit] = outQCOQubit; - // Replace the Quartz operation's bit result with the Flux bit result + // Replace the QC operation's bit result with the QCO bit result rewriter.replaceOp(op, newBit); return success(); @@ -701,44 +694,43 @@ struct ConvertQuartzMeasureOp final }; /** - * @brief Converts quartz.reset to flux.reset + * @brief Converts qc.reset to qco.reset * * @details * Reset operations force a qubit to the |0⟩ state. The semantic difference: - * - Quartz: Resets in-place (no result value) - * - Flux: Consumes input qubit, returns reset output qubit + * - QC: Resets in-place (no result value) + * - QCO: Consumes input qubit, returns reset output qubit * - * The conversion looks up the latest Flux value, performs the reset, - * and updates the mapping with the output qubit. The Quartz operation + * The conversion looks up the latest QCO value, performs the reset, + * and updates the mapping with the output qubit. The QC operation * is erased as it has no results to replace. * * Example transformation: * ```mlir - * quartz.reset %q : !quartz.qubit + * qc.reset %q : !qc.qubit * // becomes (where %q maps to %q_in): - * %q_out = flux.reset %q_in : !flux.qubit -> !flux.qubit + * %q_out = qco.reset %q_in : !qco.qubit -> !qco.qubit * // state updated: %q now maps to %q_out * ``` */ -struct ConvertQuartzResetOp final - : StatefulOpConversionPattern { +struct ConvertQCResetOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::ResetOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap; - const auto& quartzQubit = op.getQubit(); + const auto& qcQubit = op.getQubit(); - // Get the latest Flux qubit value from the state map - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - const auto& fluxQubit = qubitMap[quartzQubit]; + // Get the latest QCO qubit value from the state map + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + const auto& qcoQubit = qubitMap[qcQubit]; - // Create flux.reset (consumes input, produces output) - auto fluxOp = rewriter.create(op.getLoc(), fluxQubit); + // Create qco.reset (consumes input, produces output) + auto qcoOp = rewriter.create(op.getLoc(), qcoQubit); - // Update mapping: the Quartz qubit now corresponds to the reset output - qubitMap[quartzQubit] = fluxOp.getQubitOut(); + // Update mapping: the QC qubit now corresponds to the reset output + qubitMap[qcQubit] = qcoOp.getQubitOut(); // Erase the old (it has no results to replace) rewriter.eraseOp(op); @@ -751,26 +743,26 @@ struct ConvertQuartzResetOp final #define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM) \ + * qc.OP_NAME(%PARAM) \ * ``` \ * is converted to \ * ```mlir \ - * flux.OP_NAME(%PARAM) \ + * qco.OP_NAME(%PARAM) \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertZeroTargetOneParameter(op, rewriter, \ - getState()); \ + return convertZeroTargetOneParameter(op, rewriter, \ + getState()); \ } \ }; @@ -782,26 +774,26 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME %q : !quartz.qubit \ + * qc.OP_NAME %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q_out = flux.OP_NAME %q_in : !flux.qubit -> !flux.qubit \ + * %q_out = qco.OP_NAME %q_in : !qco.qubit -> !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetZeroParameter(op, rewriter, \ - getState()); \ + return convertOneTargetZeroParameter(op, rewriter, \ + getState()); \ } \ }; @@ -823,26 +815,26 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM) %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM) %q_in : !flux.qubit -> !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM) %q_in : !qco.qubit -> !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetOneParameter(op, rewriter, \ - getState()); \ + return convertOneTargetOneParameter(op, rewriter, \ + getState()); \ } \ }; @@ -857,27 +849,27 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM1, %PARAM2) %q_in : !flux.qubit -> \ - * !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2) %q_in : !qco.qubit -> \ + * !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetTwoParameter(op, rewriter, \ - getState()); \ + return convertOneTargetTwoParameter(op, rewriter, \ + getState()); \ } \ }; @@ -891,27 +883,27 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ PARAM3) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q_out = flux.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !flux.qubit \ - * -> !flux.qubit \ + * %q_out = qco.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q_in : !qco.qubit \ + * -> !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertOneTargetThreeParameter(op, rewriter, \ - getState()); \ + return convertOneTargetThreeParameter(op, rewriter, \ + getState()); \ } \ }; @@ -923,27 +915,27 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME %q0_in, %q1_in : !flux.qubit, !flux.qubit \ - * -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME %q0_in, %q1_in : !qco.qubit, !qco.qubit \ + * -> !qco.qubit, !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertTwoTargetZeroParameter(op, rewriter, \ - getState()); \ + return convertTwoTargetZeroParameter(op, rewriter, \ + getState()); \ } \ }; @@ -958,27 +950,27 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME(%PARAM) %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME(%PARAM) %q0_in, %q1_in : !flux.qubit, \ - * !flux.qubit -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM) %q0_in, %q1_in : !qco.qubit, \ + * !qco.qubit -> !qco.qubit, !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertTwoTargetOneParameter(op, rewriter, \ - getState()); \ + return convertTwoTargetOneParameter(op, rewriter, \ + getState()); \ } \ }; @@ -993,27 +985,27 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) #define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ /** \ - * @brief Converts quartz.OP_NAME to flux.OP_NAME \ + * @brief Converts qc.OP_NAME to qco.OP_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME(%PARAM1, %PARAM2) %q0, %q1 : !qc.qubit, !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ - * %q0_out, %q1_out = flux.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ - * !flux.qubit, !flux.qubit -> !flux.qubit, !flux.qubit \ + * %q0_out, %q1_out = qco.OP_NAME(%PARAM1, %PARAM2) %q0_in, %q1_in : \ + * !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS final \ - : StatefulOpConversionPattern { \ + struct ConvertQC##OP_CLASS final \ + : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ LogicalResult \ - matchAndRewrite(quartz::OP_CLASS op, OpAdaptor /*adaptor*/, \ + matchAndRewrite(qc::OP_CLASS op, OpAdaptor /*adaptor*/, \ ConversionPatternRewriter& rewriter) const override { \ - return convertTwoTargetTwoParameter(op, rewriter, \ - getState()); \ + return convertTwoTargetTwoParameter(op, rewriter, \ + getState()); \ } \ }; @@ -1025,44 +1017,43 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) // BarrierOp /** - * @brief Converts quartz.barrier to flux.barrier + * @brief Converts qc.barrier to qco.barrier * * @par Example: * ```mlir - * quartz.barrier %q0, %q1 : !quartz.qubit, !quartz.qubit + * qc.barrier %q0, %q1 : !qc.qubit, !qc.qubit * ``` * is converted to * ```mlir - * %q0_out, %q1_out = flux.barrier %q0_in, %q1_in : !flux.qubit, !flux.qubit -> - * !flux.qubit, !flux.qubit + * %q0_out, %q1_out = qco.barrier %q0_in, %q1_in : !qco.qubit, !qco.qubit -> + * !qco.qubit, !qco.qubit * ``` */ -struct ConvertQuartzBarrierOp final - : StatefulOpConversionPattern { +struct ConvertQCBarrierOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::BarrierOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::BarrierOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); auto& qubitMap = state.qubitMap; - // Get Flux qubits from state map - const auto& quartzQubits = op.getQubits(); - SmallVector fluxQubits; - fluxQubits.reserve(quartzQubits.size()); - for (const auto& quartzQubit : quartzQubits) { - assert(qubitMap.contains(quartzQubit) && "Quartz qubit not found"); - fluxQubits.push_back(qubitMap[quartzQubit]); + // Get QCO qubits from state map + const auto& qcQubits = op.getQubits(); + SmallVector qcoQubits; + qcoQubits.reserve(qcQubits.size()); + for (const auto& qcQubit : qcQubits) { + assert(qubitMap.contains(qcQubit) && "QC qubit not found"); + qcoQubits.push_back(qubitMap[qcQubit]); } - // Create flux.barrier - auto fluxOp = rewriter.create(op.getLoc(), fluxQubits); + // Create qco.barrier + auto qcoOp = rewriter.create(op.getLoc(), qcoQubits); // Update state map - for (const auto& [quartzQubit, fluxQubitOut] : - llvm::zip(quartzQubits, fluxOp.getQubitsOut())) { - qubitMap[quartzQubit] = fluxQubitOut; + for (const auto& [qcQubit, qcoQubitOut] : + llvm::zip(qcQubits, qcoOp.getQubitsOut())) { + qubitMap[qcQubit] = qcoQubitOut; } rewriter.eraseOp(op); @@ -1071,76 +1062,76 @@ struct ConvertQuartzBarrierOp final }; /** - * @brief Converts quartz.ctrl to flux.ctrl + * @brief Converts qc.ctrl to qco.ctrl * * @par Example: * ```mlir - * quartz.ctrl(%q0) { - * quartz.x %q1 - * quartz.yield + * qc.ctrl(%q0) { + * qc.x %q1 + * qc.yield * } * ``` * is converted to * ```mlir - * %controls_out, %targets_out = flux.ctrl(%q0_in) %q1_in { - * %q1_res = flux.x %q1_in : !flux.qubit -> !flux.qubit - * flux.yield %q1_res - * } : ({!flux.qubit}, {!flux.qubit}) -> ({!flux.qubit}, {!flux.qubit}) + * %controls_out, %targets_out = qco.ctrl(%q0_in) %q1_in { + * %q1_res = qco.x %q1_in : !qco.qubit -> !qco.qubit + * qco.yield %q1_res + * } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) * ``` */ -struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { +struct ConvertQCCtrlOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::CtrlOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); auto& qubitMap = state.qubitMap; - // Get Flux controls from state map - const auto& quartzControls = op.getControls(); - SmallVector fluxControls; - fluxControls.reserve(quartzControls.size()); - for (const auto& quartzControl : quartzControls) { - assert(qubitMap.contains(quartzControl) && "Quartz qubit not found"); - fluxControls.push_back(qubitMap[quartzControl]); + // Get QCO controls from state map + const auto& qcControls = op.getControls(); + SmallVector qcoControls; + qcoControls.reserve(qcControls.size()); + for (const auto& qcControl : qcControls) { + assert(qubitMap.contains(qcControl) && "QC qubit not found"); + qcoControls.push_back(qubitMap[qcControl]); } - // Get Flux targets from state map + // Get QCO targets from state map const auto numTargets = op.getNumTargets(); - SmallVector fluxTargets; - fluxTargets.reserve(numTargets); + SmallVector qcoTargets; + qcoTargets.reserve(numTargets); for (size_t i = 0; i < numTargets; ++i) { - const auto& quartzTarget = op.getTarget(i); - assert(qubitMap.contains(quartzTarget) && "Quartz qubit not found"); - const auto& fluxTarget = qubitMap[quartzTarget]; - fluxTargets.push_back(fluxTarget); + const auto& qcTarget = op.getTarget(i); + assert(qubitMap.contains(qcTarget) && "QC qubit not found"); + const auto& qcoTarget = qubitMap[qcTarget]; + qcoTargets.push_back(qcoTarget); } - // Create flux.ctrl - auto fluxOp = - rewriter.create(op.getLoc(), fluxControls, fluxTargets); + // Create qco.ctrl + auto qcoOp = + rewriter.create(op.getLoc(), qcoControls, qcoTargets); // Update state map if this is a top-level CtrlOp // Nested CtrlOps are managed via the targetsIn and targetsOut maps if (state.inCtrlOp == 0) { - for (const auto& [quartzControl, fluxControl] : - llvm::zip(quartzControls, fluxOp.getControlsOut())) { - qubitMap[quartzControl] = fluxControl; + for (const auto& [qcControl, qcoControl] : + llvm::zip(qcControls, qcoOp.getControlsOut())) { + qubitMap[qcControl] = qcoControl; } - const auto& targetsOut = fluxOp.getTargetsOut(); + const auto& targetsOut = qcoOp.getTargetsOut(); for (size_t i = 0; i < numTargets; ++i) { - const auto& quartzTarget = op.getTarget(i); - qubitMap[quartzTarget] = targetsOut[i]; + const auto& qcTarget = op.getTarget(i); + qubitMap[qcTarget] = targetsOut[i]; } } // Update modifier information state.inCtrlOp++; - state.targetsIn.try_emplace(state.inCtrlOp, fluxTargets); + state.targetsIn.try_emplace(state.inCtrlOp, qcoTargets); - // Clone body region from Quartz to Flux - auto& dstRegion = fluxOp.getBody(); + // Clone body region from QC to QCO + auto& dstRegion = qcoOp.getBody(); rewriter.cloneRegionBefore(op.getBody(), dstRegion, dstRegion.end()); rewriter.eraseOp(op); @@ -1149,27 +1140,26 @@ struct ConvertQuartzCtrlOp final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.yield to flux.yield + * @brief Converts qc.yield to qco.yield * * @par Example: * ```mlir - * quartz.yield + * qc.yield * ``` * is converted to * ```mlir - * flux.yield %targets + * qco.yield %targets * ``` */ -struct ConvertQuartzYieldOp final - : StatefulOpConversionPattern { +struct ConvertQCYieldOp final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(quartz::YieldOp op, OpAdaptor /*adaptor*/, + matchAndRewrite(qc::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); const auto& targets = state.targetsOut[state.inCtrlOp]; - rewriter.replaceOpWithNewOp(op, targets); + rewriter.replaceOpWithNewOp(op, targets); state.targetsOut.erase(state.inCtrlOp); state.inCtrlOp--; return success(); @@ -1177,30 +1167,30 @@ struct ConvertQuartzYieldOp final }; /** - * @brief Pass implementation for Quartz-to-Flux conversion + * @brief Pass implementation for QC-to-QCO conversion * * @details - * This pass converts Quartz dialect operations (reference - * semantics) to Flux dialect operations (value semantics). + * This pass converts QC dialect operations (reference + * semantics) to QCO dialect operations (value semantics). * The conversion is essential for enabling optimization * passes that rely on SSA form and explicit dataflow * analysis. * * The pass operates in several phases: - * 1. Type conversion: !quartz.qubit -> !flux.qubit - * 2. Operation conversion: Each Quartz op is converted to - * its Flux equivalent + * 1. Type conversion: !qc.qubit -> !qco.qubit + * 2. Operation conversion: Each QC op is converted to + * its QCO equivalent * 3. State tracking: A LoweringState maintains qubit value * mappings * 4. Function/control-flow adaptation: Function signatures - * and control flow are updated to use Flux types + * and control flow are updated to use QCO types * * The conversion maintains semantic equivalence while * transforming the representation from imperative * (mutation-based) to functional (SSA-based). */ -struct QuartzToFlux final : impl::QuartzToFluxBase { - using QuartzToFluxBase::QuartzToFluxBase; +struct QCToQCO final : impl::QCToQCOBase { + using QCToQCOBase::QCToQCOBase; void runOnOperation() override { MLIRContext* context = &getContext(); @@ -1211,31 +1201,28 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { ConversionTarget target(*context); RewritePatternSet patterns(context); - QuartzToFluxTypeConverter typeConverter(context); + QCToQCOTypeConverter typeConverter(context); - // Configure conversion target: Quartz illegal, Flux + // Configure conversion target: QC illegal, QCO // legal - target.addIllegalDialect(); - target.addLegalDialect(); + target.addIllegalDialect(); + target.addLegalDialect(); // Register operation conversion patterns with state // tracking - patterns.add< - ConvertQuartzAllocOp, ConvertQuartzDeallocOp, ConvertQuartzStaticOp, - ConvertQuartzMeasureOp, ConvertQuartzResetOp, ConvertQuartzGPhaseOp, - ConvertQuartzIdOp, ConvertQuartzXOp, ConvertQuartzYOp, ConvertQuartzZOp, - ConvertQuartzHOp, ConvertQuartzSOp, ConvertQuartzSdgOp, - ConvertQuartzTOp, ConvertQuartzTdgOp, ConvertQuartzSXOp, - ConvertQuartzSXdgOp, ConvertQuartzRXOp, ConvertQuartzRYOp, - ConvertQuartzRZOp, ConvertQuartzPOp, ConvertQuartzROp, - ConvertQuartzU2Op, ConvertQuartzUOp, ConvertQuartzSWAPOp, - ConvertQuartziSWAPOp, ConvertQuartzDCXOp, ConvertQuartzECROp, - ConvertQuartzRXXOp, ConvertQuartzRYYOp, ConvertQuartzRZXOp, - ConvertQuartzRZZOp, ConvertQuartzXXPlusYYOp, ConvertQuartzXXMinusYYOp, - ConvertQuartzBarrierOp, ConvertQuartzCtrlOp, ConvertQuartzYieldOp>( - typeConverter, context, &state); - - // Conversion of quartz types in func.func signatures + patterns.add(typeConverter, context, &state); + + // Conversion of qc types in func.func signatures // Note: This currently has limitations with signature // changes populateFunctionOpInterfaceTypeConversionPattern( @@ -1245,17 +1232,17 @@ struct QuartzToFlux final : impl::QuartzToFluxBase { typeConverter.isLegal(&op.getBody()); }); - // Conversion of quartz types in func.return + // Conversion of qc types in func.return populateReturnOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); - // Conversion of quartz types in func.call + // Conversion of qc types in func.call populateCallOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp( [&](const func::CallOp op) { return typeConverter.isLegal(op); }); - // Conversion of quartz types in control-flow ops (e.g., + // Conversion of qc types in control-flow ops (e.g., // cf.br, cf.cond_br) populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); diff --git a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt b/mlir/lib/Conversion/QCToQIR/CMakeLists.txt similarity index 89% rename from mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt rename to mlir/lib/Conversion/QCToQIR/CMakeLists.txt index 78b2929f91..a2715355f5 100644 --- a/mlir/lib/Conversion/QuartzToQIR/CMakeLists.txt +++ b/mlir/lib/Conversion/QCToQIR/CMakeLists.txt @@ -9,15 +9,15 @@ file(GLOB CONVERSION_SOURCES *.cpp) add_mlir_library( - QuartzToQIR + QCToQIR ${CONVERSION_SOURCES} DEPENDS - QuartzToQIRIncGen + QCToQIRIncGen LINK_LIBS PUBLIC MLIRQIRUtils MLIRLLVMDialect - MLIRQuartzDialect + MLIRQCDialect MLIRArithDialect MLIRFuncDialect MLIRFuncToLLVM diff --git a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp similarity index 85% rename from mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp rename to mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 534e416cce..419b396382 100644 --- a/mlir/lib/Conversion/QuartzToQIR/QuartzToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -8,10 +8,10 @@ * Licensed under the MIT License */ -#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h" +#include "mlir/Conversion/QCToQIR/QCToQIR.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QIR/Utils/QIRUtils.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" #include #include @@ -51,11 +51,11 @@ namespace mlir { -using namespace quartz; +using namespace qc; using namespace qir; -#define GEN_PASS_DEF_QUARTZTOQIR -#include "mlir/Conversion/QuartzToQIR/QuartzToQIR.h.inc" +#define GEN_PASS_DEF_QCTOQIR +#include "mlir/Conversion/QCToQIR/QCToQIR.h.inc" namespace { @@ -63,7 +63,7 @@ namespace { * @brief State object for tracking lowering information during QIR conversion * * @details - * This struct maintains state during the conversion of Quartz dialect + * This struct maintains state during the conversion of QC dialect * operations to QIR (Quantum Intermediate Representation). It tracks: * - Qubit and result counts for QIR metadata * - Pointer value caching for reuse @@ -113,12 +113,12 @@ class StatefulOpConversionPattern : public OpConversionPattern { }; /** - * @brief Helper to convert a Quartz operation to a LLVM CallOp + * @brief Helper to convert a QC operation to a LLVM CallOp * - * @tparam QuartzOpType The operation type of the Quartz operation - * @tparam QuartzOpAdaptorType The OpAdaptor type of the Quartz operation - * @param op The Quartz operation instance to convert - * @param adaptor The OpAdaptor of the Quartz operation + * @tparam QCOpType The operation type of the QC operation + * @tparam QCOpAdaptorType The OpAdaptor type of the QC operation + * @param op The QC operation instance to convert + * @param adaptor The OpAdaptor of the QC operation * @param rewriter The pattern rewriter * @param ctx The MLIR context * @param state The lowering state @@ -127,12 +127,12 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @param numParams The number of parameters * @return LogicalResult Success or failure of the conversion */ -template -LogicalResult -convertUnitaryToCallOp(QuartzOpType& op, QuartzOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, MLIRContext* ctx, - LoweringState& state, StringRef fnName, - size_t numTargets, size_t numParams) { +template +LogicalResult convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName, size_t numTargets, + size_t numParams) { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; const SmallVector posCtrls = @@ -184,16 +184,16 @@ convertUnitaryToCallOp(QuartzOpType& op, QuartzOpAdaptorType& adaptor, } // namespace /** - * @brief Type converter for lowering Quartz dialect types to LLVM types + * @brief Type converter for lowering QC dialect types to LLVM types * * @details - * Converts Quartz dialect types to their LLVM equivalents for QIR emission. + * Converts QC dialect types to their LLVM equivalents for QIR emission. * * Type conversions: - * - `!quartz.qubit` -> `!llvm.ptr` (opaque pointer to qubit in QIR) + * - `!qc.qubit` -> `!llvm.ptr` (opaque pointer to qubit in QIR) */ -struct QuartzToQIRTypeConverter final : LLVMTypeConverter { - explicit QuartzToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { +struct QCToQIRTypeConverter final : LLVMTypeConverter { + explicit QCToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { // Convert QubitType to LLVM pointer (QIR uses opaque pointers for qubits) addConversion( [ctx](QubitType /*type*/) { return LLVM::LLVMPointerType::get(ctx); }); @@ -203,10 +203,10 @@ struct QuartzToQIRTypeConverter final : LLVMTypeConverter { namespace { /** - * @brief Converts quartz.alloc operation to static QIR qubit allocations + * @brief Converts qc.alloc operation to static QIR qubit allocations * * @details - * QIR 2.0 does not support dynamic qubit allocation. Therefore, quartz.alloc + * QIR 2.0 does not support dynamic qubit allocation. Therefore, qc.alloc * operations are converted to static qubit references using inttoptr with a * constant index. * @@ -215,7 +215,7 @@ namespace { * * @par Example: * ```mlir - * %q = quartz.alloc : !quartz.qubit + * %q = qc.alloc : !qc.qubit * ``` * becomes: * ```mlir @@ -223,7 +223,7 @@ namespace { * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr * ``` */ -struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { +struct ConvertQCAllocQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -288,7 +288,7 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { }; /** - * @brief Erases quartz.dealloc operations + * @brief Erases qc.dealloc operations * * @details * Since QIR 2.0 does not support dynamic qubit allocation, dynamic allocations @@ -297,14 +297,14 @@ struct ConvertQuartzAllocQIR final : StatefulOpConversionPattern { * * @par Example: * ```mlir - * quartz.dealloc %q : !quartz.qubit + * qc.dealloc %q : !qc.qubit * ``` * becomes: * ```mlir * // (removed) * ``` */ -struct ConvertQuartzDeallocQIR final : OpConversionPattern { +struct ConvertQCDeallocQIR final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -316,7 +316,7 @@ struct ConvertQuartzDeallocQIR final : OpConversionPattern { }; /** - * @brief Converts quartz.static operation to QIR inttoptr + * @brief Converts qc.static operation to QIR inttoptr * * @details * Converts a static qubit reference to an LLVM pointer by creating a constant @@ -325,7 +325,7 @@ struct ConvertQuartzDeallocQIR final : OpConversionPattern { * * @par Example: * ```mlir - * %q0 = quartz.static 0 : !quartz.qubit + * %q0 = qc.static 0 : !qc.qubit * ``` * becomes: * ```mlir @@ -333,7 +333,7 @@ struct ConvertQuartzDeallocQIR final : OpConversionPattern { * %q0 = llvm.inttoptr %c0 : i64 to !llvm.ptr * ``` */ -struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { +struct ConvertQCStaticQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -363,7 +363,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.measure operation to QIR measurement + * @brief Converts qc.measure operation to QIR measurement * * @details * Converts qubit measurement to a QIR call to `__quantum__qis__mz__body`. @@ -377,7 +377,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { * * @par Example (with register): * ```mlir - * %result = quartz.measure("c", 2, 0) %q : !quartz.qubit -> i1 + * %result = qc.measure("c", 2, 0) %q : !qc.qubit -> i1 * ``` * becomes: * ```mlir @@ -387,7 +387,7 @@ struct ConvertQuartzStaticQIR final : StatefulOpConversionPattern { * -> () * ``` */ -struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { +struct ConvertQCMeasureQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -451,7 +451,7 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { const auto fnDecl = getOrCreateFunctionDeclaration(rewriter, op, QIR_MEASURE, fnSignature); - // Create CallOp and replace quartz.measure with result pointer + // Create CallOp and replace qc.measure with result pointer rewriter.create(op.getLoc(), fnDecl, ValueRange{adaptor.getQubit(), resultValue}); rewriter.replaceOp(op, resultValue); @@ -460,7 +460,7 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { }; /** - * @brief Converts quartz.reset operation to QIR reset + * @brief Converts qc.reset operation to QIR reset * * @details * Converts qubit reset to a call to the QIR __quantum__qis__reset__body @@ -468,14 +468,14 @@ struct ConvertQuartzMeasureQIR final : StatefulOpConversionPattern { * * @par Example: * ```mlir - * quartz.reset %q : !quartz.qubit + * qc.reset %q : !qc.qubit * ``` * becomes: * ```mlir * llvm.call @__quantum__qis__reset__body(%q) : (!llvm.ptr) -> () * ``` */ -struct ConvertQuartzResetQIR final : OpConversionPattern { +struct ConvertQCResetQIR final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -499,18 +499,18 @@ struct ConvertQuartzResetQIR final : OpConversionPattern { // GPhaseOp /** - * @brief Converts quartz.gphase to QIR gphase + * @brief Converts qc.gphase to QIR gphase * * @par Example: * ```mlir - * quartz.gphase(%theta) + * qc.gphase(%theta) * ``` * is converted to * ```mlir * llvm.call @__quantum__qis__gphase__body(%theta) : (f64) -> () * ``` */ -struct ConvertQuartzGPhaseOpQIR final : StatefulOpConversionPattern { +struct ConvertQCGPhaseOpQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -530,18 +530,18 @@ struct ConvertQuartzGPhaseOpQIR final : StatefulOpConversionPattern { #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL %q : !quartz.qubit \ + * qc.OP_NAME_SMALL %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ * llvm.call @__quantum__qis__QIR_NAME__body(%q) : (!llvm.ptr) -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -577,11 +577,11 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, SXDG, sxdg, sxdg) #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM) %q : !quartz.qubit \ + * qc.OP_NAME_SMALL(%PARAM) %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ @@ -589,7 +589,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, SXDG, sxdg, sxdg) * -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -618,11 +618,11 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, P, p, p, theta) #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM1, PARAM2) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM1, %PARAM2) %q : !quartz.qubit \ + * qc.OP_NAME_SMALL(%PARAM1, %PARAM2) %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ @@ -630,7 +630,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, P, p, p, theta) * (!llvm.ptr, f64, f64) -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -657,11 +657,11 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, U2, u2, u2, phi, lambda) #define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME_BIG, \ OP_NAME_SMALL, QIR_NAME) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM1, %PARAM2, %PARAM3) %q : !quartz.qubit \ + * qc.OP_NAME_SMALL(%PARAM1, %PARAM2, %PARAM3) %q : !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ @@ -669,7 +669,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, U2, u2, u2, phi, lambda) * : (!llvm.ptr, f64, f64, f64) -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -695,11 +695,11 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, U, u, u3) #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME_SMALL %q1, %q2 : !qc.qubit, !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ @@ -707,7 +707,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, U, u, u3) * !llvm.ptr) -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -736,11 +736,11 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM) %q1, %q2 : !quartz.qubit, !quartz.qubit \ + * qc.OP_NAME_SMALL(%PARAM) %q1, %q2 : !qc.qubit, !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ @@ -748,7 +748,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ECR, ecr, ecr) * (!llvm.ptr, !llvm.ptr, f64) -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -777,12 +777,12 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) #define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME_BIG, OP_NAME_SMALL, \ QIR_NAME, PARAM1, PARAM2) \ /** \ - * @brief Converts quartz.OP_NAME_SMALL operation to QIR QIR_NAME \ + * @brief Converts qc.OP_NAME_SMALL operation to QIR QIR_NAME \ * \ * @par Example: \ * ```mlir \ - * quartz.OP_NAME_SMALL(%PARAM1, %PARAM2) %q1, %q2 : !quartz.qubit, \ - * !quartz.qubit \ + * qc.OP_NAME_SMALL(%PARAM1, %PARAM2) %q1, %q2 : !qc.qubit, \ + * !qc.qubit \ * ``` \ * is converted to \ * ```mlir \ @@ -790,7 +790,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, RZZ, rzz, rzz, theta) * (!llvm.ptr, !llvm.ptr, f64, f64) -> () \ * ``` \ */ \ - struct ConvertQuartz##OP_CLASS##QIR final \ + struct ConvertQC##OP_CLASS##QIR final \ : StatefulOpConversionPattern { \ using StatefulOpConversionPattern::StatefulOpConversionPattern; \ \ @@ -817,9 +817,9 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, XXMINUSYY, xx_minus_yy, // BarrierOp /** - * @brief Erases quartz.barrier operation, as it is a no-op in QIR + * @brief Erases qc.barrier operation, as it is a no-op in QIR */ -struct ConvertQuartzBarrierQIR final : StatefulOpConversionPattern { +struct ConvertQCBarrierQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -831,9 +831,9 @@ struct ConvertQuartzBarrierQIR final : StatefulOpConversionPattern { }; /** - * @brief Inlines quartz.ctrl region removes the operation + * @brief Inlines qc.ctrl region removes the operation */ -struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { +struct ConvertQCCtrlQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -855,9 +855,9 @@ struct ConvertQuartzCtrlQIR final : StatefulOpConversionPattern { }; /** - * @brief Erases quartz.yield operation + * @brief Erases qc.yield operation */ -struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { +struct ConvertQCYieldQIR final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult @@ -871,10 +871,10 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { } // namespace /** - * @brief Pass for converting Quartz dialect operations to QIR + * @brief Pass for converting QC dialect operations to QIR * * @details - * This pass converts Quartz dialect quantum operations to QIR (Quantum + * This pass converts QC dialect quantum operations to QIR (Quantum * Intermediate Representation) by lowering them to LLVM dialect operations * that call QIR runtime functions. * @@ -882,7 +882,7 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { * 1. Convert func dialect to LLVM * 2. Ensure proper block structure for QIR base profile and add * initialization - * 3. Convert Quartz operations to QIR calls + * 3. Convert QC operations to QIR calls * 4. Set QIR metadata attributes * 5. Convert arith and cf dialects to LLVM * 6. Reconcile unrealized casts @@ -891,8 +891,8 @@ struct ConvertQuartzYieldQIR final : StatefulOpConversionPattern { * The entry function must have a single block. The pass will restructure it * into a 4-block layout. Multi-block functions are currently unsupported. */ -struct QuartzToQIR final : impl::QuartzToQIRBase { - using QuartzToQIRBase::QuartzToQIRBase; +struct QCToQIR final : impl::QCToQIRBase { + using QCToQIRBase::QCToQIRBase; /** * @brief Ensures proper block structure for QIR base profile @@ -1120,7 +1120,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { } /** - * @brief Executes the Quartz to QIR conversion pass + * @brief Executes the QC to QIR conversion pass * * @details * Performs the conversion in six stages: @@ -1134,8 +1134,8 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { * irreversible, end blocks) and insert the `__quantum__rt__initialize` call * in the entry block. * - * **Stage 3: Quartz to LLVM** - * Convert Quartz dialect operations to QIR calls and add output recording to + * **Stage 3: QC to LLVM** + * Convert QC dialect operations to QIR calls and add output recording to * the output block. * * **Stage 4: QIR attributes** @@ -1154,7 +1154,7 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { MLIRContext* ctx = &getContext(); auto* moduleOp = getOperation(); ConversionTarget target(*ctx); - QuartzToQIRTypeConverter typeConverter(ctx); + QCToQIRTypeConverter typeConverter(ctx); target.addLegalDialect(); @@ -1184,53 +1184,51 @@ struct QuartzToQIR final : impl::QuartzToQIRBase { LoweringState state; - // Stage 3: Convert Quartz dialect to LLVM (QIR calls) + // Stage 3: Convert QC dialect to LLVM (QIR calls) { - RewritePatternSet quartzPatterns(ctx); - target.addIllegalDialect(); - - // Add conversion patterns for Quartz operations - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, - &state); - quartzPatterns.add(typeConverter, ctx, - &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - quartzPatterns.add(typeConverter, ctx, &state); - - if (applyPartialConversion(moduleOp, target, std::move(quartzPatterns)) + RewritePatternSet qcPatterns(ctx); + target.addIllegalDialect(); + + // Add conversion patterns for QC operations + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + qcPatterns.add(typeConverter, ctx, &state); + + if (applyPartialConversion(moduleOp, target, std::move(qcPatterns)) .failed()) { signalPassFailure(); return; diff --git a/mlir/lib/Dialect/CMakeLists.txt b/mlir/lib/Dialect/CMakeLists.txt index 79232ed264..ce032ab729 100644 --- a/mlir/lib/Dialect/CMakeLists.txt +++ b/mlir/lib/Dialect/CMakeLists.txt @@ -8,6 +8,6 @@ add_subdirectory(MQTOpt) add_subdirectory(MQTRef) -add_subdirectory(Flux) +add_subdirectory(QCO) add_subdirectory(QIR) -add_subdirectory(Quartz) +add_subdirectory(QC) diff --git a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt b/mlir/lib/Dialect/QC/Builder/CMakeLists.txt similarity index 52% rename from mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt rename to mlir/lib/Dialect/QC/Builder/CMakeLists.txt index d3a129dccb..ddd7989417 100644 --- a/mlir/lib/Dialect/Quartz/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Builder/CMakeLists.txt @@ -7,20 +7,19 @@ # Licensed under the MIT License add_mlir_library( - MLIRQuartzProgramBuilder - QuartzProgramBuilder.cpp + MLIRQCProgramBuilder + QCProgramBuilder.cpp LINK_LIBS PUBLIC MLIRArithDialect MLIRFuncDialect MLIRSCFDialect - MLIRQuartzDialect) + MLIRQCDialect) # collect header files -file(GLOB_RECURSE BUILDER_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/Builder/*.h) +file(GLOB_RECURSE BUILDER_HEADERS_SOURCE ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/Builder/*.h) # add public headers using file sets target_sources( - MLIRQuartzProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${BUILDER_HEADERS_SOURCE}) + MLIRQCProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp similarity index 85% rename from mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp rename to mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 1e6a4b4358..f9139835fe 100644 --- a/mlir/lib/Dialect/Quartz/Builder/QuartzProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -8,9 +8,9 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include #include @@ -29,15 +29,15 @@ #include #include -namespace mlir::quartz { +namespace mlir::qc { -QuartzProgramBuilder::QuartzProgramBuilder(MLIRContext* context) +QCProgramBuilder::QCProgramBuilder(MLIRContext* context) : OpBuilder(context), ctx(context), loc(getUnknownLoc()), module(ModuleOp::create(loc)) { - ctx->loadDialect(); + ctx->loadDialect(); } -void QuartzProgramBuilder::initialize() { +void QCProgramBuilder::initialize() { // Set insertion point to the module body setInsertionPointToStart(module.getBody()); @@ -54,7 +54,7 @@ void QuartzProgramBuilder::initialize() { setInsertionPointToStart(&entryBlock); } -Value QuartzProgramBuilder::allocQubit() { +Value QCProgramBuilder::allocQubit() { checkFinalized(); // Create the AllocOp without register metadata @@ -67,7 +67,7 @@ Value QuartzProgramBuilder::allocQubit() { return qubit; } -Value QuartzProgramBuilder::staticQubit(const int64_t index) { +Value QCProgramBuilder::staticQubit(const int64_t index) { checkFinalized(); if (index < 0) { @@ -81,8 +81,8 @@ Value QuartzProgramBuilder::staticQubit(const int64_t index) { } llvm::SmallVector -QuartzProgramBuilder::allocQubitRegister(const int64_t size, - const std::string& name) { +QCProgramBuilder::allocQubitRegister(const int64_t size, + const std::string& name) { checkFinalized(); if (size <= 0) { @@ -107,9 +107,9 @@ QuartzProgramBuilder::allocQubitRegister(const int64_t size, return qubits; } -QuartzProgramBuilder::ClassicalRegister -QuartzProgramBuilder::allocClassicalBitRegister(const int64_t size, - std::string name) const { +QCProgramBuilder::ClassicalRegister +QCProgramBuilder::allocClassicalBitRegister(const int64_t size, + std::string name) const { checkFinalized(); if (size <= 0) { @@ -123,14 +123,13 @@ QuartzProgramBuilder::allocClassicalBitRegister(const int64_t size, // Measurement and Reset //===----------------------------------------------------------------------===// -Value QuartzProgramBuilder::measure(Value qubit) { +Value QCProgramBuilder::measure(Value qubit) { checkFinalized(); auto measureOp = MeasureOp::create(*this, loc, qubit); return measureOp.getResult(); } -QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, - const Bit& bit) { +QCProgramBuilder& QCProgramBuilder::measure(Value qubit, const Bit& bit) { checkFinalized(); auto nameAttr = getStringAttr(bit.registerName); auto sizeAttr = getI64IntegerAttr(bit.registerSize); @@ -139,7 +138,7 @@ QuartzProgramBuilder& QuartzProgramBuilder::measure(Value qubit, return *this; } -QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { +QCProgramBuilder& QCProgramBuilder::reset(Value qubit) { checkFinalized(); ResetOp::create(*this, loc, qubit); return *this; @@ -152,18 +151,18 @@ QuartzProgramBuilder& QuartzProgramBuilder::reset(Value qubit) { // ZeroTargetOneParameter #define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ const std::variant&(PARAM)) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, PARAM); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control) { \ checkFinalized(); \ return mc##OP_NAME(PARAM, {control}); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls) { \ checkFinalized(); \ CtrlOp::create(*this, loc, controls, \ @@ -178,18 +177,18 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit) { \ + QCProgramBuilder& QCProgramBuilder::OP_NAME(Value qubit) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, qubit); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME(Value control, \ - Value target) { \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME(Value control, \ + Value target) { \ checkFinalized(); \ return mc##OP_NAME({control}, target); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME(ValueRange controls, \ - Value target) { \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME(ValueRange controls, \ + Value target) { \ checkFinalized(); \ CtrlOp::create(*this, loc, controls, \ [&](OpBuilder& b) { OP_CLASS::create(b, loc, target); }); \ @@ -213,19 +212,19 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) // OneTargetOneParameter #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, qubit, PARAM); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, \ Value target) { \ checkFinalized(); \ return mc##OP_NAME(PARAM, {control}, target); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value target) { \ checkFinalized(); \ @@ -245,21 +244,21 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) // OneTargetTwoParameter #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, \ Value target) { \ checkFinalized(); \ return mc##OP_NAME(PARAM1, PARAM2, {control}, target); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value target) { \ @@ -279,7 +278,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ PARAM3) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value qubit) { \ @@ -287,7 +286,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2, PARAM3); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value control, \ @@ -295,7 +294,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) checkFinalized(); \ return mc##OP_NAME(PARAM1, PARAM2, PARAM3, {control}, target); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), ValueRange controls, \ @@ -314,18 +313,17 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME(Value qubit0, \ - Value qubit1) { \ + QCProgramBuilder& QCProgramBuilder::OP_NAME(Value qubit0, Value qubit1) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, qubit0, qubit1); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ - Value control, Value qubit0, Value qubit1) { \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME(Value control, Value qubit0, \ + Value qubit1) { \ checkFinalized(); \ return mc##OP_NAME({control}, qubit0, qubit1); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value qubit0, Value qubit1) { \ checkFinalized(); \ CtrlOp::create(*this, loc, controls, [&](OpBuilder& b) { \ @@ -344,19 +342,19 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) // TwoTargetOneParameter #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, Value qubit0, \ Value qubit1) { \ checkFinalized(); \ return mc##OP_NAME(PARAM, {control}, qubit0, qubit1); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value qubit0, Value qubit1) { \ checkFinalized(); \ @@ -376,7 +374,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) // TwoTargetTwoParameter #define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ - QuartzProgramBuilder& QuartzProgramBuilder::OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ @@ -384,14 +382,14 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM1, PARAM2); \ return *this; \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::c##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, Value qubit0, \ Value qubit1) { \ checkFinalized(); \ return mc##OP_NAME(PARAM1, PARAM2, {control}, qubit0, qubit1); \ } \ - QuartzProgramBuilder& QuartzProgramBuilder::mc##OP_NAME( \ + QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value qubit0, Value qubit1) { \ @@ -409,7 +407,7 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) // BarrierOp -QuartzProgramBuilder& QuartzProgramBuilder::barrier(ValueRange qubits) { +QCProgramBuilder& QCProgramBuilder::barrier(ValueRange qubits) { checkFinalized(); BarrierOp::create(*this, loc, qubits); return *this; @@ -419,9 +417,9 @@ QuartzProgramBuilder& QuartzProgramBuilder::barrier(ValueRange qubits) { // Modifiers //===----------------------------------------------------------------------===// -QuartzProgramBuilder& -QuartzProgramBuilder::ctrl(ValueRange controls, - const std::function& body) { +QCProgramBuilder& +QCProgramBuilder::ctrl(ValueRange controls, + const std::function& body) { checkFinalized(); CtrlOp::create(*this, loc, controls, body); return *this; @@ -431,7 +429,7 @@ QuartzProgramBuilder::ctrl(ValueRange controls, // Deallocation //===----------------------------------------------------------------------===// -QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { +QCProgramBuilder& QCProgramBuilder::dealloc(Value qubit) { checkFinalized(); // Check if the qubit is in the tracking set @@ -452,14 +450,13 @@ QuartzProgramBuilder& QuartzProgramBuilder::dealloc(Value qubit) { // Finalization //===----------------------------------------------------------------------===// -void QuartzProgramBuilder::checkFinalized() const { +void QCProgramBuilder::checkFinalized() const { if (ctx == nullptr) { - llvm::reportFatalUsageError( - "QuartzProgramBuilder instance has been finalized"); + llvm::reportFatalUsageError("QCProgramBuilder instance has been finalized"); } } -OwningOpRef QuartzProgramBuilder::finalize() { +OwningOpRef QCProgramBuilder::finalize() { checkFinalized(); // Ensure that main function exists and insertion point is valid @@ -512,4 +509,4 @@ OwningOpRef QuartzProgramBuilder::finalize() { return module; } -} // namespace mlir::quartz +} // namespace mlir::qc diff --git a/mlir/lib/Dialect/Quartz/CMakeLists.txt b/mlir/lib/Dialect/QC/CMakeLists.txt similarity index 100% rename from mlir/lib/Dialect/Quartz/CMakeLists.txt rename to mlir/lib/Dialect/QC/CMakeLists.txt diff --git a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt b/mlir/lib/Dialect/QC/IR/CMakeLists.txt similarity index 83% rename from mlir/lib/Dialect/Flux/IR/CMakeLists.txt rename to mlir/lib/Dialect/QC/IR/CMakeLists.txt index 7b2a47961f..e76c161d66 100644 --- a/mlir/lib/Dialect/Flux/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/IR/CMakeLists.txt @@ -11,28 +11,28 @@ file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp") file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") add_mlir_dialect_library( - MLIRFluxDialect - FluxOps.cpp + MLIRQCDialect + QCOps.cpp ${MODIFIERS} ${OPERATIONS} ${QUBIT_MANAGEMENT} ADDITIONAL_HEADER_DIRS - ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Flux + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/QC DEPENDS - MLIRFluxOpsIncGen - MLIRFluxInterfacesIncGen + MLIRQCOpsIncGen + MLIRQCInterfacesIncGen LINK_LIBS PUBLIC MLIRIR MLIRSideEffectInterfaces) # collect header files -file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Flux/IR/*.h") -file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/Flux/IR/*.inc") +file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/IR/*.h") +file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QC/IR/*.inc") # add public headers using file sets target_sources( - MLIRFluxDialect + MLIRQCDialect PUBLIC FILE_SET HEADERS BASE_DIRS diff --git a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp similarity index 98% rename from mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp rename to mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp index b386aef369..ecda8cb0a3 100644 --- a/mlir/lib/Dialect/Quartz/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include #include @@ -24,7 +24,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/MeasureOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/MeasureOp.cpp index c9d6ecbf5e..bf2bfeb207 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/MeasureOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/MeasureOp.cpp @@ -8,12 +8,12 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qc; LogicalResult MeasureOp::verify() { const auto registerName = getRegisterName(); diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp similarity index 94% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp index 794c1df9ef..80708dda34 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; size_t BarrierOp::getNumQubits() { return getNumTargets(); } diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/GPhaseOp.cpp similarity index 88% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/GPhaseOp.cpp index 9511e85288..82ead6b8dc 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/GPhaseOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void GPhaseOp::build(OpBuilder& builder, OperationState& state, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/POp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/POp.cpp index 23860fff15..8619fe04c6 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/POp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void POp::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/ROp.cpp similarity index 90% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/ROp.cpp index a9342ebdab..5405e3f764 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/ROp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void ROp::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXOp.cpp index cc50e6bf09..e8d108caed 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RXOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXXOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXXOp.cpp index 682e46461f..dad4d0610a 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RXXOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RXXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYOp.cpp index 304ef93866..973c7fd194 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RYOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYYOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYYOp.cpp index 5fbf16e278..b6550b1f6d 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RYYOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RYYOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZOp.cpp index 6b51d56a96..9f770797e5 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RZOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZXOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZXOp.cpp index 42068e181e..ba9a821cd0 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZXOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RZXOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZZOp.cpp similarity index 89% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZZOp.cpp index cf993078ba..67fe5ce2e7 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/RZZOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void RZZOp::build(OpBuilder& builder, OperationState& state, Value qubit0In, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/U2Op.cpp similarity index 90% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/U2Op.cpp index b7f64d9a74..03251e2157 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/U2Op.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void U2Op::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/UOp.cpp similarity index 91% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/UOp.cpp index 948813b33d..456de9175c 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/UOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void UOp::build(OpBuilder& builder, OperationState& state, Value qubitIn, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXMinusYYOp.cpp similarity index 91% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXMinusYYOp.cpp index 847f24a7be..50f72a4cca 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void XXMinusYYOp::build(OpBuilder& builder, OperationState& state, diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXPlusYYOp.cpp similarity index 91% rename from mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp rename to mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXPlusYYOp.cpp index 4050aa0461..d93f0b3519 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; using namespace mlir::utils; void XXPlusYYOp::build(OpBuilder& builder, OperationState& state, diff --git a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp similarity index 74% rename from mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp rename to mlir/lib/Dialect/QC/IR/QCOps.cpp index f8c8547231..06903c98c9 100644 --- a/mlir/lib/Dialect/Quartz/IR/QuartzOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" // IWYU pragma: associated +#include "mlir/Dialect/QC/IR/QCDialect.h" // IWYU pragma: associated // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -18,25 +18,25 @@ // IWYU pragma: end_keep using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; //===----------------------------------------------------------------------===// // Dialect //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Quartz/IR/QuartzOpsDialect.cpp.inc" +#include "mlir/Dialect/QC/IR/QCOpsDialect.cpp.inc" -void QuartzDialect::initialize() { +void QCDialect::initialize() { // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape) addTypes< #define GET_TYPEDEF_LIST -#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" +#include "mlir/Dialect/QC/IR/QCOpsTypes.cpp.inc" >(); addOperations< #define GET_OP_LIST -#include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" +#include "mlir/Dialect/QC/IR/QCOps.cpp.inc" >(); } @@ -46,17 +46,17 @@ void QuartzDialect::initialize() { //===----------------------------------------------------------------------===// #define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Quartz/IR/QuartzOpsTypes.cpp.inc" +#include "mlir/Dialect/QC/IR/QCOpsTypes.cpp.inc" //===----------------------------------------------------------------------===// // Interfaces //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Quartz/IR/QuartzInterfaces.cpp.inc" +#include "mlir/Dialect/QC/IR/QCInterfaces.cpp.inc" //===----------------------------------------------------------------------===// // Operations //===----------------------------------------------------------------------===// #define GET_OP_CLASSES -#include "mlir/Dialect/Quartz/IR/QuartzOps.cpp.inc" +#include "mlir/Dialect/QC/IR/QCOps.cpp.inc" diff --git a/mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp b/mlir/lib/Dialect/QC/IR/QubitManagement/AllocOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp rename to mlir/lib/Dialect/QC/IR/QubitManagement/AllocOp.cpp index 7885ed146d..2048f4cc65 100644 --- a/mlir/lib/Dialect/Flux/IR/QubitManagement/AllocOp.cpp +++ b/mlir/lib/Dialect/QC/IR/QubitManagement/AllocOp.cpp @@ -8,12 +8,12 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qc; LogicalResult AllocOp::verify() { const auto registerName = getRegisterName(); diff --git a/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/QC/IR/QubitManagement/DeallocOp.cpp similarity index 94% rename from mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp rename to mlir/lib/Dialect/QC/IR/QubitManagement/DeallocOp.cpp index 91b7bedbe3..6fea22d66b 100644 --- a/mlir/lib/Dialect/Quartz/IR/QubitManagement/DeallocOp.cpp +++ b/mlir/lib/Dialect/QC/IR/QubitManagement/DeallocOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" #include #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qc; namespace { diff --git a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt similarity index 57% rename from mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt rename to mlir/lib/Dialect/QC/Translation/CMakeLists.txt index 70ef2a68bb..5414559cb6 100644 --- a/mlir/lib/Dialect/Quartz/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt @@ -9,20 +9,19 @@ add_compile_options("-fexceptions") add_mlir_library( - MLIRQuartzTranslation - TranslateQuantumComputationToQuartz.cpp + MLIRQCTranslation + TranslateQuantumComputationToQC.cpp LINK_LIBS MLIRArithDialect MLIRFuncDialect MLIRSCFDialect - MLIRQuartzDialect + MLIRQCDialect MQT::CoreIR) # collect header files file(GLOB_RECURSE TRANSLATION_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/Translation/*.h) + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/Translation/*.h) # add public headers using file sets -target_sources( - MLIRQuartzTranslation PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${TRANSLATION_HEADERS_SOURCE}) +target_sources(MLIRQCTranslation PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES ${TRANSLATION_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp similarity index 70% rename from mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp rename to mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index 16e47b1fe6..e12d3a897b 100644 --- a/mlir/lib/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" +#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" #include "ir/QuantumComputation.hpp" #include "ir/Register.hpp" @@ -16,7 +16,7 @@ #include "ir/operations/NonUnitaryOperation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/Operation.hpp" -#include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" #include #include @@ -35,7 +35,7 @@ namespace mlir { -using namespace quartz; +using namespace qc; namespace { @@ -44,35 +44,35 @@ namespace { * * @details * Maps quantum registers from the input QuantumComputation to their - * corresponding MLIR qubit values allocated via the QuartzProgramBuilder. + * corresponding MLIR qubit values allocated via the QCProgramBuilder. */ struct QregInfo { - const qc::QuantumRegister* qregPtr; + const ::qc::QuantumRegister* qregPtr; SmallVector qubits; }; -using BitMemInfo = std::pair; // (register ref, localIdx) using BitIndexVec = SmallVector; /** - * @brief Allocates quantum registers using the QuartzProgramBuilder + * @brief Allocates quantum registers using the QCProgramBuilder * * @details * Processes all quantum and ancilla registers from the QuantumComputation, * sorting them by start index, and allocates them using the builder's - * allocQubitRegister method which generates quartz.alloc operations with + * allocQubitRegister method which generates qc.alloc operations with * proper register metadata. * - * @param builder The QuartzProgramBuilder used to create operations + * @param builder The QCProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate * @return Vector containing information about all quantum registers */ SmallVector -allocateQregs(QuartzProgramBuilder& builder, - const qc::QuantumComputation& quantumComputation) { +allocateQregs(QCProgramBuilder& builder, + const ::qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - SmallVector qregPtrs; + SmallVector qregPtrs; qregPtrs.reserve(quantumComputation.getQuantumRegisters().size() + quantumComputation.getAncillaRegisters().size()); for (const auto& qreg : @@ -85,10 +85,10 @@ allocateQregs(QuartzProgramBuilder& builder, } // Sort by start index - std::ranges::sort( - qregPtrs, [](const qc::QuantumRegister* a, const qc::QuantumRegister* b) { - return a->getStartIndex() < b->getStartIndex(); - }); + std::ranges::sort(qregPtrs, [](const ::qc::QuantumRegister* a, + const ::qc::QuantumRegister* b) { + return a->getStartIndex() < b->getStartIndex(); + }); // Allocate quantum registers using the builder SmallVector qregs; @@ -114,7 +114,7 @@ allocateQregs(QuartzProgramBuilder& builder, * @return Flat vector of qubit values indexed by physical qubit index */ SmallVector -buildQubitMap(const qc::QuantumComputation& quantumComputation, +buildQubitMap(const ::qc::QuantumComputation& quantumComputation, const SmallVector& qregs) { SmallVector flatQubits; const auto maxPhys = quantumComputation.getHighestPhysicalQubitIndex(); @@ -131,22 +131,22 @@ buildQubitMap(const qc::QuantumComputation& quantumComputation, } /** - * @brief Allocates classical registers using the QuartzProgramBuilder + * @brief Allocates classical registers using the QCProgramBuilder * * @details * Creates classical bit registers and builds a mapping from global classical * bit indices to (register, local_index) pairs. This is used for measurement * result storage. * - * @param builder The QuartzProgramBuilder used to create operations + * @param builder The QCProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate * @return Vector mapping global bit indices to register and local indices */ BitIndexVec -allocateClassicalRegisters(QuartzProgramBuilder& builder, - const qc::QuantumComputation& quantumComputation) { +allocateClassicalRegisters(QCProgramBuilder& builder, + const ::qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting - SmallVector cregPtrs; + SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); for (const auto& reg : quantumComputation.getClassicalRegisters() | std::views::values) { @@ -154,8 +154,8 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, } // Sort by start index - std::ranges::sort(cregPtrs, [](const qc::ClassicalRegister* a, - const qc::ClassicalRegister* b) { + std::ranges::sort(cregPtrs, [](const ::qc::ClassicalRegister* a, + const ::qc::ClassicalRegister* b) { return a->getStartIndex() < b->getStartIndex(); }); @@ -179,17 +179,17 @@ allocateClassicalRegisters(QuartzProgramBuilder& builder, * * @details * Translates measurement operations from the QuantumComputation to - * quartz.measure operations, storing results in classical registers. + * qc.measure operations, storing results in classical registers. * - * @param builder The QuartzProgramBuilder used to create operations + * @param builder The QCProgramBuilder used to create operations * @param operation The measurement operation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index * @param bitMap Mapping from global bit index to (register, local_index) */ -void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, +void addMeasureOp(QCProgramBuilder& builder, const ::qc::Operation& operation, const SmallVector& qubits, const BitIndexVec& bitMap) { const auto& measureOp = - dynamic_cast(operation); + dynamic_cast(operation); const auto& targets = measureOp.getTargets(); const auto& classics = measureOp.getClassics(); @@ -208,13 +208,13 @@ void addMeasureOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * * @details * Translates reset operations from the QuantumComputation to - * quartz.reset operations. + * qc.reset operations. * - * @param builder The QuartzProgramBuilder used to create operations + * @param builder The QCProgramBuilder used to create operations * @param operation The reset operation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index */ -void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, +void addResetOp(QCProgramBuilder& builder, const ::qc::Operation& operation, const SmallVector& qubits) { for (const auto& target : operation.getTargets()) { auto qubit = qubits[target]; @@ -233,11 +233,11 @@ void addResetOp(QuartzProgramBuilder& builder, const qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index * @return Vector of qubit values corresponding to positive controls */ -SmallVector getPosControls(const qc::Operation& operation, +SmallVector getPosControls(const ::qc::Operation& operation, const SmallVector& qubits) { SmallVector controls; for (const auto& [control, type] : operation.getControls()) { - if (type == qc::Control::Type::Neg) { + if (type == ::qc::Control::Type::Neg) { continue; } controls.push_back(qubits[control]); @@ -247,27 +247,27 @@ SmallVector getPosControls(const qc::Operation& operation, // OneTargetZeroParameter -#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(target); \ + builder.OP_QC(target); \ } else { \ - builder.mc##OP_QUARTZ(posControls, target); \ + builder.mc##OP_QC(posControls, target); \ } \ } @@ -287,28 +287,28 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) // OneTargetOneParameter -#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(param, target); \ + builder.OP_QC(param, target); \ } else { \ - builder.mc##OP_QUARTZ(param, posControls, target); \ + builder.mc##OP_QC(param, posControls, target); \ } \ } @@ -319,29 +319,29 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) // OneTargetTwoParameter -#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(param1, param2, target); \ + builder.OP_QC(param1, param2, target); \ } else { \ - builder.mc##OP_QUARTZ(param1, param2, posControls, target); \ + builder.mc##OP_QC(param1, param2, posControls, target); \ } \ } @@ -352,30 +352,30 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) // OneTargetThreeParameter -#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& param3 = operation.getParameter()[2]; \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(param1, param2, param3, target); \ + builder.OP_QC(param1, param2, param3, target); \ } else { \ - builder.mc##OP_QUARTZ(param1, param2, param3, posControls, target); \ + builder.mc##OP_QC(param1, param2, param3, posControls, target); \ } \ } @@ -385,28 +385,28 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) // TwoTargetZeroParameter -#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(target0, target1); \ + builder.OP_QC(target0, target1); \ } else { \ - builder.mc##OP_QUARTZ(posControls, target0, target1); \ + builder.mc##OP_QC(posControls, target0, target1); \ } \ } @@ -419,29 +419,29 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) // TwoTargetOneParameter -#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(param, target0, target1); \ + builder.OP_QC(param, target0, target1); \ } else { \ - builder.mc##OP_QUARTZ(param, posControls, target0, target1); \ + builder.mc##OP_QC(param, posControls, target0, target1); \ } \ } @@ -454,30 +454,30 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) // TwoTargetTwoParameter -#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_QC, OP_QUARTZ) \ +#define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CORE, OP_QC) \ /** \ - * @brief Adds a quartz.OP_QUARTZ operation \ + * @brief Adds a qc.OP_QC operation \ * \ * @details \ - * Translates an OP_QC operation from the QuantumComputation to \ - * quartz.OP_QUARTZ. \ + * Translates an OP_CORE operation from the QuantumComputation to \ + * qc.OP_QC. \ * \ - * @param builder The QuartzProgramBuilder used to create operations \ - * @param operation The OP_QC operation to translate \ + * @param builder The QCProgramBuilder used to create operations \ + * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_QC##Op(QuartzProgramBuilder& builder, \ - const qc::Operation& operation, \ - const SmallVector& qubits) { \ + void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ - builder.OP_QUARTZ(param1, param2, target0, target1); \ + builder.OP_QC(param1, param2, target0, target1); \ } else { \ - builder.mc##OP_QUARTZ(param1, param2, posControls, target0, target1); \ + builder.mc##OP_QC(param1, param2, posControls, target0, target1); \ } \ } @@ -488,7 +488,7 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) // BarrierOp -void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, +void addBarrierOp(QCProgramBuilder& builder, const ::qc::Operation& operation, const SmallVector& qubits) { SmallVector targets; for (const auto& targetIdx : operation.getTargets()) { @@ -497,27 +497,27 @@ void addBarrierOp(QuartzProgramBuilder& builder, const qc::Operation& operation, builder.barrier(targets); } -#define ADD_OP_CASE(OP_QC) \ - case qc::OpType::OP_QC: \ - add##OP_QC##Op(builder, *operation, qubits); \ +#define ADD_OP_CASE(OP_CORE) \ + case ::qc::OpType::OP_CORE: \ + add##OP_CORE##Op(builder, *operation, qubits); \ break; /** - * @brief Translates operations from QuantumComputation to Quartz dialect + * @brief Translates operations from QuantumComputation to QC dialect * * @details * Iterates through all operations in the QuantumComputation and translates - * them to Quartz operations. + * them to QC operations. * - * @param builder The QuartzProgramBuilder used to create operations + * @param builder The QCProgramBuilder used to create operations * @param quantumComputation The quantum computation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index * @param bitMap Mapping from global bit index to (register, local_index) * @return Success if all supported operations were translated */ LogicalResult -translateOperations(QuartzProgramBuilder& builder, - const qc::QuantumComputation& quantumComputation, +translateOperations(QCProgramBuilder& builder, + const ::qc::QuantumComputation& quantumComputation, const SmallVector& qubits, const BitIndexVec& bitMap) { if (quantumComputation.hasGlobalPhase()) { @@ -525,7 +525,7 @@ translateOperations(QuartzProgramBuilder& builder, } for (const auto& operation : quantumComputation) { switch (operation->getType()) { - case qc::OpType::Measure: + case ::qc::OpType::Measure: addMeasureOp(builder, *operation, qubits, bitMap); break; ADD_OP_CASE(Reset) @@ -559,8 +559,7 @@ translateOperations(QuartzProgramBuilder& builder, ADD_OP_CASE(XXminusYY) ADD_OP_CASE(Barrier) default: - llvm::errs() << operation->getName() - << " cannot be translated to Quartz\n"; + llvm::errs() << operation->getName() << " cannot be translated to QC\n"; return failure(); } } @@ -573,19 +572,19 @@ translateOperations(QuartzProgramBuilder& builder, } // namespace /** - * @brief Translates a QuantumComputation to an MLIR module with Quartz + * @brief Translates a QuantumComputation to an MLIR module with QC * operations * * @details * This function takes a quantum computation and translates it into an MLIR - * module containing Quartz operations. It uses the QuartzProgramBuilder to + * module containing QC operations. It uses the QCProgramBuilder to * handle module and function creation, resource allocation, and operation * translation. * * The translation process: - * 1. Creates a QuartzProgramBuilder and initializes it (creates the main + * 1. Creates a QCProgramBuilder and initializes it (creates the main * function) - * 2. Allocates quantum registers using quartz.alloc + * 2. Allocates quantum registers using qc.alloc * 3. Tracks classical registers for measurement results * 4. Translates operations * 5. Finalizes the module (adds return statement with exit code 0) @@ -597,10 +596,10 @@ translateOperations(QuartzProgramBuilder& builder, * @param quantumComputation The quantum computation to translate * @return OwningOpRef containing the translated MLIR module */ -OwningOpRef translateQuantumComputationToQuartz( - MLIRContext* context, const qc::QuantumComputation& quantumComputation) { +OwningOpRef translateQuantumComputationToQC( + MLIRContext* context, const ::qc::QuantumComputation& quantumComputation) { // Create and initialize the builder (creates module and main function) - QuartzProgramBuilder builder(context); + QCProgramBuilder builder(context); builder.initialize(); // Allocate quantum registers using the builder @@ -616,7 +615,7 @@ OwningOpRef translateQuantumComputationToQuartz( if (translateOperations(builder, quantumComputation, qubits, bitMap) .failed()) { llvm::reportFatalInternalError( - "Failed to translate QuantumComputation to Quartz"); + "Failed to translate QuantumComputation to QC"); } // Finalize and return the module (adds return statement and transfers diff --git a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt b/mlir/lib/Dialect/QCO/Builder/CMakeLists.txt similarity index 59% rename from mlir/lib/Dialect/Flux/Builder/CMakeLists.txt rename to mlir/lib/Dialect/QCO/Builder/CMakeLists.txt index c9acd94cc7..6446970949 100644 --- a/mlir/lib/Dialect/Flux/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Builder/CMakeLists.txt @@ -7,20 +7,20 @@ # Licensed under the MIT License add_mlir_library( - MLIRFluxProgramBuilder - FluxProgramBuilder.cpp + MLIRQCOProgramBuilder + QCOProgramBuilder.cpp LINK_LIBS PUBLIC MLIRArithDialect MLIRFuncDialect MLIRSCFDialect - MLIRFluxDialect) + MLIRQCODialect) # collect header files file(GLOB_RECURSE BUILDER_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Flux/Builder/*.h) + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Builder/*.h) # add public headers using file sets target_sources( - MLIRFluxProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${BUILDER_HEADERS_SOURCE}) + MLIRQCOProgramBuilder PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${BUILDER_HEADERS_SOURCE}) diff --git a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp similarity index 88% rename from mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp rename to mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 71c9977c89..c9ea8dc2ce 100644 --- a/mlir/lib/Dialect/Flux/Builder/FluxProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -8,9 +8,9 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -30,15 +30,15 @@ #include #include -namespace mlir::flux { +namespace mlir::qco { -FluxProgramBuilder::FluxProgramBuilder(MLIRContext* context) +QCOProgramBuilder::QCOProgramBuilder(MLIRContext* context) : OpBuilder(context), ctx(context), loc(getUnknownLoc()), module(ModuleOp::create(loc)) { - ctx->loadDialect(); + ctx->loadDialect(); } -void FluxProgramBuilder::initialize() { +void QCOProgramBuilder::initialize() { // Set insertion point to the module body setInsertionPointToStart(module.getBody()); @@ -55,7 +55,7 @@ void FluxProgramBuilder::initialize() { setInsertionPointToStart(&entryBlock); } -Value FluxProgramBuilder::allocQubit() { +Value QCOProgramBuilder::allocQubit() { checkFinalized(); auto allocOp = AllocOp::create(*this, loc); @@ -67,7 +67,7 @@ Value FluxProgramBuilder::allocQubit() { return qubit; } -Value FluxProgramBuilder::staticQubit(const int64_t index) { +Value QCOProgramBuilder::staticQubit(const int64_t index) { checkFinalized(); if (index < 0) { @@ -85,8 +85,8 @@ Value FluxProgramBuilder::staticQubit(const int64_t index) { } llvm::SmallVector -FluxProgramBuilder::allocQubitRegister(const int64_t size, - const std::string& name) { +QCOProgramBuilder::allocQubitRegister(const int64_t size, + const std::string& name) { checkFinalized(); if (size <= 0) { @@ -110,9 +110,9 @@ FluxProgramBuilder::allocQubitRegister(const int64_t size, return qubits; } -FluxProgramBuilder::ClassicalRegister -FluxProgramBuilder::allocClassicalBitRegister(const int64_t size, - std::string name) const { +QCOProgramBuilder::ClassicalRegister +QCOProgramBuilder::allocClassicalBitRegister(const int64_t size, + std::string name) const { checkFinalized(); if (size <= 0) { @@ -126,7 +126,7 @@ FluxProgramBuilder::allocClassicalBitRegister(const int64_t size, // Linear Type Tracking Helpers //===----------------------------------------------------------------------===// -void FluxProgramBuilder::validateQubitValue(Value qubit) const { +void QCOProgramBuilder::validateQubitValue(Value qubit) const { if (!validQubits.contains(qubit)) { llvm::errs() << "Attempting to use an invalid qubit SSA value. " << "The value may have been consumed by a previous operation " @@ -136,8 +136,8 @@ void FluxProgramBuilder::validateQubitValue(Value qubit) const { } } -void FluxProgramBuilder::updateQubitTracking(Value inputQubit, - Value outputQubit) { +void QCOProgramBuilder::updateQubitTracking(Value inputQubit, + Value outputQubit) { // Validate the input qubit validateQubitValue(inputQubit); @@ -152,7 +152,7 @@ void FluxProgramBuilder::updateQubitTracking(Value inputQubit, // Measurement and Reset //===----------------------------------------------------------------------===// -std::pair FluxProgramBuilder::measure(Value qubit) { +std::pair QCOProgramBuilder::measure(Value qubit) { checkFinalized(); auto measureOp = MeasureOp::create(*this, loc, qubit); @@ -165,7 +165,7 @@ std::pair FluxProgramBuilder::measure(Value qubit) { return {qubitOut, result}; } -Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { +Value QCOProgramBuilder::measure(Value qubit, const Bit& bit) { checkFinalized(); auto nameAttr = getStringAttr(bit.registerName); @@ -181,7 +181,7 @@ Value FluxProgramBuilder::measure(Value qubit, const Bit& bit) { return qubitOut; } -Value FluxProgramBuilder::reset(Value qubit) { +Value QCOProgramBuilder::reset(Value qubit) { checkFinalized(); auto resetOp = ResetOp::create(*this, loc, qubit); @@ -200,12 +200,11 @@ Value FluxProgramBuilder::reset(Value qubit) { // ZeroTargetOneParameter #define DEFINE_ZERO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ - void FluxProgramBuilder::OP_NAME( \ - const std::variant&(PARAM)) { \ + void QCOProgramBuilder::OP_NAME(const std::variant&(PARAM)) { \ checkFinalized(); \ OP_CLASS::create(*this, loc, PARAM); \ } \ - Value FluxProgramBuilder::c##OP_NAME( \ + Value QCOProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control) { \ checkFinalized(); \ const auto controlsOut = \ @@ -217,7 +216,7 @@ Value FluxProgramBuilder::reset(Value qubit) { .first; \ return controlsOut[0]; \ } \ - ValueRange FluxProgramBuilder::mc##OP_NAME( \ + ValueRange QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls) { \ checkFinalized(); \ const auto controlsOut = \ @@ -237,15 +236,15 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) // OneTargetZeroParameter #define DEFINE_ONE_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ - Value FluxProgramBuilder::OP_NAME(Value qubit) { \ + Value QCOProgramBuilder::OP_NAME(Value qubit) { \ checkFinalized(); \ auto op = OP_CLASS::create(*this, loc, qubit); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ } \ - std::pair FluxProgramBuilder::c##OP_NAME(Value control, \ - Value target) { \ + std::pair QCOProgramBuilder::c##OP_NAME(Value control, \ + Value target) { \ checkFinalized(); \ const auto [controlsOut, targetsOut] = ctrl( \ control, target, [&](OpBuilder& b, ValueRange targets) -> ValueRange { \ @@ -254,7 +253,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) }); \ return {controlsOut[0], targetsOut[0]}; \ } \ - std::pair FluxProgramBuilder::mc##OP_NAME( \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value target) { \ checkFinalized(); \ const auto [controlsOut, targetsOut] = \ @@ -283,15 +282,15 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) // OneTargetOneParameter #define DEFINE_ONE_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ - Value FluxProgramBuilder::OP_NAME(const std::variant&(PARAM), \ - Value qubit) { \ + Value QCOProgramBuilder::OP_NAME(const std::variant&(PARAM), \ + Value qubit) { \ checkFinalized(); \ auto op = OP_CLASS::create(*this, loc, qubit, PARAM); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ } \ - std::pair FluxProgramBuilder::c##OP_NAME( \ + std::pair QCOProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, \ Value target) { \ checkFinalized(); \ @@ -302,7 +301,7 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) }); \ return {controlsOut[0], targetsOut[0]}; \ } \ - std::pair FluxProgramBuilder::mc##OP_NAME( \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value target) { \ checkFinalized(); \ @@ -325,16 +324,16 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) // OneTargetTwoParameter #define DEFINE_ONE_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ - Value FluxProgramBuilder::OP_NAME( \ - const std::variant&(PARAM1), \ - const std::variant&(PARAM2), Value qubit) { \ + Value QCOProgramBuilder::OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + Value qubit) { \ checkFinalized(); \ auto op = OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ } \ - std::pair FluxProgramBuilder::c##OP_NAME( \ + std::pair QCOProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, \ Value target) { \ @@ -347,7 +346,7 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, phi) }); \ return {controlsOut[0], targetsOut[0]}; \ } \ - std::pair FluxProgramBuilder::mc##OP_NAME( \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value target) { \ @@ -371,17 +370,17 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) #define DEFINE_ONE_TARGET_THREE_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2, \ PARAM3) \ - Value FluxProgramBuilder::OP_NAME( \ - const std::variant&(PARAM1), \ - const std::variant&(PARAM2), \ - const std::variant&(PARAM3), Value qubit) { \ + Value QCOProgramBuilder::OP_NAME(const std::variant&(PARAM1), \ + const std::variant&(PARAM2), \ + const std::variant&(PARAM3), \ + Value qubit) { \ checkFinalized(); \ auto op = OP_CLASS::create(*this, loc, qubit, PARAM1, PARAM2, PARAM3); \ const auto& qubitOut = op.getQubitOut(); \ updateQubitTracking(qubit, qubitOut); \ return qubitOut; \ } \ - std::pair FluxProgramBuilder::c##OP_NAME( \ + std::pair QCOProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), Value control, \ @@ -395,7 +394,7 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) }); \ return {controlsOut[0], targetsOut[0]}; \ } \ - std::pair FluxProgramBuilder::mc##OP_NAME( \ + std::pair QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), \ const std::variant&(PARAM3), ValueRange controls, \ @@ -418,8 +417,8 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) // TwoTargetZeroParameter #define DEFINE_TWO_TARGET_ZERO_PARAMETER(OP_CLASS, OP_NAME) \ - std::pair FluxProgramBuilder::OP_NAME(Value qubit0, \ - Value qubit1) { \ + std::pair QCOProgramBuilder::OP_NAME(Value qubit0, \ + Value qubit1) { \ checkFinalized(); \ auto op = OP_CLASS::create(*this, loc, qubit0, qubit1); \ const auto& qubit0Out = op.getQubit0Out(); \ @@ -428,7 +427,7 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) updateQubitTracking(qubit1, qubit1Out); \ return {qubit0Out, qubit1Out}; \ } \ - std::pair> FluxProgramBuilder::c##OP_NAME( \ + std::pair> QCOProgramBuilder::c##OP_NAME( \ Value control, Value qubit0, Value qubit1) { \ checkFinalized(); \ const auto [controlsOut, targetsOut] = \ @@ -441,8 +440,8 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ - FluxProgramBuilder::mc##OP_NAME(ValueRange controls, Value qubit0, \ - Value qubit1) { \ + QCOProgramBuilder::mc##OP_NAME(ValueRange controls, Value qubit0, \ + Value qubit1) { \ checkFinalized(); \ const auto [controlsOut, targetsOut] = \ ctrl(controls, {qubit0, qubit1}, \ @@ -464,7 +463,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) // TwoTargetOneParameter #define DEFINE_TWO_TARGET_ONE_PARAMETER(OP_CLASS, OP_NAME, PARAM) \ - std::pair FluxProgramBuilder::OP_NAME( \ + std::pair QCOProgramBuilder::OP_NAME( \ const std::variant&(PARAM), Value qubit0, Value qubit1) { \ checkFinalized(); \ auto op = OP_CLASS::create(*this, loc, qubit0, qubit1, PARAM); \ @@ -474,7 +473,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) updateQubitTracking(qubit1, qubit1Out); \ return {qubit0Out, qubit1Out}; \ } \ - std::pair> FluxProgramBuilder::c##OP_NAME( \ + std::pair> QCOProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM), Value control, Value qubit0, \ Value qubit1) { \ checkFinalized(); \ @@ -488,7 +487,7 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ - FluxProgramBuilder::mc##OP_NAME( \ + QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM), ValueRange controls, \ Value qubit0, Value qubit1) { \ checkFinalized(); \ @@ -512,7 +511,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) // TwoTargetTwoParameter #define DEFINE_TWO_TARGET_TWO_PARAMETER(OP_CLASS, OP_NAME, PARAM1, PARAM2) \ - std::pair FluxProgramBuilder::OP_NAME( \ + std::pair QCOProgramBuilder::OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value qubit0, \ Value qubit1) { \ @@ -524,7 +523,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) updateQubitTracking(qubit1, qubit1Out); \ return {qubit0Out, qubit1Out}; \ } \ - std::pair> FluxProgramBuilder::c##OP_NAME( \ + std::pair> QCOProgramBuilder::c##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), Value control, Value qubit0, \ Value qubit1) { \ @@ -539,7 +538,7 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) return {controlsOut[0], {targetsOut[0], targetsOut[1]}}; \ } \ std::pair> \ - FluxProgramBuilder::mc##OP_NAME( \ + QCOProgramBuilder::mc##OP_NAME( \ const std::variant&(PARAM1), \ const std::variant&(PARAM2), ValueRange controls, \ Value qubit0, Value qubit1) { \ @@ -561,7 +560,7 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXMinusYYOp, xx_minus_yy, theta, beta) // BarrierOp -ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { +ValueRange QCOProgramBuilder::barrier(ValueRange qubits) { checkFinalized(); auto op = BarrierOp::create(*this, loc, qubits); @@ -576,7 +575,7 @@ ValueRange FluxProgramBuilder::barrier(ValueRange qubits) { // Modifiers //===----------------------------------------------------------------------===// -std::pair FluxProgramBuilder::ctrl( +std::pair QCOProgramBuilder::ctrl( ValueRange controls, ValueRange targets, const std::function& body) { checkFinalized(); @@ -600,7 +599,7 @@ std::pair FluxProgramBuilder::ctrl( // Deallocation //===----------------------------------------------------------------------===// -FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { +QCOProgramBuilder& QCOProgramBuilder::dealloc(Value qubit) { checkFinalized(); validateQubitValue(qubit); @@ -615,14 +614,14 @@ FluxProgramBuilder& FluxProgramBuilder::dealloc(Value qubit) { // Finalization //===----------------------------------------------------------------------===// -void FluxProgramBuilder::checkFinalized() const { +void QCOProgramBuilder::checkFinalized() const { if (ctx == nullptr) { llvm::reportFatalUsageError( - "FluxProgramBuilder instance has been finalized"); + "QCOProgramBuilder instance has been finalized"); } } -OwningOpRef FluxProgramBuilder::finalize() { +OwningOpRef QCOProgramBuilder::finalize() { checkFinalized(); // Ensure that main function exists and insertion point is valid @@ -672,4 +671,4 @@ OwningOpRef FluxProgramBuilder::finalize() { return module; } -} // namespace mlir::flux +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/Flux/CMakeLists.txt b/mlir/lib/Dialect/QCO/CMakeLists.txt similarity index 100% rename from mlir/lib/Dialect/Flux/CMakeLists.txt rename to mlir/lib/Dialect/QCO/CMakeLists.txt diff --git a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt b/mlir/lib/Dialect/QCO/IR/CMakeLists.txt similarity index 82% rename from mlir/lib/Dialect/Quartz/IR/CMakeLists.txt rename to mlir/lib/Dialect/QCO/IR/CMakeLists.txt index a97a13acc6..567a80611b 100644 --- a/mlir/lib/Dialect/Quartz/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/IR/CMakeLists.txt @@ -11,28 +11,28 @@ file(GLOB_RECURSE OPERATIONS "${CMAKE_CURRENT_SOURCE_DIR}/Operations/*.cpp") file(GLOB_RECURSE QUBIT_MANAGEMENT "${CMAKE_CURRENT_SOURCE_DIR}/QubitManagement/*.cpp") add_mlir_dialect_library( - MLIRQuartzDialect - QuartzOps.cpp + MLIRQCODialect + QCOOps.cpp ${MODIFIERS} ${OPERATIONS} ${QUBIT_MANAGEMENT} ADDITIONAL_HEADER_DIRS - ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/Quartz + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/QCO DEPENDS - MLIRQuartzOpsIncGen - MLIRQuartzInterfacesIncGen + MLIRQCOOpsIncGen + MLIRQCOInterfacesIncGen LINK_LIBS PUBLIC MLIRIR MLIRSideEffectInterfaces) # collect header files -file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/Quartz/IR/*.h") -file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/Quartz/IR/*.inc") +file(GLOB_RECURSE IR_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/IR/*.h") +file(GLOB_RECURSE IR_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QCO/IR/*.inc") # add public headers using file sets target_sources( - MLIRQuartzDialect + MLIRQCODialect PUBLIC FILE_SET HEADERS BASE_DIRS diff --git a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp similarity index 99% rename from mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp rename to mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index c15fd8e37c..8ca6c0db89 100644 --- a/mlir/lib/Dialect/Flux/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -24,7 +24,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp similarity index 92% rename from mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp index 30a6eed8c5..7910f6c043 100644 --- a/mlir/lib/Dialect/Quartz/IR/Operations/MeasureOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp @@ -8,12 +8,12 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qco; LogicalResult MeasureOp::verify() { const auto registerName = getRegisterName(); diff --git a/mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp similarity index 94% rename from mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp index 489e667db5..861b6ca331 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/ResetOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp similarity index 98% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp index f5a1106c23..012490645c 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -22,7 +22,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ECROp.cpp similarity index 89% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ECROp.cpp index b5b416c103..fb733e5fdb 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ECROp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ECROp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/GPhaseOp.cpp similarity index 95% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/GPhaseOp.cpp index c57ad97ed3..2078d40ce5 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/GPhaseOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/GPhaseOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -21,7 +21,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/HOp.cpp similarity index 89% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/HOp.cpp index a53a77e8e1..3b8aa08cd6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/HOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/HOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/IdOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/IdOp.cpp index 245102810d..7bd721efb8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/IdOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/IdOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/POp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/POp.cpp index 9fefc91e86..776092ce99 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/POp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/POp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ROp.cpp similarity index 97% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ROp.cpp index 63004e44e7..eee14013b6 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ROp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ROp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -22,7 +22,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXOp.cpp index 2cc700e73b..3be4877abd 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXXOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXXOp.cpp index edacf6ed08..b3232e11d8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RXXOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RXXOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYOp.cpp index 60c760766d..8cc3047c2e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYYOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYYOp.cpp index 0eb31330cc..bf8ad965b7 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RYYOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RYYOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZOp.cpp index c65c77ad89..92bfe37b71 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZXOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZXOp.cpp index 5bbd5cb7ac..c1fcdaaa51 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZXOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZXOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZZOp.cpp similarity index 93% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZZOp.cpp index 8791f30d38..df5e35f5fd 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/RZZOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/RZZOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -20,7 +20,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SOp.cpp index b3636af804..85a98cf235 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SWAPOp.cpp similarity index 89% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SWAPOp.cpp index f795578a31..8946bd107f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SWAPOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SWAPOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXOp.cpp index 818f1af953..898c5a77d8 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXdgOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXdgOp.cpp index cd662e2211..f82bc7889f 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SXdgOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SXdgOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SdgOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SdgOp.cpp index 0b620d55d3..01e64a1399 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/SdgOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/SdgOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TOp.cpp index e0fd1f70e8..6f692a0d89 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TdgOp.cpp similarity index 92% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TdgOp.cpp index c43e162762..301d8e1477 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/TdgOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/TdgOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/U2Op.cpp similarity index 98% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/U2Op.cpp index a34341fa3f..f576f2ba57 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/U2Op.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/U2Op.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -21,7 +21,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/UOp.cpp similarity index 98% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/UOp.cpp index 36f66fc4ae..422c785bf4 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/UOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/UOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -22,7 +22,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XOp.cpp similarity index 89% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XOp.cpp index 06ed2a74ca..8ae30880be 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp similarity index 97% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp index d362467458..2ac73ec8bf 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXMinusYYOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -22,7 +22,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp similarity index 97% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp index b58683122f..edae5d24f3 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/XXPlusYYOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/Utils/Utils.h" #include @@ -21,7 +21,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; using namespace mlir::utils; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/YOp.cpp similarity index 89% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/YOp.cpp index 5946636c18..b498928b4e 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/YOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/YOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ZOp.cpp similarity index 89% rename from mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp rename to mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ZOp.cpp index 16bd3f5d62..a8d86e7082 100644 --- a/mlir/lib/Dialect/Flux/IR/Operations/StandardGates/ZOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/ZOp.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/FluxUtils.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/QCOUtils.h" #include #include @@ -17,7 +17,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp similarity index 76% rename from mlir/lib/Dialect/Flux/IR/FluxOps.cpp rename to mlir/lib/Dialect/QCO/IR/QCOOps.cpp index b1c7527174..c5307f94a2 100644 --- a/mlir/lib/Dialect/Flux/IR/FluxOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" // IWYU pragma: associated +#include "mlir/Dialect/QCO/IR/QCODialect.h" // IWYU pragma: associated // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -19,24 +19,24 @@ // IWYU pragma: end_keep using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; //===----------------------------------------------------------------------===// // Dialect //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Flux/IR/FluxOpsDialect.cpp.inc" +#include "mlir/Dialect/QCO/IR/QCOOpsDialect.cpp.inc" -void FluxDialect::initialize() { +void QCODialect::initialize() { // NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape) addTypes< #define GET_TYPEDEF_LIST -#include "mlir/Dialect/Flux/IR/FluxOpsTypes.cpp.inc" +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.cpp.inc" >(); addOperations< #define GET_OP_LIST -#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" +#include "mlir/Dialect/QCO/IR/QCOOps.cpp.inc" >(); } @@ -45,17 +45,17 @@ void FluxDialect::initialize() { //===----------------------------------------------------------------------===// #define GET_TYPEDEF_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOpsTypes.cpp.inc" +#include "mlir/Dialect/QCO/IR/QCOOpsTypes.cpp.inc" //===----------------------------------------------------------------------===// // Interfaces //===----------------------------------------------------------------------===// -#include "mlir/Dialect/Flux/IR/FluxInterfaces.cpp.inc" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.cpp.inc" //===----------------------------------------------------------------------===// // Operations //===----------------------------------------------------------------------===// #define GET_OP_CLASSES -#include "mlir/Dialect/Flux/IR/FluxOps.cpp.inc" +#include "mlir/Dialect/QCO/IR/QCOOps.cpp.inc" diff --git a/mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/AllocOp.cpp similarity index 92% rename from mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp rename to mlir/lib/Dialect/QCO/IR/QubitManagement/AllocOp.cpp index b49bc7aa0b..6a2f36a259 100644 --- a/mlir/lib/Dialect/Quartz/IR/QubitManagement/AllocOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/AllocOp.cpp @@ -8,12 +8,12 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include using namespace mlir; -using namespace mlir::quartz; +using namespace mlir::qco; LogicalResult AllocOp::verify() { const auto registerName = getRegisterName(); diff --git a/mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp similarity index 94% rename from mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp rename to mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp index 18ab51a235..cc0eeacc87 100644 --- a/mlir/lib/Dialect/Flux/IR/QubitManagement/DeallocOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -16,7 +16,7 @@ #include using namespace mlir; -using namespace mlir::flux; +using namespace mlir::qco; namespace { diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 2493d6b410..25c80cb941 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -9,8 +9,8 @@ */ #include "mlir/Compiler/CompilerPipeline.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include #include @@ -111,8 +111,8 @@ int main(int argc, char** argv) { // Set up MLIR context with all required dialects DialectRegistry registry; - registry.insert(); - registry.insert(); + registry.insert(); + registry.insert(); registry.insert(); registry.insert(); registry.insert(); diff --git a/mlir/unittests/pipeline/CMakeLists.txt b/mlir/unittests/pipeline/CMakeLists.txt index 0362fc8751..a7b268e84c 100644 --- a/mlir/unittests/pipeline/CMakeLists.txt +++ b/mlir/unittests/pipeline/CMakeLists.txt @@ -12,9 +12,9 @@ target_link_libraries( mqt-core-mlir-compiler-pipeline-test PRIVATE GTest::gtest_main MQTCompilerPipeline - MLIRQuartzTranslation - MLIRQuartzProgramBuilder - MLIRFluxProgramBuilder + MLIRQCTranslation + MLIRQCProgramBuilder + MLIRQCOProgramBuilder MLIRQIRProgramBuilder MLIRParser MQT::CoreIR) diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index f01ba03b58..583ba5214a 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -10,12 +10,12 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" -#include "mlir/Dialect/Flux/Builder/FluxProgramBuilder.h" -#include "mlir/Dialect/Flux/IR/FluxDialect.h" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" -#include "mlir/Dialect/Quartz/Builder/QuartzProgramBuilder.h" -#include "mlir/Dialect/Quartz/IR/QuartzDialect.h" -#include "mlir/Dialect/Quartz/Translation/TranslateQuantumComputationToQuartz.h" #include "mlir/Support/PrettyPrinting.h" #include @@ -511,10 +511,10 @@ bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { * match which expected IR. */ struct StageExpectations { - ModuleOp quartzImport; - ModuleOp fluxConversion; + ModuleOp qcImport; + ModuleOp qcoConversion; ModuleOp optimization; - ModuleOp quartzConversion; + ModuleOp qcConversion; ModuleOp qirConversion; }; @@ -536,16 +536,16 @@ class CompilerPipelineTest : public testing::Test { QuantumCompilerConfig config; CompilationRecord record; - OwningOpRef emptyQuartz; - OwningOpRef emptyFlux; + OwningOpRef emptyQC; + OwningOpRef emptyQCO; OwningOpRef emptyQIR; void SetUp() override { // Register all dialects needed for the full compilation pipeline DialectRegistry registry; - registry.insert(); + registry.insert(); context = std::make_unique(); context->appendDialectRegistry(registry); @@ -557,8 +557,8 @@ class CompilerPipelineTest : public testing::Test { config.printIRAfterAllStages = true; /// TODO: Change back after everything is running - emptyQuartz = buildQuartzIR([](quartz::QuartzProgramBuilder&) {}); - emptyFlux = buildFluxIR([](flux::FluxProgramBuilder&) {}); + emptyQC = buildQCIR([](mlir::qc::QCProgramBuilder&) {}); + emptyQCO = buildQCOIR([](qco::QCOProgramBuilder&) {}); emptyQIR = buildQIR([](qir::QIRProgramBuilder&) {}); } @@ -571,7 +571,8 @@ class CompilerPipelineTest : public testing::Test { * * @param qc The quantum computation to print */ - static void prettyPrintQuantumComputation(const qc::QuantumComputation& qc) { + static void + prettyPrintQuantumComputation(const ::qc::QuantumComputation& comp) { llvm::errs() << "\n"; printBoxTop(); @@ -585,7 +586,7 @@ class CompilerPipelineTest : public testing::Test { // Capture the internal representation std::ostringstream internalRepr; - internalRepr << qc; + internalRepr << comp; const std::string internalStr = internalRepr.str(); // Print with line wrapping @@ -597,7 +598,7 @@ class CompilerPipelineTest : public testing::Test { printBoxLine("OpenQASM3 Representation:"); printBoxLine(""); - const auto qasmStr = qc.toQASM(); + const auto qasmStr = comp.toQASM(); // Print with line wrapping printBoxText(qasmStr); @@ -607,14 +608,14 @@ class CompilerPipelineTest : public testing::Test { } /** - * @brief Import a QuantumComputation into Quartz dialect + * @brief Import a QuantumComputation into QC dialect */ [[nodiscard]] OwningOpRef - importQuantumCircuit(const qc::QuantumComputation& qc) const { + importQuantumCircuit(const ::qc::QuantumComputation& qc) const { if (config.printIRAfterAllStages) { prettyPrintQuantumComputation(qc); } - return translateQuantumComputationToQuartz(context.get(), qc); + return translateQuantumComputationToQC(context.get(), qc); } /** @@ -642,12 +643,11 @@ class CompilerPipelineTest : public testing::Test { } /** - * @brief Build expected Quartz IR programmatically and run canonicalization + * @brief Build expected QC IR programmatically and run canonicalization */ - [[nodiscard]] OwningOpRef buildQuartzIR( - const std::function& buildFunc) - const { - quartz::QuartzProgramBuilder builder(context.get()); + [[nodiscard]] OwningOpRef buildQCIR( + const std::function& buildFunc) const { + mlir::qc::QCProgramBuilder builder(context.get()); builder.initialize(); buildFunc(builder); auto module = builder.finalize(); @@ -656,11 +656,11 @@ class CompilerPipelineTest : public testing::Test { } /** - * @brief Build expected Flux IR programmatically and run canonicalization + * @brief Build expected QCO IR programmatically and run canonicalization */ - [[nodiscard]] OwningOpRef buildFluxIR( - const std::function& buildFunc) const { - flux::FluxProgramBuilder builder(context.get()); + [[nodiscard]] OwningOpRef buildQCOIR( + const std::function& buildFunc) const { + qco::QCOProgramBuilder builder(context.get()); builder.initialize(); buildFunc(builder); auto module = builder.finalize(); @@ -693,14 +693,14 @@ class CompilerPipelineTest : public testing::Test { * Stages without expectations are skipped. */ void verifyAllStages(const StageExpectations& expectations) const { - if (expectations.quartzImport != nullptr) { - EXPECT_TRUE(verify("Quartz Import", record.afterInitialCanon, - expectations.quartzImport)); + if (expectations.qcImport != nullptr) { + EXPECT_TRUE( + verify("QC Import", record.afterInitialCanon, expectations.qcImport)); } - if (expectations.fluxConversion != nullptr) { - EXPECT_TRUE(verify("Flux Conversion", record.afterFluxCanon, - expectations.fluxConversion)); + if (expectations.qcoConversion != nullptr) { + EXPECT_TRUE(verify("QCO Conversion", record.afterQCOCanon, + expectations.qcoConversion)); } if (expectations.optimization != nullptr) { @@ -708,9 +708,9 @@ class CompilerPipelineTest : public testing::Test { expectations.optimization)); } - if (expectations.quartzConversion != nullptr) { - EXPECT_TRUE(verify("Quartz Conversion", record.afterQuartzCanon, - expectations.quartzConversion)); + if (expectations.qcConversion != nullptr) { + EXPECT_TRUE(verify("QC Conversion", record.afterQCCanon, + expectations.qcConversion)); } if (expectations.qirConversion != nullptr) { @@ -721,22 +721,22 @@ class CompilerPipelineTest : public testing::Test { void TearDown() override { // Verify all stages were recorded (basic sanity check) - EXPECT_FALSE(record.afterQuartzImport.empty()) - << "Quartz import stage was not recorded"; + EXPECT_FALSE(record.afterQCImport.empty()) + << "QC import stage was not recorded"; EXPECT_FALSE(record.afterInitialCanon.empty()) << "Initial canonicalization stage was not recorded"; - EXPECT_FALSE(record.afterFluxConversion.empty()) - << "Flux conversion stage was not recorded"; - EXPECT_FALSE(record.afterFluxCanon.empty()) - << "Flux canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterQCOConversion.empty()) + << "QCO conversion stage was not recorded"; + EXPECT_FALSE(record.afterQCOCanon.empty()) + << "QCO canonicalization stage was not recorded"; EXPECT_FALSE(record.afterOptimization.empty()) << "Optimization stage was not recorded"; EXPECT_FALSE(record.afterOptimizationCanon.empty()) << "Optimization canonicalization stage was not recorded"; - EXPECT_FALSE(record.afterQuartzConversion.empty()) - << "Quartz conversion stage was not recorded"; - EXPECT_FALSE(record.afterQuartzCanon.empty()) - << "Quartz canonicalization stage was not recorded"; + EXPECT_FALSE(record.afterQCConversion.empty()) + << "QC conversion stage was not recorded"; + EXPECT_FALSE(record.afterQCCanon.empty()) + << "QC canonicalization stage was not recorded"; if (config.convertToQIR) { EXPECT_FALSE(record.afterQIRConversion.empty()) @@ -764,10 +764,10 @@ class CompilerPipelineTest : public testing::Test { */ TEST_F(CompilerPipelineTest, EmptyCircuit) { // Create empty circuit - const qc::QuantumComputation qc; + const ::qc::QuantumComputation comp; - // Import to Quartz dialect - const auto module = importQuantumCircuit(qc); + // Import to QC dialect + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); // Run compilation @@ -775,10 +775,10 @@ TEST_F(CompilerPipelineTest, EmptyCircuit) { // Verify all stages verifyAllStages({ - .quartzImport = emptyQuartz.get(), - .fluxConversion = emptyFlux.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = emptyQC.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -792,26 +792,26 @@ TEST_F(CompilerPipelineTest, EmptyCircuit) { * * @details * Since the register is unused, it should be removed during canonicalization - * in the Flux dialect. + * in the QCO dialect. */ TEST_F(CompilerPipelineTest, SingleQubitRegister) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = buildQuartzIR( - [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); - const auto fluxExpected = buildFluxIR( - [](flux::FluxProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); + const auto qcExpected = buildQCIR( + [](mlir::qc::QCProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); + const auto qcoExpected = buildQCOIR( + [](qco::QCOProgramBuilder& b) { b.allocQubitRegister(1, "q"); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = qcExpected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -820,23 +820,23 @@ TEST_F(CompilerPipelineTest, SingleQubitRegister) { * @brief Test: Multi-qubit register allocation */ TEST_F(CompilerPipelineTest, MultiQubitRegister) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = buildQuartzIR( - [](quartz::QuartzProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); - const auto fluxExpected = buildFluxIR( - [](flux::FluxProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); + const auto qcExpected = buildQCIR( + [](mlir::qc::QCProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); + const auto qcoExpected = buildQCOIR( + [](qco::QCOProgramBuilder& b) { b.allocQubitRegister(3, "q"); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = qcExpected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -845,29 +845,28 @@ TEST_F(CompilerPipelineTest, MultiQubitRegister) { * @brief Test: Multiple quantum registers */ TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.addQubitRegister(3, "aux"); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.addQubitRegister(3, "aux"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzExpected = - buildQuartzIR([](quartz::QuartzProgramBuilder& b) { - b.allocQubitRegister(2, "q"); - b.allocQubitRegister(3, "aux"); - }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcExpected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { + b.allocQubitRegister(2, "q"); + b.allocQubitRegister(3, "aux"); + }); + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { b.allocQubitRegister(2, "q"); b.allocQubitRegister(3, "aux"); }); verifyAllStages({ - .quartzImport = quartzExpected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = qcExpected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -876,10 +875,10 @@ TEST_F(CompilerPipelineTest, MultipleQuantumRegisters) { * @brief Test: Large qubit register allocation */ TEST_F(CompilerPipelineTest, LargeQubitRegister) { - qc::QuantumComputation qc; - qc.addQubitRegister(100, "q"); + ::qc::QuantumComputation comp; + comp.addQubitRegister(100, "q"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); } @@ -895,22 +894,22 @@ TEST_F(CompilerPipelineTest, LargeQubitRegister) { * Since the register is unused, it should be removed during canonicalization. */ TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { - qc::QuantumComputation qc; - qc.addClassicalRegister(1, "c"); + ::qc::QuantumComputation comp; + comp.addClassicalRegister(1, "c"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { std::ignore = b.allocClassicalBitRegister(1, "c"); }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = emptyFlux.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = expected.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -922,22 +921,22 @@ TEST_F(CompilerPipelineTest, SingleClassicalBitRegister) { * Since the register is unused, it should be removed during canonicalization. */ TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { - qc::QuantumComputation qc; - qc.addClassicalRegister(5, "c"); + ::qc::QuantumComputation comp; + comp.addClassicalRegister(5, "c"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { std::ignore = b.allocClassicalBitRegister(5, "c"); }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = emptyFlux.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = expected.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -950,24 +949,24 @@ TEST_F(CompilerPipelineTest, MultiBitClassicalRegister) { * canonicalization. */ TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { - qc::QuantumComputation qc; - qc.addClassicalRegister(3, "c"); - qc.addClassicalRegister(2, "d"); + ::qc::QuantumComputation comp; + comp.addClassicalRegister(3, "c"); + comp.addClassicalRegister(2, "d"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { std::ignore = b.allocClassicalBitRegister(3, "c"); std::ignore = b.allocClassicalBitRegister(2, "d"); }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = emptyFlux.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = expected.get(), + .qcoConversion = emptyQCO.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -976,10 +975,10 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegisters) { * @brief Test: Large classical bit register */ TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { - qc::QuantumComputation qc; - qc.addClassicalRegister(128, "c"); + ::qc::QuantumComputation comp; + comp.addClassicalRegister(128, "c"); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); } @@ -996,27 +995,27 @@ TEST_F(CompilerPipelineTest, LargeClassicalBitRegister) { * canonicalization. */ TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { - qc::QuantumComputation qc(1); - qc.reset(0); + ::qc::QuantumComputation comp(1); + comp.reset(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { const auto q = b.allocQubitRegister(1, "q"); b.reset(q[0]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { const auto q = b.allocQubitRegister(1, "q"); b.reset(q[0]); }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -1030,23 +1029,23 @@ TEST_F(CompilerPipelineTest, SingleResetInSingleQubitCircuit) { * allocation, it should be removed as well. */ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { - qc::QuantumComputation qc(1); - qc.reset(0); - qc.reset(0); - qc.reset(0); + ::qc::QuantumComputation comp(1); + comp.reset(0); + comp.reset(0); + comp.reset(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { const auto q = b.allocQubitRegister(1, "q"); b.reset(q[0]); b.reset(q[0]); b.reset(q[0]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1, "q"); q[0] = b.reset(q[0]); q[0] = b.reset(q[0]); @@ -1054,10 +1053,10 @@ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -1066,31 +1065,31 @@ TEST_F(CompilerPipelineTest, ConsecutiveResetOperations) { * @brief Test: Separate resets in two qubit system */ TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { - qc::QuantumComputation qc(2); - qc.reset(0); - qc.reset(1); + ::qc::QuantumComputation comp(2); + comp.reset(0); + comp.reset(1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { const auto q = b.allocQubitRegister(2, "q"); b.reset(q[0]); b.reset(q[1]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2, "q"); q[0] = b.reset(q[0]); q[1] = b.reset(q[1]); }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } @@ -1103,20 +1102,20 @@ TEST_F(CompilerPipelineTest, SeparateResetsInTwoQubitSystem) { * @brief Test: Single measurement to single bit */ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { - qc::QuantumComputation qc(1, 1); - qc.measure(0, 0); + ::qc::QuantumComputation comp(1, 1); + comp.measure(0, 0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); b.measure(q[0], c[0]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); b.measure(q[0], c[0]); @@ -1129,10 +1128,10 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = expected.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), .qirConversion = qirExpected.get(), }); } @@ -1141,22 +1140,22 @@ TEST_F(CompilerPipelineTest, SingleMeasurementToSingleBit) { * @brief Test: Repeated measurement to same bit */ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { - qc::QuantumComputation qc(1, 1); - qc.measure(0, 0); - qc.measure(0, 0); + ::qc::QuantumComputation comp(1, 1); + comp.measure(0, 0); + comp.measure(0, 0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); b.measure(q[0], c[0]); b.measure(q[0], c[0]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(1); q[0] = b.measure(q[0], c[0]); @@ -1171,10 +1170,10 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = expected.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), .qirConversion = qirExpected.get(), }); } @@ -1183,17 +1182,17 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementToSameBit) { * @brief Test: Repeated measurement on separate bits */ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { - qc::QuantumComputation qc(1); - qc.addClassicalRegister(3); - qc.measure(0, 0); - qc.measure(0, 1); - qc.measure(0, 2); + ::qc::QuantumComputation comp(1); + comp.addClassicalRegister(3); + comp.measure(0, 0); + comp.measure(0, 1); + comp.measure(0, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(3); b.measure(q[0], c[0]); @@ -1201,7 +1200,7 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { b.measure(q[0], c[2]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto& c = b.allocClassicalBitRegister(3); q[0] = b.measure(q[0], c[0]); @@ -1218,10 +1217,10 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = expected.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), .qirConversion = qirExpected.get(), }); } @@ -1230,17 +1229,17 @@ TEST_F(CompilerPipelineTest, RepeatedMeasurementOnSeparateBits) { * @brief Test: Multiple classical registers with measurements */ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { - qc::QuantumComputation qc(2); - const auto& c1 = qc.addClassicalRegister(1, "c1"); - const auto& c2 = qc.addClassicalRegister(1, "c2"); - qc.measure(0, c1[0]); - qc.measure(1, c2[0]); + ::qc::QuantumComputation comp(2); + const auto& c1 = comp.addClassicalRegister(1, "c1"); + const auto& c2 = comp.addClassicalRegister(1, "c2"); + comp.measure(0, c1[0]); + comp.measure(1, c2[0]); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto expected = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto expected = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); @@ -1248,7 +1247,7 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { b.measure(q[1], creg2[0]); }); - const auto fluxExpected = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoExpected = buildQCOIR([](qco::QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); const auto& creg1 = b.allocClassicalBitRegister(1, "c1"); const auto& creg2 = b.allocClassicalBitRegister(1, "c2"); @@ -1265,10 +1264,10 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { }); verifyAllStages({ - .quartzImport = expected.get(), - .fluxConversion = fluxExpected.get(), - .optimization = fluxExpected.get(), - .quartzConversion = expected.get(), + .qcImport = expected.get(), + .qcoConversion = qcoExpected.get(), + .optimization = qcoExpected.get(), + .qcConversion = expected.get(), .qirConversion = qirExpected.get(), }); } @@ -1278,19 +1277,19 @@ TEST_F(CompilerPipelineTest, MultipleClassicalRegistersAndMeasurements) { // ################################################## TEST_F(CompilerPipelineTest, GPhase) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.gphase(1.0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.gphase(1.0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.gphase(1.0); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.gphase(1.0); }); @@ -1300,27 +1299,27 @@ TEST_F(CompilerPipelineTest, GPhase) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, CGPhase) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.cgphase(1.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.p(1.0, reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.p(1.0, reg[0]); }); @@ -1330,27 +1329,27 @@ TEST_F(CompilerPipelineTest, CGPhase) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCGPhase) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.mcgphase(1.0, {reg[0], reg[1]}); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cp(1.0, reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cp(1.0, reg[0], reg[1]); }); @@ -1360,98 +1359,98 @@ TEST_F(CompilerPipelineTest, MCGPhase) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, Id) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.i(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.i(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.id(reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.id(reg[0]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } TEST_F(CompilerPipelineTest, CId) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.ci(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.ci(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cid(reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cid(reg[0], reg[1]); }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = emptyFlux.get(), - .quartzConversion = emptyQuartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = emptyQCO.get(), + .qcConversion = emptyQC.get(), .qirConversion = emptyQIR.get(), }); } TEST_F(CompilerPipelineTest, X) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.x(0); - qc.x(0); - qc.x(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.x(0); + comp.x(0); + comp.x(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.x(q); b.x(q); b.x(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.x(q); q = b.x(q); b.x(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.x(reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.x(reg[0]); }); @@ -1461,28 +1460,28 @@ TEST_F(CompilerPipelineTest, X) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CX) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.cx(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cx(reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cx(reg[0], reg[1]); }); @@ -1492,26 +1491,26 @@ TEST_F(CompilerPipelineTest, CX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, CX3) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.cx(0, 1); - qc.cx(1, 0); - qc.cx(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cx(0, 1); + comp.cx(1, 0); + comp.cx(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -1519,7 +1518,7 @@ TEST_F(CompilerPipelineTest, CX3) { b.cx(q1, q0); b.cx(q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -1537,28 +1536,28 @@ TEST_F(CompilerPipelineTest, CX3) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCX) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.mcx({0, 1}, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcx({0, 1}, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcx({reg[0], reg[1]}, reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcx({reg[0], reg[1]}, reg[2]); }); @@ -1568,30 +1567,30 @@ TEST_F(CompilerPipelineTest, MCX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCXNested) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.ctrl(reg[0], [&](OpBuilder& b) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) - static_cast(b).cx(reg[1], reg[2]); + static_cast(b).cx(reg[1], reg[2]); }); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcx({reg[0], reg[1]}, reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcx({reg[0], reg[1]}, reg[2]); }); @@ -1601,27 +1600,27 @@ TEST_F(CompilerPipelineTest, MCXNested) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCXTrivial) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.mcx({}, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.x(reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.x(reg[0]); }); @@ -1631,44 +1630,44 @@ TEST_F(CompilerPipelineTest, MCXTrivial) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, Y) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.y(0); - qc.y(0); - qc.y(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.y(0); + comp.y(0); + comp.y(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.y(q); b.y(q); b.y(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.y(q); q = b.y(q); b.y(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.y(reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.y(reg[0]); }); @@ -1678,44 +1677,44 @@ TEST_F(CompilerPipelineTest, Y) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, Z) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.z(0); - qc.z(0); - qc.z(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.z(0); + comp.z(0); + comp.z(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.z(q); b.z(q); b.z(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.z(q); q = b.z(q); b.z(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.z(reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.z(reg[0]); }); @@ -1725,44 +1724,44 @@ TEST_F(CompilerPipelineTest, Z) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, H) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.h(0); - qc.h(0); - qc.h(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.h(0); + comp.h(0); + comp.h(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.h(q); b.h(q); b.h(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.h(q); q = b.h(q); b.h(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.h(reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.h(reg[0]); }); @@ -1772,28 +1771,28 @@ TEST_F(CompilerPipelineTest, H) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, S) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.s(0); - qc.sdg(0); - qc.s(0); - qc.s(0); - qc.s(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.s(0); + comp.sdg(0); + comp.s(0); + comp.s(0); + comp.s(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.s(q); @@ -1802,7 +1801,7 @@ TEST_F(CompilerPipelineTest, S) { b.s(q); b.s(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.s(q); @@ -1811,13 +1810,13 @@ TEST_F(CompilerPipelineTest, S) { q = b.s(q); q = b.s(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.z(q); b.s(q); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.z(q); @@ -1831,28 +1830,28 @@ TEST_F(CompilerPipelineTest, S) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, Sdg) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.sdg(0); - qc.s(0); - qc.sdg(0); - qc.sdg(0); - qc.sdg(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.sdg(0); + comp.s(0); + comp.sdg(0); + comp.sdg(0); + comp.sdg(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sdg(q); @@ -1861,7 +1860,7 @@ TEST_F(CompilerPipelineTest, Sdg) { b.sdg(q); b.sdg(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.sdg(q); @@ -1870,13 +1869,13 @@ TEST_F(CompilerPipelineTest, Sdg) { q = b.sdg(q); q = b.sdg(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.z(q); b.sdg(q); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.z(q); @@ -1890,28 +1889,28 @@ TEST_F(CompilerPipelineTest, Sdg) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, T) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.t(0); - qc.tdg(0); - qc.t(0); - qc.t(0); - qc.t(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.t(0); + comp.tdg(0); + comp.t(0); + comp.t(0); + comp.t(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.t(q); @@ -1920,7 +1919,7 @@ TEST_F(CompilerPipelineTest, T) { b.t(q); b.t(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.t(q); @@ -1929,13 +1928,13 @@ TEST_F(CompilerPipelineTest, T) { q = b.t(q); b.t(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.s(q); q = b.t(q); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.s(q); @@ -1949,28 +1948,28 @@ TEST_F(CompilerPipelineTest, T) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, Tdg) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.tdg(0); - qc.t(0); - qc.tdg(0); - qc.tdg(0); - qc.tdg(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.tdg(0); + comp.t(0); + comp.tdg(0); + comp.tdg(0); + comp.tdg(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.tdg(q); @@ -1979,7 +1978,7 @@ TEST_F(CompilerPipelineTest, Tdg) { b.tdg(q); b.tdg(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.tdg(q); @@ -1988,13 +1987,13 @@ TEST_F(CompilerPipelineTest, Tdg) { q = b.tdg(q); b.tdg(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.sdg(q); b.tdg(q); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sdg(q); @@ -2008,28 +2007,28 @@ TEST_F(CompilerPipelineTest, Tdg) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, SX) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.sx(0); - qc.sxdg(0); - qc.sx(0); - qc.sx(0); - qc.sx(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.sx(0); + comp.sxdg(0); + comp.sx(0); + comp.sx(0); + comp.sx(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sx(q); @@ -2038,7 +2037,7 @@ TEST_F(CompilerPipelineTest, SX) { b.sx(q); b.sx(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.sx(q); @@ -2047,13 +2046,13 @@ TEST_F(CompilerPipelineTest, SX) { q = b.sx(q); q = b.sx(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.x(q); b.sx(q); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.x(q); @@ -2067,28 +2066,28 @@ TEST_F(CompilerPipelineTest, SX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, SXdg) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.sxdg(0); - qc.sx(0); - qc.sxdg(0); - qc.sxdg(0); - qc.sxdg(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.sxdg(0); + comp.sx(0); + comp.sxdg(0); + comp.sxdg(0); + comp.sxdg(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.sxdg(q); @@ -2097,7 +2096,7 @@ TEST_F(CompilerPipelineTest, SXdg) { b.sxdg(q); b.sxdg(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.sxdg(q); @@ -2106,13 +2105,13 @@ TEST_F(CompilerPipelineTest, SXdg) { q = b.sxdg(q); q = b.sxdg(q); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto q = reg[0]; q = b.x(q); b.sxdg(q); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.x(q); @@ -2126,27 +2125,27 @@ TEST_F(CompilerPipelineTest, SXdg) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, RX) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.rx(1.0, 0); - qc.rx(0.5, 0); - qc.rx(1.0, 1); - qc.rx(-1.0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.rx(1.0, 0); + comp.rx(0.5, 0); + comp.rx(1.0, 1); + comp.rx(-1.0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -2155,7 +2154,7 @@ TEST_F(CompilerPipelineTest, RX) { b.rx(1.0, q1); b.rx(-1.0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -2164,11 +2163,11 @@ TEST_F(CompilerPipelineTest, RX) { q1 = b.rx(1.0, q1); b.rx(-1.0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.rx(1.5, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.rx(1.5, reg[0]); }); @@ -2178,28 +2177,28 @@ TEST_F(CompilerPipelineTest, RX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CRX) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.crx(1.0, 0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.crx(1.0, 0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.crx(1.0, reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.crx(1.0, reg[0], reg[1]); }); @@ -2209,28 +2208,28 @@ TEST_F(CompilerPipelineTest, CRX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCRX) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.mcrx(1.0, {0, 1}, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcrx(1.0, {0, 1}, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcrx(1.0, {reg[0], reg[1]}, reg[2]); }); @@ -2240,27 +2239,27 @@ TEST_F(CompilerPipelineTest, MCRX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, RY) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.ry(1.0, 0); - qc.ry(0.5, 0); - qc.ry(1.0, 1); - qc.ry(-1.0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.ry(1.0, 0); + comp.ry(0.5, 0); + comp.ry(1.0, 1); + comp.ry(-1.0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -2269,7 +2268,7 @@ TEST_F(CompilerPipelineTest, RY) { b.ry(1.0, q1); b.ry(-1.0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -2278,11 +2277,11 @@ TEST_F(CompilerPipelineTest, RY) { q1 = b.ry(1.0, q1); b.ry(-1.0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.ry(1.5, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.ry(1.5, reg[0]); }); @@ -2292,27 +2291,27 @@ TEST_F(CompilerPipelineTest, RY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, RZ) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.rz(1.0, 0); - qc.rz(0.5, 0); - qc.rz(1.0, 1); - qc.rz(-1.0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.rz(1.0, 0); + comp.rz(0.5, 0); + comp.rz(1.0, 1); + comp.rz(-1.0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -2321,7 +2320,7 @@ TEST_F(CompilerPipelineTest, RZ) { b.rz(1.0, q1); b.rz(-1.0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -2330,11 +2329,11 @@ TEST_F(CompilerPipelineTest, RZ) { q1 = b.rz(1.0, q1); b.rz(-1.0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.rz(1.5, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.rz(1.5, reg[0]); }); @@ -2344,27 +2343,27 @@ TEST_F(CompilerPipelineTest, RZ) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, P) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.p(1.0, 0); - qc.p(0.5, 0); - qc.p(1.0, 1); - qc.p(-1.0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.p(1.0, 0); + comp.p(0.5, 0); + comp.p(1.0, 1); + comp.p(-1.0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -2373,7 +2372,7 @@ TEST_F(CompilerPipelineTest, P) { b.p(1.0, q1); b.p(-1.0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -2382,11 +2381,11 @@ TEST_F(CompilerPipelineTest, P) { q1 = b.p(1.0, q1); b.p(-1.0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.p(1.5, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.p(1.5, reg[0]); }); @@ -2396,28 +2395,28 @@ TEST_F(CompilerPipelineTest, P) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, R) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.r(1.0, 0.5, 0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.r(1.0, 0.5, 0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, 0.5, reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, 0.5, reg[0]); }); @@ -2427,35 +2426,35 @@ TEST_F(CompilerPipelineTest, R) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, RToRX) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, 0.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, 0.0, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, 0.0, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.rx(1.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.rx(1.0, reg[0]); }); @@ -2465,35 +2464,35 @@ TEST_F(CompilerPipelineTest, RToRX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, RToRY) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, std::numbers::pi / 2, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, std::numbers::pi / 2, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.r(1.0, std::numbers::pi / 2, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.ry(1.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.ry(1.0, reg[0]); }); @@ -2503,28 +2502,28 @@ TEST_F(CompilerPipelineTest, RToRY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CR) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.cr(1.0, 0.5, 0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cr(1.0, 0.5, 0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cr(1.0, 0.5, reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cr(1.0, 0.5, reg[0], reg[1]); }); @@ -2534,28 +2533,28 @@ TEST_F(CompilerPipelineTest, CR) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCR) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.mcr(1.0, 0.5, {0, 1}, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcr(1.0, 0.5, {0, 1}, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcr(1.0, 0.5, {reg[0], reg[1]}, reg[2]); }); @@ -2565,28 +2564,28 @@ TEST_F(CompilerPipelineTest, MCR) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, U2) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.u2(1.0, 0.5, 0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.u2(1.0, 0.5, 0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(1.0, 0.5, reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(1.0, 0.5, reg[0]); }); @@ -2596,35 +2595,35 @@ TEST_F(CompilerPipelineTest, U2) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, U2ToH) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(0.0, std::numbers::pi, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(0.0, std::numbers::pi, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(0.0, std::numbers::pi, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.h(reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.h(reg[0]); }); @@ -2634,35 +2633,35 @@ TEST_F(CompilerPipelineTest, U2ToH) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, U2ToRX) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(-std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.rx(std::numbers::pi / 2.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.rx(std::numbers::pi / 2.0, reg[0]); }); @@ -2672,35 +2671,35 @@ TEST_F(CompilerPipelineTest, U2ToRX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, U2ToRY) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(0.0, 0.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(0.0, 0.0, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u2(0.0, 0.0, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.ry(std::numbers::pi / 2.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.ry(std::numbers::pi / 2.0, reg[0]); }); @@ -2710,28 +2709,28 @@ TEST_F(CompilerPipelineTest, U2ToRY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, U) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.u(1.0, 0.5, 0.2, 0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.u(1.0, 0.5, 0.2, 0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, 0.5, 0.2, reg[0]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, 0.5, 0.2, reg[0]); }); @@ -2741,35 +2740,35 @@ TEST_F(CompilerPipelineTest, U) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, UToP) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(0.0, 0.0, 1.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(0.0, 0.0, 1.0, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(0.0, 0.0, 1.0, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.p(1.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.p(1.0, reg[0]); }); @@ -2779,35 +2778,35 @@ TEST_F(CompilerPipelineTest, UToP) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, UToRX) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, -std::numbers::pi / 2.0, std::numbers::pi / 2.0, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.rx(1.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.rx(1.0, reg[0]); }); @@ -2817,35 +2816,35 @@ TEST_F(CompilerPipelineTest, UToRX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, UToRY) { - auto input = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + auto input = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, 0.0, 0.0, reg[0]); }); ASSERT_TRUE(runPipeline(input.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, 0.0, 0.0, reg[0]); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.u(1.0, 0.0, 0.0, reg[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.ry(1.0, reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.ry(1.0, reg[0]); }); @@ -2855,28 +2854,28 @@ TEST_F(CompilerPipelineTest, UToRY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CU) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.cu(1.0, 0.5, 0.2, 0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.cu(1.0, 0.5, 0.2, 0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.cu(1.0, 0.5, 0.2, reg[0], reg[1]); }); @@ -2886,28 +2885,28 @@ TEST_F(CompilerPipelineTest, CU) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCU) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.mcu(1.0, 0.5, 0.2, {0, 1}, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.mcu(1.0, 0.5, 0.2, {0, 1}, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.mcu(1.0, 0.5, 0.2, {reg[0], reg[1]}, reg[2]); }); @@ -2917,26 +2916,26 @@ TEST_F(CompilerPipelineTest, MCU) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, SWAP) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.swap(0, 1); - qc.swap(0, 1); - qc.swap(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.swap(0, 1); + comp.swap(0, 1); + comp.swap(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -2944,7 +2943,7 @@ TEST_F(CompilerPipelineTest, SWAP) { b.swap(q0, q1); b.swap(q0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -2952,11 +2951,11 @@ TEST_F(CompilerPipelineTest, SWAP) { std::tie(q0, q1) = b.swap(q0, q1); b.swap(q0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.swap(reg[0], reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.swap(reg[0], reg[1]); }); @@ -2966,28 +2965,28 @@ TEST_F(CompilerPipelineTest, SWAP) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CSWAP) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.cswap(0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.cswap(0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.cswap(reg[0], reg[1], reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.cswap(reg[0], reg[1], reg[2]); }); @@ -2997,28 +2996,28 @@ TEST_F(CompilerPipelineTest, CSWAP) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCSWAP) { - qc::QuantumComputation qc; - qc.addQubitRegister(4, "q"); - qc.mcswap({0, 1}, 2, 3); + ::qc::QuantumComputation comp; + comp.addQubitRegister(4, "q"); + comp.mcswap({0, 1}, 2, 3); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4, "q"); b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4, "q"); b.mcswap({reg[0], reg[1]}, reg[2], reg[3]); }); @@ -3028,28 +3027,28 @@ TEST_F(CompilerPipelineTest, MCSWAP) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, iSWAP) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.iswap(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.iswap(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.iswap(reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.iswap(reg[0], reg[1]); }); @@ -3059,28 +3058,28 @@ TEST_F(CompilerPipelineTest, iSWAP) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, DCX) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.dcx(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.dcx(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.dcx(reg[0], reg[1]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.dcx(reg[0], reg[1]); }); @@ -3090,26 +3089,26 @@ TEST_F(CompilerPipelineTest, DCX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, ECR) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.ecr(0, 1); - qc.ecr(0, 1); - qc.ecr(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.ecr(0, 1); + comp.ecr(0, 1); + comp.ecr(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3117,7 +3116,7 @@ TEST_F(CompilerPipelineTest, ECR) { b.ecr(q0, q1); b.ecr(q0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3125,11 +3124,11 @@ TEST_F(CompilerPipelineTest, ECR) { std::tie(q0, q1) = b.ecr(q0, q1); b.ecr(q0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.ecr(reg[0], reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); b.ecr(reg[0], reg[1]); }); @@ -3139,27 +3138,27 @@ TEST_F(CompilerPipelineTest, ECR) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, RXX) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.rxx(1.0, 0, 1); - qc.rxx(0.5, 0, 1); - qc.rxx(1.0, 1, 2); - qc.rxx(-1.0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.rxx(1.0, 0, 1); + comp.rxx(0.5, 0, 1); + comp.rxx(1.0, 1, 2); + comp.rxx(-1.0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3169,7 +3168,7 @@ TEST_F(CompilerPipelineTest, RXX) { b.rxx(1.0, q1, q2); b.rxx(-1.0, q1, q2); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3179,11 +3178,11 @@ TEST_F(CompilerPipelineTest, RXX) { std::tie(q1, q2) = b.rxx(1.0, q1, q2); b.rxx(-1.0, q1, q2); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.rxx(1.5, reg[0], reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.rxx(1.5, reg[0], reg[1]); }); @@ -3193,28 +3192,28 @@ TEST_F(CompilerPipelineTest, RXX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CRXX) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.crxx(1.0, 0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.crxx(1.0, 0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.crxx(1.0, reg[0], reg[1], reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.crxx(1.0, reg[0], reg[1], reg[2]); }); @@ -3224,28 +3223,28 @@ TEST_F(CompilerPipelineTest, CRXX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCRXX) { - qc::QuantumComputation qc; - qc.addQubitRegister(4, "q"); - qc.mcrxx(1.0, {0, 1}, 2, 3); + ::qc::QuantumComputation comp; + comp.addQubitRegister(4, "q"); + comp.mcrxx(1.0, {0, 1}, 2, 3); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4, "q"); b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4, "q"); b.mcrxx(1.0, {reg[0], reg[1]}, reg[2], reg[3]); }); @@ -3255,27 +3254,27 @@ TEST_F(CompilerPipelineTest, MCRXX) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, RYY) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.ryy(1.0, 0, 1); - qc.ryy(0.5, 0, 1); - qc.ryy(1.0, 1, 2); - qc.ryy(-1.0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.ryy(1.0, 0, 1); + comp.ryy(0.5, 0, 1); + comp.ryy(1.0, 1, 2); + comp.ryy(-1.0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3285,7 +3284,7 @@ TEST_F(CompilerPipelineTest, RYY) { b.ryy(1.0, q1, q2); b.ryy(-1.0, q1, q2); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3295,11 +3294,11 @@ TEST_F(CompilerPipelineTest, RYY) { std::tie(q1, q2) = b.ryy(1.0, q1, q2); b.ryy(-1.0, q1, q2); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.ryy(1.5, reg[0], reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.ryy(1.5, reg[0], reg[1]); }); @@ -3309,27 +3308,27 @@ TEST_F(CompilerPipelineTest, RYY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, RZX) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.rzx(1.0, 0, 1); - qc.rzx(0.5, 0, 1); - qc.rzx(1.0, 1, 2); - qc.rzx(-1.0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.rzx(1.0, 0, 1); + comp.rzx(0.5, 0, 1); + comp.rzx(1.0, 1, 2); + comp.rzx(-1.0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3339,7 +3338,7 @@ TEST_F(CompilerPipelineTest, RZX) { b.rzx(1.0, q1, q2); b.rzx(-1.0, q1, q2); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3349,11 +3348,11 @@ TEST_F(CompilerPipelineTest, RZX) { std::tie(q1, q2) = b.rzx(1.0, q1, q2); b.rzx(-1.0, q1, q2); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.rzx(1.5, reg[0], reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.rzx(1.5, reg[0], reg[1]); }); @@ -3363,27 +3362,27 @@ TEST_F(CompilerPipelineTest, RZX) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, RZZ) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.rzz(1.0, 0, 1); - qc.rzz(0.5, 0, 1); - qc.rzz(1.0, 1, 2); - qc.rzz(-1.0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.rzz(1.0, 0, 1); + comp.rzz(0.5, 0, 1); + comp.rzz(1.0, 1, 2); + comp.rzz(-1.0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3393,7 +3392,7 @@ TEST_F(CompilerPipelineTest, RZZ) { b.rzz(1.0, q1, q2); b.rzz(-1.0, q1, q2); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3403,11 +3402,11 @@ TEST_F(CompilerPipelineTest, RZZ) { std::tie(q1, q2) = b.rzz(1.0, q1, q2); b.rzz(-1.0, q1, q2); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.rzz(1.5, reg[0], reg[1]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.rzz(1.5, reg[0], reg[1]); }); @@ -3417,26 +3416,26 @@ TEST_F(CompilerPipelineTest, RZZ) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, XXPlusYY) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.xx_plus_yy(1.0, 0.5, 0, 1); - qc.xx_plus_yy(0.5, 0.5, 0, 1); - qc.xx_plus_yy(1.0, 1.0, 0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.xx_plus_yy(1.0, 0.5, 0, 1); + comp.xx_plus_yy(0.5, 0.5, 0, 1); + comp.xx_plus_yy(1.0, 1.0, 0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3444,7 +3443,7 @@ TEST_F(CompilerPipelineTest, XXPlusYY) { b.xx_plus_yy(0.5, 0.5, q0, q1); b.xx_plus_yy(1.0, 1.0, q0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3452,14 +3451,14 @@ TEST_F(CompilerPipelineTest, XXPlusYY) { std::tie(q0, q1) = b.xx_plus_yy(0.5, 0.5, q0, q1); b.xx_plus_yy(1.0, 1.0, q0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; std::tie(q0, q1) = b.xx_plus_yy(1.5, 0.5, q0, q1); b.xx_plus_yy(1.0, 1.0, q0, q1); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3475,28 +3474,28 @@ TEST_F(CompilerPipelineTest, XXPlusYY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, CXXPlusYY) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.cxx_plus_yy(1.0, 0.5, 0, 1, 2); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.cxx_plus_yy(1.0, 0.5, 0, 1, 2); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.cxx_plus_yy(1.0, 0.5, reg[0], reg[1], reg[2]); }); @@ -3506,28 +3505,28 @@ TEST_F(CompilerPipelineTest, CXXPlusYY) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, MCXXPlusYY) { - qc::QuantumComputation qc; - qc.addQubitRegister(4, "q"); - qc.mcxx_plus_yy(1.0, 0.5, {0, 1}, 2, 3); + ::qc::QuantumComputation comp; + comp.addQubitRegister(4, "q"); + comp.mcxx_plus_yy(1.0, 0.5, {0, 1}, 2, 3); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4, "q"); b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(4, "q"); b.mcxx_plus_yy(1.0, 0.5, {reg[0], reg[1]}, reg[2], reg[3]); }); @@ -3537,26 +3536,26 @@ TEST_F(CompilerPipelineTest, MCXXPlusYY) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } TEST_F(CompilerPipelineTest, XXMinusYY) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.xx_minus_yy(1.0, 0.5, 0, 1); - qc.xx_minus_yy(0.5, 0.5, 0, 1); - qc.xx_minus_yy(1.0, 1.0, 0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.xx_minus_yy(1.0, 0.5, 0, 1); + comp.xx_minus_yy(0.5, 0.5, 0, 1); + comp.xx_minus_yy(1.0, 1.0, 0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3564,7 +3563,7 @@ TEST_F(CompilerPipelineTest, XXMinusYY) { b.xx_minus_yy(0.5, 0.5, q0, q1); b.xx_minus_yy(1.0, 1.0, q0, q1); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3572,14 +3571,14 @@ TEST_F(CompilerPipelineTest, XXMinusYY) { std::tie(q0, q1) = b.xx_minus_yy(0.5, 0.5, q0, q1); b.xx_minus_yy(1.0, 1.0, q0, q1); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; std::tie(q0, q1) = b.xx_minus_yy(1.5, 0.5, q0, q1); b.xx_minus_yy(1.0, 1.0, q0, q1); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3595,64 +3594,64 @@ TEST_F(CompilerPipelineTest, XXMinusYY) { }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = qirOpt.get(), }); } TEST_F(CompilerPipelineTest, Barrier1) { - qc::QuantumComputation qc; - qc.addQubitRegister(1, "q"); - qc.barrier(0); - qc.barrier(0); + ::qc::QuantumComputation comp; + comp.addQubitRegister(1, "q"); + comp.barrier(0); + comp.barrier(0); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); const auto q = reg[0]; b.barrier(q); b.barrier(q); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); auto qubitsOut = b.barrier(reg[0]); b.barrier(qubitsOut[0]); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.barrier(reg[0]); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(1, "q"); b.barrier(reg[0]); }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = emptyQIR.get(), }); } TEST_F(CompilerPipelineTest, Barrier2) { - qc::QuantumComputation qc; - qc.addQubitRegister(3, "q"); - qc.barrier({0, 1}); - qc.barrier({1, 2}); + ::qc::QuantumComputation comp; + comp.addQubitRegister(3, "q"); + comp.barrier({0, 1}); + comp.barrier({1, 2}); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartzInit = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcInit = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; @@ -3660,49 +3659,49 @@ TEST_F(CompilerPipelineTest, Barrier2) { b.barrier({q0, q1}); b.barrier({q1, q2}); }); - const auto fluxInit = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoInit = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); auto qubitsOut = b.barrier({reg[0], reg[1]}); b.barrier({qubitsOut[1], reg[2]}); }); - const auto fluxOpt = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qcoOpt = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.barrier(reg[0]); b.barrier({reg[1], reg[2]}); }); - const auto quartzOpt = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qcOpt = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3, "q"); b.barrier(reg[0]); b.barrier({reg[1], reg[2]}); }); verifyAllStages({ - .quartzImport = quartzInit.get(), - .fluxConversion = fluxInit.get(), - .optimization = fluxOpt.get(), - .quartzConversion = quartzOpt.get(), + .qcImport = qcInit.get(), + .qcoConversion = qcoInit.get(), + .optimization = qcoOpt.get(), + .qcConversion = qcOpt.get(), .qirConversion = emptyQIR.get(), }); } TEST_F(CompilerPipelineTest, Bell) { - qc::QuantumComputation qc; - qc.addQubitRegister(2, "q"); - qc.h(0); - qc.cx(0, 1); + ::qc::QuantumComputation comp; + comp.addQubitRegister(2, "q"); + comp.h(0); + comp.cx(0, 1); - const auto module = importQuantumCircuit(qc); + const auto module = importQuantumCircuit(comp); ASSERT_TRUE(module); ASSERT_TRUE(runPipeline(module.get()).succeeded()); - const auto quartz = buildQuartzIR([](quartz::QuartzProgramBuilder& b) { + const auto qc = buildQCIR([](mlir::qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); const auto q0 = reg[0]; const auto q1 = reg[1]; b.h(q0); b.cx(q0, q1); }); - const auto flux = buildFluxIR([](flux::FluxProgramBuilder& b) { + const auto qco = buildQCOIR([](qco::QCOProgramBuilder& b) { auto reg = b.allocQubitRegister(2, "q"); auto q0 = reg[0]; auto q1 = reg[1]; @@ -3716,10 +3715,10 @@ TEST_F(CompilerPipelineTest, Bell) { }); verifyAllStages({ - .quartzImport = quartz.get(), - .fluxConversion = flux.get(), - .optimization = flux.get(), - .quartzConversion = quartz.get(), + .qcImport = qc.get(), + .qcoConversion = qco.get(), + .optimization = qco.get(), + .qcConversion = qc.get(), .qirConversion = qir.get(), }); } From 7e7348074cd554ab9b2729687e0449901fb76200 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 01:43:55 +0100 Subject: [PATCH 399/419] Do not check llvm-include-order --- mlir/.clang-tidy | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/.clang-tidy b/mlir/.clang-tidy index 413a0f29ea..9f2230b31f 100644 --- a/mlir/.clang-tidy +++ b/mlir/.clang-tidy @@ -1,6 +1,5 @@ InheritParentConfig: true Checks: | - llvm-include-order, llvm-namespace-comment, llvm-prefer-isa-or-dyn-cast-in-conditionals, llvm-prefer-register-over-unsigned, From 476d743c26547de75a6483849a2dffa86ab7570e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:02:48 +0100 Subject: [PATCH 400/419] Fix linter errors --- mlir/lib/Compiler/CompilerPipeline.cpp | 8 +- .../MQTOptToMQTRef/MQTOptToMQTRef.cpp | 16 ++-- .../Conversion/MQTRefToQIR/MQTRefToQIR.cpp | 24 +++--- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 20 ++--- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 48 ++++++------ mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 14 ++-- mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp | 6 +- .../ToQuantumComputationPattern.cpp | 10 +-- .../Transpilation/sc/PlacementPass.cpp | 45 ++++++----- .../Translation/ImportQuantumComputation.cpp | 72 +++++++++--------- .../TranslateQuantumComputationToQC.cpp | 75 ++++++++++--------- .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 1 + .../Dialect/QIR/Builder/QIRProgramBuilder.cpp | 1 + mlir/lib/Support/PrettyPrinting.cpp | 6 +- mlir/unittests/dialect/test_wireiterator.cpp | 10 +-- .../pipeline/test_compiler_pipeline.cpp | 37 +++++---- 16 files changed, 200 insertions(+), 193 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index d595ac7ead..d8cbd3b8be 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -25,8 +25,6 @@ namespace mlir { -namespace { - /** * @brief Pretty print IR with ASCII art borders and stage identifier * @@ -35,8 +33,8 @@ namespace { * @param stageNumber Current stage number * @param totalStages Total number of stages (for progress indication) */ -void prettyPrintStage(ModuleOp module, const llvm::StringRef stageName, - const int stageNumber, const int totalStages) { +static void prettyPrintStage(ModuleOp module, const llvm::StringRef stageName, + const int stageNumber, const int totalStages) { llvm::errs() << "\n"; printBoxTop(); @@ -60,8 +58,6 @@ void prettyPrintStage(ModuleOp module, const llvm::StringRef stageName, llvm::errs().flush(); } -} // namespace - void QuantumCompilerPipeline::addCleanupPasses(PassManager& pm) { // Always run canonicalization and dead value removal pm.addPass(createCanonicalizerPass()); diff --git a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp index 28a8ed910f..68ad2615d5 100644 --- a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp +++ b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp @@ -44,34 +44,32 @@ using namespace mlir; #define GEN_PASS_DEF_MQTOPTTOMQTREF #include "mlir/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.h.inc" -namespace { - -bool isQubitType(const MemRefType type) { +static bool isQubitType(const MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(memref::AllocOp op) { return isQubitType(op.getType()); } +static bool isQubitType(memref::AllocOp op) { + return isQubitType(op.getType()); +} -bool isQubitType(memref::DeallocOp op) { +static bool isQubitType(memref::DeallocOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::LoadOp op) { +static bool isQubitType(memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::StoreOp op) { +static bool isQubitType(memref::StoreOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -} // namespace - class MQTOptToMQTRefTypeConverter final : public TypeConverter { public: explicit MQTOptToMQTRefTypeConverter(MLIRContext* ctx) { diff --git a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp index 81b9811e88..dcfc88e078 100644 --- a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp +++ b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp @@ -62,8 +62,6 @@ using namespace mlir; #define GEN_PASS_DEF_MQTREFTOQIR #include "mlir/Conversion/MQTRefToQIR/MQTRefToQIR.h.inc" -namespace { - /** * @brief Look up the function declaration with a given name. If it does not exist create one and return it. @@ -74,9 +72,9 @@ namespace { * @param fnType The type signature of the function. * @return The LLVM funcOp declaration with the requested name and signature. */ -LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, - Operation* op, StringRef fnName, - Type fnType) { +static LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, + Operation* op, StringRef fnName, + Type fnType) { // check if the function already exists auto* fnDecl = SymbolTable::lookupNearestSymbolFrom(op, rewriter.getStringAttr(fnName)); @@ -102,6 +100,8 @@ LLVM::LLVMFuncOp getFunctionDeclaration(PatternRewriter& rewriter, return static_cast(fnDecl); } +namespace { + struct LoweringState { // map a given index to a pointer value, to reuse the value instead of // creating a new one every time @@ -136,26 +136,28 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; -bool isQubitType(const MemRefType type) { +} // namespace + +static bool isQubitType(const MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(memref::AllocOp op) { return isQubitType(op.getType()); } +static bool isQubitType(memref::AllocOp op) { + return isQubitType(op.getType()); +} -bool isQubitType(memref::DeallocOp op) { +static bool isQubitType(memref::DeallocOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::LoadOp op) { +static bool isQubitType(memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -} // namespace - struct MQTRefToQIRTypeConverter final : LLVMTypeConverter { explicit MQTRefToQIRTypeConverter(MLIRContext* ctx) : LLVMTypeConverter(ctx) { // QubitType conversion diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 1dad4af715..9e89791b02 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -32,8 +32,6 @@ using namespace qc; #define GEN_PASS_DEF_QCOTOQC #include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" -namespace { - /** * @brief Converts a zero-target, one-parameter QCO operation to QC * @@ -44,7 +42,7 @@ namespace { * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertZeroTargetOneParameter(QCOOpType& op, ConversionPatternRewriter& rewriter) { rewriter.create(op.getLoc(), op.getParameter(0)); @@ -64,7 +62,7 @@ convertZeroTargetOneParameter(QCOOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertOneTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit @@ -91,7 +89,7 @@ convertOneTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertOneTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit @@ -118,7 +116,7 @@ convertOneTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertOneTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit @@ -146,7 +144,7 @@ convertOneTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertOneTargetThreeParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubit @@ -174,7 +172,7 @@ convertOneTargetThreeParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertTwoTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubits @@ -202,7 +200,7 @@ convertTwoTargetZeroParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertTwoTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubits @@ -231,7 +229,7 @@ convertTwoTargetOneParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult +static LogicalResult convertTwoTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, ConversionPatternRewriter& rewriter) { // OpAdaptor provides the already type-converted input qubits @@ -248,8 +246,6 @@ convertTwoTargetTwoParameter(QCOOpType& op, QCOOpAdaptorType& adaptor, return success(); } -} // namespace - /** * @brief Type converter for QCO-to-QC conversion * diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index b2d395710a..1dd795c172 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -107,6 +107,8 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +} // namespace + /** * @brief Converts a zero-target, one-parameter QC operation to QCO * @@ -118,9 +120,9 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertZeroTargetOneParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertZeroTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { const auto inCtrlOp = state.inCtrlOp; rewriter.create(op.getLoc(), op.getParameter(0)); @@ -148,9 +150,9 @@ LogicalResult convertZeroTargetOneParameter(QCOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertOneTargetZeroParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertOneTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -194,9 +196,9 @@ LogicalResult convertOneTargetZeroParameter(QCOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertOneTargetOneParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertOneTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -241,9 +243,9 @@ LogicalResult convertOneTargetOneParameter(QCOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertOneTargetTwoParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertOneTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -288,7 +290,7 @@ LogicalResult convertOneTargetTwoParameter(QCOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertOneTargetThreeParameter( +static LogicalResult convertOneTargetThreeParameter( QCOpType& op, ConversionPatternRewriter& rewriter, LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -335,9 +337,9 @@ LogicalResult convertOneTargetThreeParameter( * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertTwoTargetZeroParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertTwoTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -393,9 +395,9 @@ LogicalResult convertTwoTargetZeroParameter(QCOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertTwoTargetOneParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertTwoTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -448,9 +450,9 @@ LogicalResult convertTwoTargetOneParameter(QCOpType& op, * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertTwoTargetTwoParameter(QCOpType& op, - ConversionPatternRewriter& rewriter, - LoweringState& state) { +static LogicalResult +convertTwoTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, + LoweringState& state) { auto& qubitMap = state.qubitMap; const auto inCtrlOp = state.inCtrlOp; @@ -493,8 +495,6 @@ LogicalResult convertTwoTargetTwoParameter(QCOpType& op, return success(); } -} // namespace - /** * @brief Type converter for QC-to-QCO conversion * diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 419b396382..4da58ef739 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -112,6 +112,8 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +} // namespace + /** * @brief Helper to convert a QC operation to a LLVM CallOp * @@ -128,11 +130,11 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @return LogicalResult Success or failure of the conversion */ template -LogicalResult convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName, size_t numTargets, - size_t numParams) { +static LogicalResult +convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, MLIRContext* ctx, + LoweringState& state, StringRef fnName, + size_t numTargets, size_t numParams) { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; const SmallVector posCtrls = @@ -181,8 +183,6 @@ LogicalResult convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, return success(); } -} // namespace - /** * @brief Type converter for lowering QC dialect types to LLVM types * diff --git a/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp b/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp index 4d32a15f4a..a8f553744c 100644 --- a/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp +++ b/mlir/lib/Dialect/MQTOpt/IR/MQTOptOps.cpp @@ -71,19 +71,19 @@ void mqt::ir::opt::MQTOptDialect::initialize() { #include "mlir/Dialect/MQTOpt/IR/MQTOptOps.cpp.inc" namespace mqt::ir::opt { -namespace { + /** * @brief Prints the given list of types as a comma-separated list * * @param printer The printer to use. * @param types The types to print. **/ -void printCommaSeparated(mlir::OpAsmPrinter& printer, mlir::TypeRange types) { +static void printCommaSeparated(mlir::OpAsmPrinter& printer, + mlir::TypeRange types) { llvm::interleaveComma(llvm::make_range(types.begin(), types.end()), printer.getStream(), [&printer](mlir::Type t) { printer.printType(t); }); } -} // namespace mlir::ParseResult parseOptOutputTypes(mlir::OpAsmParser& parser, diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp index 06c64a6b1f..638312c604 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp @@ -39,24 +39,23 @@ namespace mqt::ir::opt { -namespace { -bool isQubitType(const mlir::MemRefType type) { +static bool isQubitType(const mlir::MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(mlir::memref::LoadOp op) { +static bool isQubitType(mlir::memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(mlir::memref::StoreOp op) { +static bool isQubitType(mlir::memref::StoreOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isSupportedMemRefOp(mlir::Operation* op) { +static bool isSupportedMemRefOp(mlir::Operation* op) { if (auto loadOp = llvm::dyn_cast(op)) { return isQubitType(loadOp); } @@ -65,7 +64,6 @@ bool isSupportedMemRefOp(mlir::Operation* op) { } return false; } -} // namespace /// Analysis pattern that filters out all quantum operations from a given /// program and creates a quantum computation from them. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index 76b036738a..44888fb168 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -45,11 +45,12 @@ namespace mqt::ir::opt { +using namespace mlir; + #define GEN_PASS_DEF_PLACEMENTPASSSC #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" namespace { -using namespace mlir; /** * @brief A queue of hardware indices. @@ -120,12 +121,14 @@ struct PlacementContext { LayoutStack stack{}; }; +} // namespace + /** * @brief Adds the necessary hardware qubits for entry_point functions and * prepares the stack for the qubit placement in the function body. */ -WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { assert(ctx.stack.empty() && "handleFunc: stack must be empty"); rewriter.setInsertionPointToStart(&op.getBody().front()); @@ -161,7 +164,7 @@ WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, * @brief Indicates the end of a region defined by a function. Consequently, pop * the region's state from the stack. */ -WalkResult handleReturn(PlacementContext& ctx) { +static WalkResult handleReturn(PlacementContext& ctx) { ctx.stack.pop(); return WalkResult::advance(); } @@ -173,8 +176,8 @@ WalkResult handleReturn(PlacementContext& ctx) { * Prepares the stack for the placement of the loop body by adding a copy of the * current state to the stack. Forwards the results in the parent state. */ -WalkResult handleFor(scf::ForOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleFor(scf::ForOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { const std::size_t nargs = op.getBody()->getNumArguments(); const std::size_t nresults = op->getNumResults(); @@ -220,8 +223,8 @@ WalkResult handleFor(scf::ForOp op, PlacementContext& ctx, * a copy of the current state to the stack for each branch. Forwards the * results in the parent state. */ -WalkResult handleIf(scf::IfOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleIf(scf::IfOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { const std::size_t nresults = op->getNumResults(); /// Construct new result types. @@ -266,8 +269,8 @@ WalkResult handleIf(scf::IfOp op, PlacementContext& ctx, * @brief Indicates the end of a region defined by a branching op. Consequently, * we pop the region's state from the stack. */ -WalkResult handleYield(scf::YieldOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleYield(scf::YieldOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { if (!isa(op->getParentOp()) && !isa(op->getParentOp())) { return WalkResult::skip(); @@ -285,8 +288,8 @@ WalkResult handleYield(scf::YieldOp op, PlacementContext& ctx, * @brief Retrieve free qubit from pool and replace the allocated qubit with it. * Reset the qubit if it has already been allocated before. */ -WalkResult handleAlloc(AllocQubitOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleAlloc(AllocQubitOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { if (ctx.pool.empty()) { return op.emitOpError( "requires one too many qubits for the targeted architecture"); @@ -319,8 +322,8 @@ WalkResult handleAlloc(AllocQubitOp op, PlacementContext& ctx, /** * @brief Release hardware qubit and erase dealloc operation. */ -WalkResult handleDealloc(DeallocQubitOp op, PlacementContext& ctx, - PatternRewriter& rewriter) { +static WalkResult handleDealloc(DeallocQubitOp op, PlacementContext& ctx, + PatternRewriter& rewriter) { const std::size_t index = ctx.stack.top().lookupHardwareIndex(op.getQubit()); ctx.pool.push_back(index); rewriter.eraseOp(op); @@ -330,7 +333,7 @@ WalkResult handleDealloc(DeallocQubitOp op, PlacementContext& ctx, /** * @brief Update layout. */ -WalkResult handleReset(ResetOp op, PlacementContext& ctx) { +static WalkResult handleReset(ResetOp op, PlacementContext& ctx) { ctx.stack.top().remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -338,7 +341,7 @@ WalkResult handleReset(ResetOp op, PlacementContext& ctx) { /** * @brief Update layout. */ -WalkResult handleMeasure(MeasureOp op, PlacementContext& ctx) { +static WalkResult handleMeasure(MeasureOp op, PlacementContext& ctx) { ctx.stack.top().remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -346,7 +349,7 @@ WalkResult handleMeasure(MeasureOp op, PlacementContext& ctx) { /** * @brief Update layout. */ -WalkResult handleUnitary(UnitaryInterface op, PlacementContext& ctx) { +static WalkResult handleUnitary(UnitaryInterface op, PlacementContext& ctx) { for (const auto [in, out] : llvm::zip(op.getAllInQubits(), op.getAllOutQubits())) { ctx.stack.top().remapQubitValue(in, out); @@ -355,8 +358,8 @@ WalkResult handleUnitary(UnitaryInterface op, PlacementContext& ctx) { return WalkResult::advance(); } -LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, - PlacementContext& ctx) { +static LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, + PlacementContext& ctx) { PatternRewriter rewriter(mlirCtx); /// Prepare work-list. @@ -420,6 +423,8 @@ LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, return success(); } +namespace { + /** * @brief This pass maps dynamic qubits to static qubits on superconducting * quantum devices using initial placement strategies. @@ -478,5 +483,7 @@ struct PlacementPassSC final : impl::PlacementPassSCBase { return success(); } }; + } // namespace + } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp index 5e6f16a982..e37fe50931 100644 --- a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp +++ b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp @@ -52,6 +52,8 @@ struct QregInfo { using BitMemInfo = std::pair; // (memref, localIdx) using BitIndexVec = llvm::SmallVector; +} // namespace + /** * @brief Allocates a quantum register in the MLIR module. * @@ -60,8 +62,9 @@ using BitIndexVec = llvm::SmallVector; * @param numQubits The number of qubits to allocate in the register * @return mlir::Value The allocated quantum register value */ -mlir::Value allocateQreg(mlir::OpBuilder& builder, mlir::MLIRContext* context, - const std::size_t numQubits) { +static mlir::Value allocateQreg(mlir::OpBuilder& builder, + mlir::MLIRContext* context, + const std::size_t numQubits) { const auto& qubitType = mqt::ir::ref::QubitType::get(context); auto memRefType = mlir::MemRefType::get({static_cast(numQubits)}, qubitType); @@ -78,9 +81,9 @@ mlir::Value allocateQreg(mlir::OpBuilder& builder, mlir::MLIRContext* context, * @param numQubits The number of qubits to extract * @return llvm::SmallVector Vector of extracted qubit values */ -llvm::SmallVector extractQubits(mlir::OpBuilder& builder, - mlir::Value qreg, - const std::size_t numQubits) { +static llvm::SmallVector +extractQubits(mlir::OpBuilder& builder, mlir::Value qreg, + const std::size_t numQubits) { llvm::SmallVector qubits; qubits.reserve(numQubits); @@ -106,7 +109,7 @@ llvm::SmallVector extractQubits(mlir::OpBuilder& builder, * @return llvm::SmallVector Vector containing information about all * quantum registers */ -llvm::SmallVector +static llvm::SmallVector getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting @@ -147,7 +150,7 @@ getQregs(mlir::OpBuilder& builder, mlir::MLIRContext* context, * @param qregs Vector containing information about all quantum registers * @return llvm::SmallVector Sorted vector of qubit values */ -llvm::SmallVector +static llvm::SmallVector getQubits(const qc::QuantumComputation& quantumComputation, llvm::SmallVector& qregs) { llvm::SmallVector flatQubits; @@ -170,7 +173,7 @@ getQubits(const qc::QuantumComputation& quantumComputation, * @param builder The MLIR OpBuilder used to create operations * @param qreg The quantum register to deallocate */ -void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { +static void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { builder.create(builder.getUnknownLoc(), qreg); } @@ -181,7 +184,7 @@ void deallocateQreg(mlir::OpBuilder& builder, mlir::Value qreg) { * @param numBits The number of bits to allocate in the register * @return mlir::Value The allocated classical register value */ -mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { +static mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { auto memRefType = mlir::MemRefType::get({numBits}, builder.getI1Type()); auto memref = builder.create(builder.getUnknownLoc(), memRefType); @@ -195,8 +198,8 @@ mlir::Value allocateBits(mlir::OpBuilder& builder, int64_t numBits) { * @param numBits The number of bits to allocate in the register * @return mlir::Value The allocated classical register value */ -BitIndexVec getBitMap(mlir::OpBuilder& builder, - const qc::QuantumComputation& quantumComputation) { +static BitIndexVec getBitMap(mlir::OpBuilder& builder, + const qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting llvm::SmallVector cregPtrs; cregPtrs.reserve(quantumComputation.getClassicalRegisters().size()); @@ -233,8 +236,9 @@ BitIndexVec getBitMap(mlir::OpBuilder& builder, * @param qubits The qubits of the quantum register */ template -void addUnitaryOp(mlir::OpBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { +static void addUnitaryOp(mlir::OpBuilder& builder, + const qc::Operation& operation, + const llvm::SmallVector& qubits) { // Define operation parameters mlir::DenseF64ArrayAttr staticParamsAttr = nullptr; if (const auto& parameters = operation.getParameter(); !parameters.empty()) { @@ -279,9 +283,10 @@ void addUnitaryOp(mlir::OpBuilder& builder, const qc::Operation& operation, * @param bitMap The mapping from global classical bit index to (memref, * localIdx) */ -void addMeasureOp(mlir::OpBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static void addMeasureOp(mlir::OpBuilder& builder, + const qc::Operation& operation, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -307,8 +312,8 @@ void addMeasureOp(mlir::OpBuilder& builder, const qc::Operation& operation, * @param operation The reset operation to add * @param qubits The qubits of the quantum register */ -void addResetOp(mlir::OpBuilder& builder, const qc::Operation& operation, - const llvm::SmallVector& qubits) { +static void addResetOp(mlir::OpBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits) { for (const auto& target : operation.getTargets()) { const mlir::Value inQubit = qubits[target]; builder.create(builder.getUnknownLoc(), inQubit); @@ -316,10 +321,10 @@ void addResetOp(mlir::OpBuilder& builder, const qc::Operation& operation, } // Forward declaration -llvm::LogicalResult addOperation(mlir::OpBuilder& builder, - const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap); +static llvm::LogicalResult +addOperation(mlir::OpBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap); /** * @brief Compute integer value from a classical register (memref). @@ -328,8 +333,8 @@ llvm::LogicalResult addOperation(mlir::OpBuilder& builder, * @param bits The bits of the classical register * @return mlir::Value The integer value of the register */ -mlir::Value getIntegerValueFromRegister(mlir::OpBuilder& builder, - const mlir::Value bits) { +static mlir::Value getIntegerValueFromRegister(mlir::OpBuilder& builder, + const mlir::Value bits) { const auto loc = builder.getUnknownLoc(); // Extract length (assumed 1-D static memref) @@ -382,10 +387,10 @@ mlir::Value getIntegerValueFromRegister(mlir::OpBuilder& builder, * @return mlir::LogicalResult Success if all operations were added, failure * otherwise */ -llvm::LogicalResult addIfElseOp(mlir::OpBuilder& builder, - const qc::Operation& op, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static llvm::LogicalResult +addIfElseOp(mlir::OpBuilder& builder, const qc::Operation& op, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { const auto loc = builder.getUnknownLoc(); const auto& ifElse = dynamic_cast(op); @@ -491,10 +496,10 @@ llvm::LogicalResult addIfElseOp(mlir::OpBuilder& builder, * @return mlir::LogicalResult Success if all operations were added, failure * otherwise */ -llvm::LogicalResult addOperation(mlir::OpBuilder& builder, - const qc::Operation& operation, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static llvm::LogicalResult +addOperation(mlir::OpBuilder& builder, const qc::Operation& operation, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { switch (operation.getType()) { ADD_OP_CASE(Barrier) ADD_OP_CASE(I) @@ -554,7 +559,7 @@ llvm::LogicalResult addOperation(mlir::OpBuilder& builder, * @return mlir::LogicalResult Success if all operations were added, failure * otherwise */ -llvm::LogicalResult addOperations( +static llvm::LogicalResult addOperations( mlir::OpBuilder& builder, const qc::QuantumComputation& quantumComputation, const llvm::SmallVector& qubits, const BitIndexVec& bitMap) { for (const auto& operation : quantumComputation) { @@ -565,7 +570,6 @@ llvm::LogicalResult addOperations( } return llvm::success(); } -} // namespace /** * @brief Translates a QuantumComputation to an MLIR module with MQTRef diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index e12d3a897b..b889aa2229 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,8 @@ using BitMemInfo = std::pair; // (register ref, localIdx) using BitIndexVec = SmallVector; +} // namespace + /** * @brief Allocates quantum registers using the QCProgramBuilder * @@ -68,7 +71,7 @@ using BitIndexVec = SmallVector; * @param quantumComputation The quantum computation to translate * @return Vector containing information about all quantum registers */ -SmallVector +static SmallVector allocateQregs(QCProgramBuilder& builder, const ::qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting @@ -113,7 +116,7 @@ allocateQregs(QCProgramBuilder& builder, * @param qregs Vector containing information about all quantum registers * @return Flat vector of qubit values indexed by physical qubit index */ -SmallVector +static SmallVector buildQubitMap(const ::qc::QuantumComputation& quantumComputation, const SmallVector& qregs) { SmallVector flatQubits; @@ -142,7 +145,7 @@ buildQubitMap(const ::qc::QuantumComputation& quantumComputation, * @param quantumComputation The quantum computation to translate * @return Vector mapping global bit indices to register and local indices */ -BitIndexVec +static BitIndexVec allocateClassicalRegisters(QCProgramBuilder& builder, const ::qc::QuantumComputation& quantumComputation) { // Build list of pointers for sorting @@ -186,8 +189,10 @@ allocateClassicalRegisters(QCProgramBuilder& builder, * @param qubits Flat vector of qubit values indexed by physical qubit index * @param bitMap Mapping from global bit index to (register, local_index) */ -void addMeasureOp(QCProgramBuilder& builder, const ::qc::Operation& operation, - const SmallVector& qubits, const BitIndexVec& bitMap) { +static void addMeasureOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + const SmallVector& qubits, + const BitIndexVec& bitMap) { const auto& measureOp = dynamic_cast(operation); const auto& targets = measureOp.getTargets(); @@ -214,8 +219,9 @@ void addMeasureOp(QCProgramBuilder& builder, const ::qc::Operation& operation, * @param operation The reset operation to translate * @param qubits Flat vector of qubit values indexed by physical qubit index */ -void addResetOp(QCProgramBuilder& builder, const ::qc::Operation& operation, - const SmallVector& qubits) { +static void addResetOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + const SmallVector& qubits) { for (const auto& target : operation.getTargets()) { auto qubit = qubits[target]; builder.reset(qubit); @@ -233,8 +239,8 @@ void addResetOp(QCProgramBuilder& builder, const ::qc::Operation& operation, * @param qubits Flat vector of qubit values indexed by physical qubit index * @return Vector of qubit values corresponding to positive controls */ -SmallVector getPosControls(const ::qc::Operation& operation, - const SmallVector& qubits) { +static SmallVector getPosControls(const ::qc::Operation& operation, + const SmallVector& qubits) { SmallVector controls; for (const auto& [control, type] : operation.getControls()) { if (type == ::qc::Control::Type::Neg) { @@ -259,9 +265,9 @@ SmallVector getPosControls(const ::qc::Operation& operation, * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ posControls.empty()) { \ @@ -299,9 +305,9 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target = qubits[operation.getTargets()[0]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ @@ -331,9 +337,9 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target = qubits[operation.getTargets()[0]]; \ @@ -364,9 +370,9 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& param3 = operation.getParameter()[2]; \ @@ -397,9 +403,9 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ if (const auto& posControls = getPosControls(operation, qubits); \ @@ -431,9 +437,9 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ @@ -466,9 +472,9 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) * @param operation The OP_CORE operation to translate \ * @param qubits Flat vector of qubit values indexed by physical qubit index \ */ \ - void add##OP_CORE##Op(QCProgramBuilder& builder, \ - const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + static void add##OP_CORE##Op(QCProgramBuilder& builder, \ + const ::qc::Operation& operation, \ + const SmallVector& qubits) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ @@ -488,8 +494,9 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) // BarrierOp -void addBarrierOp(QCProgramBuilder& builder, const ::qc::Operation& operation, - const SmallVector& qubits) { +static void addBarrierOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + const SmallVector& qubits) { SmallVector targets; for (const auto& targetIdx : operation.getTargets()) { targets.push_back(qubits[targetIdx]); @@ -515,7 +522,7 @@ void addBarrierOp(QCProgramBuilder& builder, const ::qc::Operation& operation, * @param bitMap Mapping from global bit index to (register, local_index) * @return Success if all supported operations were translated */ -LogicalResult +static LogicalResult translateOperations(QCProgramBuilder& builder, const ::qc::QuantumComputation& quantumComputation, const SmallVector& qubits, @@ -569,8 +576,6 @@ translateOperations(QCProgramBuilder& builder, #undef ADD_OP_CASE -} // namespace - /** * @brief Translates a QuantumComputation to an MLIR module with QC * operations diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index c9ea8dc2ce..a3fa2123b7 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp index 2dc82bf9aa..373ebce5ca 100644 --- a/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp +++ b/mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Support/PrettyPrinting.cpp b/mlir/lib/Support/PrettyPrinting.cpp index fe1563096b..edc2f7eed3 100644 --- a/mlir/lib/Support/PrettyPrinting.cpp +++ b/mlir/lib/Support/PrettyPrinting.cpp @@ -18,18 +18,14 @@ namespace mlir { -namespace { - /** * @brief Trim trailing whitespace from a string */ -std::string trimTrailingWhitespace(const std::string& str) { +static std::string trimTrailingWhitespace(const std::string& str) { const size_t end = str.find_last_not_of(" \t\r\n"); return (end == std::string::npos) ? "" : str.substr(0, end + 1); } -} // namespace - constexpr auto TOTAL_WIDTH = 120; constexpr auto BORDER_WIDTH = 2; // "║ " on each side diff --git a/mlir/unittests/dialect/test_wireiterator.cpp b/mlir/unittests/dialect/test_wireiterator.cpp index 562dd4a535..0493872be3 100644 --- a/mlir/unittests/dialect/test_wireiterator.cpp +++ b/mlir/unittests/dialect/test_wireiterator.cpp @@ -33,11 +33,10 @@ using namespace mlir; using namespace mqt::ir::opt; -namespace { /** @returns a module containing the circuit from the "Tackling the Qubit * Mapping Problem for NISQ-Era Quantum Devices" paper by Li et al. */ -OwningOpRef getModule(MLIRContext& ctx) { +static OwningOpRef getModule(MLIRContext& ctx) { const char* ir = R"mlir( module { %0 = mqtopt.allocQubit @@ -74,7 +73,7 @@ module { return parseSourceString(ir, &ctx); } -std::string toString(Operation* op) { +static std::string toString(Operation* op) { std::string opStr; llvm::raw_string_ostream os(opStr); os << *op; @@ -82,14 +81,13 @@ std::string toString(Operation* op) { return opStr; } -void checkOperationEqual(Operation* op, const std::string& expected) { +static void checkOperationEqual(Operation* op, const std::string& expected) { ASSERT_EQ(expected, toString(op)); } -void checkOperationStartsWith(Operation* op, const std::string& prefix) { +static void checkOperationStartsWith(Operation* op, const std::string& prefix) { ASSERT_TRUE(toString(op).starts_with(prefix)); } -} // namespace class WireIteratorTest : public ::testing::Test { protected: diff --git a/mlir/unittests/pipeline/test_compiler_pipeline.cpp b/mlir/unittests/pipeline/test_compiler_pipeline.cpp index 583ba5214a..70c32a10cc 100644 --- a/mlir/unittests/pipeline/test_compiler_pipeline.cpp +++ b/mlir/unittests/pipeline/test_compiler_pipeline.cpp @@ -120,10 +120,12 @@ struct OperationStructuralEquality { /// Map to track value equivalence between two modules. using ValueEquivalenceMap = llvm::DenseMap; +} // namespace + /// Compare two operations for structural equivalence. /// Updates valueMap to track corresponding SSA values. -bool areOperationsEquivalent(Operation* lhs, Operation* rhs, - ValueEquivalenceMap& valueMap) { +static bool areOperationsEquivalent(Operation* lhs, Operation* rhs, + ValueEquivalenceMap& valueMap) { // Check operation name if (lhs->getName() != rhs->getName()) { return false; @@ -190,11 +192,12 @@ bool areOperationsEquivalent(Operation* lhs, Operation* rhs, } /// Forward declaration for mutual recursion. -bool areBlocksEquivalent(Block& lhs, Block& rhs, ValueEquivalenceMap& valueMap); +static bool areBlocksEquivalent(Block& lhs, Block& rhs, + ValueEquivalenceMap& valueMap); /// Compare two regions for structural equivalence. -bool areRegionsEquivalent(Region& lhs, Region& rhs, - ValueEquivalenceMap& valueMap) { +static bool areRegionsEquivalent(Region& lhs, Region& rhs, + ValueEquivalenceMap& valueMap) { if (lhs.getBlocks().size() != rhs.getBlocks().size()) { return false; } @@ -210,7 +213,7 @@ bool areRegionsEquivalent(Region& lhs, Region& rhs, /// Check if an operation has memory effects or control flow side effects /// that would prevent reordering. -bool hasOrderingConstraints(Operation* op) { +static bool hasOrderingConstraints(Operation* op) { // Terminators must maintain their position if (op->hasTrait()) { return true; @@ -247,7 +250,7 @@ bool hasOrderingConstraints(Operation* op) { /// Build a dependence graph for operations. /// Returns a map from each operation to the set of operations it depends on. -llvm::DenseMap> +static llvm::DenseMap> buildDependenceGraph(ArrayRef ops) { llvm::DenseMap> dependsOn; llvm::DenseMap valueProducers; @@ -274,7 +277,7 @@ buildDependenceGraph(ArrayRef ops) { /// Partition operations into groups that can be compared as multisets. /// Operations in the same group are independent and can be reordered. -std::vector> +static std::vector> partitionIndependentGroups(ArrayRef ops) { std::vector> groups; if (ops.empty()) { @@ -326,8 +329,8 @@ partitionIndependentGroups(ArrayRef ops) { } /// Compare two groups of independent operations using multiset equivalence. -bool areIndependentGroupsEquivalent(ArrayRef lhsOps, - ArrayRef rhsOps) { +static bool areIndependentGroupsEquivalent(ArrayRef lhsOps, + ArrayRef rhsOps) { if (lhsOps.size() != rhsOps.size()) { return false; } @@ -366,8 +369,8 @@ bool areIndependentGroupsEquivalent(ArrayRef lhsOps, /// Compare two blocks for structural equivalence, allowing permutations /// of independent operations. -bool areBlocksEquivalent(Block& lhs, Block& rhs, - ValueEquivalenceMap& valueMap) { +static bool areBlocksEquivalent(Block& lhs, Block& rhs, + ValueEquivalenceMap& valueMap) { // Check block arguments if (lhs.getNumArguments() != rhs.getNumArguments()) { return false; @@ -456,7 +459,7 @@ bool areBlocksEquivalent(Block& lhs, Block& rhs, /// Compare two MLIR modules for structural equivalence, allowing permutations /// of speculatable operations. -bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { +static bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { ValueEquivalenceMap valueMap; return areRegionsEquivalent(lhs.getBodyRegion(), rhs.getBodyRegion(), valueMap); @@ -470,9 +473,9 @@ bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { * @param expectedModule Expected module to compare against * @return True if modules match, false otherwise with diagnostic output */ -[[nodiscard]] testing::AssertionResult verify(const std::string& stageName, - const std::string& actualIR, - ModuleOp expectedModule) { +[[nodiscard]] static testing::AssertionResult +verify(const std::string& stageName, const std::string& actualIR, + ModuleOp expectedModule) { // Parse the actual IR string into a ModuleOp const auto actualModule = parseSourceString(actualIR, expectedModule.getContext()); @@ -499,6 +502,8 @@ bool areModulesEquivalentWithPermutations(ModuleOp lhs, ModuleOp rhs) { return testing::AssertionSuccess(); } +namespace { + //===----------------------------------------------------------------------===// // Stage Expectation Builder //===----------------------------------------------------------------------===// From e1d3de82474430c198b5eaddfc899c000bf02c81 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 02:05:48 +0100 Subject: [PATCH 401/419] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe3dcbccd..6837fbfc3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264]) ([**@burgholzer**], [**@denialhaag**]) - ✨ Add bi-directional iterator that traverses the def-use chain of a qubit value ([#1310]) ([**@MatthiasReumann**]) ### Changed From b44ba896d44b650eee5fccbfef19eb2c20c8a91a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 03:08:22 +0100 Subject: [PATCH 402/419] Address the Rabbit's comments --- .../mlir/Conversion/QCToQIR/QCToQIR.td | 6 ++--- .../MQTOptToMQTRef/MQTOptToMQTRef.cpp | 2 +- .../Conversion/MQTRefToQIR/MQTRefToQIR.cpp | 2 +- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 14 ++++------- mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp | 12 +++++----- .../TranslateQuantumComputationToQC.cpp | 3 ++- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 24 ++++++++++++++----- 7 files changed, 36 insertions(+), 27 deletions(-) diff --git a/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td index 34dad74d75..205cc9b101 100644 --- a/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td +++ b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td @@ -37,9 +37,9 @@ def QCToQIR : Pass<"qc-to-qir"> { - Blocks are connected via unconditional branches in the order listed above. - Non-quantum dialects are lowered via MLIR's built-in conversions. - Producing LLVM IR: - - After conversion to the LLVM dialect, produce LLVM IR with: - mlir-translate --mlir-to-llvmir input.mlir > output.ll + Producing LLVM IR: + - After conversion to the LLVM dialect, produce LLVM IR with: + mlir-translate --mlir-to-llvmir input.mlir > output.ll }]; diff --git a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp index 68ad2615d5..2c0b993e91 100644 --- a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp +++ b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp @@ -44,7 +44,7 @@ using namespace mlir; #define GEN_PASS_DEF_MQTOPTTOMQTREF #include "mlir/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.h.inc" -static bool isQubitType(const MemRefType type) { +static bool isQubitType(MemRefType type) { return llvm::isa(type.getElementType()); } diff --git a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp index dcfc88e078..59977c8a00 100644 --- a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp +++ b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp @@ -138,7 +138,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { } // namespace -static bool isQubitType(const MemRefType type) { +static bool isQubitType(MemRefType type) { return llvm::isa(type.getElementType()); } diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 1dd795c172..a1972a18ea 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -364,15 +364,11 @@ convertTwoTargetZeroParameter(QCOpType& op, ConversionPatternRewriter& rewriter, // Create the QCO operation (consumes input, produces output) auto qcoOp = rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1); - // Update state map + // Update the state map if (inCtrlOp == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); qubitMap[qcQubit0] = qcoOp.getQubit0Out(); qubitMap[qcQubit1] = qcoOp.getQubit1Out(); } else { - assert(state.targetsIn[inCtrlOp].size() == 2 && - "Invalid number of input targets"); state.targetsIn.erase(inCtrlOp); const SmallVector targetsOut( {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); @@ -423,7 +419,7 @@ convertTwoTargetOneParameter(QCOpType& op, ConversionPatternRewriter& rewriter, auto qcoOp = rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1, op.getParameter(0)); - // Update state map + // Update the state map if (inCtrlOp == 0) { qubitMap[qcQubit0] = qcoOp.getQubit0Out(); qubitMap[qcQubit1] = qcoOp.getQubit1Out(); @@ -479,7 +475,7 @@ convertTwoTargetTwoParameter(QCOpType& op, ConversionPatternRewriter& rewriter, rewriter.create(op.getLoc(), qcoQubit0, qcoQubit1, op.getParameter(0), op.getParameter(1)); - // Update state map + // Update the state map if (inCtrlOp == 0) { qubitMap[qcQubit0] = qcoOp.getQubit0Out(); qubitMap[qcQubit1] = qcoOp.getQubit1Out(); @@ -1050,7 +1046,7 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { // Create qco.barrier auto qcoOp = rewriter.create(op.getLoc(), qcoQubits); - // Update state map + // Update the state map for (const auto& [qcQubit, qcoQubitOut] : llvm::zip(qcQubits, qcoOp.getQubitsOut())) { qubitMap[qcQubit] = qcoQubitOut; @@ -1112,7 +1108,7 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { auto qcoOp = rewriter.create(op.getLoc(), qcoControls, qcoTargets); - // Update state map if this is a top-level CtrlOp + // Update the state map if this is a top-level CtrlOp // Nested CtrlOps are managed via the targetsIn and targetsOut maps if (state.inCtrlOp == 0) { for (const auto& [qcControl, qcoControl] : diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp index ecda8cb0a3..cd88dcc052 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp @@ -82,15 +82,15 @@ struct CtrlInlineGPhase final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto gPhaseOp = - llvm::dyn_cast(op.getBodyUnitary().getOperation()); - if (!gPhaseOp) { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumPosControls() == 0) { return failure(); } - // Require at least one positive control; otherwise let other patterns - // (e.g., RemoveTrivialCtrl) handle the trivial case. - if (op.getNumPosControls() == 0) { + auto gPhaseOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!gPhaseOp) { return failure(); } diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index b889aa2229..f76268a76b 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -244,7 +244,8 @@ static SmallVector getPosControls(const ::qc::Operation& operation, SmallVector controls; for (const auto& [control, type] : operation.getControls()) { if (type == ::qc::Control::Type::Neg) { - continue; + llvm::reportFatalInternalError( + "Negative controls cannot be translated to QC at the moment"); } controls.push_back(qubits[control]); } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 8ca6c0db89..d96b5c88a5 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -36,15 +36,15 @@ struct MergeNestedCtrl final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto bodyCtrlOp = - llvm::dyn_cast(op.getBodyUnitary().getOperation()); - if (!bodyCtrlOp) { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumPosControls() == 0) { return failure(); } - // Require at least one positive control; otherwise let other patterns - // (e.g., RemoveTrivialCtrl) handle the trivial case. - if (op.getNumPosControls() == 0) { + auto bodyCtrlOp = + llvm::dyn_cast(op.getBodyUnitary().getOperation()); + if (!bodyCtrlOp) { return failure(); } @@ -91,6 +91,12 @@ struct CtrlInlineGPhase final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumPosControls() == 0) { + return failure(); + } + auto gPhaseOp = llvm::dyn_cast(op.getBodyUnitary().getOperation()); if (!gPhaseOp) { @@ -122,6 +128,12 @@ struct CtrlInlineId final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { + // Require at least one positive control + // Trivial case is handled by RemoveTrivialCtrl + if (op.getNumPosControls() == 0) { + return failure(); + } + if (!llvm::isa(op.getBodyUnitary().getOperation())) { return failure(); } From e195428341a9210a91cc9def38afafacddf69a49 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:34:19 +0100 Subject: [PATCH 403/419] Fix remaining linter errors --- .../MQTRefToMQTOpt/MQTRefToMQTOpt.cpp | 14 ++-- .../Transforms/Transpilation/LayeredUnit.cpp | 6 +- .../Translation/ImportQuantumComputation.cpp | 8 +- mlir/tools/mqt-cc/mqt-cc.cpp | 10 ++- .../translation/test_translation.cpp | 78 ++++++++++++++----- 5 files changed, 81 insertions(+), 35 deletions(-) diff --git a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp index e7903e7096..367f9c3998 100644 --- a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp +++ b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp @@ -74,26 +74,28 @@ class StatefulOpConversionPattern : public mlir::OpConversionPattern { LoweringState* state_; }; -bool isQubitType(const MemRefType type) { +} // namespace + +static bool isQubitType(const MemRefType type) { return llvm::isa(type.getElementType()); } -bool isQubitType(memref::AllocOp op) { return isQubitType(op.getType()); } +static bool isQubitType(memref::AllocOp op) { + return isQubitType(op.getType()); +} -bool isQubitType(memref::DeallocOp op) { +static bool isQubitType(memref::DeallocOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -bool isQubitType(memref::LoadOp op) { +static bool isQubitType(memref::LoadOp op) { const auto& memRef = op.getMemref(); const auto& memRefType = llvm::cast(memRef.getType()); return isQubitType(memRefType); } -} // namespace - class MQTRefToMQTOptTypeConverter final : public TypeConverter { public: explicit MQTRefToMQTOptTypeConverter(MLIRContext* ctx) { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 8c0e46b503..05809e188e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -80,9 +80,10 @@ class SynchronizationMap { /// @brief Maps operations to ref counts. mlir::DenseMap refCount; }; +} // namespace -mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, - Layer& opLayer) { +static mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, + Layer& opLayer) { assert(wires.size() == 2 && "expected two wires"); auto [it0, index0] = wires[0]; @@ -130,7 +131,6 @@ mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, return {Wire(it0, index0), Wire(it1, index1)}; } -} // namespace LayeredUnit LayeredUnit::fromEntryPointFunction(mlir::func::FuncOp func, const std::size_t nqubits) { diff --git a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp index 5dcb4afda0..49548caf1f 100644 --- a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp +++ b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp @@ -337,10 +337,10 @@ addOperation(mlir::OpBuilder& builder, const qc::Operation& operation, * localIdx) * @return llvm::LogicalResult whether the adding of the blockOps was successful */ -llvm::LogicalResult addBlockOps(mlir::OpBuilder& builder, - const qc::Operation* operationInBlock, - const llvm::SmallVector& qubits, - const BitIndexVec& bitMap) { +static llvm::LogicalResult +addBlockOps(mlir::OpBuilder& builder, const qc::Operation* operationInBlock, + const llvm::SmallVector& qubits, + const BitIndexVec& bitMap) { if (operationInBlock->isCompoundOperation()) { for (const auto& operation : dynamic_cast(*operationInBlock)) { diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 25c80cb941..5800d2b727 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -67,10 +67,13 @@ const cl::opt cl::desc("Print IR after each compiler stage"), cl::init(false)); +} // namespace + /** * @brief Load and parse a .mlir file */ -OwningOpRef loadMLIRFile(StringRef filename, MLIRContext* context) { +static OwningOpRef loadMLIRFile(StringRef filename, + MLIRContext* context) { // Set up the input file std::string errorMessage; auto file = openInputFile(filename, &errorMessage); @@ -88,7 +91,8 @@ OwningOpRef loadMLIRFile(StringRef filename, MLIRContext* context) { /** * @brief Write the module to the output file */ -mlir::LogicalResult writeOutput(ModuleOp module, const StringRef filename) { +static mlir::LogicalResult writeOutput(ModuleOp module, + const StringRef filename) { std::string errorMessage; const auto output = openOutputFile(filename, &errorMessage); if (!output) { @@ -101,8 +105,6 @@ mlir::LogicalResult writeOutput(ModuleOp module, const StringRef filename) { return mlir::success(); } -} // namespace - int main(int argc, char** argv) { const InitLLVM y(argc, argv); diff --git a/mlir/unittests/translation/test_translation.cpp b/mlir/unittests/translation/test_translation.cpp index 72cc7fa3c6..0cb89aa73b 100644 --- a/mlir/unittests/translation/test_translation.cpp +++ b/mlir/unittests/translation/test_translation.cpp @@ -43,10 +43,10 @@ #include #include -namespace { - using namespace qc; +namespace { + class ImportTest : public ::testing::Test { protected: std::unique_ptr context; @@ -77,11 +77,13 @@ class ImportTest : public ::testing::Test { void TearDown() override {} }; +} // namespace + // ################################################## // # Helper functions // ################################################## -std::string getOutputString(mlir::OwningOpRef* module) { +static std::string getOutputString(mlir::OwningOpRef* module) { std::string outputString; llvm::raw_string_ostream os(outputString); (*module)->print(os); @@ -89,7 +91,7 @@ std::string getOutputString(mlir::OwningOpRef* module) { return outputString; } -std::string formatTargets(std::initializer_list targets) { +static std::string formatTargets(std::initializer_list targets) { std::string s; bool first = true; for (auto t : targets) { @@ -102,7 +104,7 @@ std::string formatTargets(std::initializer_list targets) { return s; } -std::string formatParams(std::initializer_list params) { +static std::string formatParams(std::initializer_list params) { if (params.size() == 0) { return ""; } @@ -122,12 +124,12 @@ std::string formatParams(std::initializer_list params) { return os.str(); } -std::string getCheckStringOperation(const char* op, - std::initializer_list targets) { +static std::string +getCheckStringOperation(const char* op, std::initializer_list targets) { return std::string("CHECK: mqtref.") + op + "() " + formatTargets(targets); } -std::string +static std::string getCheckStringOperationParams(const char* op, std::initializer_list params, std::initializer_list targets) { @@ -137,8 +139,8 @@ getCheckStringOperationParams(const char* op, // Adapted from // https://github.com/llvm/llvm-project/blob/d2b3e86321eaf954451e0a49534fa654dd67421e/llvm/unittests/MIR/MachineMetadata.cpp#L181 -bool checkOutput(const std::string& checkString, - const std::string& outputString) { +static bool checkOutput(const std::string& checkString, + const std::string& outputString) { auto checkBuffer = llvm::MemoryBuffer::getMemBuffer(checkString, ""); auto outputBuffer = llvm::MemoryBuffer::getMemBuffer(outputString, "Output", false); @@ -166,6 +168,8 @@ bool checkOutput(const std::string& checkString, // # Basic tests // ################################################## +namespace { + TEST_F(ImportTest, EntryPoint) { const QuantumComputation qc{}; @@ -291,10 +295,14 @@ TEST_F(ImportTest, Reset0) { ASSERT_TRUE(checkOutput(checkString, outputString)); } +} // namespace + // ################################################## // # Test unitary operations // ################################################## +namespace { + struct TestCaseUnitary { std::string name; // Name of the test case size_t numQubits; @@ -302,11 +310,15 @@ struct TestCaseUnitary { std::string checkStringOperation; }; -std::ostream& operator<<(std::ostream& os, const TestCaseUnitary& testCase) { +} // namespace + +static std::ostream& operator<<(std::ostream& os, + const TestCaseUnitary& testCase) { return os << testCase.name; } -std::string getCheckStringTestCaseUnitary(const TestCaseUnitary& testCase) { +static std::string +getCheckStringTestCaseUnitary(const TestCaseUnitary& testCase) { std::string result; // Add entry point @@ -341,6 +353,8 @@ std::string getCheckStringTestCaseUnitary(const TestCaseUnitary& testCase) { return result; } +namespace { + class OperationTestUnitary : public ImportTest, public ::testing::WithParamInterface {}; @@ -575,21 +589,28 @@ INSTANTIATE_TEST_SUITE_P( .checkStringOperation = "CHECK: mqtref.x() %[[Q0]] nctrl %[[Q1]], %[[Q2]]"})); +} // namespace + // ################################################## // # Test register-controlled if-else operations // ################################################## +namespace { + struct TestCaseIfRegister { std::string name; // Name of the test case ComparisonKind comparisonKind; std::string predicate; }; -std::ostream& operator<<(std::ostream& os, const TestCaseIfRegister& testCase) { +} // namespace + +static std::ostream& operator<<(std::ostream& os, + const TestCaseIfRegister& testCase) { return os << testCase.name; } -std::string +static std::string getCheckStringTestCaseIfRegister(const TestCaseIfRegister& testCase) { std::string result; @@ -618,6 +639,8 @@ getCheckStringTestCaseIfRegister(const TestCaseIfRegister& testCase) { return result; } +namespace { + class OperationTestIfRegister : public ImportTest, public ::testing::WithParamInterface {}; @@ -830,10 +853,14 @@ TEST_F(ImportTest, IfElseHandlingFromQasmMultipleStatements) { ASSERT_TRUE(checkOutput(checkString, outputString)); } +} // namespace + // ################################################## // # Test bit-controlled if-else operations // ################################################## +namespace { + struct TestCaseIfBit { std::string name; // Name of the test case ComparisonKind comparisonKind; @@ -841,11 +868,14 @@ struct TestCaseIfBit { bool expectedValueOutput; }; -std::ostream& operator<<(std::ostream& os, const TestCaseIfBit& testCase) { +} // namespace + +static std::ostream& operator<<(std::ostream& os, + const TestCaseIfBit& testCase) { return os << testCase.name; } -std::string getCheckStringTestCaseIfBit(const TestCaseIfBit& testCase) { +static std::string getCheckStringTestCaseIfBit(const TestCaseIfBit& testCase) { std::string result; result += R"( @@ -882,6 +912,8 @@ std::string getCheckStringTestCaseIfBit(const TestCaseIfBit& testCase) { return result; } +namespace { + class OperationTestIfBit : public ImportTest, public ::testing::WithParamInterface { }; @@ -929,11 +961,15 @@ struct TestCaseIfElseBit { std::string elseOperation; }; -std::ostream& operator<<(std::ostream& os, const TestCaseIfElseBit& testCase) { +} // namespace + +static std::ostream& operator<<(std::ostream& os, + const TestCaseIfElseBit& testCase) { return os << testCase.name; } -std::string getCheckStringTestCaseIfElseBit(const TestCaseIfElseBit& testCase) { +static std::string +getCheckStringTestCaseIfElseBit(const TestCaseIfElseBit& testCase) { std::string result; result += R"( @@ -958,6 +994,8 @@ std::string getCheckStringTestCaseIfElseBit(const TestCaseIfElseBit& testCase) { return result; } +namespace { + class OperationTestIfElseBit : public ImportTest, public ::testing::WithParamInterface {}; @@ -1003,10 +1041,14 @@ INSTANTIATE_TEST_SUITE_P( .thenOperation = "x", .elseOperation = "y"})); +} // namespace + // ################################################## // # Test full programs // ################################################## +namespace { + TEST_F(ImportTest, GHZ) { QuantumComputation qc(3, 3); qc.h(0); From 9cb28d0923d8ad43856f426d9e2725cff9dea959 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:42:12 +0100 Subject: [PATCH 404/419] Address the Rabit's remaining comments --- mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td | 6 +++--- mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td index 205cc9b101..15462eb20d 100644 --- a/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td +++ b/mlir/include/mlir/Conversion/QCToQIR/QCToQIR.td @@ -21,14 +21,14 @@ def QCToQIR : Pass<"qc-to-qir"> { Requirements: - Input is a valid module in the QC dialect. - The entry function must be marked with the `entry_point` attribute. - - The entry function must have a single block. - Multi-block functions are currently unsupported. + - The input entry function must consist of a single block. + Multi-block input functions are currently not supported. - The program must have straight-line control flow (i.e., Base Profile QIR). Behavior: - Each QC quantum operation is replaced by a call to the corresponding QIR function in the LLVM dialect. - Required QIR module flags are attached as attributes to the entry function. - - The entry function is split into four blocks to satisfy QIR Base Profile constraints: + - The pass transforms the single-block entry function into four blocks to satisfy QIR Base Profile constraints: 0. Initialization block: Sets up the execution environment and performs required runtime initialization. 1. Reversible operations block: Contains only void-returning calls to reversible quantum operations. 2. Irreversible operations block: Contains only void-returning calls to operations marked irreversible, e.g.: diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 4da58ef739..9586104e35 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -888,8 +888,9 @@ struct ConvertQCYieldQIR final : StatefulOpConversionPattern { * 6. Reconcile unrealized casts * * @pre - * The entry function must have a single block. The pass will restructure it - * into a 4-block layout. Multi-block functions are currently unsupported. + * The input entry function must consist of a single block. The pass will + * restructure it into a four blocks. Multi-block input functions are currently + * not supported. */ struct QCToQIR final : impl::QCToQIRBase { using QCToQIRBase::QCToQIRBase; From 421cf4b081446cdf835ec066c7b86e78a0f496c6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:45:33 +0100 Subject: [PATCH 405/419] Update dialect descriptions --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 8 +++----- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 69c2036c57..4e3b8b6d2a 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -37,14 +37,12 @@ def QCDialect : Dialect { - Direct compatibility with imperative quantum programming languages - Straightforward backend code generation - The name "QC" reflects the crystalline, structured nature of - hardware-oriented representations—operations have fixed positions and - transform states in place, like atoms in a crystal lattice. + The name "QC" stands for "Quantum Circuit." Example: ```mlir - qc.h %q // Applies Hadamard to qubit %q in place - qc.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets + qc.h %q // Applies Hadamard to qubit %q in place + qc.swap %q0, %q1 // Applies SWAP using %q0, %q1 as targets ``` }]; diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 1454d91cd3..56674b971b 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -36,15 +36,12 @@ def QCODialect : Dialect { - Advanced transformation passes - Explicit dependency tracking - The name "QCO" captures the flowing, transformative nature of - value-based representations—quantum states flow through operations, - each transformation producing new values like a river flowing through - a landscape. + The name "QCO" stands for "Quantum Circuit Optimization." Example: ```mlir - %q_out = qco.h %q_in // Consumes %q_in, produces %q_out - %q0_out, %q1_out = qco.swap %q0_in, %q1_in // Consumes inputs, produces outputs + %q_out = qco.h %q_in // Consumes %q_in, produces %q_out + %q0_out, %q1_out = qco.swap %q0_in, %q1_in // Consumes inputs, produces outputs ``` }]; From c76577cea85d9838008eef504954597d69535db1 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:50:13 +0100 Subject: [PATCH 406/419] One more fix for the Rabbit --- .../Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp index 638312c604..3387ec26b3 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp @@ -39,7 +39,7 @@ namespace mqt::ir::opt { -static bool isQubitType(const mlir::MemRefType type) { +static bool isQubitType(mlir::MemRefType type) { return llvm::isa(type.getElementType()); } From b2837ad1a481ce7feb20ce056e79a03ab9a7aa81 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:41:28 +0100 Subject: [PATCH 407/419] Do not mark type as const --- mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp index 367f9c3998..fcfb8f18fd 100644 --- a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp +++ b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp @@ -76,7 +76,7 @@ class StatefulOpConversionPattern : public mlir::OpConversionPattern { } // namespace -static bool isQubitType(const MemRefType type) { +static bool isQubitType(MemRefType type) { return llvm::isa(type.getElementType()); } From e3b24f3aa351901e673d5ae3bf92e23b2bb84f3d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:41:56 +0100 Subject: [PATCH 408/419] Do not annotate F64 values --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 40 +++++++++++----------- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 40 +++++++++++----------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 4e3b8b6d2a..95c638d3ef 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -258,7 +258,7 @@ def GPhaseOp : QCOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParamet ``` }]; - let arguments = (ins Arg:$theta); + let arguments = (ins Arg:$theta); let assemblyFormat = "$theta attr-dict"; let extraClassDeclaration = [{ @@ -491,7 +491,7 @@ def RXOp : QCOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -515,7 +515,7 @@ def RYOp : QCOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -539,7 +539,7 @@ def RZOp : QCOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -563,7 +563,7 @@ def POp : QCOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -587,8 +587,8 @@ def ROp : QCOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta, - Arg:$phi); + Arg:$theta, + Arg:$phi); let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -612,8 +612,8 @@ def U2Op : QCOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$phi, - Arg:$lambda); + Arg:$phi, + Arg:$lambda); let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -637,9 +637,9 @@ def UOp : QCOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta, - Arg:$phi, - Arg:$lambda); + Arg:$theta, + Arg:$phi, + Arg:$lambda); let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict"; let extraClassDeclaration = [{ @@ -744,7 +744,7 @@ def RXXOp : QCOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ @@ -769,7 +769,7 @@ def RYYOp : QCOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ @@ -794,7 +794,7 @@ def RZXOp : QCOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ @@ -819,7 +819,7 @@ def RZZOp : QCOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ @@ -844,8 +844,8 @@ def XXPlusYYOp : QCOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoPa let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta, - Arg:$beta); + Arg:$theta, + Arg:$beta); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ @@ -870,8 +870,8 @@ def XXMinusYYOp : QCOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwo let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta, - Arg:$beta); + Arg:$theta, + Arg:$beta); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; let extraClassDeclaration = [{ diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 56674b971b..d9ae310270 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -267,7 +267,7 @@ def GPhaseOp : QCOOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParame ``` }]; - let arguments = (ins Arg:$theta); + let arguments = (ins Arg:$theta); let assemblyFormat = "$theta attr-dict"; let extraClassDeclaration = [{ @@ -535,7 +535,7 @@ def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -562,7 +562,7 @@ def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -589,7 +589,7 @@ def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -616,7 +616,7 @@ def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -643,8 +643,8 @@ def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta, - Arg:$phi); + Arg:$theta, + Arg:$phi); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -671,8 +671,8 @@ def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$phi, - Arg:$lambda); + Arg:$phi, + Arg:$lambda); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -699,9 +699,9 @@ def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { }]; let arguments = (ins Arg:$qubit_in, - Arg:$theta, - Arg:$phi, - Arg:$lambda); + Arg:$theta, + Arg:$phi, + Arg:$lambda); let results = (outs QubitType:$qubit_out); let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in) `->` type($qubit_out)"; @@ -817,7 +817,7 @@ def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; @@ -845,7 +845,7 @@ def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; @@ -873,7 +873,7 @@ def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; @@ -901,7 +901,7 @@ def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta); + Arg:$theta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; @@ -929,8 +929,8 @@ def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoP let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta, - Arg:$beta); + Arg:$theta, + Arg:$beta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; @@ -958,8 +958,8 @@ def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTw let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, - Arg:$theta, - Arg:$beta); + Arg:$theta, + Arg:$beta); let results = (outs QubitType:$qubit0_out, QubitType:$qubit1_out); let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in) `->` type($qubit0_out) `,` type($qubit1_out)"; From e017879129397d2c52d3a56325e7be0f46faccad Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:50:53 +0100 Subject: [PATCH 409/419] Fix intermediate recording --- mlir/tools/mqt-cc/mqt-cc.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 5800d2b727..328a3143f1 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -140,12 +140,34 @@ int main(int argc, char** argv) { config.printIRAfterAllStages = PRINT_IR_AFTER_ALL_STAGES; // Run the compilation pipeline + CompilationRecord record; if (const QuantumCompilerPipeline pipeline(config); - pipeline.runPipeline(module.get()).failed()) { + pipeline + .runPipeline(module.get(), RECORD_INTERMEDIATES ? &record : nullptr) + .failed()) { errs() << "Compilation pipeline failed\n"; return 1; } + if (RECORD_INTERMEDIATES) { + outs() << "=== Compilation Record ===\n"; + outs() << "After QC Import:\n" << record.afterQCImport << "\n"; + outs() << "After QC Canonicalization:\n" + << record.afterInitialCanon << "\n"; + outs() << "After QC-to-QCO Conversion:\n" + << record.afterQCOConversion << "\n"; + outs() << "After QCO Canonicalization:\n" << record.afterQCOCanon << "\n"; + outs() << "After Optimization:\n" << record.afterOptimization << "\n"; + outs() << "After QCO Canonicalization:\n" + << record.afterOptimizationCanon << "\n"; + outs() << "After QCO-to-QC Conversion:\n" + << record.afterQCConversion << "\n"; + outs() << "After QC Canonicalization:\n" << record.afterQCCanon << "\n"; + outs() << "After QC-to-QIR Conversion:\n" + << record.afterQIRConversion << "\n"; + outs() << "After QIR Canonicalization:\n" << record.afterQIRCanon << "\n"; + } + // Write the output if (writeOutput(module.get(), OUTPUT_FILENAME).failed()) { errs() << "Failed to write output file: " << OUTPUT_FILENAME << "\n"; From f315640a9459f003a2b82e414589e4257d93709c Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:04:09 +0100 Subject: [PATCH 410/419] Improve description of CtrlOp --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 95c638d3ef..866578b2ac 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -937,7 +937,10 @@ def CtrlOp : QCOp<"ctrl", A modifier operation that adds control qubits to the unitary operation defined in its body region. The controlled operation applies the underlying unitary only when all control qubits are in the |1⟩ state. - The control qubits are not modified by this operation. + + Note that control qubits are logically unmodified by this operation in that + their quantum state remains unchanged. However, the `controls` argument + is marked with `MemWrite` to ensure correct dependency tracking in MLIR. Example: ```mlir From c2998d3ae5fcbd2cbd7d022579088244092815a6 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:12:38 +0100 Subject: [PATCH 411/419] Mark operator<< as maybe unused --- mlir/unittests/translation/test_translation.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mlir/unittests/translation/test_translation.cpp b/mlir/unittests/translation/test_translation.cpp index 0cb89aa73b..7be01a00f6 100644 --- a/mlir/unittests/translation/test_translation.cpp +++ b/mlir/unittests/translation/test_translation.cpp @@ -312,8 +312,8 @@ struct TestCaseUnitary { } // namespace -static std::ostream& operator<<(std::ostream& os, - const TestCaseUnitary& testCase) { +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseUnitary& testCase) { return os << testCase.name; } @@ -605,8 +605,8 @@ struct TestCaseIfRegister { } // namespace -static std::ostream& operator<<(std::ostream& os, - const TestCaseIfRegister& testCase) { +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseIfRegister& testCase) { return os << testCase.name; } @@ -870,8 +870,8 @@ struct TestCaseIfBit { } // namespace -static std::ostream& operator<<(std::ostream& os, - const TestCaseIfBit& testCase) { +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseIfBit& testCase) { return os << testCase.name; } @@ -963,8 +963,8 @@ struct TestCaseIfElseBit { } // namespace -static std::ostream& operator<<(std::ostream& os, - const TestCaseIfElseBit& testCase) { +[[maybe_unused]] static std::ostream& +operator<<(std::ostream& os, const TestCaseIfElseBit& testCase) { return os << testCase.name; } From 3db1dfe94f592395a6e560da4b426f94855d66a3 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:19:37 +0100 Subject: [PATCH 412/419] Fix naming of QIR blocks --- mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 9586104e35..e82a73b240 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -889,7 +889,7 @@ struct ConvertQCYieldQIR final : StatefulOpConversionPattern { * * @pre * The input entry function must consist of a single block. The pass will - * restructure it into a four blocks. Multi-block input functions are currently + * restructure it into four blocks. Multi-block input functions are currently * not supported. */ struct QCToQIR final : impl::QCToQIRBase { @@ -906,9 +906,8 @@ struct QCToQIR final : impl::QCToQIRBase { * reset, dealloc) * 4. **Output block**: Contains output recording calls * - * Blocks are connected with unconditional jumps (entry → body → - * measurements → output). This structure ensures proper QIR Base - * Profile semantics. + * Blocks are connected with unconditional jumps (entry, body, measurements, + * output). This structure ensures proper QIR Base Profile semantics. * * If the function already has multiple blocks, this function does nothing. * @@ -1055,11 +1054,10 @@ struct QCToQIR final : impl::QCToQIRBase { OpBuilder builder(ctx); const auto ptrType = LLVM::LLVMPointerType::get(ctx); - // Find the output block (4th block: entry, body, measurements, output, - // end) + // Find the output block auto& outputBlock = main.getBlocks().back(); - // Insert before the branch to end block + // Insert before the branch to output block builder.setInsertionPoint(&outputBlock.back()); // Group measurements by register @@ -1132,7 +1130,7 @@ struct QCToQIR final : impl::QCToQIRBase { * * **Stage 2: Block structure and initialization** * Create proper 4-block structure for QIR base profile (entry, main, - * irreversible, end blocks) and insert the `__quantum__rt__initialize` call + * irreversible, output) and insert the `__quantum__rt__initialize` call * in the entry block. * * **Stage 3: QC to LLVM** From 5acaff91a64edbf565af361214168bf608bf74ff Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:25:03 +0100 Subject: [PATCH 413/419] Do not mark filename as const --- mlir/tools/mqt-cc/mqt-cc.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 328a3143f1..1243bd0e29 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -91,8 +91,7 @@ static OwningOpRef loadMLIRFile(StringRef filename, /** * @brief Write the module to the output file */ -static mlir::LogicalResult writeOutput(ModuleOp module, - const StringRef filename) { +static mlir::LogicalResult writeOutput(ModuleOp module, StringRef filename) { std::string errorMessage; const auto output = openOutputFile(filename, &errorMessage); if (!output) { From eb903d0aef1d49ef83952318706eb6d50f4132a0 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:30:42 +0100 Subject: [PATCH 414/419] Streamline assembly formats --- .../Dialect/QC/Builder/QCProgramBuilder.h | 34 +++++----- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 64 +++++++++---------- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 4 +- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 4 +- 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index 80aff6bca5..2887405614 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -271,7 +271,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q) { \ * qc.OP_NAME(%PARAM) \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ @@ -290,7 +290,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME(%PARAM) \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ @@ -332,7 +332,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0) { \ * qc.OP_NAME %q1 : !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(Value control, Value target); \ @@ -350,7 +350,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME %q2 : !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(ValueRange controls, Value target); @@ -404,7 +404,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0) { \ * qc.OP_NAME(%PARAM) %q1 : !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ @@ -424,7 +424,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME(%PARAM) %q2 : !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ @@ -475,7 +475,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0) { \ * qc.OP_NAME(%PARAM1, %PARAM2) %q1 : !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ @@ -497,7 +497,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME(%PARAM1, %PARAM2) %q2 : !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ @@ -551,7 +551,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0) { \ * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ @@ -575,7 +575,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ @@ -621,7 +621,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0) { \ * qc.OP_NAME %q1, %q2 : !qc.qubit, !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(Value control, Value qubit0, Value qubit1); \ @@ -640,7 +640,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME %q2, %q3 : !qc.qubit, !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(ValueRange controls, Value qubit0, \ @@ -690,7 +690,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0) { \ * qc.OP_NAME(%PARAM) %q1, %q2 : !qc.qubit, !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM), \ @@ -711,7 +711,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME(%PARAM) %q2, %q3 : !qc.qubit, !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM), \ @@ -766,7 +766,7 @@ class QCProgramBuilder final : public OpBuilder { * qc.ctrl(%q0) { \ * qc.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !qc.qubit, \ * !qc.qubit \ - * } \ + * } : !qc.qubit \ * ``` \ */ \ QCProgramBuilder& c##OP_NAME(const std::variant&(PARAM1), \ @@ -789,7 +789,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir \ * qc.ctrl(%q0, %q1) { \ * qc.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !qc.qubit, !qc.qubit \ - * } \ + * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ QCProgramBuilder& mc##OP_NAME(const std::variant&(PARAM1), \ @@ -838,7 +838,7 @@ class QCProgramBuilder final : public OpBuilder { * ```mlir * qc.ctrl(%q0) { * qc.x %q1 : !qc.qubit - * } + * } : !qc.qubit * ``` */ QCProgramBuilder& ctrl(ValueRange controls, diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 866578b2ac..7428830093 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -254,12 +254,12 @@ def GPhaseOp : QCOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParamet Example: ```mlir - qc.gphase(%theta) : () + qc.gphase(%theta) ``` }]; let arguments = (ins Arg:$theta); - let assemblyFormat = "$theta attr-dict"; + let assemblyFormat = "`(` $theta `)` attr-dict"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "gphase"; } @@ -282,7 +282,7 @@ def IdOp : QCOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "id"; } @@ -301,7 +301,7 @@ def XOp : QCOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "x"; } @@ -320,7 +320,7 @@ def YOp : QCOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "y"; } @@ -339,7 +339,7 @@ def ZOp : QCOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "z"; } @@ -358,7 +358,7 @@ def HOp : QCOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "h"; } @@ -377,7 +377,7 @@ def SOp : QCOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "s"; } @@ -396,7 +396,7 @@ def SdgOp : QCOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "sdg"; } @@ -415,7 +415,7 @@ def TOp : QCOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "t"; } @@ -434,7 +434,7 @@ def TdgOp : QCOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "tdg"; } @@ -453,7 +453,7 @@ def SXOp : QCOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "sx"; } @@ -472,7 +472,7 @@ def SXdgOp : QCOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> }]; let arguments = (ins Arg:$qubit_in); - let assemblyFormat = "$qubit_in attr-dict"; + let assemblyFormat = "$qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "sxdg"; } @@ -492,7 +492,7 @@ def RXOp : QCOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let arguments = (ins Arg:$qubit_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "rx"; } @@ -516,7 +516,7 @@ def RYOp : QCOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let arguments = (ins Arg:$qubit_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "ry"; } @@ -540,7 +540,7 @@ def RZOp : QCOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let arguments = (ins Arg:$qubit_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "rz"; } @@ -564,7 +564,7 @@ def POp : QCOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let arguments = (ins Arg:$qubit_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "p"; } @@ -589,7 +589,7 @@ def ROp : QCOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let arguments = (ins Arg:$qubit_in, Arg:$theta, Arg:$phi); - let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $theta `,` $phi `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "r"; } @@ -614,7 +614,7 @@ def U2Op : QCOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let arguments = (ins Arg:$qubit_in, Arg:$phi, Arg:$lambda); - let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "u2"; } @@ -640,7 +640,7 @@ def UOp : QCOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { Arg:$theta, Arg:$phi, Arg:$lambda); - let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict"; + let assemblyFormat = "`(` $theta `,` $phi `,` $lambda `)` $qubit_in attr-dict `:` type($qubit_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "u"; } @@ -664,7 +664,7 @@ def SWAPOp : QCOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "swap"; } @@ -684,7 +684,7 @@ def iSWAPOp : QCOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "iswap"; } @@ -704,7 +704,7 @@ def DCXOp : QCOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "dcx"; } @@ -724,7 +724,7 @@ def ECROp : QCOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in); - let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "$qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "ecr"; } @@ -745,7 +745,7 @@ def RXXOp : QCOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "rxx"; } @@ -770,7 +770,7 @@ def RYYOp : QCOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "ryy"; } @@ -795,7 +795,7 @@ def RZXOp : QCOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "rzx"; } @@ -820,7 +820,7 @@ def RZZOp : QCOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let arguments = (ins Arg:$qubit0_in, Arg:$qubit1_in, Arg:$theta); - let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "`(` $theta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "rzz"; } @@ -846,7 +846,7 @@ def XXPlusYYOp : QCOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoPa Arg:$qubit1_in, Arg:$theta, Arg:$beta); - let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "xx_plus_yy"; } @@ -872,7 +872,7 @@ def XXMinusYYOp : QCOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwo Arg:$qubit1_in, Arg:$theta, Arg:$beta); - let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict"; + let assemblyFormat = "`(` $theta `,` $beta `)` $qubit0_in `,` $qubit1_in attr-dict `:` type($qubit0_in) `,` type($qubit1_in)"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "xx_minus_yy"; } @@ -895,7 +895,7 @@ def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { }]; let arguments = (ins Arg, "the target qubits", [MemRead, MemWrite]>:$qubits); - let assemblyFormat = "$qubits attr-dict"; + let assemblyFormat = "$qubits attr-dict `:` type($qubits)"; let extraClassDeclaration = [{ size_t getNumQubits(); @@ -952,7 +952,7 @@ def CtrlOp : QCOp<"ctrl", let arguments = (ins Arg, "the control qubits", [MemRead, MemWrite]>:$controls); let regions = (region SizedRegion<1>:$body); - let assemblyFormat = "`(` $controls `)` $body attr-dict"; + let assemblyFormat = "`(` $controls `)` $body attr-dict `:` type($controls)"; let extraClassDeclaration = [{ [[nodiscard]] UnitaryOpInterface getBodyUnitary(); diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index d9ae310270..27b2f4cd97 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -263,12 +263,12 @@ def GPhaseOp : QCOOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParame Example: ```mlir - qco.gphase(%theta) : () + qco.gphase(%theta) ``` }]; let arguments = (ins Arg:$theta); - let assemblyFormat = "$theta attr-dict"; + let assemblyFormat = "`(` $theta `)` attr-dict"; let extraClassDeclaration = [{ static StringRef getBaseSymbol() { return "gphase"; } diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index a1972a18ea..224afb3c9e 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -1063,9 +1063,9 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { * @par Example: * ```mlir * qc.ctrl(%q0) { - * qc.x %q1 + * qc.x %q1 : !qc.qubit * qc.yield - * } + * } : !qc.qubit * ``` * is converted to * ```mlir From aa56d9685e6b0ca1904481e73e1026e9a93cee03 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:38:26 +0100 Subject: [PATCH 415/419] Improve description of YieldOp --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 27b2f4cd97..5043dd6ce7 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -1024,7 +1024,16 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { def YieldOp : QCOOp<"yield", traits = [Terminator]> { let summary = "Yield from a modifier region"; let description = [{ - Terminates a modifier region, yielding control back to the enclosing operation. + Terminates a modifier region, yielding the transformed target qubits back to the enclosing modifier operation. + The targets must match the expected output signature of the modifier region. + + Example: + ```mlir + %ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in { + %tgt_q_res = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit + qco.yield %tgt_q_res : !qco.qubit + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + ``` }]; let arguments = (ins Variadic:$targets); @@ -1053,8 +1062,8 @@ def CtrlOp : QCOOp<"ctrl", traits = Example: ```mlir %ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in { - %tgt_q_out = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit - qco.yield %tgt_q_out : !qco.qubit + %tgt_q_res = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit + qco.yield %tgt_q_res : !qco.qubit } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) ``` }]; From 6a116a75133bf90d8e27ed0acc564a5a1f748bfe Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:39:39 +0100 Subject: [PATCH 416/419] Improve compilation-record messages --- mlir/tools/mqt-cc/mqt-cc.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 1243bd0e29..5ad5f67395 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -151,17 +151,19 @@ int main(int argc, char** argv) { if (RECORD_INTERMEDIATES) { outs() << "=== Compilation Record ===\n"; outs() << "After QC Import:\n" << record.afterQCImport << "\n"; - outs() << "After QC Canonicalization:\n" + outs() << "After Initial QC Canonicalization:\n" << record.afterInitialCanon << "\n"; outs() << "After QC-to-QCO Conversion:\n" << record.afterQCOConversion << "\n"; - outs() << "After QCO Canonicalization:\n" << record.afterQCOCanon << "\n"; + outs() << "After Initial QCO Canonicalization:\n" + << record.afterQCOCanon << "\n"; outs() << "After Optimization:\n" << record.afterOptimization << "\n"; - outs() << "After QCO Canonicalization:\n" + outs() << "After Final QCO Canonicalization:\n" << record.afterOptimizationCanon << "\n"; outs() << "After QCO-to-QC Conversion:\n" << record.afterQCConversion << "\n"; - outs() << "After QC Canonicalization:\n" << record.afterQCCanon << "\n"; + outs() << "After Final QC Canonicalization:\n" + << record.afterQCCanon << "\n"; outs() << "After QC-to-QIR Conversion:\n" << record.afterQIRConversion << "\n"; outs() << "After QIR Canonicalization:\n" << record.afterQIRCanon << "\n"; From 1f9378e2c9a77fc51287fd55c96801a6e2e1c191 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:59:58 +0100 Subject: [PATCH 417/419] Throw out negative controls altogether --- mlir/include/mlir/Dialect/QC/IR/QCDialect.h | 8 +-- .../mlir/Dialect/QC/IR/QCInterfaces.td | 16 +----- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 10 +--- mlir/include/mlir/Dialect/QCO/IR/QCODialect.h | 13 +---- .../mlir/Dialect/QCO/IR/QCOInterfaces.td | 24 ++------ mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 16 ++---- mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp | 30 +++------- .../IR/Operations/StandardGates/BarrierOp.cpp | 10 +--- .../TranslateQuantumComputationToQC.cpp | 46 +++++++-------- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 56 +++++++------------ .../IR/Operations/StandardGates/BarrierOp.cpp | 16 +----- 11 files changed, 70 insertions(+), 175 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h index 67da004902..f07fc0afe0 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h +++ b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h @@ -69,8 +69,6 @@ template class TargetAndParameterArityTrait { static size_t getNumQubits() { return T; } static size_t getNumTargets() { return T; } static size_t getNumControls() { return 0; } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } Value getQubit(size_t i) { if constexpr (T == 0) { @@ -91,11 +89,7 @@ template class TargetAndParameterArityTrait { return this->getOperation()->getOperand(i); } - static Value getPosControl([[maybe_unused]] size_t i) { - llvm::reportFatalUsageError("Operation does not have controls"); - } - - static Value getNegControl([[maybe_unused]] size_t i) { + static Value getControl([[maybe_unused]] size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } diff --git a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td index 9f5bc5d030..f63bdadee3 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td @@ -41,14 +41,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the number of control qubits (both positive and negative).", "size_t", "getNumControls", (ins) >, - InterfaceMethod< - "Returns the number of positive control qubits.", - "size_t", "getNumPosControls", (ins) - >, - InterfaceMethod< - "Returns the number of negative control qubits.", - "size_t", "getNumNegControls", (ins) - >, InterfaceMethod< "Returns the i-th qubit (targets + controls combined).", "Value", "getQubit", (ins "size_t":$i) @@ -58,12 +50,8 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Value", "getTarget", (ins "size_t":$i) >, InterfaceMethod< - "Returns the i-th positive control qubit.", - "Value", "getPosControl", (ins "size_t":$i) - >, - InterfaceMethod< - "Returns the i-th negative control qubit.", - "Value", "getNegControl", (ins "size_t":$i) + "Returns the i-th control qubit.", + "Value", "getControl", (ins "size_t":$i) >, // Parameter handling diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 7428830093..7ed127dbfb 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -901,12 +901,9 @@ def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { size_t getNumQubits(); size_t getNumTargets(); static size_t getNumControls(); - static size_t getNumPosControls(); - static size_t getNumNegControls(); Value getQubit(size_t i); Value getTarget(size_t i); - static Value getPosControl(size_t i); - static Value getNegControl(size_t i); + static Value getControl(size_t i); static size_t getNumParams(); static Value getParameter(size_t i); static StringRef getBaseSymbol() { return "barrier"; } @@ -959,12 +956,9 @@ def CtrlOp : QCOp<"ctrl", size_t getNumQubits(); size_t getNumTargets(); size_t getNumControls(); - size_t getNumPosControls(); - size_t getNumNegControls(); Value getQubit(size_t i); Value getTarget(size_t i); - Value getPosControl(size_t i); - Value getNegControl(size_t i); + Value getControl(size_t i); size_t getNumParams(); Value getParameter(size_t i); static StringRef getBaseSymbol() { return "ctrl"; } diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h index 97d2f76543..d1087ab6f7 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h +++ b/mlir/include/mlir/Dialect/QCO/IR/QCODialect.h @@ -69,8 +69,6 @@ template class TargetAndParameterArityTrait { static size_t getNumQubits() { return T; } static size_t getNumTargets() { return T; } static size_t getNumControls() { return 0; } - static size_t getNumPosControls() { return 0; } - static size_t getNumNegControls() { return 0; } Value getInputQubit(size_t i) { if constexpr (T == 0) { @@ -94,17 +92,10 @@ template class TargetAndParameterArityTrait { Value getInputTarget(const size_t i) { return getInputQubit(i); } Value getOutputTarget(const size_t i) { return getOutputQubit(i); } - static Value getInputPosControl([[maybe_unused]] size_t i) { + static Value getInputControl([[maybe_unused]] size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } - static Value getOutputPosControl([[maybe_unused]] size_t i) { - llvm::reportFatalUsageError("Operation does not have controls"); - } - static Value getInputNegControl([[maybe_unused]] size_t i) { - llvm::reportFatalUsageError("Operation does not have controls"); - } - - static Value getOutputNegControl([[maybe_unused]] size_t i) { + static Value getOutputControl([[maybe_unused]] size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td b/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td index 3079a40003..8e71a52a55 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td @@ -42,14 +42,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Returns the number of control qubits (both positive and negative).", "size_t", "getNumControls", (ins) >, - InterfaceMethod< - "Returns the number of positive control qubits.", - "size_t", "getNumPosControls", (ins) - >, - InterfaceMethod< - "Returns the number of negative control qubits.", - "size_t", "getNumNegControls", (ins) - >, InterfaceMethod< "Returns the i-th input qubit (targets + controls combined).", "Value", "getInputQubit", (ins "size_t":$i) @@ -67,20 +59,12 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "Value", "getOutputTarget", (ins "size_t":$i) >, InterfaceMethod< - "Returns the i-th positive control input qubit.", - "Value", "getInputPosControl", (ins "size_t":$i) - >, - InterfaceMethod< - "Returns the i-th positive control output qubit.", - "Value", "getOutputPosControl", (ins "size_t":$i) - >, - InterfaceMethod< - "Returns the i-th negative control input qubit.", - "Value", "getInputNegControl", (ins "size_t":$i) + "Returns the i-th control input qubit.", + "Value", "getInputControl", (ins "size_t":$i) >, InterfaceMethod< - "Returns the i-th negative control output qubit.", - "Value", "getOutputNegControl", (ins "size_t":$i) + "Returns the i-th control output qubit.", + "Value", "getOutputControl", (ins "size_t":$i) >, InterfaceMethod< "Returns the input qubit corresponding to the given output qubit.", diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 5043dd6ce7..69f89eb5e0 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -993,16 +993,12 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { size_t getNumQubits(); size_t getNumTargets(); static size_t getNumControls(); - static size_t getNumPosControls(); - static size_t getNumNegControls(); Value getInputQubit(size_t i); Value getOutputQubit(size_t i); Value getInputTarget(size_t i); Value getOutputTarget(size_t i); - static Value getInputPosControl(size_t i); - static Value getOutputPosControl(size_t i); - static Value getInputNegControl(size_t i); - static Value getOutputNegControl(size_t i); + static Value getInputControl(size_t i); + static Value getOutputControl(size_t i); Value getInputForOutput(Value output); Value getOutputForInput(Value input); static size_t getNumParams(); @@ -1085,16 +1081,12 @@ def CtrlOp : QCOOp<"ctrl", traits = size_t getNumQubits(); size_t getNumTargets(); size_t getNumControls(); - size_t getNumPosControls(); - size_t getNumNegControls(); Value getInputQubit(size_t i); Value getOutputQubit(size_t i); Value getInputTarget(size_t i); Value getOutputTarget(size_t i); - Value getInputPosControl(size_t i); - Value getOutputPosControl(size_t i); - Value getInputNegControl(size_t i); - Value getOutputNegControl(size_t i); + Value getInputControl(size_t i); + Value getOutputControl(size_t i); Value getInputForOutput(Value output); Value getOutputForInput(Value input); size_t getNumParams(); diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp index cd88dcc052..8651cbb12e 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp @@ -60,7 +60,7 @@ struct RemoveTrivialCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - if (op.getNumPosControls() > 0) { + if (op.getNumControls() > 0) { return failure(); } @@ -84,7 +84,7 @@ struct CtrlInlineGPhase final : OpRewritePattern { PatternRewriter& rewriter) const override { // Require at least one positive control // Trivial case is handled by RemoveTrivialCtrl - if (op.getNumPosControls() == 0) { + if (op.getNumControls() == 0) { return failure(); } @@ -116,23 +116,15 @@ size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } size_t CtrlOp::getNumTargets() { return getBodyUnitary().getNumTargets(); } -size_t CtrlOp::getNumControls() { - return getNumPosControls() + getNumNegControls(); -} - -size_t CtrlOp::getNumPosControls() { return getControls().size(); } - -size_t CtrlOp::getNumNegControls() { - return getBodyUnitary().getNumNegControls(); -} +size_t CtrlOp::getNumControls() { return getControls().size(); } Value CtrlOp::getQubit(const size_t i) { - const auto numPosControls = getNumPosControls(); - if (i < numPosControls) { + const auto numControls = getNumControls(); + if (i < numControls) { return getControls()[i]; } - if (numPosControls <= i && i < getNumQubits()) { - return getBodyUnitary().getQubit(i - numPosControls); + if (numControls <= i && i < getNumQubits()) { + return getBodyUnitary().getQubit(i - numControls); } llvm::reportFatalUsageError("Invalid qubit index"); } @@ -141,17 +133,13 @@ Value CtrlOp::getTarget(const size_t i) { return getBodyUnitary().getTarget(i); } -Value CtrlOp::getPosControl(const size_t i) { - if (i >= getNumPosControls()) { +Value CtrlOp::getControl(const size_t i) { + if (i >= getNumControls()) { llvm::reportFatalUsageError("Control index out of bounds"); } return getControls()[i]; } -Value CtrlOp::getNegControl(const size_t i) { - return getBodyUnitary().getNegControl(i); -} - size_t CtrlOp::getNumParams() { return getBodyUnitary().getNumParams(); } Value CtrlOp::getParameter(const size_t i) { diff --git a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp index 80708dda34..d4cd113931 100644 --- a/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Operations/StandardGates/BarrierOp.cpp @@ -24,10 +24,6 @@ size_t BarrierOp::getNumTargets() { return getQubits().size(); } size_t BarrierOp::getNumControls() { return 0; } -size_t BarrierOp::getNumPosControls() { return 0; } - -size_t BarrierOp::getNumNegControls() { return 0; } - Value BarrierOp::getQubit(const size_t i) { return getTarget(i); } Value BarrierOp::getTarget(const size_t i) { @@ -37,11 +33,7 @@ Value BarrierOp::getTarget(const size_t i) { llvm::reportFatalUsageError("Invalid qubit index"); } -Value BarrierOp::getPosControl(const size_t /*i*/) { - llvm::reportFatalUsageError("BarrierOp cannot be controlled"); -} - -Value BarrierOp::getNegControl(const size_t /*i*/) { +Value BarrierOp::getControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index f76268a76b..0a15224437 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -239,8 +239,8 @@ static void addResetOp(QCProgramBuilder& builder, * @param qubits Flat vector of qubit values indexed by physical qubit index * @return Vector of qubit values corresponding to positive controls */ -static SmallVector getPosControls(const ::qc::Operation& operation, - const SmallVector& qubits) { +static SmallVector getControls(const ::qc::Operation& operation, + const SmallVector& qubits) { SmallVector controls; for (const auto& [control, type] : operation.getControls()) { if (type == ::qc::Control::Type::Neg) { @@ -270,11 +270,11 @@ static SmallVector getPosControls(const ::qc::Operation& operation, const ::qc::Operation& operation, \ const SmallVector& qubits) { \ const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(target); \ } else { \ - builder.mc##OP_QC(posControls, target); \ + builder.mc##OP_QC(controls, target); \ } \ } @@ -311,11 +311,11 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) const SmallVector& qubits) { \ const auto& param = operation.getParameter()[0]; \ const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(param, target); \ } else { \ - builder.mc##OP_QC(param, posControls, target); \ + builder.mc##OP_QC(param, controls, target); \ } \ } @@ -344,11 +344,11 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(param1, param2, target); \ } else { \ - builder.mc##OP_QC(param1, param2, posControls, target); \ + builder.mc##OP_QC(param1, param2, controls, target); \ } \ } @@ -378,11 +378,11 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) const auto& param2 = operation.getParameter()[1]; \ const auto& param3 = operation.getParameter()[2]; \ const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(param1, param2, param3, target); \ } else { \ - builder.mc##OP_QC(param1, param2, param3, posControls, target); \ + builder.mc##OP_QC(param1, param2, param3, controls, target); \ } \ } @@ -409,11 +409,11 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) const SmallVector& qubits) { \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(target0, target1); \ } else { \ - builder.mc##OP_QC(posControls, target0, target1); \ + builder.mc##OP_QC(controls, target0, target1); \ } \ } @@ -444,11 +444,11 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) const auto& param = operation.getParameter()[0]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(param, target0, target1); \ } else { \ - builder.mc##OP_QC(param, posControls, target0, target1); \ + builder.mc##OP_QC(param, controls, target0, target1); \ } \ } @@ -480,11 +480,11 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) const auto& param2 = operation.getParameter()[1]; \ const auto& target0 = qubits[operation.getTargets()[0]]; \ const auto& target1 = qubits[operation.getTargets()[1]]; \ - if (const auto& posControls = getPosControls(operation, qubits); \ - posControls.empty()) { \ + if (const auto& controls = getControls(operation, qubits); \ + controls.empty()) { \ builder.OP_QC(param1, param2, target0, target1); \ } else { \ - builder.mc##OP_QC(param1, param2, posControls, target0, target1); \ + builder.mc##OP_QC(param1, param2, controls, target0, target1); \ } \ } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index d96b5c88a5..44a40bd64c 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -38,7 +38,7 @@ struct MergeNestedCtrl final : OpRewritePattern { PatternRewriter& rewriter) const override { // Require at least one positive control // Trivial case is handled by RemoveTrivialCtrl - if (op.getNumPosControls() == 0) { + if (op.getNumControls() == 0) { return failure(); } @@ -69,7 +69,7 @@ struct RemoveTrivialCtrl final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - if (op.getNumPosControls() > 0) { + if (op.getNumControls() > 0) { return failure(); } @@ -93,7 +93,7 @@ struct CtrlInlineGPhase final : OpRewritePattern { PatternRewriter& rewriter) const override { // Require at least one positive control // Trivial case is handled by RemoveTrivialCtrl - if (op.getNumPosControls() == 0) { + if (op.getNumControls() == 0) { return failure(); } @@ -130,7 +130,7 @@ struct CtrlInlineId final : OpRewritePattern { PatternRewriter& rewriter) const override { // Require at least one positive control // Trivial case is handled by RemoveTrivialCtrl - if (op.getNumPosControls() == 0) { + if (op.getNumControls() == 0) { return failure(); } @@ -141,7 +141,7 @@ struct CtrlInlineId final : OpRewritePattern { auto idOp = rewriter.create(op.getLoc(), op.getTargetsIn().front()); SmallVector newOperands; - newOperands.reserve(op.getNumPosControls() + 1); + newOperands.reserve(op.getNumControls() + 1); newOperands.append(op.getControlsIn().begin(), op.getControlsIn().end()); newOperands.push_back(idOp.getQubitOut()); rewriter.replaceOp(op, newOperands); @@ -160,34 +160,26 @@ size_t CtrlOp::getNumQubits() { return getNumTargets() + getNumControls(); } size_t CtrlOp::getNumTargets() { return getTargetsIn().size(); } -size_t CtrlOp::getNumControls() { - return getNumPosControls() + getNumNegControls(); -} - -size_t CtrlOp::getNumPosControls() { return getControlsIn().size(); } - -size_t CtrlOp::getNumNegControls() { - return getBodyUnitary().getNumNegControls(); -} +size_t CtrlOp::getNumControls() { return getControlsIn().size(); } Value CtrlOp::getInputQubit(const size_t i) { - const auto numPosControls = getNumPosControls(); - if (i < numPosControls) { + const auto numControls = getNumControls(); + if (i < numControls) { return getControlsIn()[i]; } - if (numPosControls <= i && i < getNumQubits()) { - return getBodyUnitary().getInputQubit(i - numPosControls); + if (numControls <= i && i < getNumQubits()) { + return getBodyUnitary().getInputQubit(i - numControls); } llvm::reportFatalUsageError("Invalid qubit index"); } Value CtrlOp::getOutputQubit(const size_t i) { - const auto numPosControls = getNumPosControls(); - if (i < numPosControls) { + const auto numControls = getNumControls(); + if (i < numControls) { return getControlsOut()[i]; } - if (numPosControls <= i && i < getNumQubits()) { - return getBodyUnitary().getOutputQubit(i - numPosControls); + if (numControls <= i && i < getNumQubits()) { + return getBodyUnitary().getOutputQubit(i - numControls); } llvm::reportFatalUsageError("Invalid qubit index"); } @@ -206,30 +198,22 @@ Value CtrlOp::getOutputTarget(const size_t i) { return getTargetsOut()[i]; } -Value CtrlOp::getInputPosControl(const size_t i) { - if (i >= getNumPosControls()) { +Value CtrlOp::getInputControl(const size_t i) { + if (i >= getNumControls()) { llvm::reportFatalUsageError("Control index out of bounds"); } return getControlsIn()[i]; } -Value CtrlOp::getOutputPosControl(const size_t i) { - if (i >= getNumPosControls()) { +Value CtrlOp::getOutputControl(const size_t i) { + if (i >= getNumControls()) { llvm::reportFatalUsageError("Control index out of bounds"); } return getControlsOut()[i]; } -Value CtrlOp::getInputNegControl(const size_t i) { - return getBodyUnitary().getInputNegControl(i); -} - -Value CtrlOp::getOutputNegControl(const size_t i) { - return getBodyUnitary().getOutputNegControl(i); -} - Value CtrlOp::getInputForOutput(Value output) { - for (size_t i = 0; i < getNumPosControls(); ++i) { + for (size_t i = 0; i < getNumControls(); ++i) { if (output == getControlsOut()[i]) { return getControlsIn()[i]; } @@ -243,7 +227,7 @@ Value CtrlOp::getInputForOutput(Value output) { } Value CtrlOp::getOutputForInput(Value input) { - for (size_t i = 0; i < getNumPosControls(); ++i) { + for (size_t i = 0; i < getNumControls(); ++i) { if (input == getControlsIn()[i]) { return getControlsOut()[i]; } diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp index 012490645c..c108f87cf3 100644 --- a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp @@ -82,10 +82,6 @@ size_t BarrierOp::getNumTargets() { return getQubitsIn().size(); } size_t BarrierOp::getNumControls() { return 0; } -size_t BarrierOp::getNumPosControls() { return 0; } - -size_t BarrierOp::getNumNegControls() { return 0; } - Value BarrierOp::getInputQubit(const size_t i) { return getInputTarget(i); } Value BarrierOp::getOutputQubit(const size_t i) { return getOutputTarget(i); } @@ -104,19 +100,11 @@ Value BarrierOp::getOutputTarget(const size_t i) { llvm::reportFatalUsageError("Invalid qubit index"); } -Value BarrierOp::getInputPosControl(const size_t /*i*/) { - llvm::reportFatalUsageError("BarrierOp cannot be controlled"); -} - -Value BarrierOp::getOutputPosControl(const size_t /*i*/) { - llvm::reportFatalUsageError("BarrierOp cannot be controlled"); -} - -Value BarrierOp::getInputNegControl(const size_t /*i*/) { +Value BarrierOp::getInputControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } -Value BarrierOp::getOutputNegControl(const size_t /*i*/) { +Value BarrierOp::getOutputControl(const size_t /*i*/) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } From d536db384fe323f0c962618a3d13b379a0d02d2d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:13:45 +0100 Subject: [PATCH 418/419] Use correct register map --- mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index e82a73b240..27096d3b72 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -129,12 +129,12 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @param numParams The number of parameters * @return LogicalResult Success or failure of the conversion */ -template -static LogicalResult -convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, MLIRContext* ctx, - LoweringState& state, StringRef fnName, - size_t numTargets, size_t numParams) { +static template +LogicalResult convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, + MLIRContext* ctx, LoweringState& state, + StringRef fnName, size_t numTargets, + size_t numParams) { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; const SmallVector posCtrls = @@ -431,7 +431,9 @@ struct ConvertQCMeasureQIR final : StatefulOpConversionPattern { } else { // Choose a safe default register name StringRef defaultRegName = "c"; - if (state.registerStartIndexMap.contains("c")) { + if (llvm::any_of(registerResultMap, [](const auto& entry) { + return entry.first.first == "c"; + })) { defaultRegName = "__unnamed__"; } // No register info, check if ptr has already been allocated (as a Qubit) From 4de31e183104f35cd4bfdd4f307092e378f56ff9 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:40:58 +0100 Subject: [PATCH 419/419] Undo accidental change --- mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 27096d3b72..01595932f8 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -129,12 +129,12 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @param numParams The number of parameters * @return LogicalResult Success or failure of the conversion */ -static template -LogicalResult convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, - ConversionPatternRewriter& rewriter, - MLIRContext* ctx, LoweringState& state, - StringRef fnName, size_t numTargets, - size_t numParams) { +template +static LogicalResult +convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, + ConversionPatternRewriter& rewriter, MLIRContext* ctx, + LoweringState& state, StringRef fnName, + size_t numTargets, size_t numParams) { // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; const SmallVector posCtrls =