diff --git a/.agent/audits/qc-qco-combined-2716-20261018.md b/.agent/audits/qc-qco-combined-2716-20261018.md new file mode 100644 index 0000000000..e78973818c --- /dev/null +++ b/.agent/audits/qc-qco-combined-2716-20261018.md @@ -0,0 +1,162 @@ +# Combined QC/QCO audit and PR #2716 + +Date: 2026-10-18. Combined implementation for PR #2716. + +## Decision + +Combine the [QC/QCO audit](qc-qco-upstream-20261017.md) with +[PR #2716](https://github.com/munich-quantum-toolkit/core/pull/2716). The audit +repairs builder ownership, region and tensor contracts, and consumer traversal; +the PR simplifies forward conversion. The changes preserve the same supported +input contract and share the consolidated conversion fixtures. + +The combined implementation is based on upstream `main` at +`470094596af808df39cd25563377534bd44c2129`. Upstream advanced during the +measurements; the frozen four-way comparison remains based on +`056bef24722dd0a1f0813f3f7b4197c10f8eb950`. The original PR head was +`9d929c5947ddb34bed3a9ef729635509d91674bf`, originally based on +`575acd1b9ca52e4608ccf4833e7d60640913cef4`. The sole application conflict was +the call-pattern signature beside an obsolete callee lookup. Keep main's removal +of that lookup and use the PR's ordinary pattern signature. The original audit +worktree remains unchanged. The subsequent upstream commits apply cleanly; their +only overlapping builder edit changes loop canonicalization walk order and is +retained. The latest main change only reorders lint hooks. The original PR +commits retain their authorship after rebasing; the audit and further +simplifications follow in a separate commit. + +## Further simplifications + +- Release preflight register provenance before normalization and rebuild it in + the same state afterward. Keep both validation phases: folding can reveal + aliases that were not apparent in the original indices. +- Move structured capture vectors out of their source SetVectors; erase the old + entries instead of retaining two copies and their membership tables. +- Resolve structured outputs through borrowed views into one reserved vector. + Release completed region mappings after copying terminator operands. Retain + shared output ordering while sibling regions still need it. +- Keep unitary attributes throughout lowering. The direct rewrite driver does + not verify partially converted IR, so temporary removal/restoration is unused. +- Refresh load provenance directly in the source-retention branch and cache + dialect handles. Remove the load-only pattern and the final QC scan: every + source QC operation must be rewritten or checked and erased. Boundary + verification and checks for remaining reference uses stay in place. + +These follow-up changes remove another 44 net converter lines from the initial +combination. Against upstream, the complete prototype removes 581 net +source/test lines: 146 production and 435 test lines. It preserves the shared +132 forward and 133 reverse conversion cases and all five PR regressions; this +follow-up adds no tests. Independent design and correctness reviews, including +the final ponytail review, found no remaining concrete issue or necessary +regression gap. The final review covered converter lifetimes and scaling, +builder ownership and callers, iterator correspondence, consumer traversal, and +public validation boundaries. + +The source-operation snapshot remains deliberate. Structural rewrites move +children, and deferred erasure preserves reference identity. Replacing it with a +custom mutation-aware walker would add lifetime rules without measured benefit. +The entry-block allocation limit, single-block structured input, tensor slot +contract, and positional quantum correspondence remain explicit constraints. + +## Measurement method + +Four variants isolate the contributions: audit alone, PR alone, initial +combination, and simplified combination. All use Clang 23.1.4, portable +LLVM/MLIR 23.1.0, release `-O3`, ThinLTO, and assertions on DGX Spark. Five +fresh-process rounds rotate variant order on CPU 0; factor247 retains three +complete rounds. Further factor247 repetitions stopped after external CPU +contention became pronounced; its incomplete fourth round is saved separately +and excluded. The host/core is not reserved. MLIR multithreading is disabled. +Medians are descriptive local measurements, not a statistical claim for small +changes. Other compiler, lint, and benchmark jobs were observed on the host, +including CPU 0; wall-clock results are diagnostic. + +The native probe times `QCProgram::intoQCO`; full-pipeline cases also time +`compileForTarget` and `intoQC`. Import, initial copy, target construction, +extra output checks, inspection, and serialization are outside timing. Peak RSS +includes import and the retained source/copy, and is sampled before output +serialization. Output IR hashes and gate counts must agree across every variant +and repetition. Hashing in the parent uses streaming to avoid inflating child +RSS; an interrupted initial run with an inflated RSS floor is saved separately +and excluded. + +Seven frozen QASMBench circuits come from Benchpress +`36d550d38fe03fdbb2f00f045942b4315d9e4864`; four generated MLIR cases isolate +flat gates, register loads, sibling loops, and wide helper signatures. Full +compilation uses seed 42, ten mapping trials, one iteration, and the +`sx/x/rz/cz/measure/reset` basis without calibration metadata. Results do not +replace the PR's historical broader topology campaign. + +## Results + +All 252 retained samples pass output verification and agree on output IR hashes +and gate counts within each case: 212 forward-conversion and 40 full-pipeline +samples. No additional test case was needed. + +Forward-conversion medians follow. RSS columns compare the initial and +simplified combination. The Audit and PR columns separate their contributions. + +| Case | Audit (ms) | PR (ms) | Combined (ms) | Simplified (ms) | Combined RSS (MiB) | Simplified RSS (MiB) | +| ------------------- | ---------: | --------: | ------------: | --------------: | -----------------: | -------------------: | +| qft_n18 | 13.113 | 8.015 | 8.068 | 7.807 | 18.57 | 18.57 | +| qram_n20 | 1.487 | 0.893 | 0.922 | 0.892 | 16.35 | 16.54 | +| ising_n26 | 4.964 | 3.114 | 3.111 | 3.055 | 17.38 | 17.38 | +| factor247_n15 | 17702.400 | 11910.900 | 10539.000 | 11205.000 | 1722.50 | 1702.43 | +| cat_n260 | 14.662 | 10.188 | 10.215 | 10.085 | 18.32 | 18.32 | +| wstate_n380 | 43.493 | 29.210 | 29.218 | 28.839 | 22.41 | 22.44 | +| qft_n160 | 1404.180 | 856.447 | 873.410 | 823.506 | 196.03 | 190.62 | +| flat-1m | 1873.690 | 835.906 | 1188.950 | 818.760 | 492.00 | 492.07 | +| register-loads-100k | 1245.130 | 805.668 | 770.021 | 751.645 | 172.32 | 166.30 | +| sibling-loops-2k-32 | 185.160 | 103.468 | 102.871 | 93.460 | 64.99 | 59.27 | +| wide-helper-8192 | 7.503 | 2.564 | 2.583 | 2.613 | 17.72 | 17.91 | + +The further cleanup reduces peak RSS by 3.5% for register loads and 8.8% for +sibling loops. QFT-160 saves 5.4 MiB; factor247 saves 20.1 MiB. The flat +million-gate case is unchanged: imported/copied IR dominates its peak. + +The broader combination has larger memory effects: audit-only versus final peak +RSS falls from 736.3 to 492.1 MiB for one million flat gates, and from 259.5 to +190.6 MiB for QFT-160. These are process peaks for this harness, not +steady-state converter allocations. + +Full pipeline medians (QFT-18 linear; Ising-26 all-to-all): + +| Case | Audit (ms) | PR (ms) | Combined (ms) | Simplified (ms) | Combined RSS (MiB) | Simplified RSS (MiB) | +| --------- | ---------: | ------: | ------------: | --------------: | -----------------: | -------------------: | +| qft_n18 | 187.668 | 183.307 | 182.307 | 182.356 | 23.23 | 23.29 | +| ising_n26 | 22.152 | 20.375 | 20.250 | 20.218 | 20.41 | 20.41 | + +The further cleanup gives no meaningful full-pipeline improvement in these two +cases. Wall-time differences under contention do not establish small speedups or +regressions; in particular, factor247's simplified median is slower than the +initial combination's. Do not multiply component ratios or claim a universal +compiler speedup. The PR's historical factor247/square regression was not +remeasured here. + +## Validation and artifacts + +- The final `470094596` full `release-clang-ipo` build and CTest pass: 4,030 + passed, one expected `ScQDMIJobSpecificationTest.QueryJobId` skip; 2,925 MLIR + tests included. This includes the new upstream OpenQASM and jeff cases. +- Both full-pipeline cases were rerun with the refreshed binary; output IR + hashes and gate counts match the frozen comparison. +- Repository whole-file C++ lint passes for its 19 selected files. The final + capture-vector transfer also passes a complete converter translation-unit + recheck with no source diagnostics. +- MLIR reference generation, repository lint, and final diff checks pass. +- The production and test files are byte-identical to the reviewed and + C++-linted `8ed992b64` combination. Rebasing onto `470094596` adds only a + lint-hook ordering change; repository lint also passes with that change. +- Python bindings and sanitizer builds were not run for this combined + implementation. Hosted checks on the original PR head do not validate the + combination; final-head CI status is tracked on PR #2716. + +The earlier builder change makes builders move-only; borrowed contexts must +outlive their builders and IR. No in-tree caller needs builder copies. The +prototype does not broaden conversion support or promise a general full-compiler +speedup. + +External artifacts are in +`/home/nvidia/.codex/experiments/core-qc-qco-combined-20261018/measurements/`: +source patches, native probe/build/runner scripts, generated inputs, +environment, input/binary hashes, raw samples, and summaries. The combined +worktree is its sibling `core/`; `pr-only/` supplies the separate PR baseline. diff --git a/.agent/audits/qc-qco-upstream-20261017.md b/.agent/audits/qc-qco-upstream-20261017.md new file mode 100644 index 0000000000..34de709a2d --- /dev/null +++ b/.agent/audits/qc-qco-upstream-20261017.md @@ -0,0 +1,202 @@ +# QC/QCO builder and conversion audit + +Baseline: upstream `main` at `056bef24722dd0a1f0813f3f7b4197c10f8eb950`. Date: +2026-10-18. Status: fixes applied and locally validated. The initial audit patch +was carried forward from `6b6efeec9` without conflicts. + +This record covers the audit before integration with PR #2716. See the +[combined prototype](qc-qco-combined-2716-20261018.md) for the current result. + +Scope: QC/QCO program builders, both conversions, wire/tensor traversal, +function correspondence, and their tests and immediate consumers. Work started +from a clean isolated checkout. LLVM/MLIR 23.1 and Clang 23 were used. + +## Findings and changes + +1. **Nested QC allocations could crash conversion.** A valid `func.func` with an + outer `qc.alloc` and a second allocation inside `scf.if` reached the + `lookupMappedQubit` assertion through `mqt-cc --emit=qco` (exit 134). + `validateSupportedInput` now requires quantum allocations in a function's + entry block and diagnoses unsupported input before rewriting. A focused test + covers scalar allocation, register allocation, and stack allocation, and + checks that rejected IR is unchanged. The pass reference states the limit. + +2. **While iterators assumed the wrong correspondence.** `WireIterator` used raw + operand/result positions although classical loop state may change shape; + `TensorIterator` walked the before-region and failed when a borrowed tensor + passed through a helper call. The existing while test now exercises different + classical positions, a scalar wire, two tensor sizes, and a helper call. + Before the fix it selected a classical result as a qubit and then aborted. + Both iterators now use the quantum subsequence correspondence established by + `qco::verifyLinearity`; recursive tensor-body traversal is deleted. + +3. **Builder bookkeeping allowed invalid quantum captures and escapes.** Tracked + values could be consumed from the wrong region, and implicit cleanup could + use extracted values after their region ended. Shared validation now checks + the insertion region. Explicit argument/result transfers bypass that scope + check while retaining ownership checks. Single-target modifiers reuse their + range overloads. One focused rejection test covers scalar and tensor + captures, extracted-value escape, and modifier capture. + +4. **Tensor insertion did not enforce its existing slot contract.** + `QTensorOps.td` requires reinsertion into the original register and slot. The + program builder now rejects foreign/fresh scalars and unequal constant slots + using its existing tracking. Public builder documentation states the + contract. One rejection test covers these three cases. Dynamic index equality + and disjoint outstanding extractions remain program preconditions; no second + whole-IR slot analysis was added. + +5. **QC finalization disposed returned allocations.** `finalize(returnValues)` + now excludes directly returned scalar and register allocations from automatic + disposal, matching the existing function-building behavior. The allocation + order test now also checks retained return values. Returning loaded register + elements does not acquire new alias-analysis support. + +6. **Call-origin collection repeated argument scans.** `collectWireOrigins` now + resolves a callee and collects quantum argument indices once per call, then + maps matching trailing results. This preserves fresh leading results; tying + every quantum operand to every quantum result would be incorrect. The + existing tensor-lifetime recursion limit is documented and applies to depths + greater than or equal to 64. + +7. **Gate and measurement scans traversed rejected operations.** Rotation + merging, single-qubit fusion, and measurement routing now inspect direct + single-use consumers. They stop at the same boundaries without asking an + iterator to translate loop or call results first. Fusion deletion follows the + already-scanned chain backward. Existing boundary, synthesis, and routing + tests cover these paths. + +8. **Unfinished builders leaked their modules.** QC and QCO builders held raw + module pointers without a destructor. OpenQASM emission failures and + exhausted emission budgets can return before finalization; Qiskit import can + also throw during construction. Both builders now use `OwningOpRef` + and release ownership in `finalize()`. The owning handle also replaces the + separate context sentinel. Builders are move-only; their borrowed MLIR + context must outlive the builder and its IR. No in-tree callers require + copying builders. + +9. **Flat verification retained every wire origin unnecessarily.** The existing + linear-use walk now detects quantum function arguments or non-module regions. + Only these cases need the second positional walk and its origin map. Static + placement, uniqueness, and every linear-use check still run. Public + `collectWireOrigins` remains complete for conversion consumers; unitary + correspondence uses its already-verified quantum views without copying them. + +10. **Builder calls and tensor cleanup repeated bookkeeping.** Calls now pass + mixed operands directly to `prepareInitArgs`, removing the quantum gather + vector and scatter loop. Scalar-only boundaries no longer allocate a scalar + membership set needed only for register alias checks. Implicit reinsertion + builds its local tensor chain and updates tensor tracking once. Per-qubit + validation, deterministic order, and slot ownership checks remain intact. + +11. **The default pipeline repeated successful conversion validation.** QC, + OpenQASM, and jeff conversion paths already end with QCO linearity + validation. The immediate duplicate check is removed. Existing QCO inputs + still get validated because the public API exposes mutable IR. + +## Test reduction + +The final source/test diff removes 617 net lines, including 632 test lines. + +- Share 119 identical QC/QCO builder pairs between the two conversion suites. + All 132 QC-to-QCO and 133 QCO-to-QC cases retain their names, builders, flags, + and equivalence assertions. Independent mechanical comparison confirmed the + case multisets. Only suite prefixes change to common/special cases. +- Remove the always-false reference-comparison skip flag. +- Remove two tensor scenarios that moved qubits between slots contrary to the + documented contract. The builder rejection test retains the relevant negative + check. Remove their obsolete fixture and swap-target option. +- Replace an invalid, self-comparing index-mismatch fixture with a distinct + constant-index row in the existing adjacent-index test. Consolidate its three + former tests into one four-row table. Equal constants, distinct constants, + identical dynamic values, and potentially aliasing dynamic values each retain + explicit folding expectations and now check linearity before and after. +- Extend existing tests for returned allocations and while traversal. Only the + three focused builder/conversion rejection tests are new. + +## Design limits and overlap + +- Quantum values cross regions explicitly; classical captures remain supported. +- Positional quantum correspondence is a verified QCO contract. Historical + scalar-permutation assumptions do not override the current verifier. +- General while crossing scans the signature in linear time. Mapping still needs + that correspondence; no cache with IR-mutation invalidation was added. No + end-to-end speedup is attributed to the iterator changes. +- The original audit left the QC-to-QCO source-order rewrite to PR #2716. The + linked follow-up combines both. Direct QCO export redesign remains outside + this builder/conversion audit. + +## Performance follow-up + +The baseline is the refreshed upstream commit plus the initial audit patch; +measurements compare findings 8–11 against that baseline. Both binaries use the +same release preset: Clang 23.1.4, portable LLVM/MLIR 23.1.0, `-O3`, ThinLTO, +and assertions. The DGX Spark probe was pinned to Cortex-X925 CPU 0, with seven +alternating baseline/optimized process pairs. The host and core were not +reserved. These are workload-specific local measurements. + +The external C++ harness builds identical IR, runs ordinary verification outside +timing, and times three or five linearity checks per process. It covers 10,000, +100,000, and 1,000,000 gates over one or 128 wires, borrowed helper arguments, +structured regions, wide calls, and extracted-register cleanup. All completed +modules pass ordinary and linearity verification. Values below are medians. + +| Workload and measurement | Baseline | Updated | +| --------------------------------------------------------- | ---------: | ---------: | +| 1,000,000 flat gates, one wire: linearity verification | 140.91 ms | 11.87 ms | +| Same process: peak resident memory | 209.07 MiB | 152.20 MiB | +| 1,000,000 flat gates, 128 wires: linearity verification | 145.31 ms | 12.56 ms | +| 100,000 gates in borrowed helper, 128 wires: verification | 13.10 ms | 10.87 ms | +| 100,000 gates in a loop, 128 wires: verification | 13.11 ms | 10.83 ms | +| 5,000 mixed classical/64-qubit calls: construction | 43.29 ms | 35.01 ms | +| 100,000 extracted register slots: finalization | 98.06 ms | 96.51 ms | + +Tensor-finalization ranges overlap; no meaningful speedup is claimed for the +batched tracking change. Scalar gate construction was about 1–4% slower in these +runs, with overlapping ranges for the million-gate case; it is not a claimed +optimization. Iterator-returning tracking validators and additional caches were +left out: they need separate evidence to justify their extra API machinery. + +Twenty abandoned 20,000-gate builders retained 51,214,288 bytes for QC and +57,614,416 bytes for QCO before the fix. Afterward the retained allocator bytes +were 4,496 and 5,120 respectively. Increasing to 100 abandoned builders left +those values unchanged. The context is warmed and remains alive during this +measurement; this checks cleanup rather than allocator RSS release. + +Complete `mqt-cc --emit=qco` runs on 100,000-gate inputs showed much smaller +changes: QC input took 379.17 → 368.83 ms, and OpenQASM took 409.04 → 399.59 ms. +Peak memory was essentially unchanged. Baseline and updated output files had +identical SHA-256 hashes for each input format. These runs include parsing, +conversion, verification, formatting, and writing to `/dev/null`; they do not +establish a general compiler speedup. + +Reproduction artifacts remain outside the repository in +`/home/nvidia/.codex/experiments/core-qc-qco-audit-20261017/performance-20261018/`: +`prior-audit.patch`, `probe.cpp`, `build-probe.py`, `run-probes.py`, +`measurements.csv`, `run-cli.py`, `cli-measurements.csv`, and +`environment.json`. + +## Validation + +- Full `release-clang-ipo` configure/build passed. One Clang frontend crash + reported invalid bytes in an intact generated header; retry passed without a + source workaround. +- `ctest --preset release-clang-ipo -j6 --output-on-failure`: 4,030 passed, one + expected `ScQDMIJobSpecificationTest.QueryJobId` skip, no failures; 41.96 + seconds, including 2,925 MLIR tests and all 265 conversion cases. +- `uvx nox -s lint`: passed. Subsequent ownership documentation and this audit + also passed their relevant hooks. +- `uvx nox -s cpp-lint`: passed all 19 eligible changed C++ files, with zero + formatting or clang-tidy findings. +- `cmake --build --preset release-clang-ipo --target mlir-doc`: passed. +- External probes verify module cleanup, move-only builder types, successful IR + verification, and byte-identical compiler output. No new repository tests were + added for the performance follow-up. Existing tests cover non-positional + function/region results, static/use-count failures, mixed calls, borrowed + registers, implicit reinsertion, and mutable QCO pipeline inputs. +- Specialist design review and independent cross-review found no remaining + correctness issue in ownership transfer, positional checks, or the removal of + repeated validation. + +No hosted CI or Python suite was run. Changes remain local; nothing was +committed or published. diff --git a/.agent/audits/qc-qco-wire-followup-20261019.md b/.agent/audits/qc-qco-wire-followup-20261019.md new file mode 100644 index 0000000000..1e37edc61a --- /dev/null +++ b/.agent/audits/qc-qco-wire-followup-20261019.md @@ -0,0 +1,127 @@ +# QC/QCO wire and converter follow-up + +Date: 2026-10-19. Follow-up to the +[combined audit](qc-qco-combined-2716-20261018.md) for +[PR #2716](https://github.com/munich-quantum-toolkit/core/pull/2716). + +## Changes and contracts + +Neither `WireIterator` nor `WireRange` had been deleted. The earlier audit +replaced three read-only range uses with direct scans, leaving the range unused +in production. Those bundled measurements did not isolate a benefit from the +caller changes. This follow-up restores `WireRange` in rotation-chain +collection, single-qubit fusion scanning, and measurement routing. The direct +tail-first fusion deletion loop remains: it obtains the predecessor before +erasing the current operation. + +The iterator now uses its sole `OpOperand` position to select the corresponding +unitary output, respecting the quantum operand range offset. Walking all W wires +through a W-wide barrier or unitary call therefore requires O(W), rather than +O(W²), correspondence work. Redundant tail classification is removed; the +existing default branch preserves the same terminal input value. Backward lookup +already uses result positions for wide operations and remains unchanged. Scalar +QCO qubits, linear def-use chains, and positional unitary wire identity +(including SWAP) are documented requirements. Generic calls and unknown carriers +remain boundaries. + +The converter removes three bookkeeping maps: structured capture order now lives +in one record before and after conversion, and modifier yields use their +preserved block arguments. The record moves only after region seeding, avoiding +references invalidated by map growth. Scalar capture filtering uses a vector +because the source capture set is already unique; register identities still need +deduplication. Two total type helpers replace `TypeConverter` and its pattern +plumbing without broadening the supported type domain. Region-local live +mappings, preflight validation, source retention, and deferred erasure stay. + +Independent review found that removing modifier bookkeeping also removed an +implicit rejection: a misplaced `qc.yield` can verify, then assert while +resolving classical block arguments. An explicit converted-modifier parent check +preserves graceful rejection. The frozen baseline exits 1, the intermediate +prototype aborts, and the final CLI exits 1 again. One small regression test +covers this; an existing wire-call test now checks two wires and a leading +classical operand. Final specialist reviews found no remaining issue. + +This follow-up removes 73 net production lines, including 58 in `QCToQCO.cpp`. +The complete PR removes 219 production and 409 test lines against its refreshed +upstream base, retaining the shared 132 forward and 133 reverse conversion +cases. + +## Measurements + +The baseline is `217874933586cc93170f89e14cd96a6ecd2d2683`: the prior PR content +rebased onto upstream `3f1ccfd69b12f101587be35c9f5eb05c42adf211`. All variants +use Clang 23.1.4, LLVM/MLIR 23.1.0, release `-O3`, assertions, and ThinLTO on +DGX Spark. Five fresh-process rounds rotate variant order on CPU 0. The host and +core are shared, not reserved; timings are diagnostic. Our builds and test jobs +were stopped during measurement. + +Wire timings exclude parsing and setup. Forward traversal includes allocations, +the unitary operation, and sinks; backward traversal starts at the final +produced qubits. All 90 samples agree on traversal counts. The following medians +are normalized to one traversal of all wires, or the entire single-qubit chain: + +| Case | Baseline forward (ms) | Updated forward (ms) | +| ----------------------------------- | --------------------: | -------------------: | +| Barrier, 32 wires | 0.002107 | 0.001552 | +| Barrier, 512 wires | 0.211623 | 0.024890 | +| Barrier, 4,096 wires | 12.365600 | 0.198579 | +| Barrier, 8,192 wires | 48.888000 | 0.394025 | +| Unitary call, 32 wires | 0.001459 | 0.001676 | +| Unitary call, 512 wires | 0.060364 | 0.027184 | +| Unitary call, 4,096 wires | 4.550105 | 0.219462 | +| Unitary call, 8,192 wires | 19.587000 | 0.437436 | +| Single-qubit H chain, 100,000 gates | 2.722025 | 2.560005 | + +The wide barrier/call improvements reach 124×/45×. This is not a universal +speedup: the 32-wire call is 15% slower (about 0.22 microseconds per traversal). +Backward chain time is effectively unchanged, 2.723 versus 2.769 ms. + +The conversion/pipeline harness compares baseline, simplified converter and +iterator with direct callers, and the same code with restored range callers. It +uses the previous audit's frozen inputs and configuration. Output IR hashes and +gate counts must agree. RSS is the process peak including imported/copied IR, +sampled before serialization; it is not converter-only live memory. + +All 105 conversion/pipeline samples agree on output IR hashes and gate counts. +Medians below compare the baseline with the final range variant. + +| Case | Baseline (ms) | Final (ms) | Baseline RSS (MiB) | Final RSS (MiB) | +| ------------------- | ------------: | ---------: | -----------------: | --------------: | +| flat-1m | 663.080 | 648.090 | 492.07 | 492.00 | +| register-loads-100k | 728.253 | 726.299 | 166.30 | 166.11 | +| sibling-loops-2k-32 | 90.842 | 88.852 | 59.27 | 57.59 | +| wide-helper-8192 | 2.578 | 2.553 | 17.90 | 17.54 | +| qft_n160 | 824.704 | 824.437 | 190.68 | 190.31 | +| qft_n18 | 181.577 | 181.105 | 23.35 | 23.23 | +| ising_n26 | 20.200 | 20.232 | 20.41 | 20.41 | + +The sibling-loop case saves 1.69 MiB of peak RSS (2.8%). Other memory changes +are small. Conversion timing is essentially unchanged. Restoring the three range +callers changes the full QFT-18 median from 180.379 to 181.105 ms and Ising-26 +from 20.210 to 20.232 ms: no material regression in these cases. The +full-pipeline measurements do not establish a general compiler speedup. + +## Validation and artifacts + +- Full release build and CTest: 4,015 tests pass, including 2,910 MLIR tests, + with one expected `ScQDMIJobSpecificationTest.QueryJobId` skip. +- All 90 wire and 105 conversion/pipeline samples pass their output checks. +- The misplaced-yield CLI reproducer rejects cleanly, and its regression passes. +- MLIR reference generation and repository lint pass. +- Whole-file C++ lint passes for all 20 selected files, including the updated + iterator test. Final rebuilt probes reproduce the sibling-loop and both + full-pipeline output hashes and gate counts. + +No Python, wheel, sanitizer, or native Windows runs were added for this +follow-up. The preceding portable death-test fix remains in the branch. Hosted +CI must validate the published head separately. + +External artifacts are in +`/home/nvidia/.codex/experiments/core-qc-qco-combined-20261018/followup-20261019/`: +wire source and inputs, runner, manifests, raw samples, reproducer, build and +validation logs. Conversion probes and their compile/link commands are in the +sibling `measurements/` directory under the `followup-` labels. The +direct-caller variant uses the baseline versions of the three consumer files; +all other source matches the measured range variant. Final lint fixes relocate +static helpers and format initializers without changing the algorithm. No +benchmark harness or generated input is shipped. diff --git a/.agent/plans/qc-to-qco-on-boundary-verification.md b/.agent/plans/qc-to-qco-on-boundary-verification.md new file mode 100644 index 0000000000..bdff92c814 --- /dev/null +++ b/.agent/plans/qc-to-qco-on-boundary-verification.md @@ -0,0 +1,121 @@ +# Source-ordered QC → QCO on boundary verification + +This plan records PR #2716 before combination with the QC/QCO audit. See the +[combined prototype](../audits/qc-qco-combined-2716-20261018.md) for current +source, validation, and performance. Historical end-to-end performance below is +mixed. Human review remains a merge gate. + +## Scope and decisions + +The implementation changes only `mlir/lib/Conversion/QCToQCO/QCToQCO.cpp` and +`mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp`. Use ordinary rewrite +patterns in explicit source order. Keep source reference identities alive until +their users have been lowered; move nested regions without destroying their +original children. Preserve type conversion, static normalization, supported +input checks, and linear output validation. + +Append a complete converted function argument list, redirect classical uses, and +retain the original quantum references until lowering finishes. Remove the old +argument prefix in one operation. Individual middle insertions and erasures were +quadratic for wide signatures; bulk replacement preserves interleaved argument +order, attributes, and locations without that cost. + +The rebase onto `575acd1b9` retains main's #2756 preflight-state reuse when +normalization is skipped. Recollect register accesses after normalization +because it can invalidate source identities. The historical selected OpenQASM +register workloads always normalized, so they do not measure this reuse path. + +Sampling the factor247 conversion identified region liveness cleanup inside the +greedy normalizer as a substantial cost. Use MLIR's existing +`GreedySimplifyRegionLevel::Disabled` for normalization; this pass needs index +folding and static-reference normalization, not control-flow optimization. + +The historical benchmark baseline is PR #2698 at +`2cf5d436150a6fe953fbc6d8f0c44f126bba3c1f`. QCO → QC lowering, +boundary-verification policy, mapping, and all other pipelines remain outside +the scope of this patch. Existing ordered reverse-conversion work is not +included. + +## Validation + +The 2026-10-15 rebase passes 619 native tests: 176 QCToQCO, 154 QCOToQC, and 289 +Compiler, with two expected macOS `/dev/full` skips. Executable docs, repository +lint, and whole-file C++ lint pass. The wide-signature test's string +construction was adjusted for current lint; all 176 forward-conversion tests +passed again. No benchmarks or sanitizer runs were repeated for this rebase. + +### Historical validation and performance + +Both historical variants use the Release preset, standalone native builds, and +the same assertion-free LLVM/MLIR 23.1.0 toolchain. Run the QCToQCO, QCOToQC, +and Compiler GoogleTest targets under `build/release/mlir/unittests/` to +reproduce the native checks. The pre-rebase candidate passed 606 tests: 176 +QCToQCO, 158 QCOToQC, and 272 Compiler. Added tests cover interleaved call +signatures with owned results, unitary calls inside nested regions, register +aliasing exposed by index folding, wide interleaved signatures, and remapped +register index arguments. + +AddressSanitizer with assertions passes all 176 forward-conversion tests. The +converter and its test translation unit are instrumented; dependencies are not. +Leak detection is disabled. Repository lint (`uvx nox -s lint`) and +`git diff --check` pass. + +Bulk argument replacement also passes 138 differential comparisons against +`0dde327c1`, with identical output for forward conversion and roundtrips. These +cover nested structured control flow, register and scalar qubits, calls, +attributes, and helper declaration order. + +For private helpers with 10,000, 20,000, and 40,000 unused borrowed qubit +arguments, median whole-CLI time falls from 151/649/2156 ms to 24/37/67 ms. Each +size uses three serial, alternating before/after pairs with identical output, +running only `builtin.module(qc-to-qco)` through `mqt-cc --run-pipeline`. Both +binaries use the same Release toolchain. These synthetic timings include parsing +and output; they are not isolated pass timings or Benchpress results. + +The following historical performance comparison predates the bulk-argument fix +and does not measure the current source. It uses frozen Benchpress inputs from +revision `045e7ca6a8e5302826d82f07d42b5689ca61db3c`: QFT with 18 and 160 qubits, +QRAM with 20 qubits, Ising with 26 qubits, cat with 260 qubits, W-state with 380 +qubits, and factor247 with 15 qubits. Seven unique circuits cover forward +conversion; seven circuit/topology pairs cover full compilation, excluding +QFT-160 and including QFT-18 on both all-to-all and linear connectivity. + +The final direct comparison includes both changes against unmodified PR #2698, +not a product of earlier incremental ratios. Each pair has three serial rounds +with alternating variant order and seed 42. All 84 samples pass: 42 per variant, +without failures, errors, skips, timeouts, or interrupted samples. Source, +input, configuration, binary, and output hashes are checked. Every paired output +IR and returned gate count is identical, including repetitions. + +Forward conversion has seven complete pairs: geometric candidate/baseline ratio +0.616, with summed case means of 11.393 s before and 6.460 s after. Factor247 +improves from 10.520 s to 5.968 s; QFT-160 improves from 0.827 s to 0.461 s. + +Full compilation also has seven complete pairs: geometric ratio 0.986, but +summed case means increase from 65.070 s to 70.430 s. QFT-18/linear regresses +13.5% and factor247/square regresses 8.3%. Factor247's mean target-compilation +phase rises from 47.344 s to 56.035 s while its forward conversion falls from +10.561 s to 6.608 s. The cause of the end-to-end regression is not established. +Keep all samples and do not claim a universal full-compilation speedup. + +The historical isolated wheel passed 977 focused Python tests with no skips: +`test_mlir.py`, `test_mlir_loops.py`, `test_mlir_qiskit_translation.py`, +`test_mlir_qiskit_target.py`, `test_mlir_parameter_binding.py`, and +`test_mlir_integer_interchange.py`. Run them with `python -m pytest` and +`-o addopts=` in the wheel's environment. + +Forward-conversion timing excludes the input copy. Full timing includes the QC +copy, QC → QCO, target compilation, and QCO → QC. QASM loading, target setup, +extra output verification, inspection, and serialization are outside timers. The +native targets use frozen FlexibleBackend connectivity and the common `sx`, `x`, +`rz`, `cz`, measurement, and reset basis without calibration metadata. + +## Limits + +This is a native API comparison on selected Benchpress inputs, not a run of the +pytest-benchmark suite. Each fresh process performs one timed call, with no +warmup or adaptive calibration and a 600-second whole-process deadline. Three +fixed-seed rounds on a shared desktop do not establish statistical significance +for small differences. Two-qubit depth is not independently measured. The +supported conversion subset is unchanged; this work does not add support for +unstructured control flow. diff --git a/mlir/include/mqt/Conversion/QCToQCO/QCToQCO.td b/mlir/include/mqt/Conversion/QCToQCO/QCToQCO.td index c7b84aa883..e41a599aa4 100644 --- a/mlir/include/mqt/Conversion/QCToQCO/QCToQCO.td +++ b/mlir/include/mqt/Conversion/QCToQCO/QCToQCO.td @@ -17,6 +17,8 @@ def QCToQCO : Pass<"qc-to-qco", "mlir::ModuleOp"> { Control flow must use structured SCF operations. Operations with block successors, including `cf.br`, `cf.cond_br`, and `cf.switch`, are diagnosed before conversion. + Quantum allocations must be in a function's entry block. Nested regions + may use outer allocations through the supported SCF operations. Private helpers may borrow fixed-size rank-one qubit memrefs with identity layout and default memory space. They become qubit tensors. Updated scalar qubits and complete tensors are appended to ordinary results in argument diff --git a/mlir/include/mqt/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mqt/Dialect/QC/Builder/QCProgramBuilder.h index aeb658ffa4..7c527d39b9 100644 --- a/mlir/include/mqt/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mqt/Dialect/QC/Builder/QCProgramBuilder.h @@ -17,6 +17,7 @@ #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinOps.h" #include "mlir/IR/OwningOpRef.h" #include "mlir/IR/Value.h" #include "mlir/Support/LLVM.h" @@ -32,8 +33,6 @@ namespace mlir { // Forward declarations class MLIRContext; -class ModuleOp; -class Operation; class ValueRange; namespace qc { @@ -43,7 +42,9 @@ namespace qc { /// 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. +/// hardware execution models. The builder destroys unfinished IR on +/// destruction; `finalize()` transfers ownership to its caller. Builders are +/// move-only. The borrowed MLIR context must outlive the builder and its IR. /// /// @par Qubit addressing: /// A program must use either static qubits (`staticQubit`) or dynamic @@ -1331,8 +1332,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { /// @param returnValues Values representing the return values of the main /// function. /// - /// Automatically deallocates all remaining valid qubits and tensors of - /// qubits, adds a return statement with the given return values, and + /// Deallocates owned qubits and registers except those returned directly, + /// adds a return statement with the given return values, and /// transfers ownership of the module to the caller. The builder should not be /// used after calling this method. /// @@ -1369,8 +1370,7 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { private: enum class AllocationMode : uint8_t { Unset, Static, Dynamic }; - MLIRContext* ctx{}; - Operation* moduleOp_; + OwningOpRef moduleOp_; /// Track allocated qubits for automatic deallocation SetVector allocatedQubits; diff --git a/mlir/include/mqt/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mqt/Dialect/QCO/Builder/QCOProgramBuilder.h index 910bc87ae5..8e62c377b2 100644 --- a/mlir/include/mqt/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mqt/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -16,6 +16,7 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinOps.h" #include "mlir/IR/OwningOpRef.h" #include "mlir/IR/Value.h" #include "mlir/Support/LLVM.h" @@ -32,7 +33,6 @@ namespace mlir { // Forward declarations class MLIRContext; -class ModuleOp; class Operation; class ValueRange; @@ -42,7 +42,10 @@ namespace qco { /// /// Operations consume qubit SSA values and return their replacements. The /// builder tracks live values and terminates with a usage error when a -/// consumed or untracked value is reused. +/// consumed or untracked value is reused. The builder destroys unfinished IR +/// on destruction; `finalize()` transfers ownership to its caller. Builders +/// are move-only. The borrowed MLIR context must outlive the builder and its +/// IR. /// /// @par Qubit addressing: /// A program must use either static qubits (`staticQubit`) or dynamic @@ -53,6 +56,8 @@ namespace qco { /// `mqt.entry_point` function. Helpers receive allocated qubits as arguments. /// /// @par Structured control flow: +/// Quantum values must enter regions through explicit arguments; classical +/// captures remain supported. /// These rules apply to linear results; `qcoIf` may prepend classical results. /// Callbacks for `qcoIf`, `qcoIndexSwitch`, `scfFor`, and `scfWhile` must /// preserve each input's type and qubit or register identity by result @@ -381,6 +386,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /// extracted qubit is added to the qubit tracking and the tracking of the /// source tensor is updated. /// + /// The index must identify an unextracted slot; dynamic index equality is a + /// program precondition. + /// /// @param tensor Source tensor (must be valid/unconsumed) /// @param index The index from where the qubit is extracted /// @return Pair of (outTensor, extractedQubit) @@ -401,6 +409,9 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /// given index. The inserted qubit is consumed and removed from the qubit /// tracking while the tracking for the source tensor is updated. /// + /// The scalar must return to its original register and slot; equality of + /// dynamic extraction and insertion indices is a program precondition. + /// /// @param scalar The scalar qubit that is inserted (must be valid/unconsumed) /// @param tensor The tensor where the qubit is inserted (must be /// valid/unconsumed) @@ -1814,8 +1825,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { private: enum class AllocationMode : uint8_t { Unset, Static, Dynamic }; - MLIRContext* ctx{}; - Operation* moduleOp_; + OwningOpRef moduleOp_; /// Check if the builder has been finalized void checkFinalized() const; @@ -1828,13 +1838,14 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /// /// @param qubit Qubit value to validate /// Terminates with a usage error if the qubit is consumed or untracked. - void validateQubitValue(Value qubit) const; + void validateQubitValue(Value qubit, bool checkScope = true) const; /// 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); + void updateQubitTracking(Value inputQubit, Value outputQubit, + bool checkScope = true); /// Count unique tensors int64_t tensorCounter = 0; @@ -1858,13 +1869,14 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /// /// @param tensor Tensor value to validate /// Terminates with a usage error if the tensor is consumed or untracked. - void validateTensorValue(Value tensor) const; + void validateTensorValue(Value tensor, bool checkScope = true) const; /// Update tracking when an operation consumes and produces a tensor /// /// @param inputTensor Input tensor being consumed (must be valid) /// @param outputTensor New output tensor being produced - void updateTensorTracking(Value inputTensor, Value outputTensor); + void updateTensorTracking(Value inputTensor, Value outputTensor, + bool checkScope = true); /// Dispose of every live linear value in the current function. void disposeLinearValues(); diff --git a/mlir/include/mqt/Dialect/QCO/IR/QCOInterfaces.td b/mlir/include/mqt/Dialect/QCO/IR/QCOInterfaces.td index 5933139dda..0524255d30 100644 --- a/mlir/include/mqt/Dialect/QCO/IR/QCOInterfaces.td +++ b/mlir/include/mqt/Dialect/QCO/IR/QCOInterfaces.td @@ -25,6 +25,9 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { The interface enables uniform introspection and composition capabilities across all unitary operations with value semantics. + Input and output qubit ranges correspond positionally: the i-th output + continues the i-th input wire, including for SWAP operations. + Gate parameters must be finite, including values produced at runtime. Operation verification only checks direct constants as a sanity check. }]; diff --git a/mlir/include/mqt/Dialect/QCO/QCOUtils.h b/mlir/include/mqt/Dialect/QCO/QCOUtils.h index 524ae12c10..abda0dc053 100644 --- a/mlir/include/mqt/Dialect/QCO/QCOUtils.h +++ b/mlir/include/mqt/Dialect/QCO/QCOUtils.h @@ -21,7 +21,9 @@ #include "mlir/Support/LLVM.h" #include "mlir/Support/LogicalResult.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/ErrorHandling.h" #include #include @@ -57,6 +59,24 @@ inline bool checkDeadGate(Operation* op) { isa(shapedType.getElementType()); } +/// Return the corresponding quantum value, ignoring classical positions. +/// +/// Requires positional quantum signatures validated by @ref verifyLinearity. +/// Runs in linear time in the input and output signature widths. +[[nodiscard]] inline Value getCorrespondingQuantumValue(ValueRange inputs, + ValueRange outputs, + unsigned inputIndex) { + auto ordinal = llvm::count_if(inputs.take_front(inputIndex), [](Value value) { + return isLinearQubitType(value.getType()); + }); + for (auto output : outputs) { + if (isLinearQubitType(output.getType()) && ordinal-- == 0) { + return output; + } + } + llvm_unreachable("expected corresponding quantum value"); +} + /// Verify linear uses, static roots, and positional quantum state flow. /// /// Requires ordinary MLIR verification first. Every linear QCO value has diff --git a/mlir/include/mqt/Dialect/QCO/Utils/FunctionUtils.h b/mlir/include/mqt/Dialect/QCO/Utils/FunctionUtils.h index bac877b763..4b036b902b 100644 --- a/mlir/include/mqt/Dialect/QCO/Utils/FunctionUtils.h +++ b/mlir/include/mqt/Dialect/QCO/Utils/FunctionUtils.h @@ -50,5 +50,6 @@ namespace mlir::qco { /// /// Slot identity and disjoint extractions are program preconditions; known /// violations and positional region correspondence are checked separately. +/// Returns false at nesting depth 64 or greater to bound recursive stack use. [[nodiscard]] bool hasCompleteTensorLifetime(Value tensor, unsigned depth = 0); } // namespace mlir::qco diff --git a/mlir/include/mqt/Dialect/QCO/Utils/WireIterator.h b/mlir/include/mqt/Dialect/QCO/Utils/WireIterator.h index 01ccce71cd..f602121fb1 100644 --- a/mlir/include/mqt/Dialect/QCO/Utils/WireIterator.h +++ b/mlir/include/mqt/Dialect/QCO/Utils/WireIterator.h @@ -22,6 +22,7 @@ namespace mlir::qco { /// /// The iterator follows the flow of a qubit through a sequence of quantum /// operations while respecting the semantics of each operation. +/// It requires a scalar QCO qubit and linear def-use chains. /// Unitary calls preserve the wire without entering the callee. Generic /// `func.call` operations end traversal; clients manage callee traversal. class [[nodiscard]] WireIterator { @@ -38,8 +39,6 @@ class [[nodiscard]] WireIterator { : op_(qubit.getDefiningOp()), qubit_(qubit) { if (op_ == nullptr || isHead(op_)) { pos_ = Position::Head; - } else if (isTail(op_)) { - pos_ = Position::Tail; } else { pos_ = Position::Between; } @@ -91,9 +90,6 @@ class [[nodiscard]] WireIterator { /// Labels the position on the wire. enum class Position : uint8_t { BeforeHead, Head, Between, Tail, PastTail }; - /// Return true, if an op doesn't return, but only consumes, a qubit value. - static bool isTail(Operation*); - /// Return true, if an op doesn't consume, but only returns, a qubit value. static bool isHead(Operation*); diff --git a/mlir/lib/Compiler/Pipeline.cpp b/mlir/lib/Compiler/Pipeline.cpp index 66d9d96348..0eaa25aaeb 100644 --- a/mlir/lib/Compiler/Pipeline.cpp +++ b/mlir/lib/Compiler/Pipeline.cpp @@ -464,6 +464,9 @@ runDefaultPipelineImpl(CompilerInput&& program, ProgramFormat output, [](T&& value) -> FailureOr { using ProgramType = std::remove_cvref_t; if constexpr (std::is_same_v) { + if (failed(qco::verifyLinearity(value.module()))) { + return failure(); + } return std::forward(value); } else if constexpr (std::is_same_v) { auto qc = QCProgram::fromOpenQASMString(value.source()); @@ -476,7 +479,7 @@ runDefaultPipelineImpl(CompilerInput&& program, ProgramFormat output, } }, std::move(program)); - if (failed(qco) || failed(qco::verifyLinearity(qco->module()))) { + if (failed(qco)) { return failure(); } if (output == ProgramFormat::QCO) { diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 29be02835a..63ae28a25d 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -10,10 +10,8 @@ #include "mqt/Conversion/QCToQCO/QCToQCO.h" -#include "mqt/Conversion/ConversionUtils.h" #include "mqt/Dialect/CBit/IR/CBitDialect.h" #include "mqt/Dialect/CBit/IR/CBitOps.h" -#include "mqt/Dialect/MQT/IR/MQTDialect.h" #include "mqt/Dialect/QC/IR/QCDialect.h" #include "mqt/Dialect/QC/IR/QCInterfaces.h" #include "mqt/Dialect/QC/IR/QCOps.h" @@ -38,19 +36,19 @@ #include "mlir/IR/Types.h" #include "mlir/IR/Value.h" #include "mlir/IR/ValueRange.h" +#include "mlir/Interfaces/FunctionInterfaces.h" +#include "mlir/Rewrite/PatternApplicator.h" #include "mlir/Support/LLVM.h" #include "mlir/Support/LogicalResult.h" #include "mlir/Support/WalkResult.h" #include "mlir/Transforms/CSE.h" -#include "mlir/Transforms/DialectConversion.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/RegionUtils.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/ScopeExit.h" -#include "llvm/ADT/TypeSwitch.h" #include #include @@ -126,25 +124,9 @@ struct LoweringState { /// Local physical tensors released at function return, in allocation order. DenseMap> localReferenceRegisters; - /// Transient canonical keys for modifier block arguments replaced by - /// dialect-conversion signature conversion. - DenseMap convertedQubitAliases; - - /// Map from an operation to its used QC qubits inside its regions - DenseMap> regionQubitMap; - - /// Map from an operation to the registers used inside its regions. - DenseMap> regionRegisterMap; - - /// Original QC value order for already-converted structured operations. - /// - /// Kept separate from the legality maps above so converted SCF operations - /// remain legal. + /// Original QC capture order for source and converted structured operations. DenseMap structuredValues; - /// Qubit keys yielded by converted modifier regions, in yield order. - DenseMap> modifierRegionQubits; - /// The qubit allocation mode used in the module AllocationMode allocationMode = AllocationMode::Unset; @@ -165,25 +147,16 @@ struct LoweringState { /// Share qubit and register mappings across conversion patterns. template -class StatefulOpConversionPattern : public OpConversionPattern { +class LoweringPattern : public OpRewritePattern { public: - StatefulOpConversionPattern(TypeConverter& typeConverter, - MLIRContext* context, LoweringState* state) - : OpConversionPattern(context), state_(state), - typeConverter_(&typeConverter) {} + LoweringPattern(MLIRContext* context, LoweringState* state) + : OpRewritePattern(context), state_(state) {} [[nodiscard]] LoweringState& getState() const { return *state_; } - [[nodiscard]] TypeConverter* getQCToQCOTypeConverter() const { - return typeConverter_; - } private: LoweringState* state_; - /// Quantum operands use region-local state, not automatic materializations. - /// - /// The converter is needed only for signatures and explicit result types. - TypeConverter* typeConverter_; }; } // namespace @@ -222,21 +195,9 @@ findRegionLocalMap(DenseMap& map, Operation* anchor, return {nullptr, nullptr}; } -/// Canonicalizes a source qubit key after block signature conversion. -[[nodiscard]] static Value canonicalQubitKey(const LoweringState& state, - Value qubit) { - for (auto alias = state.convertedQubitAliases.find(qubit); - alias != state.convertedQubitAliases.end(); - alias = state.convertedQubitAliases.find(qubit)) { - qubit = alias->second; - } - return qubit; -} - /// Resolves the latest QCO SSA value for a QC qubit reference. [[nodiscard]] static Value lookupMappedQubit(LoweringState& state, Operation* anchor, Value qcQubit) { - qcQubit = canonicalQubitKey(state, qcQubit); const auto& [qubitMap, qubitValue] = findRegionLocalMap(state.qubitMap, anchor, qcQubit); assert(qubitMap != nullptr && qubitValue != nullptr && "QC qubit not found"); @@ -257,7 +218,6 @@ findRegionLocalMap(DenseMap& map, Operation* anchor, /// Updates the latest QCO SSA value for a QC qubit reference. static void assignMappedQubit(LoweringState& state, Operation* anchor, Value qcQubit, Value qcoQubit) { - qcQubit = canonicalQubitKey(state, qcQubit); auto [qubitMap, qubitValue] = findRegionLocalMap(state.qubitMap, anchor, qcQubit); if (qubitValue != nullptr) { @@ -299,16 +259,6 @@ resolveMappedQubits(LoweringState& state, Operation* anchor, }); } -/// Resolves a range of QC memrefs to their latest QTensor values. -template -[[nodiscard]] static SmallVector -resolveMappedTensors(LoweringState& state, Operation* anchor, - const Range& registers) { - return llvm::map_to_vector(registers, [&](RegisterId reg) { - return lookupMappedTensor(state, anchor, reg); - }); -} - /// Updates mappings for matching QC and QCO qubit ranges. template static void assignMappedQubits(LoweringState& state, Operation* anchor, @@ -342,7 +292,7 @@ static void seedRegionMappings(LoweringState& state, Region& region, ValueRange qcoQubits, ValueRange tensors) { auto& qubitMap = state.qubitMap[®ion]; for (auto [qcQubit, qcoQubit] : llvm::zip_equal(qcQubits, qcoQubits)) { - qubitMap[canonicalQubitKey(state, qcQubit)] = qcoQubit; + qubitMap[qcQubit] = qcoQubit; } auto& tensorMap = state.tensorMap[®ion]; for (auto [reg, tensor] : llvm::zip_equal(registers, tensors)) { @@ -412,23 +362,18 @@ static void commitQubits(LoweringState& state, Operation* anchor, /// Resolves all structured QC state to QCO and QTensor values. [[nodiscard]] static SmallVector resolveAllValues(LoweringState& state, Operation* anchor) { - SmallVector registers; - SmallVector qcQubits; auto* owner = structuredValueOwner(anchor); - if (const auto it = state.structuredValues.find(owner); - it != state.structuredValues.end()) { - llvm::append_range(registers, it->second.registers); - llvm::append_range(qcQubits, it->second.qubits); - } else { - llvm::append_range(registers, state.regionRegisterMap[owner]); - llvm::append_range(qcQubits, state.regionQubitMap[owner]); - } + const auto& [qcQubits, registers] = + state.structuredValues.find(owner)->second; SmallVector qcoTargets; qcoTargets.reserve(registers.size() + qcQubits.size()); - llvm::append_range(qcoTargets, - resolveMappedTensors(state, anchor, registers)); - llvm::append_range(qcoTargets, resolveMappedQubits(state, anchor, qcQubits)); + for (auto reg : registers) { + qcoTargets.push_back(lookupMappedTensor(state, anchor, reg)); + } + for (auto qubit : qcQubits) { + qcoTargets.push_back(lookupMappedQubit(state, anchor, qubit)); + } return qcoTargets; } @@ -447,15 +392,25 @@ static void commitQubits(LoweringState& state, Operation* anchor, } /// Hoist static qubit references and let CSE coalesce them. -[[nodiscard]] static LogicalResult normalizeStaticQubits(ModuleOp moduleOp) { +[[nodiscard]] static LogicalResult normalizeStaticQubits(ModuleOp moduleOp, + bool& changed) { RewritePatternSet patterns(moduleOp.getContext()); qc::StaticOp::getCanonicalizationPatterns(patterns, moduleOp.getContext()); - if (failed(applyPatternsGreedily(moduleOp, std::move(patterns)))) { + // Normalization needs folding and static-qubit hoisting, not CFG cleanup. + GreedyRewriteConfig config; + config.setRegionSimplificationLevel(GreedySimplifyRegionLevel::Disabled); + // Full CSE must report constant replacements before reusing register + // analysis. + config.enableConstantCSE(false); + if (failed(applyPatternsGreedily(moduleOp, std::move(patterns), config, + &changed))) { return failure(); } IRRewriter rewriter(moduleOp.getContext()); DominanceInfo dominance(moduleOp); - eliminateCommonSubExpressions(rewriter, dominance, moduleOp); + bool cseChanged = false; + eliminateCommonSubExpressions(rewriter, dominance, moduleOp, &cseChanged); + changed |= cseChanged; return success(); } @@ -505,6 +460,17 @@ static void commitQubits(LoweringState& state, Operation* anchor, } } + if (isa(operation) || + (isa(operation) && + isQubitMemrefType(operation->getResult(0).getType()))) { + auto function = operation->getParentOfType(); + if (!function || operation->getBlock() != &function.getBody().front()) { + operation->emitOpError( + "quantum allocations must be in a function's entry block"); + return WalkResult::interrupt(); + } + } + const bool isModifier = isa(operation); for (Region& region : operation->getRegions()) { for (Block& block : region) { @@ -717,18 +683,12 @@ collectRegisterAccesses(Operation* root, LoweringState& state) { } const auto distinctResult = root->walk([&](Operation* operation) { - SmallVector operationQubits; + ValueRange operands; + DenseSet borrowedRegisters; if (auto unitary = dyn_cast(operation)) { - llvm::append_range(operationQubits, unitary.getQubits()); + operands = unitary.getQubits(); } else if (auto call = dyn_cast(operation)) { - for (auto operand : call.getOperands()) { - if (isa(operand.getType())) { - operationQubits.emplace_back(operand); - } - } - } - DenseSet borrowedRegisters; - if (auto call = dyn_cast(operation)) { + operands = call.getOperands(); for (auto operand : call.getOperands()) { if (isQubitMemrefType(operand.getType()) && !borrowedRegisters.insert(lookupRegisterId(state, operand)) @@ -739,9 +699,16 @@ collectRegisterAccesses(Operation* root, LoweringState& state) { } } + if (operands.size() < 2 && borrowedRegisters.empty()) { + return WalkResult::advance(); + } + llvm::SmallDenseSet qubits; DenseMap registerIndices; - for (auto qubit : operationQubits) { + for (auto qubit : operands) { + if (!isa(qubit.getType())) { + continue; + } if (!qubits.insert(qubit).second) { operation->emitOpError("requires distinct qubit operands"); return WalkResult::interrupt(); @@ -813,10 +780,8 @@ static void collectStructuredCaptures(Operation* root, LoweringState& state) { SetVector captures; getUsedValuesDefinedAbove(operation->getRegions(), captures); - auto& qubits = state.regionQubitMap[operation]; - auto& registers = state.regionRegisterMap[operation]; - qubits.clear(); - registers.clear(); + SmallVector qubits; + SetVector registers; for (auto value : captures) { if (const auto access = state.registerAccesses.find(value); access != state.registerAccesses.end()) { @@ -824,30 +789,57 @@ static void collectStructuredCaptures(Operation* root, LoweringState& state) { continue; } if (isa(value.getType())) { - qubits.insert(value); + qubits.push_back(value); continue; } if (isQubitMemrefType(value.getType())) { registers.insert(lookupRegisterId(state, value)); } } + if (!qubits.empty() || !registers.empty()) { + state.structuredValues[operation] = { + .qubits = std::move(qubits), + .registers = registers.takeVector(), + }; + } }); } -/// Seeds region-owned modifier state after signature conversion. -static void initializeModifierRegionState(Operation* modifier, - ValueRange sourceArguments, - LoweringState& state) { - auto& region = modifier->getRegion(0); - const auto convertedArguments = region.front().getArguments(); - for (auto [source, converted] : - llvm::zip_equal(sourceArguments, convertedArguments)) { - state.convertedQubitAliases[source] = converted; +// Keep the block and its argument identities; only their types change. All +// uses inside the moved body are lowered before the pass verifier runs. +static void moveModifierRegion(Region& source, Region& destination, + PatternRewriter& rewriter) { + rewriter.inlineRegionBefore(source, destination, destination.end()); + // Modifier arguments are qubits by contract. Preserve their identities so + // the original child operations remain in the traversal worklist. + for (auto argument : destination.front().getArguments()) { + argument.setType(qco::QubitType::get(rewriter.getContext())); + } +} + +static void initializeModifierRegionState(Operation* op, LoweringState& state) { + auto& region = op->getRegion(0); + auto args = region.front().getArguments(); + seedRegionMappings(state, region, args, {}, args, {}); +} + +/// Returns whether the type changes when lowering QC references to QCO values. +[[nodiscard]] static bool isQCType(Type type) { + auto memref = dyn_cast(type); + return isa(type) || + (memref && isa(memref.getElementType())); +} + +[[nodiscard]] static Type convertQCType(Type type) { + if (isa(type)) { + return qco::QubitType::get(type.getContext()); + } + if (auto memref = dyn_cast(type); + memref && isa(memref.getElementType())) { + return RankedTensorType::get(memref.getShape(), + qco::QubitType::get(type.getContext())); } - state.modifierRegionQubits[®ion] = - SmallVector(convertedArguments.begin(), convertedArguments.end()); - seedRegionMappings(state, region, convertedArguments, {}, convertedArguments, - {}); + return type; } namespace { @@ -859,12 +851,11 @@ namespace { /// consumed exactly once. For allocations (including static qubits), the sink /// is `qco.sink`. This pattern inserts `qco.sink` operations for all still-live /// qubits tracked in the lowering state right before the return. -struct ConvertFuncReturnOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertFuncReturnOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(func::ReturnOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* funcRegion = op->getParentRegion(); auto& map = state.qubitMap[funcRegion]; @@ -875,14 +866,13 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { SmallVector returnValues; returnValues.reserve(op.getNumOperands()); DenseSet liveQubits; - for (auto [qcOperand, adaptorOperand] : - llvm::zip_equal(op.getOperands(), adaptor.getOperands())) { + for (auto qcOperand : op.getOperands()) { if (auto* it = map.find(qcOperand); it != map.end() && it->second) { auto latest = it->second; returnValues.emplace_back(latest); liveQubits.insert(latest); } else { - returnValues.emplace_back(adaptorOperand); + returnValues.emplace_back(qcOperand); } } auto function = op->getParentOfType(); @@ -913,6 +903,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { } state.localReferenceRegisters.erase(funcRegion); state.qubitMap.erase(funcRegion); + state.tensorMap.erase(funcRegion); state.functionQubitArguments.erase(function); rewriter.replaceOpWithNewOp(op, returnValues); @@ -920,56 +911,18 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { } }; -/// Type converter for QC-to-QCO conversion -/// -/// 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 QCToQCOTypeConverter final : public TypeConverter { -public: - explicit QCToQCOTypeConverter(MLIRContext* ctx) { - // Identity conversion for all types by default - addConversion([](Type type) { return type; }); - - // Convert QC qubit references to QCO qubit values - addConversion([ctx](qc::QubitType /*type*/) -> Type { - return qco::QubitType::get(ctx); - }); - addConversion([ctx](MemRefType type) -> Type { - if (isa(type.getElementType())) { - return RankedTensorType::get(type.getShape(), qco::QubitType::get(ctx)); - } - return type; - }); - } -}; - -struct ConvertFuncOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertFuncOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(func::FuncOp op, OpAdaptor, - ConversionPatternRewriter& rewriter) const override { - if (getQCToQCOTypeConverter()->isSignatureLegal(op.getFunctionType())) { + LogicalResult matchAndRewrite(func::FuncOp op, + PatternRewriter& rewriter) const override { + if (llvm::none_of(op.getArgumentTypes(), isQCType) && + llvm::none_of(op.getResultTypes(), isQCType)) { return failure(); } - TypeConverter::SignatureConversion signature(op.getNumArguments()); - if (failed(getQCToQCOTypeConverter()->convertSignatureArgs( - op.getArgumentTypes(), signature))) { - return failure(); - } - SmallVector inputs; - SmallVector results; - if (failed(getQCToQCOTypeConverter()->convertTypes(op.getArgumentTypes(), - inputs)) || - failed(getQCToQCOTypeConverter()->convertTypes(op.getResultTypes(), - results))) { - return failure(); - } + auto inputs = llvm::map_to_vector(op.getArgumentTypes(), convertQCType); + auto results = llvm::map_to_vector(op.getResultTypes(), convertQCType); SmallVector qubitArguments; SmallVector resultAttrs; @@ -993,15 +946,20 @@ struct ConvertFuncOp final : StatefulOpConversionPattern { if (op.isExternal()) { return success(); } - auto convertedEntry = rewriter.convertRegionTypes( - &op.getBody(), *getQCToQCOTypeConverter(), &signature); - if (failed(convertedEntry)) { - return failure(); + // Append the full signature to preserve interleaved order without shifting + // arguments. Keep source QC identities until their users are lowered. + auto& block = op.getBody().front(); + const auto numOriginalArguments = block.getNumArguments(); + for (auto [source, type] : llvm::zip_equal(originalArguments, inputs)) { + auto converted = block.addArgument(type, source.getLoc()); + if (source.getType() == type) { + rewriter.replaceAllUsesWith(source, converted); + } } auto& map = getState().qubitMap[&op.getBody()]; auto& functionArguments = getState().functionQubitArguments[op]; for (unsigned index : qubitArguments) { - Value converted = (*convertedEntry)->getArgument(index); + Value converted = block.getArgument(numOriginalArguments + index); if (auto reg = getState().registerIds.find(originalArguments[index]); reg != getState().registerIds.end()) { const auto id = reg->second; @@ -1016,12 +974,11 @@ struct ConvertFuncOp final : StatefulOpConversionPattern { } }; -struct ConvertFuncCallOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertFuncCallOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(func::CallOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(func::CallOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); SmallVector qcQubits; for (auto operand : op.getOperands()) { @@ -1029,13 +986,9 @@ struct ConvertFuncCallOp final : StatefulOpConversionPattern { qcQubits.emplace_back(operand); } } - SmallVector resultTypes; - if (failed(getQCToQCOTypeConverter()->convertTypes(op.getResultTypes(), - resultTypes))) { - return failure(); - } + auto resultTypes = llvm::map_to_vector(op.getResultTypes(), convertQCType); auto materialized = materializeQubits(state, op, qcQubits, rewriter); - SmallVector operands(adaptor.getOperands()); + SmallVector operands(op.getOperands()); SmallVector quantumArguments; size_t qubitIndex = 0; for (auto [index, source] : llvm::enumerate(op.getOperands())) { @@ -1077,17 +1030,27 @@ struct ConvertFuncCallOp final : StatefulOpConversionPattern { } } commitQubits(state, op, qcQubits, qubitResults, materialized, rewriter); - rewriter.replaceOp(op, call.getResults().take_front(op.getNumResults())); + // Classical values use normal SSA replacement; QC references keep their + // identity and resolve through the state map until deferred source erasure. + for (auto [source, result] : + llvm::zip_equal(op.getResults(), + call.getResults().take_front(op.getNumResults()))) { + if (!isa(source.getType())) { + rewriter.replaceAllUsesWith(source, result); + } + } + if (llvm::none_of(op.getResultTypes(), isQCType)) { + rewriter.eraseOp(op); + } return success(); } }; -struct ConvertQCCallOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCCallOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::CallOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::CallOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); const auto firstQubit = llvm::find_if(op.getOperands(), [](Value operand) { return isa(operand.getType()); @@ -1096,7 +1059,7 @@ struct ConvertQCCallOp final : StatefulOpConversionPattern { std::distance(op.getOperands().begin(), firstQubit)); auto qcQubits = op.getOperands().drop_front(numParams); auto materialized = materializeQubits(state, op, qcQubits, rewriter); - SmallVector operands(adaptor.getOperands().take_front(numParams)); + SmallVector operands(op.getOperands().take_front(numParams)); llvm::append_range(operands, materialized.values); auto call = qco::CallOp::create(rewriter, op.getLoc(), op.getCalleeAttr(), operands); @@ -1119,13 +1082,11 @@ struct ConvertQCCallOp final : StatefulOpConversionPattern { /// ```mlir /// %tensor = qtensor.alloc(%c3) : tensor<3x!qco.qubit> /// ``` -struct ConvertMemRefAllocOp final - : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertMemRefAllocOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(memref::AllocOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(memref::AllocOp op, + PatternRewriter& rewriter) const override { if (!isa(op.getType().getElementType())) { return failure(); } @@ -1142,7 +1103,7 @@ struct ConvertMemRefAllocOp final qtensor::AllocOp alloc; if (shape[0] == ShapedType::kDynamic) { alloc = qtensor::AllocOp::create(rewriter, op.getLoc(), - adaptor.getDynamicSizes()[0]); + op.getDynamicSizes()[0]); } else { auto size = arith::ConstantIndexOp::create(rewriter, op.getLoc(), shape[0]); @@ -1154,20 +1115,18 @@ struct ConvertMemRefAllocOp final auto memref = op.getResult(); assignMappedTensor(state, alloc, lookupRegisterId(state, memref), alloc.getResult()); - rewriter.replaceOp(op, alloc.getResult()); + // Source identity retained until all QC users are lowered. return success(); } }; /// Rebuild an immutable buffer of physical references as a qubit tensor. -struct ConvertMemRefAllocaOp final - : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertMemRefAllocaOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(memref::AllocaOp op, OpAdaptor, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(memref::AllocaOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); const auto buffer = state.referenceBuffers.find(op.getResult()); if (buffer == state.referenceBuffers.end()) { @@ -1190,47 +1149,17 @@ struct ConvertMemRefAllocaOp final const auto reg = lookupRegisterId(state, op.getResult()); assignMappedTensor(state, op, reg, tensor.getResult()); state.localReferenceRegisters[op->getParentRegion()].push_back(reg); - rewriter.replaceOp(op, tensor.getResult()); - return success(); - } -}; - -/// Record a stable register slot; materialize its qubit at each quantum use. -/// -/// @par Example: -/// ```mlir -/// %q = memref.load %memref[%c0] : memref<3x!qc.qubit> -/// ``` -/// Quantum operations extract and reinsert the referenced qubit locally. -struct ConvertMemRefLoadOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(memref::LoadOp op, OpAdaptor adaptor, - ConversionPatternRewriter& rewriter) const override { - if (!isa(op.getMemRefType().getElementType())) { - return failure(); - } - - auto& state = getState(); - const auto accessIt = state.registerAccesses.find(op.getResult()); - assert(accessIt != state.registerAccesses.end() && - "qubit load provenance not found"); - accessIt->second.index = adaptor.getIndices().front(); - - rewriter.eraseOp(op); - + // Source identity retained until all QC users are lowered. return success(); } }; /// A valid register store preserves the reference already in that slot. -struct ConvertMemRefStoreOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertMemRefStoreOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(memref::StoreOp op, OpAdaptor, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(memref::StoreOp op, + PatternRewriter& rewriter) const override { if (!isa(op.getValue().getType())) { return failure(); } @@ -1249,13 +1178,11 @@ struct ConvertMemRefStoreOp final : OpConversionPattern { /// ```mlir /// qtensor.dealloc %tensor : tensor<3x!qco.qubit> /// ``` -struct ConvertMemRefDeallocOp final - : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertMemRefDeallocOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(memref::DeallocOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(memref::DeallocOp op, + PatternRewriter& rewriter) const override { auto memref = op.getMemref(); if (!isa(memref.getType().getElementType())) { return failure(); @@ -1272,45 +1199,12 @@ struct ConvertMemRefDeallocOp final } }; -/// Converts qc.alloc to qco.alloc -/// -/// @par Example: -/// ```mlir -/// %q = qc.alloc : !qc.qubit -/// ``` -/// is converted to -/// ```mlir -/// %q = qco.alloc : !qco.qubit -/// ``` -struct ConvertQCAllocOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(qc::AllocOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - if (failed(state.ensureAllocationMode(AllocationMode::Dynamic, - op.getOperation()))) { - return failure(); - } - auto qcQubit = op.getResult(); - - auto qcoOp = rewriter.replaceOpWithNewOp(op); - - auto qcoQubit = qcoOp.getResult(); - assignMappedQubit(state, qcoOp, qcQubit, qcoQubit); - - return success(); - } -}; - /// Sink the latest mapped qubit value and mark its QC reference as consumed. -struct ConvertQCDeallocOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCDeallocOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(DeallocOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(DeallocOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto& qubitMap = state.qubitMap[op->getParentRegion()]; auto* operation = op.getOperation(); @@ -1327,39 +1221,13 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { } }; -/// Preserve the static qubit index and record its initial QCO value. -struct ConvertQCStaticOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; - - LogicalResult - matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { - auto& state = getState(); - if (failed(state.ensureAllocationMode(AllocationMode::Static, - op.getOperation()))) { - return failure(); - } - if (state.registerAccesses.contains(op.getQubit())) { - rewriter.eraseOp(op); - return success(); - } - auto qcQubit = op.getQubit(); - - auto qcoOp = rewriter.replaceOpWithNewOp(op, op.getIndex()); - assignMappedQubit(state, qcoOp, qcQubit, qcoOp.getQubit()); - - return success(); - } -}; - /// Measure the latest mapped qubit, retain its output in the lowering state, /// and replace the QC measurement's classical result. -struct ConvertQCMeasureOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCMeasureOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::MeasureOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcQubit = op.getQubit(); @@ -1380,12 +1248,11 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { }; /// Reset the latest mapped qubit and retain its output in the lowering state. -struct ConvertQCResetOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCResetOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::ResetOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcQubit = op.getQubit(); @@ -1406,12 +1273,11 @@ struct ConvertQCResetOp final : StatefulOpConversionPattern { template -struct ConvertQCGateToQCO final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCGateToQCO final : LoweringPattern { + using LoweringPattern::LoweringPattern; template - auto createGate(ConversionPatternRewriter& rewriter, QCOpType op, - ValueRange qcoTargets, + auto createGate(PatternRewriter& rewriter, QCOpType op, ValueRange qcoTargets, std::index_sequence /*targets*/, std::index_sequence /*params*/) const { auto params = op.getParameters(); @@ -1420,9 +1286,8 @@ struct ConvertQCGateToQCO final : StatefulOpConversionPattern { params[ParamIndices]...); } - LogicalResult - matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(QCOpType op, + PatternRewriter& rewriter) const override { auto& state = this->getState(); auto qcTargets = op.getTargets(); auto materialized = materializeQubits(state, op, qcTargets, rewriter); @@ -1440,12 +1305,11 @@ struct ConvertQCGateToQCO final : StatefulOpConversionPattern { }; /// Converts a variadic dense qc.unitary to its value-semantics form. -struct ConvertQCUnitaryOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCUnitaryOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::UnitaryOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::UnitaryOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcQubits = op.getQubits(); @@ -1471,12 +1335,11 @@ struct ConvertQCUnitaryOp final : StatefulOpConversionPattern { /// %q_out:2 = qco.barrier %q0_in, %q1_in : !qco.qubit, !qco.qubit -> /// !qco.qubit, !qco.qubit /// ``` -struct ConvertQCBarrierOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCBarrierOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::BarrierOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::BarrierOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcQubits = op.getQubits(); @@ -1508,12 +1371,11 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { /// qco.yield %a_res : !qco.qubit /// } : ({!qco.qubit}, {!qco.qubit}) /// ``` -struct ConvertQCCtrlOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCCtrlOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::CtrlOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::CtrlOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcControls = op.getControls(); @@ -1532,16 +1394,10 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { llvm::append_range(qcoQubits, qcoOp.getTargetsOut()); commitQubits(state, operation, qcQubits, qcoQubits, materialized, rewriter); - const SmallVector sourceArguments( - op.getRegion().front().getArguments()); - // Inline region and convert the block signature to QCO types. - if (failed(moveRegion(op.getRegion(), qcoOp.getRegion(), rewriter, - getQCToQCOTypeConverter()))) { - return failure(); - } + moveModifierRegion(op.getRegion(), qcoOp.getRegion(), rewriter); - initializeModifierRegionState(qcoOp, sourceArguments, state); + initializeModifierRegionState(qcoOp, state); rewriter.eraseOp(op); return success(); @@ -1563,12 +1419,11 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { /// qco.yield %a0_res : !qco.qubit /// } : {!qco.qubit} -> {!qco.qubit} /// ``` -struct ConvertQCInvOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCInvOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::InvOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::InvOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcTargets = op.getTargets(); @@ -1580,16 +1435,10 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { commitQubits(state, operation, qcTargets, qcoOp.getOutputTargets(), materialized, rewriter); - const SmallVector sourceArguments( - op.getRegion().front().getArguments()); - // Inline region and convert the block signature to QCO types. - if (failed(moveRegion(op.getRegion(), qcoOp.getRegion(), rewriter, - getQCToQCOTypeConverter()))) { - return failure(); - } + moveModifierRegion(op.getRegion(), qcoOp.getRegion(), rewriter); - initializeModifierRegionState(qcoOp, sourceArguments, state); + initializeModifierRegionState(qcoOp, state); rewriter.eraseOp(op); return success(); @@ -1611,12 +1460,11 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { /// qco.yield %a0_res /// } : {!qco.qubit} -> {!qco.qubit} /// ``` -struct ConvertQCPowOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCPowOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::PowOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::PowOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); auto qcTargets = op.getTargets(); @@ -1629,16 +1477,10 @@ struct ConvertQCPowOp final : StatefulOpConversionPattern { commitQubits(state, operation, qcTargets, qcoOp.getQubitsOut(), materialized, rewriter); - const SmallVector sourceArguments( - op.getRegion().front().getArguments()); - // Inline region and convert the block signature to QCO types. - if (failed(moveRegion(op.getRegion(), qcoOp.getRegion(), rewriter, - getQCToQCOTypeConverter()))) { - return failure(); - } + moveModifierRegion(op.getRegion(), qcoOp.getRegion(), rewriter); - initializeModifierRegionState(qcoOp, sourceArguments, state); + initializeModifierRegionState(qcoOp, state); rewriter.eraseOp(op); return success(); @@ -1655,24 +1497,23 @@ struct ConvertQCPowOp final : StatefulOpConversionPattern { /// ```mlir /// qco.yield %targets : !qco.qubit /// ``` -struct ConvertQCYieldOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertQCYieldOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(qc::YieldOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(qc::YieldOp op, + PatternRewriter& rewriter) const override { + if (!isa(op->getParentOp())) { + return rewriter.notifyMatchFailure(op, + "expected a converted QC modifier"); + } auto& state = getState(); auto* operation = op.getOperation(); auto* region = op->getParentRegion(); - const auto frame = state.modifierRegionQubits.find(region); - if (frame == state.modifierRegionQubits.end()) { - return rewriter.notifyMatchFailure(op, "missing modifier region state"); - } - - auto targets = resolveMappedQubits(state, operation, frame->second); + auto targets = + resolveMappedQubits(state, operation, region->front().getArguments()); rewriter.replaceOpWithNewOp(op, targets); state.qubitMap.erase(region); - state.modifierRegionQubits.erase(frame); + state.tensorMap.erase(region); return success(); } }; @@ -1683,32 +1524,30 @@ struct ConvertQCYieldOp final : StatefulOpConversionPattern { /// @par Example: /// ```mlir /// scf.for %iv = %lb to %ub step %step { -/// %q0 = qc.load %memref[%iv] : !memref<3x!qc.qubit> +/// %q0 = memref.load %memref[%iv] : memref<3x!qc.qubit> /// qc.h %q0 : !qc.qubit /// } /// ``` /// is converted to /// ```mlir /// %targets_out = scf.for %iv = %lb to %ub step %step iter_args(%arg0 = -/// %qtensor) -> (tensor<3x!qco.qubit) { +/// %qtensor) -> (tensor<3x!qco.qubit>) { /// %t0, %q0 = qtensor.extract %arg0[%iv] : tensor<3x!qco.qubit> /// %q1 = qco.h %q0 : !qco.qubit /// %t1 = qtensor.insert %q1 into %t0[%iv] : tensor<3x!qco.qubit> /// scf.yield %t1 : tensor<3x!qco.qubit> /// } /// ``` -struct ConvertSCFForOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertSCFForOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(scf::ForOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(scf::ForOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - auto& registerMap = state.regionRegisterMap[op]; - auto& qubitMap = state.regionQubitMap[op]; - const auto numRegisters = registerMap.size(); - const auto numQubits = qubitMap.size(); + auto& [qubits, registers] = state.structuredValues.find(op)->second; + const auto numRegisters = registers.size(); + const auto numQubits = qubits.size(); auto qcoTargets = resolveAllValues(state, operation); const auto numOriginalResults = op.getNumResults(); @@ -1720,11 +1559,11 @@ struct ConvertSCFForOp final : StatefulOpConversionPattern { op.getUpperBound(), op.getStep(), initArgs); newForOp->setDiscardableAttrs(op->getDiscardableAttrDictionary()); - assignMappedTensors(state, op.getOperation(), registerMap, + assignMappedTensors(state, op.getOperation(), registers, newForOp.getResults() .drop_front(numOriginalResults) .take_front(numRegisters)); - assignMappedQubits(state, op.getOperation(), qubitMap, + assignMappedQubits(state, op.getOperation(), qubits, newForOp->getResults().take_back(numQubits)); // Move the contents from the old block into the new block @@ -1737,18 +1576,15 @@ struct ConvertSCFForOp final : StatefulOpConversionPattern { rewriter.replaceAllUsesWith(oldArgument, newArgument); } - SmallVector qubits(qubitMap.begin(), qubitMap.end()); - SmallVector registers(registerMap.begin(), registerMap.end()); - state.structuredValues[newForOp] = { - .qubits = qubits, - .registers = registers, - }; seedRegionMappings(state, newForOp.getRegion(), qubits, registers, dstBlock.getArguments().take_back(numQubits), dstBlock.getArguments() .drop_front(1 + numOriginalResults) .take_front(numRegisters)); + auto values = std::move(state.structuredValues.find(op)->second); + state.structuredValues.erase(op); + state.structuredValues.try_emplace(newForOp, std::move(values)); rewriter.replaceOp(op, newForOp.getResults().take_front(numOriginalResults)); @@ -1772,7 +1608,7 @@ struct ConvertSCFForOp final : StatefulOpConversionPattern { /// is converted to /// ```mlir /// %targets_out = scf.while (%arg0 = %q0) : (!qco.qubit) -> !qco.qubit { -/// %q1 = qco.measure %arg0 : !qco.qubit +/// %q1, %cond = qco.measure %arg0 : !qco.qubit /// scf.condition(%cond) %q1 : !qco.qubit /// } do { /// ^bb0(%arg0: !qco.qubit): @@ -1780,18 +1616,16 @@ struct ConvertSCFForOp final : StatefulOpConversionPattern { /// scf.yield %q2 : !qco.qubit /// } /// ``` -struct ConvertSCFWhileOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertSCFWhileOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(scf::WhileOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(scf::WhileOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - auto& registerMap = state.regionRegisterMap[op]; - auto& qubitMap = state.regionQubitMap[op]; - const auto numRegisters = registerMap.size(); - const auto numQubits = qubitMap.size(); + auto& [qubits, registers] = state.structuredValues.find(op)->second; + const auto numRegisters = registers.size(); + const auto numQubits = qubits.size(); auto qcoTargets = resolveAllValues(state, operation); const auto numOriginalInits = op.getInits().size(); @@ -1806,11 +1640,11 @@ struct ConvertSCFWhileOp final : StatefulOpConversionPattern { auto newWhileOp = scf::WhileOp::create(rewriter, op.getLoc(), resultTypes, initArgs); - assignMappedTensors(state, op.getOperation(), registerMap, + assignMappedTensors(state, op.getOperation(), registers, newWhileOp.getResults() .drop_front(numOriginalResults) .take_front(numRegisters)); - assignMappedQubits(state, op.getOperation(), qubitMap, + assignMappedQubits(state, op.getOperation(), qubits, newWhileOp->getResults().take_back(numQubits)); auto& newBeforeRegion = newWhileOp.getBefore(); @@ -1844,12 +1678,6 @@ struct ConvertSCFWhileOp final : StatefulOpConversionPattern { rewriter.replaceAllUsesWith(oldArgument, newArgument); } - SmallVector qubits(qubitMap.begin(), qubitMap.end()); - SmallVector registers(registerMap.begin(), registerMap.end()); - state.structuredValues[newWhileOp] = { - .qubits = qubits, - .registers = registers, - }; seedRegionMappings(state, newWhileOp.getBefore(), qubits, registers, newBeforeBlock->getArguments().take_back(numQubits), newBeforeBlock->getArguments() @@ -1861,6 +1689,9 @@ struct ConvertSCFWhileOp final : StatefulOpConversionPattern { .drop_front(numOriginalResults) .take_front(numRegisters)); + auto values = std::move(state.structuredValues.find(op)->second); + state.structuredValues.erase(op); + state.structuredValues.try_emplace(newWhileOp, std::move(values)); rewriter.replaceOp(op, newWhileOp.getResults().take_front(numOriginalResults)); return success(); @@ -1884,27 +1715,25 @@ struct ConvertSCFWhileOp final : StatefulOpConversionPattern { /// qco.yield %arg0 : !qco.qubit /// } /// ``` -struct ConvertSCFIfOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertSCFIfOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(scf::IfOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(scf::IfOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - auto& registerMap = state.regionRegisterMap[op]; - auto& qubitMap = state.regionQubitMap[op]; - const auto numRegisters = registerMap.size(); - const auto numQubits = qubitMap.size(); + auto& [qubits, registers] = state.structuredValues.find(op)->second; + const auto numRegisters = registers.size(); + const auto numQubits = qubits.size(); auto qcoTargets = resolveAllValues(state, operation); auto newIfOp = IfOp::create(rewriter, op.getLoc(), op.getResultTypes(), ValueRange(qcoTargets).getTypes(), op.getCondition(), qcoTargets); - assignMappedTensors(state, op.getOperation(), registerMap, + assignMappedTensors(state, op.getOperation(), registers, newIfOp.getLinearResults().take_front(numRegisters)); - assignMappedQubits(state, op.getOperation(), qubitMap, + assignMappedQubits(state, op.getOperation(), qubits, newIfOp.getLinearResults().take_back(numQubits)); auto& thenRegion = newIfOp.getThenRegion(); @@ -1923,13 +1752,6 @@ struct ConvertSCFIfOp final : StatefulOpConversionPattern { thenBlock->getOperations().splice( thenBlock->end(), op.getThenRegion().front().getOperations()); - SmallVector qubits(qubitMap.begin(), qubitMap.end()); - SmallVector registers(registerMap.begin(), registerMap.end()); - state.structuredValues[newIfOp] = { - .qubits = qubits, - .registers = registers, - }; - if (!op.getElseRegion().empty()) { elseBlock->getOperations().splice( elseBlock->end(), op.getElseRegion().front().getOperations()); @@ -1947,6 +1769,9 @@ struct ConvertSCFIfOp final : StatefulOpConversionPattern { thenBlock->getArguments().take_back(numQubits), thenBlock->getArguments().take_front(numRegisters)); + auto values = std::move(state.structuredValues.find(op)->second); + state.structuredValues.erase(op); + state.structuredValues.try_emplace(newIfOp, std::move(values)); rewriter.replaceOp(op, newIfOp.getClassicalResults()); return success(); } @@ -1976,19 +1801,16 @@ struct ConvertSCFIfOp final : StatefulOpConversionPattern { /// qco.yield %q2 : !qco.qubit /// } /// ``` -struct ConvertSCFIndexSwitchOp final - : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertSCFIndexSwitchOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(scf::IndexSwitchOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(scf::IndexSwitchOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - auto& registerMap = state.regionRegisterMap[op]; - auto& qubitMap = state.regionQubitMap[op]; - const auto numRegisters = registerMap.size(); - const auto numQubits = qubitMap.size(); + auto& [qubits, registers] = state.structuredValues.find(op)->second; + const auto numRegisters = registers.size(); + const auto numQubits = qubits.size(); const auto targets = resolveAllValues(state, operation); const auto linearResultTypes = ValueRange(targets).getTypes(); @@ -1998,15 +1820,11 @@ struct ConvertSCFIndexSwitchOp final rewriter, op.getLoc(), op.getResultTypes(), linearResultTypes, op.getArg(), op.getCases(), targets, op.getNumCases()); - assignMappedTensors(state, op.getOperation(), registerMap, + assignMappedTensors(state, op.getOperation(), registers, newOp.getLinearResults().take_front(numRegisters)); - assignMappedQubits(state, op.getOperation(), qubitMap, + assignMappedQubits(state, op.getOperation(), qubits, newOp.getLinearResults().take_back(numQubits)); - SmallVector qubits(qubitMap.begin(), qubitMap.end()); - SmallVector registers(registerMap.begin(), registerMap.end()); - state.structuredValues[newOp] = {.qubits = qubits, .registers = registers}; - const auto newCaseRegions = newOp.getCaseRegions(); const auto oldCaseRegions = op.getCaseRegions(); const auto buildRegion = [&](Region& oldRegion, Region& newRegion) { @@ -2027,6 +1845,9 @@ struct ConvertSCFIndexSwitchOp final } buildRegion(op.getDefaultRegion(), newOp.getDefaultRegion()); + auto values = std::move(state.structuredValues.find(op)->second); + state.structuredValues.erase(op); + state.structuredValues.try_emplace(newOp, std::move(values)); rewriter.replaceOp(op, newOp.getClassicalResults()); return success(); } @@ -2044,17 +1865,18 @@ struct ConvertSCFIndexSwitchOp final /// ```mlir /// scf.yield %targets /// ``` -struct ConvertSCFYieldOp final : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertSCFYieldOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(scf::YieldOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(scf::YieldOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); SmallVector targets(op.getResults()); llvm::append_range(targets, resolveAllValues(state, operation)); + state.qubitMap.erase(op->getParentRegion()); + state.tensorMap.erase(op->getParentRegion()); if (isa(op->getParentOp())) { rewriter.replaceOpWithNewOp(op, targets); @@ -2077,18 +1899,18 @@ struct ConvertSCFYieldOp final : StatefulOpConversionPattern { /// ```mlir /// scf.condition(%cond) %targets /// ``` -struct ConvertSCFConditionOp final - : StatefulOpConversionPattern { - using StatefulOpConversionPattern::StatefulOpConversionPattern; +struct ConvertSCFConditionOp final : LoweringPattern { + using LoweringPattern::LoweringPattern; - LogicalResult - matchAndRewrite(scf::ConditionOp op, OpAdaptor /*adaptor*/, - ConversionPatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(scf::ConditionOp op, + PatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); SmallVector targets(op.getArgs()); llvm::append_range(targets, resolveAllValues(state, operation)); + state.qubitMap.erase(op->getParentRegion()); + state.tensorMap.erase(op->getParentRegion()); rewriter.replaceOpWithNewOp(op, op.getCondition(), targets); @@ -2096,180 +1918,168 @@ struct ConvertSCFConditionOp final } }; -/// Convert QC references to QCO values, threading quantum state through -/// functions and structured control flow. -/// -/// Lower terminators after their regions so they yield the final mapped values -/// independently of rewrite order. -struct QCToQCO final : impl::QCToQCOBase { - using QCToQCOBase::QCToQCOBase; - -protected: - void runOnOperation() override { - MLIRContext* context = &getContext(); - auto moduleOp = getOperation(); - - LoweringState preflightState; - if (failed(validateSupportedInput(moduleOp)) || - failed(collectRegisterAccesses(moduleOp, preflightState))) { - signalPassFailure(); - return; - } - - // Register indices still need normalization for alias validation. - const bool needsNormalization = !preflightState.registerIds.empty() || - !staticsAlreadyNormalized(moduleOp); - if (needsNormalization && failed(normalizeStaticQubits(moduleOp))) { - signalPassFailure(); - return; - } - - LoweringState normalizedState; - auto& state = needsNormalization ? normalizedState : preflightState; - - ConversionTarget target(*context); - RewritePatternSet patterns(context); - QCToQCOTypeConverter typeConverter(context); +} // namespace - if (needsNormalization && - failed(collectRegisterAccesses(moduleOp, state))) { - signalPassFailure(); - return; +/// Convert QC references to QCO values in source order, threading quantum state +/// through functions and structured control flow. +/// +/// Lower terminators after preceding operations so they yield the final mapped +/// values. +static LogicalResult lowerToQCO(ModuleOp moduleOp) { + LoweringState state; + if (failed(validateSupportedInput(moduleOp)) || + failed(collectRegisterAccesses(moduleOp, state))) { + return failure(); + } + // Register indices still need normalization for alias validation. + const bool needsNormalization = + !state.registerIds.empty() || !staticsAlreadyNormalized(moduleOp); + if (needsNormalization) { + bool changed = false; + if (failed(normalizeStaticQubits(moduleOp, changed))) { + return failure(); } - - SmallVector unitaryFunctions; - for (auto function : moduleOp.getOps()) { - if (mqt::isUnitaryFunction(function)) { - unitaryFunctions.emplace_back(function); - function->removeAttr(mqt::MQTDialect::UnitaryAttrHelper::getNameStr()); + if (changed) { + state = LoweringState(); + if (failed(collectRegisterAccesses(moduleOp, state))) { + return failure(); } } - auto unitaryGuard = llvm::scope_exit([&] { - for (auto function : unitaryFunctions) { - mqt::setUnitaryFunction(function); - } - }); - - // Get the quantum values captured by structured control-flow regions. - collectStructuredCaptures(moduleOp, state); - - // Configure conversion target - target.addIllegalDialect(); - target.addLegalDialect(); - - target.addDynamicallyLegalDialect([](Operation* op) { - return llvm::none_of(op->getOperandTypes(), isQubitMemrefType) && - llvm::none_of(op->getResultTypes(), isQubitMemrefType); - }); - - target.addDynamicallyLegalDialect([&](Operation* op) { - auto& regionQubitMap = state.regionQubitMap[op]; - auto& regionTensorMap = state.regionRegisterMap[op]; - return regionQubitMap.empty() && regionTensorMap.empty(); - }); - // Structured terminators are lowered in a second conversion phase. The - // first phase must finish converting every operation in a region so the - // region-local maps contain the final QCO values that the terminator has - // to yield. Converting terminators in the same worklist would make the - // result depend on dialect-conversion traversal order. - target.addLegalOp(); - - // Register operation conversion patterns with state tracking. - patterns - .add( - typeConverter, context, &state); - - patterns.add(context); - - // Not part of the central gate table. - patterns.add>( - typeConverter, context, &state); - + } + collectStructuredCaptures(moduleOp, state); + auto* ctx = moduleOp.getContext(); + auto* qcDialect = ctx->getLoadedDialect(); + auto* memrefDialect = ctx->getLoadedDialect(); + RewritePatternSet patterns(ctx); + patterns.add< + ConvertQCDeallocOp, ConvertQCMeasureOp, ConvertQCResetOp, + ConvertQCUnitaryOp, ConvertQCBarrierOp, ConvertQCCtrlOp, ConvertQCInvOp, + ConvertQCPowOp, ConvertQCYieldOp, ConvertSCFForOp, ConvertSCFWhileOp, + ConvertSCFIfOp, ConvertSCFIndexSwitchOp, ConvertSCFYieldOp, + ConvertSCFConditionOp, ConvertFuncReturnOp, ConvertMemRefAllocOp, + ConvertMemRefAllocaOp, ConvertMemRefStoreOp, ConvertMemRefDeallocOp, + ConvertFuncOp, ConvertFuncCallOp, ConvertQCCallOp>(ctx, &state); + patterns.add>(ctx, + &state); #define MQT_GATE(KEY, NAME, GETTER, TARGETS, PARAMS, SUFFIX, CTL_SUFFIX) \ patterns.add< \ ConvertQCGateToQCO>( \ - typeConverter, context, &state); + ctx, &state); #include "mqt/Conversion/GateTable.def" - - // QC qubit arguments become QCO arguments plus trailing pass-through - // results. - patterns.add(typeConverter, context, &state); - target.addDynamicallyLegalOp([&](func::FuncOp op) { - return typeConverter.isSignatureLegal(op.getFunctionType()) && - typeConverter.isLegal(&op.getBody()); - }); - - // Convert returns until borrowed results and local resource releases - // have been emitted, even if the original result types are already legal. - patterns.add(typeConverter, context, &state); - target.addDynamicallyLegalOp([&](func::ReturnOp op) { - if (!typeConverter.isLegal(op)) { - return false; + FrozenRewritePatternSet frozen(std::move(patterns)); + PatternApplicator applicator(frozen); + applicator.applyDefaultCostModel(); + PatternRewriter rewriter(ctx); + SmallVector order, sources; + SmallVector> sourceArgumentBlocks; + moduleOp.walk( + [&](Operation* op) { order.push_back(op); }); + // Structural patterns move original children, never delete them. New ops + // need no worklist: each source operation is handled exactly once. + for (auto* operation : order) { + rewriter.setInsertionPoint(operation); + if (auto alloc = dyn_cast(operation)) { + if (failed(state.ensureAllocationMode(AllocationMode::Dynamic, alloc))) { + return failure(); } - if (state.functionQubitArguments.contains( - op->getParentOfType()) || - state.localReferenceRegisters.contains(op->getParentRegion())) { - return false; + auto out = qco::AllocOp::create(rewriter, alloc.getLoc()); + assignMappedQubit(state, out, alloc.getResult(), out.getResult()); + sources.push_back(alloc); + continue; + } + if (auto ref = dyn_cast(operation)) { + if (failed(state.ensureAllocationMode(AllocationMode::Static, ref))) { + return failure(); } - const auto it = state.qubitMap.find(op->getParentRegion()); - return it == state.qubitMap.end() || it->second.empty(); - }); - - // Generic calls receive the pass-through results added to their callees. - patterns.add(typeConverter, context, &state); - target.addDynamicallyLegalOp( - [&](func::CallOp op) { return typeConverter.isLegal(op); }); - - // Convert structured parents and their contents first. - if (failed(applyPartialConversion(moduleOp, target, std::move(patterns)))) { - signalPassFailure(); - return; + if (state.registerAccesses.contains(ref.getQubit())) { + sources.push_back(ref); + continue; + } + auto out = qco::StaticOp::create(rewriter, ref.getLoc(), ref.getIndex()); + assignMappedQubit(state, out, ref.getQubit(), out.getQubit()); + sources.push_back(ref); + continue; } - // Source register values and loaded qubit references have been erased. - // Structured conversion state uses stable register identifiers from here - // on. - state.registerIds.clear(); - state.registerAccesses.clear(); - state.convertedQubitAliases.clear(); - state.regionQubitMap.clear(); - state.regionRegisterMap.clear(); - - if (state.structuredValues.empty()) { - return; + bool required = operation->getDialect() == qcDialect; + if (auto function = dyn_cast(operation)) { + required = llvm::any_of(function.getArgumentTypes(), isQCType) || + llvm::any_of(function.getResultTypes(), isQCType); + if (required && !function.isExternal()) { + sourceArgumentBlocks.emplace_back(&function.getBody().front(), + function.getNumArguments()); + } } - ConversionTarget terminatorTarget(*context); - terminatorTarget.markUnknownOpDynamicallyLegal( - [](Operation*) { return true; }); - terminatorTarget.addDynamicallyLegalOp( - [&](Operation* op) { - auto* parentOp = op->getParentOp(); - if (!state.structuredValues.contains(parentOp)) { - return true; - } + if (isa(operation)) { + const bool hasQCResults = + llvm::any_of(operation->getResultTypes(), isQCType); + required = + hasQCResults || llvm::any_of(operation->getOperandTypes(), isQCType); + if (hasQCResults) { + sources.push_back(operation); + } + } + if (isa(operation)) { + auto* region = operation->getParentRegion(); + auto it = state.qubitMap.find(region); + required = llvm::any_of(operation->getOperandTypes(), isQCType) || + state.functionQubitArguments.contains( + operation->getParentOfType()) || + state.localReferenceRegisters.contains(region) || + (it != state.qubitMap.end() && !it->second.empty()); + } + if (operation->getDialect() == memrefDialect) { + required = + llvm::any_of(operation->getOperandTypes(), isQubitMemrefType) || + llvm::any_of(operation->getResultTypes(), isQubitMemrefType); + if (required && + isa(operation)) { + sources.push_back(operation); + if (auto load = dyn_cast(operation)) { + // Earlier signature rewrites may have replaced the index value. + const auto access = state.registerAccesses.find(load.getResult()); + assert(access != state.registerAccesses.end() && + "qubit load provenance not found"); + access->second.index = load.getIndices().front(); + continue; + } + } + } + if (isa( + operation)) { + required = state.structuredValues.contains(operation); + } + if (isa(operation)) { + required = state.structuredValues.contains(operation->getParentOp()); + } + if (required && failed(applicator.matchAndRewrite(operation, rewriter))) { + return operation->emitError("cannot lower operation to QCO"); + } + } + for (auto* op : llvm::reverse(sources)) { + if (!op->use_empty()) { + return op->emitError("unconverted QC reference uses"); + } + rewriter.eraseOp(op); + } + for (auto [block, count] : sourceArgumentBlocks) { + if (llvm::any_of( + block->getArguments().take_front(count), + [](BlockArgument argument) { return !argument.use_empty(); })) { + return moduleOp.emitError("unconverted QC argument uses"); + } + block->eraseArguments(0, count); + } + return success(); +} - if (auto condition = dyn_cast(op)) { - return llvm::equal(condition.getArgs().getTypes(), - parentOp->getResultTypes()); - } - if (auto whileOp = dyn_cast(parentOp)) { - return llvm::equal(op->getOperandTypes(), - whileOp.getInits().getTypes()); - } - return llvm::equal(op->getOperandTypes(), parentOp->getResultTypes()); - }); - - RewritePatternSet terminatorPatterns(context); - terminatorPatterns.add( - typeConverter, context, &state); - if (failed(applyPartialConversion(moduleOp, terminatorTarget, - std::move(terminatorPatterns)))) { +namespace { + +struct QCToQCO final : impl::QCToQCOBase { + using QCToQCOBase::QCToQCOBase; + +protected: + void runOnOperation() override { + if (failed(lowerToQCO(getOperation()))) { signalPassFailure(); } } diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index ad8328cb92..0259a1b7df 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -156,15 +156,16 @@ FailureOr> QCProgramBuilder::LoopBuilder::finish() { QCProgramBuilder::QCProgramBuilder(MLIRContext* context) : ImplicitLocOpBuilder( FileLineColLoc::get(context, "", 1, 1), context), - ctx(context), moduleOp_(ModuleOp::create(*this)) { - ctx->loadDialect(); + moduleOp_(ModuleOp::create(*this)) { + getContext()->loadDialect(); } void QCProgramBuilder::initialize() { initialize({getI64Type()}); } void QCProgramBuilder::initialize(TypeRange returnTypes) { + checkFinalized(); // Set insertion point to the module body - setInsertionPointToStart(cast(moduleOp_).getBody()); + setInsertionPointToStart(moduleOp_->getBody()); // Create main function as entry point auto funcType = getFunctionType({}, returnTypes); @@ -178,7 +179,8 @@ void QCProgramBuilder::initialize(TypeRange returnTypes) { } void QCProgramBuilder::retype(TypeRange returnTypes) { - auto mainFunc = mqt::getEntryPoint(cast(moduleOp_)); + checkFinalized(); + auto mainFunc = mqt::getEntryPoint(*moduleOp_); if (!mainFunc) { llvm::reportFatalUsageError("Main function not found for retyping"); } @@ -191,7 +193,7 @@ func::FuncOp QCProgramBuilder::createFunction( const StringRef name, const TypeRange argumentTypes, const function_ref(ValueRange)> body) { checkFinalized(); - auto moduleOp = cast(moduleOp_); + auto moduleOp = *moduleOp_; auto mainFunc = mqt::getEntryPoint(moduleOp); if (!mainFunc) { llvm::reportFatalUsageError( @@ -259,7 +261,7 @@ func::FuncOp QCProgramBuilder::createUnitaryFunction( SmallVector QCProgramBuilder::call(func::FuncOp callee, ValueRange operands) { checkFinalized(); - if (callee->getParentOp() != moduleOp_ || + if (callee->getParentOp() != moduleOp_->getOperation() || callee.getArgumentTypes() != operands.getTypes()) { llvm::reportFatalUsageError( "Call operands must match a function in the current module"); @@ -360,10 +362,11 @@ Value QCProgramBuilder::allocQubitRegisterStorage(const int64_t size, if (size <= 0) { llvm::reportFatalUsageError("Size must be positive"); } - auto memrefType = MemRefType::get({size}, QubitType::get(ctx)); + auto memrefType = MemRefType::get({size}, QubitType::get(getContext())); auto alloc = memref::AllocOp::create(*this, memrefType); if (!name.empty()) { - ctx->getLoadedDialect() + getContext() + ->getLoadedDialect() ->getRegisterNameAttrHelper() .setAttr(alloc, getStringAttr(name)); } @@ -386,10 +389,11 @@ Value QCProgramBuilder::allocClassicalBitRegister( llvm::reportFatalUsageError("Size must be positive"); } - const auto type = cbit::RegisterType::get(ctx, size); + const auto type = cbit::RegisterType::get(getContext(), size); auto alloc = cbit::AllocOp::create(*this, type, initialization); if (!name.empty()) { - ctx->getLoadedDialect() + getContext() + ->getLoadedDialect() ->getRegisterNameAttrHelper() .setAttr(alloc, getStringAttr(name)); } @@ -944,7 +948,7 @@ QCProgramBuilder& QCProgramBuilder::dealloc(Value qubit) { //===----------------------------------------------------------------------===// void QCProgramBuilder::checkFinalized() const { - if (ctx == nullptr) { + if (!moduleOp_) { llvm::reportFatalUsageError("QCProgramBuilder instance has been finalized"); } } @@ -952,7 +956,7 @@ void QCProgramBuilder::checkFinalized() const { void QCProgramBuilder::ensureAllocationMode( const AllocationMode requestedMode) { if (requestedMode == AllocationMode::Dynamic) { - auto entryPoint = mqt::getEntryPoint(cast(moduleOp_)); + auto entryPoint = mqt::getEntryPoint(*moduleOp_); if (!entryPoint || entryPoint.getBody().empty() || getInsertionBlock() != &entryPoint.getBody().front()) { llvm::reportFatalUsageError( @@ -994,7 +998,7 @@ OwningOpRef QCProgramBuilder::finalize(ValueRange returnValues) { // Ensure that the entry-point function exists and the insertion point is // valid. auto* insertionBlock = getInsertionBlock(); - auto mainFunc = mqt::getEntryPoint(cast(moduleOp_)); + auto mainFunc = mqt::getEntryPoint(*moduleOp_); if (mainFunc == nullptr) { llvm::reportFatalUsageError("Could not find entry-point function"); } @@ -1004,6 +1008,11 @@ OwningOpRef QCProgramBuilder::finalize(ValueRange returnValues) { "Insertion point is not in entry block of main function"); } + for (Value result : returnValues) { + allocatedQubits.remove(result); + allocatedQregs.remove(result); + } + for (auto qubit : allocatedQubits) { DeallocOp::create(*this, qubit); } @@ -1017,11 +1026,8 @@ OwningOpRef QCProgramBuilder::finalize(ValueRange returnValues) { // Add return statement with the given return values to the main function func::ReturnOp::create(*this, returnValues); - // Invalidate context to prevent use-after-finalize - ctx = nullptr; - // Transfer ownership to the caller - return cast(moduleOp_); + return moduleOp_.release(); } OwningOpRef QCProgramBuilder::build( diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index b8344c9a17..84f001a49f 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -64,16 +64,18 @@ namespace mlir::qco { QCOProgramBuilder::QCOProgramBuilder(MLIRContext* context) : ImplicitLocOpBuilder( FileLineColLoc::get(context, "", 1, 1), context), - ctx(context), moduleOp_(ModuleOp::create(*this)) { - ctx->loadDialect(); + moduleOp_(ModuleOp::create(*this)) { + getContext() + ->loadDialect(); } void QCOProgramBuilder::initialize() { initialize({getI64Type()}); } void QCOProgramBuilder::initialize(TypeRange returnTypes) { + checkFinalized(); // Set insertion point to the module body - setInsertionPointToStart(cast(moduleOp_).getBody()); + setInsertionPointToStart(moduleOp_->getBody()); // Create main function as entry point auto funcType = getFunctionType({}, returnTypes); @@ -87,7 +89,8 @@ void QCOProgramBuilder::initialize(TypeRange returnTypes) { } void QCOProgramBuilder::retype(TypeRange returnTypes) { - auto mainFunc = mqt::getEntryPoint(cast(moduleOp_)); + checkFinalized(); + auto mainFunc = mqt::getEntryPoint(*moduleOp_); if (!mainFunc) { llvm::reportFatalUsageError("Main function not found for retyping"); } @@ -105,7 +108,7 @@ func::FuncOp QCOProgramBuilder::createFunction( const StringRef name, const TypeRange argumentTypes, const function_ref(ValueRange)> body) { checkFinalized(); - auto moduleOp = cast(moduleOp_); + auto moduleOp = *moduleOp_; auto mainFunc = mqt::getEntryPoint(moduleOp); if (!mainFunc) { llvm::reportFatalUsageError( @@ -195,22 +198,13 @@ func::FuncOp QCOProgramBuilder::createUnitaryFunction( SmallVector QCOProgramBuilder::call(func::FuncOp callee, ValueRange operands) { checkFinalized(); - if (callee->getParentOp() != moduleOp_ || + if (callee->getParentOp() != moduleOp_->getOperation() || callee.getArgumentTypes() != operands.getTypes()) { llvm::reportFatalUsageError( "Call operands must match a function in the current module"); } auto quantumArguments = getQuantumArgumentIndices(operands.getTypes()); - SmallVector quantumOperands; - for (const auto argument : quantumArguments) { - quantumOperands.emplace_back(operands[argument]); - } - quantumOperands = prepareInitArgs(quantumOperands); - SmallVector updatedOperands(operands); - for (auto [argument, operand] : - llvm::zip_equal(quantumArguments, quantumOperands)) { - updatedOperands[argument] = operand; - } + auto updatedOperands = prepareInitArgs(operands); SmallVector results; if (mqt::isUnitaryFunction(callee)) { @@ -230,8 +224,9 @@ SmallVector QCOProgramBuilder::call(func::FuncOp callee, const auto firstQuantumResult = results.size() - quantumArguments.size(); for (auto [index, result] : llvm::enumerate(results)) { if (index >= firstQuantumResult) { - updateQubitValueTracking(quantumOperands[index - firstQuantumResult], - result); + updateQubitValueTracking( + updatedOperands[quantumArguments[index - firstQuantumResult]], + result); } else if (isa(result.getType())) { validQubits.insert(result); } else if (isQubitTensor(result.getType())) { @@ -300,7 +295,8 @@ QCOProgramBuilder::allocQubitRegister(const int64_t size, } auto qtensor = qtensorAlloc(size); if (!name.empty()) { - ctx->getLoadedDialect() + getContext() + ->getLoadedDialect() ->getRegisterNameAttrHelper() .setAttr(qtensor.getDefiningOp(), getStringAttr(name)); } @@ -325,10 +321,11 @@ Value QCOProgramBuilder::allocClassicalBitRegister( llvm::reportFatalUsageError("Size must be positive"); } - const auto type = cbit::RegisterType::get(ctx, size); + const auto type = cbit::RegisterType::get(getContext(), size); auto alloc = cbit::AllocOp::create(*this, type, initialization); if (!name.empty()) { - ctx->getLoadedDialect() + getContext() + ->getLoadedDialect() ->getRegisterNameAttrHelper() .setAttr(alloc, getStringAttr(name)); } @@ -355,7 +352,7 @@ void QCOProgramBuilder::storeClassicalBit( // Linear Ownership Tracking Helpers //===----------------------------------------------------------------------===// -void QCOProgramBuilder::validateQubitValue(Value qubit) const { +void QCOProgramBuilder::validateQubitValue(Value qubit, bool checkScope) 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 " @@ -363,12 +360,17 @@ void QCOProgramBuilder::validateQubitValue(Value qubit) const { llvm::reportFatalUsageError( "Invalid qubit value used (either consumed or not tracked)"); } + if (checkScope && + qubit.getParentRegion() != getInsertionBlock()->getParent()) { + llvm::reportFatalUsageError( + "Quantum values must be passed explicitly through region arguments"); + } } -void QCOProgramBuilder::updateQubitTracking(Value inputQubit, - Value outputQubit) { +void QCOProgramBuilder::updateQubitTracking(Value inputQubit, Value outputQubit, + bool checkScope) { // Validate the input qubit - validateQubitValue(inputQubit); + validateQubitValue(inputQubit, checkScope); auto it = validQubits.find(inputQubit); auto trackedQubit = *it; @@ -381,7 +383,8 @@ void QCOProgramBuilder::updateQubitTracking(Value inputQubit, Qubit{outputQubit, trackedQubit.regId, trackedQubit.regIndex}); } -void QCOProgramBuilder::validateTensorValue(Value tensor) const { +void QCOProgramBuilder::validateTensorValue(Value tensor, + bool checkScope) const { if (!validTensors.contains(tensor)) { llvm::errs() << "Attempting to use an invalid tensor SSA value. " << "The value may have been consumed by a previous operation " @@ -389,6 +392,11 @@ void QCOProgramBuilder::validateTensorValue(Value tensor) const { llvm::reportFatalUsageError( "Invalid tensor value used (either consumed or not tracked)"); } + if (checkScope && + tensor.getParentRegion() != getInsertionBlock()->getParent()) { + llvm::reportFatalUsageError( + "Quantum values must be passed explicitly through region arguments"); + } auto tensorType = dyn_cast(tensor.getType()); if (!tensorType || tensorType.getRank() != 1) { @@ -400,9 +408,10 @@ void QCOProgramBuilder::validateTensorValue(Value tensor) const { } void QCOProgramBuilder::updateTensorTracking(Value inputTensor, - Value outputTensor) { + Value outputTensor, + bool checkScope) { // Validate the input tensor - validateTensorValue(inputTensor); + validateTensorValue(inputTensor, checkScope); auto it = validTensors.find(inputTensor); auto trackedTensor = *it; @@ -438,26 +447,28 @@ static bool isDefinedBefore(Value lhs, Value rhs) { Value QCOProgramBuilder::insertExtractedQubits(Value tensor, MutableArrayRef qubits) { + validateTensorValue(tensor); + auto inputTensor = tensor; llvm::sort(qubits, [](const Qubit& lhs, const Qubit& rhs) { return isDefinedBefore(lhs, rhs); }); for (const auto& qubit : qubits) { - auto newTensor = - qtensor::InsertOp::create(*this, qubit, tensor, qubit.regIndex) - .getResult(); - updateTensorTracking(tensor, newTensor); - tensor = newTensor; + validateQubitValue(qubit); + tensor = qtensor::InsertOp::create(*this, qubit, tensor, qubit.regIndex) + .getResult(); validQubits.erase(qubit); } + if (tensor != inputTensor) { + updateTensorTracking(inputTensor, tensor); + } return tensor; } SmallVector QCOProgramBuilder::prepareInitArgs(ValueRange initArgs) { - DenseSet initQubits; DenseMap> qubitsByRegister; for (auto initArg : initArgs) { if (isa(initArg.getType())) { - initQubits.insert(initArg); + validateQubitValue(initArg); } else if (isQubitTensor(initArg.getType())) { validateTensorValue(initArg); if (!qubitsByRegister.try_emplace(validTensors.find(initArg)->regId) @@ -468,6 +479,12 @@ SmallVector QCOProgramBuilder::prepareInitArgs(ValueRange initArgs) { } } if (!qubitsByRegister.empty()) { + DenseSet initQubits; + for (auto initArg : initArgs) { + if (isa(initArg.getType())) { + initQubits.insert(initArg); + } + } for (const auto& qubit : validQubits) { auto it = qubitsByRegister.find(qubit.regId); if (it != qubitsByRegister.end()) { @@ -516,10 +533,11 @@ void QCOProgramBuilder::updateQubitValueTracking(Value oldValue, if (oldValue.getType() != newValue.getType()) { llvm::reportFatalUsageError("Result types must match input types"); } + // Explicit region arguments and results transfer ownership across scopes. if (isa(oldValue.getType())) { - updateQubitTracking(oldValue, newValue); + updateQubitTracking(oldValue, newValue, false); } else if (isQubitTensor(oldValue.getType())) { - updateTensorTracking(oldValue, newValue); + updateTensorTracking(oldValue, newValue, false); } } @@ -609,12 +627,22 @@ Value QCOProgramBuilder::qtensorInsert( Value scalar, Value tensor, const std::variant& index) { checkFinalized(); + validateQubitValue(scalar); + validateTensorValue(tensor); auto indexValue = variantToValue(*this, getLoc(), index); + const auto& qubit = *validQubits.find(scalar); + if (qubit.regId != validTensors.find(tensor)->regId || !qubit.regIndex) { + llvm::reportFatalUsageError( + "Inserted qubit must belong to the destination register"); + } + auto originalIndex = getConstantIntValue(qubit.regIndex); + auto indexConstant = getConstantIntValue(indexValue); + if (originalIndex && indexConstant && originalIndex != indexConstant) { + llvm::reportFatalUsageError( + "Inserted qubit must return to its original slot"); + } auto insertOp = qtensor::InsertOp::create(*this, scalar, tensor, indexValue); - auto outTensor = insertOp.getResult(); - - validateQubitValue(scalar); validQubits.erase(scalar); updateTensorTracking(tensor, outTensor); @@ -1096,6 +1124,10 @@ QCOProgramBuilder::ctrl(ValueRange controls, ValueRange targets, function_ref(ValueRange)> body) { checkFinalized(); + for (Value qubit : controls) { + validateQubitValue(qubit); + } + auto ctrlOp = CtrlOp::create(*this, controls, targets); auto& block = ctrlOp.getBodyRegion().emplaceBlock(); auto qubitType = QubitType::get(getContext()); @@ -1115,7 +1147,7 @@ QCOProgramBuilder::ctrl(ValueRange controls, ValueRange targets, auto controlsOut = ctrlOp.getControlsOut(); for (auto [control, controlOut] : llvm::zip_equal(controls, controlsOut)) { - updateQubitTracking(control, controlOut); + updateQubitTracking(control, controlOut, false); } auto targetsOut = ctrlOp.getTargetsOut(); for (auto [target, targetOut] : @@ -1198,44 +1230,20 @@ QCOProgramBuilder::pow(const mqt::FloatParameter& exponent, ValueRange qubits, Value QCOProgramBuilder::pow(const mqt::FloatParameter& exponent, Value qubit, function_ref body) { - checkFinalized(); - - Value innerQubitOut; - auto powOp = - PowOp::create(*this, qubit, exponent, [&](Value qubitArg) -> Value { - updateQubitTracking(qubit, qubitArg); - innerQubitOut = body(qubitArg); - return innerQubitOut; - }); - - auto qubitsOut = powOp.getQubitsOut(); - assert(qubitsOut.size() == 1); - updateQubitTracking(innerQubitOut, qubitsOut.front()); - - return qubitsOut.front(); + return pow(exponent, ValueRange{qubit}, + [&](ValueRange args) { + return SmallVector{body(args.front())}; + }) + .front(); } std::pair QCOProgramBuilder::ctrl(ValueRange controls, Value target, function_ref body) { - checkFinalized(); - - Value innerTargetOut; - auto ctrlOp = - CtrlOp::create(*this, controls, target, [&](Value targetArg) -> Value { - updateQubitTracking(target, targetArg); - innerTargetOut = body(targetArg); - return innerTargetOut; + auto [controlsOut, targetsOut] = + ctrl(controls, ValueRange{target}, [&](ValueRange args) { + return SmallVector{body(args.front())}; }); - - auto controlsOut = ctrlOp.getControlsOut(); - for (auto [control, controlOut] : llvm::zip_equal(controls, controlsOut)) { - updateQubitTracking(control, controlOut); - } - auto targetsOut = ctrlOp.getTargetsOut(); - assert(targetsOut.size() == 1); - updateQubitTracking(innerTargetOut, targetsOut.front()); - return {controlsOut, targetsOut.front()}; } @@ -1248,20 +1256,11 @@ QCOProgramBuilder::ctrl(Value control, Value target, } Value QCOProgramBuilder::inv(Value qubit, function_ref body) { - checkFinalized(); - - Value innerQubitOut; - auto invOp = InvOp::create(*this, qubit, [&](Value qubitArg) -> Value { - updateQubitTracking(qubit, qubitArg); - innerQubitOut = body(qubitArg); - return innerQubitOut; - }); - - auto qubitsOut = invOp.getQubitsOut(); - assert(qubitsOut.size() == 1); - updateQubitTracking(innerQubitOut, qubitsOut.front()); - - return qubitsOut.front(); + return inv(ValueRange{qubit}, + [&](ValueRange args) { + return SmallVector{body(args.front())}; + }) + .front(); } //===----------------------------------------------------------------------===// @@ -1600,7 +1599,7 @@ QCOProgramBuilder::scfCondition(Value reg, //===----------------------------------------------------------------------===// void QCOProgramBuilder::checkFinalized() const { - if (ctx == nullptr) { + if (!moduleOp_) { llvm::reportFatalUsageError( "QCOProgramBuilder instance has been finalized"); } @@ -1609,7 +1608,7 @@ void QCOProgramBuilder::checkFinalized() const { void QCOProgramBuilder::ensureAllocationMode( const AllocationMode requestedMode) { if (requestedMode == AllocationMode::Dynamic) { - auto entryPoint = mqt::getEntryPoint(cast(moduleOp_)); + auto entryPoint = mqt::getEntryPoint(*moduleOp_); if (!entryPoint || entryPoint.getBody().empty() || getInsertionBlock() != &entryPoint.getBody().front()) { llvm::reportFatalUsageError( @@ -1661,6 +1660,7 @@ void QCOProgramBuilder::disposeLinearValues() { return isDefinedBefore(lhs, rhs); }); for (const auto& qubit : qubitsToSink) { + validateQubitValue(qubit); SinkOp::create(*this, qubit); } for (const auto& tensor : tensors) { @@ -1685,7 +1685,7 @@ OwningOpRef QCOProgramBuilder::finalize(ValueRange returnValues) { // Ensure that the entry-point function exists and the insertion point is // valid. auto* insertionBlock = getInsertionBlock(); - auto mainFunc = mqt::getEntryPoint(cast(moduleOp_)); + auto mainFunc = mqt::getEntryPoint(*moduleOp_); if (mainFunc == nullptr) { llvm::reportFatalUsageError("Could not find entry-point function"); } @@ -1713,10 +1713,7 @@ OwningOpRef QCOProgramBuilder::finalize(ValueRange returnValues) { // Add return statement with the given return values to the main function func::ReturnOp::create(*this, returnValues); - // Invalidate context to prevent use-after-finalize - ctx = nullptr; - - return cast(moduleOp_); + return moduleOp_.release(); } OwningOpRef QCOProgramBuilder::build( diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 7e0348cfbd..3b714a439a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -1823,9 +1823,7 @@ struct MappingPass : impl::MappingPassBase { DenseMap& cache) { SmallVector measurements; bool needsRouting = false; - WireIterator it(measurement.getQubitOut()); - for (; it != std::default_sentinel; ++it) { - Operation* op = it.operation(); + for (auto* op : WireRange(measurement.getQubitOut())) { if (const auto cached = cache.find(op); cached != cache.end()) { needsRouting = cached->second; break; diff --git a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp index cb65a0bbca..e68c46f675 100644 --- a/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/FuseSingleQubitUnitaryRuns.cpp @@ -82,13 +82,12 @@ scanFusableRun(UnitaryOpInterface head, const Matrix2x2& headMatrix, const CompilerTarget* target) { FusableRunScan scan; for (auto* op : WireRange(head.getOutputQubit(0))) { - auto member = dyn_cast_or_null(op); + auto member = dyn_cast(op); if (!member) { break; } - const auto matrix = member.getOperation() == head.getOperation() - ? std::optional{headMatrix} - : getRunMemberMatrix(member); + const auto matrix = op == head.getOperation() ? std::optional{headMatrix} + : getRunMemberMatrix(member); if (!matrix) { break; } @@ -111,14 +110,13 @@ scanFusableRun(UnitaryOpInterface head, const Matrix2x2& headMatrix, static void eraseFusableRun(PatternRewriter& rewriter, UnitaryOpInterface head, UnitaryOpInterface tail) { // Tail-first: each erased op is dead once its successor is gone. - auto it = WireIterator(tail.getOutputQubit(0)); - auto* target = head.getOperation(); - while (*it != target) { - auto* current = *it; - --it; - rewriter.eraseOp(current); + while (tail != head) { + auto previous = + cast(tail.getInputQubit(0).getDefiningOp()); + rewriter.eraseOp(tail); + tail = previous; } - rewriter.eraseOp(target); + rewriter.eraseOp(head); } namespace { @@ -283,11 +281,12 @@ LogicalResult SingleQubitRunFusion::apply(Operation* operation) { return; } bool predecessorHasMatrix = false; - for (auto* current : WireRange(start.getOutputQubit(0))) { - auto op = dyn_cast(current); + for (auto op = start; op; op = dyn_cast( + *op.getOutputQubit(0).user_begin())) { if (!isRunMemberCandidate(op)) { break; } + auto* current = op.getOperation(); members.push_back(current); hasRuntimeParameters = hasRuntimeParameters || diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/MergeSingleQubitRotationGates.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/MergeSingleQubitRotationGates.cpp index 78ac9bdda6..9b6d84499c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/MergeSingleQubitRotationGates.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/MergeSingleQubitRotationGates.cpp @@ -44,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -504,13 +503,12 @@ struct MergeSingleQubitRotationGatesPattern final /// @return The chain of operations in circuit order (first applied to last) static SmallVector collectChain(UnitaryOpInterface start) { - SmallVector chain{start}; - for (auto curr = std::next(WireIterator(start.getOutputQubit(0))); - curr != std::default_sentinel; ++curr) { - if (!isMergeable(curr.operation())) { + SmallVector chain; + for (auto* op : WireRange(start.getOutputQubit(0))) { + if (!isMergeable(op)) { break; } - chain.emplace_back(cast(*curr.operation())); + chain.emplace_back(cast(op)); } return chain; } diff --git a/mlir/lib/Dialect/QCO/Utils/FunctionUtils.cpp b/mlir/lib/Dialect/QCO/Utils/FunctionUtils.cpp index 198a62e98d..6ff05b86d0 100644 --- a/mlir/lib/Dialect/QCO/Utils/FunctionUtils.cpp +++ b/mlir/lib/Dialect/QCO/Utils/FunctionUtils.cpp @@ -128,8 +128,8 @@ FailureOr mlir::qco::traceQubitArgument(func::FuncOp function, } bool mlir::qco::hasCompleteTensorLifetime(Value tensor, unsigned depth) { - // Reject nesting at depth 64 to bound recursive stack use. - if (depth == 64) { + // Bound recursive stack use at nesting depth 64. + if (depth >= 64) { return false; } const auto isTensor = [](Value value) { @@ -236,7 +236,10 @@ LogicalResult mlir::qco::collectWireOrigins(Operation* root, }; auto result = root->walk([&](Operation* op, const WalkStage& stage) { if (auto unitary = dyn_cast(op)) { - tie(unitary.getInputQubits(), unitary.getOutputQubits()); + for (auto [input, output] : llvm::zip_equal(unitary.getInputQubits(), + unitary.getOutputQubits())) { + origins[output] = origin(input); + } return WalkResult::skip(); } if (!stage.isAfterAllRegions()) { @@ -303,10 +306,14 @@ LogicalResult mlir::qco::collectWireOrigins(Operation* root, } else if (auto insert = dyn_cast(op)) { origins[insert.getResult()] = origin(insert.getDest()); } else if (auto call = dyn_cast(op)) { - for (auto [index, result] : llvm::enumerate(call.getResults())) { - if (auto argument = qco::getCallArgumentForResult(call, index); - succeeded(argument)) { - origins[result] = origin(call.getOperand(*argument)); + auto arguments = getQuantumArgumentIndices(call.getOperandTypes()); + if (call.getNumResults() >= arguments.size()) { + for (auto [argument, result] : llvm::zip_equal( + arguments, call.getResults().take_back(arguments.size()))) { + auto input = call.getOperand(argument); + if (result.getType() == input.getType()) { + origins[result] = origin(input); + } } } } @@ -336,7 +343,16 @@ LogicalResult mlir::qco::verifyLinearity(Operation* root) { } DenseSet staticIndices; + bool needsPositionalChecks = false; const auto walkResult = root->walk([&](Operation* op) { + if (!needsPositionalChecks) { + if (auto function = dyn_cast(op)) { + needsPositionalChecks = + !getQuantumArgumentIndices(function.getArgumentTypes()).empty(); + } else { + needsPositionalChecks = op->getNumRegions() != 0 && !isa(op); + } + } if (auto staticOp = dyn_cast(op)) { if (entryPoint && (entryPoint.isDeclaration() || @@ -374,6 +390,10 @@ LogicalResult mlir::qco::verifyLinearity(Operation* root) { if (walkResult.wasInterrupted()) { return failure(); } + // Flat owned circuits have no positional signatures to check. + if (!needsPositionalChecks) { + return success(); + } DenseMap origins; return collectWireOrigins(root, origins); } diff --git a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp index fd98ae984b..8dc2480c35 100644 --- a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp +++ b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp @@ -13,9 +13,9 @@ #include "mqt/Dialect/QCO/IR/QCODialect.h" #include "mqt/Dialect/QCO/IR/QCOInterfaces.h" #include "mqt/Dialect/QCO/IR/QCOOps.h" +#include "mqt/Dialect/QCO/QCOUtils.h" #include "mqt/Dialect/QTensor/IR/QTensorOps.h" -#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/Operation.h" #include "mlir/IR/Types.h" @@ -32,13 +32,6 @@ namespace mlir::qco { -bool WireIterator::isTail(Operation* op) { - // `qtensor.from_elements` takes qubits into a tensor just like - // `qtensor.insert` does, so a wire reaching either of them ends there. - return isa(op); -} - bool WireIterator::isHead(Operation* op) { return isa(op); } @@ -79,36 +72,27 @@ void WireIterator::forward() { // Find the user-operation of the qubit SSA value. assert(qubit_.hasOneUse() && "expected linear semantics"); - op_ = *qubit_.user_begin(); - - if (isTail(op_)) { - pos_ = Position::Tail; - return; - } + auto& use = *qubit_.use_begin(); + op_ = use.getOwner(); // Find the output from the input qubit SSA value. pos_ = Position::Between; TypeSwitch(op_) - .Case( - [&](UnitaryOpInterface op) { qubit_ = op.getOutputForInput(qubit_); }) + .Case([&](UnitaryOpInterface op) { + const auto index = + use.getOperandNumber() - op.getInputQubits().getBeginOperandIndex(); + qubit_ = op.getOutputQubit(index); + }) .Case([&](MeasureOp op) { qubit_ = op.getQubitOut(); }) .Case([&](ResetOp op) { qubit_ = op.getQubitOut(); }) - .Case([&](scf::ForOp op) { - qubit_ = op.getTiedLoopResult(qubit_.use_begin().getOperand()); - }) + .Case([&](scf::ForOp op) { qubit_ = op.getTiedLoopResult(&use); }) .Case([&](scf::WhileOp op) { - // Because the scf::WhileOp doesn't implement "getLoopResults", we - // have to fallback to the following instead of using - // "getTiedLoopResult". - - OpOperand* operand = qubit_.use_begin().getOperand(); - qubit_ = op->getResult(operand->getOperandNumber()); - }) - .Case([&](IfOp op) { qubit_ = op.getTiedResult(&(*qubit_.use_begin())); }) - .Case([&](IndexSwitchOp op) { - qubit_ = op.getTiedResult(&(*qubit_.use_begin())); + qubit_ = getCorrespondingQuantumValue(op.getInits(), op.getResults(), + use.getOperandNumber()); }) + .Case([&](IfOp op) { qubit_ = op.getTiedResult(&use); }) + .Case([&](IndexSwitchOp op) { qubit_ = op.getTiedResult(&use); }) .Default([&](Operation*) { pos_ = Position::Tail; }); } @@ -153,16 +137,9 @@ void WireIterator::backward() { llvm::reportFatalInternalError("expected result lookup"); }) .Case([&](scf::WhileOp op) { - // Because the scf::WhileOp doesn't implement "getLoopResults", we - // have to fallback to the following instead of using - // "getTiedLoopInit". - - if (auto result = dyn_cast(qubit_)) { - qubit_ = op.getInits()[result.getResultNumber()]; - return; - } - - llvm::reportFatalInternalError("expected result lookup"); + qubit_ = getCorrespondingQuantumValue( + op.getResults(), op.getInits(), + cast(qubit_).getResultNumber()); }) .Case([&](IfOp op) { if (auto result = dyn_cast(qubit_)) { diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 5a201f55ce..e43d56e290 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -12,6 +12,7 @@ #include "mqt/Dialect/QCO/IR/QCODialect.h" #include "mqt/Dialect/QCO/IR/QCOOps.h" +#include "mqt/Dialect/QCO/QCOUtils.h" #include "mqt/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" @@ -26,7 +27,6 @@ #include "llvm/Support/ErrorHandling.h" #include -#include #include namespace mlir::qtensor { @@ -41,51 +41,6 @@ TypedValue TensorIterator::tensor() const { return tensor_; } -[[nodiscard]] static TypedValue -whileResultForInit(scf::WhileOp op, OpOperand& init) { - auto current = cast>( - op.getBeforeBody()->getArgument(init.getOperandNumber())); - TensorIterator iterator(current); - while (true) { - assert(current.hasOneUse() && "expected linear semantics"); - auto* user = *current.user_begin(); - if (auto condition = dyn_cast(user)) { - const auto result = llvm::find(condition.getArgs(), current); - if (result == condition.getArgs().end()) { - llvm::reportFatalInternalError( - "expected scf.while tensor in condition arguments"); - } - const auto resultNumber = static_cast( - std::distance(condition.getArgs().begin(), result)); - return cast>(op.getResult(resultNumber)); - } - ++iterator; - if (iterator == std::default_sentinel) { - llvm::reportFatalInternalError( - "expected scf.while tensor to reach its condition"); - } - current = iterator.tensor(); - } -} - -[[nodiscard]] static TypedValue -whileInitForResult(scf::WhileOp op, OpResult result) { - auto condition = cast(op.getBeforeBody()->getTerminator()); - auto current = cast>( - condition.getArgs()[result.getResultNumber()]); - TensorIterator iterator(current); - while (iterator.operation() != nullptr) { - --iterator; - } - auto argument = dyn_cast(iterator.tensor()); - if (!argument || argument.getOwner() != op.getBeforeBody()) { - llvm::reportFatalInternalError( - "expected scf.while tensor to originate from a before-region argument"); - } - return cast>( - op.getInits()[argument.getArgNumber()]); -} - void TensorIterator::forward() { // If the iterator is a sentinel already, there is nothing to do. if (isSentinel_) { @@ -121,7 +76,10 @@ void TensorIterator::forward() { op.getTiedLoopResult(&*tensor_.use_begin())); }) .Case([&](scf::WhileOp op) { - tensor_ = whileResultForInit(op, *tensor_.use_begin().getOperand()); + tensor_ = cast>( + qco::getCorrespondingQuantumValue( + op.getInits(), op.getResults(), + tensor_.use_begin()->getOperandNumber())); }) .Case([&](qco::IfOp op) { tensor_ = cast>( @@ -188,13 +146,10 @@ void TensorIterator::backward() { "expected scf.for result for tied init lookup"); }) .Case([&](scf::WhileOp op) { - if (auto result = dyn_cast(tensor_)) { - tensor_ = whileInitForResult(op, result); - return; - } - - llvm::reportFatalInternalError( - "expected scf.while result for tied init lookup"); + tensor_ = cast>( + qco::getCorrespondingQuantumValue( + op.getResults(), op.getInits(), + cast(tensor_).getResultNumber())); }) .Case([&](qco::IfOp op) { if (auto res = dyn_cast(tensor_)) { diff --git a/mlir/unittests/Conversion/ConversionTestCases.h b/mlir/unittests/Conversion/ConversionTestCases.h new file mode 100644 index 0000000000..9de176c9f5 --- /dev/null +++ b/mlir/unittests/Conversion/ConversionTestCases.h @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "TestCaseUtils.h" +#include "programs/qc_programs.h" +#include "programs/qco_programs.h" + +#include +#include + +namespace mqt::test { + +inline constexpr std::array COMMON_QC_QCO_CONVERSION_CASES{ + std::tuple("StaticQubits", MQT_NAMED_BUILDER(mlir::qc::staticQubits), + MQT_NAMED_BUILDER(mlir::qco::staticQubits), false), + std::tuple("StaticQubitsWithOps", + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(mlir::qco::staticQubitsWithOps), false), + std::tuple("StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(mlir::qco::staticQubitsWithParametricOps), + false), + std::tuple("StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(mlir::qco::staticQubitsWithTwoTargetOps), + false), + std::tuple("StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(mlir::qco::staticQubitsWithCtrl), false), + std::tuple("StaticQubitsWithInv", + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(mlir::qco::staticQubitsWithInv), false), + std::tuple("CtrlPowSx", MQT_NAMED_BUILDER(mlir::qc::ctrlPowSx), + MQT_NAMED_BUILDER(mlir::qco::ctrlPowSx), false), + std::tuple("PowTwo", MQT_NAMED_BUILDER(mlir::qc::powTwo), + MQT_NAMED_BUILDER(mlir::qco::powTwo), false), + std::tuple("CtrlTwo", MQT_NAMED_BUILDER(mlir::qc::ctrlTwo), + MQT_NAMED_BUILDER(mlir::qco::ctrlTwo), false), + std::tuple("CtrlTwoMixed", MQT_NAMED_BUILDER(mlir::qc::ctrlTwoMixed), + MQT_NAMED_BUILDER(mlir::qco::ctrlTwoMixed), false), + std::tuple("CtrlInvTwo", MQT_NAMED_BUILDER(mlir::qc::ctrlInvTwo), + MQT_NAMED_BUILDER(mlir::qco::ctrlInvTwo), false), + std::tuple("InverseiSWAP", MQT_NAMED_BUILDER(mlir::qc::inverseIswap), + MQT_NAMED_BUILDER(mlir::qco::inverseIswap), false), + std::tuple("InverseMultipleControllediSWAP", + MQT_NAMED_BUILDER(mlir::qc::inverseMultipleControlledIswap), + MQT_NAMED_BUILDER(mlir::qco::inverseMultipleControlledIswap), + false), + std::tuple("InvTwo", MQT_NAMED_BUILDER(mlir::qc::invTwo), + MQT_NAMED_BUILDER(mlir::qco::invTwo), false), + std::tuple("Barrier", MQT_NAMED_BUILDER(mlir::qc::barrier), + MQT_NAMED_BUILDER(mlir::qco::barrier), false), + std::tuple("BarrierTwoQubits", + MQT_NAMED_BUILDER(mlir::qc::barrierTwoQubits), + MQT_NAMED_BUILDER(mlir::qco::barrierTwoQubits), false), + std::tuple("BarrierMultipleQubits", + MQT_NAMED_BUILDER(mlir::qc::barrierMultipleQubits), + MQT_NAMED_BUILDER(mlir::qco::barrierMultipleQubits), false), + std::tuple("DCX", MQT_NAMED_BUILDER(mlir::qc::dcx), + MQT_NAMED_BUILDER(mlir::qco::dcx), false), + std::tuple("SingleControlledDCX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledDcx), + MQT_NAMED_BUILDER(mlir::qco::singleControlledDcx), false), + std::tuple("MultipleControlledDCX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledDcx), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledDcx), false), + std::tuple("ECR", MQT_NAMED_BUILDER(mlir::qc::ecr), + MQT_NAMED_BUILDER(mlir::qco::ecr), false), + std::tuple("SingleControlledECR", + MQT_NAMED_BUILDER(mlir::qc::singleControlledEcr), + MQT_NAMED_BUILDER(mlir::qco::singleControlledEcr), false), + std::tuple("MultipleControlledECR", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledEcr), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledEcr), false), + std::tuple("GlobalPhase", MQT_NAMED_BUILDER(mlir::qc::globalPhase), + MQT_NAMED_BUILDER(mlir::qco::globalPhase), false), + std::tuple("H", MQT_NAMED_BUILDER(mlir::qc::h), + MQT_NAMED_BUILDER(mlir::qco::h), false), + std::tuple("SingleControlledH", + MQT_NAMED_BUILDER(mlir::qc::singleControlledH), + MQT_NAMED_BUILDER(mlir::qco::singleControlledH), false), + std::tuple("MultipleControlledH", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledH), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledH), false), + std::tuple("HWithoutRegister", + MQT_NAMED_BUILDER(mlir::qc::hWithoutRegister), + MQT_NAMED_BUILDER(mlir::qco::hWithoutRegister), false), + std::tuple("iSWAP", MQT_NAMED_BUILDER(mlir::qc::iswap), + MQT_NAMED_BUILDER(mlir::qco::iswap), false), + std::tuple("SingleControllediSWAP", + MQT_NAMED_BUILDER(mlir::qc::singleControlledIswap), + MQT_NAMED_BUILDER(mlir::qco::singleControlledIswap), false), + std::tuple("MultipleControllediSWAP", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledIswap), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledIswap), false), + std::tuple("P", MQT_NAMED_BUILDER(mlir::qc::p), + MQT_NAMED_BUILDER(mlir::qco::p), false), + std::tuple("SingleControlledP", + MQT_NAMED_BUILDER(mlir::qc::singleControlledP), + MQT_NAMED_BUILDER(mlir::qco::singleControlledP), false), + std::tuple("MultipleControlledP", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledP), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledP), false), + std::tuple("RCCX", MQT_NAMED_BUILDER(mlir::qc::rccx), + MQT_NAMED_BUILDER(mlir::qco::rccx), false), + std::tuple("SingleControlledRCCX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRccx), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRccx), false), + std::tuple("MultipleControlledRCCX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRccx), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRccx), false), + std::tuple("R", MQT_NAMED_BUILDER(mlir::qc::r), + MQT_NAMED_BUILDER(mlir::qco::r), false), + std::tuple("SingleControlledR", + MQT_NAMED_BUILDER(mlir::qc::singleControlledR), + MQT_NAMED_BUILDER(mlir::qco::singleControlledR), false), + std::tuple("MultipleControlledR", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledR), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledR), false), + std::tuple("RX", MQT_NAMED_BUILDER(mlir::qc::rx), + MQT_NAMED_BUILDER(mlir::qco::rx), false), + std::tuple("SingleControlledRX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRx), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRx), false), + std::tuple("MultipleControlledRX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRx), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRx), false), + std::tuple("RXX", MQT_NAMED_BUILDER(mlir::qc::rxx), + MQT_NAMED_BUILDER(mlir::qco::rxx), false), + std::tuple("SingleControlledRXX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRxx), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRxx), false), + std::tuple("MultipleControlledRXX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRxx), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRxx), false), + std::tuple("RY", MQT_NAMED_BUILDER(mlir::qc::ry), + MQT_NAMED_BUILDER(mlir::qco::ry), false), + std::tuple("SingleControlledRY", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRy), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRy), false), + std::tuple("MultipleControlledRY", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRy), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRy), false), + std::tuple("RYY", MQT_NAMED_BUILDER(mlir::qc::ryy), + MQT_NAMED_BUILDER(mlir::qco::ryy), false), + std::tuple("SingleControlledRYY", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRyy), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRyy), false), + std::tuple("MultipleControlledRYY", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRyy), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRyy), false), + std::tuple("RZ", MQT_NAMED_BUILDER(mlir::qc::rz), + MQT_NAMED_BUILDER(mlir::qco::rz), false), + std::tuple("SingleControlledRZ", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRz), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRz), false), + std::tuple("MultipleControlledRZ", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRz), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRz), false), + std::tuple("RZX", MQT_NAMED_BUILDER(mlir::qc::rzx), + MQT_NAMED_BUILDER(mlir::qco::rzx), false), + std::tuple("SingleControlledRZX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRzx), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRzx), false), + std::tuple("MultipleControlledRZX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRzx), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRzx), false), + std::tuple("RZZ", MQT_NAMED_BUILDER(mlir::qc::rzz), + MQT_NAMED_BUILDER(mlir::qco::rzz), false), + std::tuple("SingleControlledRZZ", + MQT_NAMED_BUILDER(mlir::qc::singleControlledRzz), + MQT_NAMED_BUILDER(mlir::qco::singleControlledRzz), false), + std::tuple("MultipleControlledRZZ", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledRzz), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledRzz), false), + std::tuple("S", MQT_NAMED_BUILDER(mlir::qc::s), + MQT_NAMED_BUILDER(mlir::qco::s), false), + std::tuple("SingleControlledS", + MQT_NAMED_BUILDER(mlir::qc::singleControlledS), + MQT_NAMED_BUILDER(mlir::qco::singleControlledS), false), + std::tuple("MultipleControlledS", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledS), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledS), false), + std::tuple("Sdg", MQT_NAMED_BUILDER(mlir::qc::sdg), + MQT_NAMED_BUILDER(mlir::qco::sdg), false), + std::tuple("SingleControlledSdg", + MQT_NAMED_BUILDER(mlir::qc::singleControlledSdg), + MQT_NAMED_BUILDER(mlir::qco::singleControlledSdg), false), + std::tuple("MultipleControlledSdg", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledSdg), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledSdg), false), + std::tuple("SWAP", MQT_NAMED_BUILDER(mlir::qc::swap), + MQT_NAMED_BUILDER(mlir::qco::swap), false), + std::tuple("SingleControlledSWAP", + MQT_NAMED_BUILDER(mlir::qc::singleControlledSwap), + MQT_NAMED_BUILDER(mlir::qco::singleControlledSwap), false), + std::tuple("MultipleControlledSWAP", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledSwap), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledSwap), false), + std::tuple("SX", MQT_NAMED_BUILDER(mlir::qc::sx), + MQT_NAMED_BUILDER(mlir::qco::sx), false), + std::tuple("SingleControlledSX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledSx), + MQT_NAMED_BUILDER(mlir::qco::singleControlledSx), false), + std::tuple("MultipleControlledSX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledSx), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledSx), false), + std::tuple("SXdg", MQT_NAMED_BUILDER(mlir::qc::sxdg), + MQT_NAMED_BUILDER(mlir::qco::sxdg), false), + std::tuple("SingleControlledSXdg", + MQT_NAMED_BUILDER(mlir::qc::singleControlledSxdg), + MQT_NAMED_BUILDER(mlir::qco::singleControlledSxdg), false), + std::tuple("MultipleControlledSXdg", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledSxdg), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledSxdg), false), + std::tuple("T", MQT_NAMED_BUILDER(mlir::qc::t_), + MQT_NAMED_BUILDER(mlir::qco::t_), false), + std::tuple("SingleControlledT", + MQT_NAMED_BUILDER(mlir::qc::singleControlledT), + MQT_NAMED_BUILDER(mlir::qco::singleControlledT), false), + std::tuple("MultipleControlledT", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledT), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledT), false), + std::tuple("Tdg", MQT_NAMED_BUILDER(mlir::qc::tdg), + MQT_NAMED_BUILDER(mlir::qco::tdg), false), + std::tuple("SingleControlledTdg", + MQT_NAMED_BUILDER(mlir::qc::singleControlledTdg), + MQT_NAMED_BUILDER(mlir::qco::singleControlledTdg), false), + std::tuple("MultipleControlledTdg", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledTdg), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledTdg), false), + std::tuple("U2", MQT_NAMED_BUILDER(mlir::qc::u2), + MQT_NAMED_BUILDER(mlir::qco::u2), false), + std::tuple("SingleControlledU2", + MQT_NAMED_BUILDER(mlir::qc::singleControlledU2), + MQT_NAMED_BUILDER(mlir::qco::singleControlledU2), false), + std::tuple("MultipleControlledU2", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledU2), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledU2), false), + std::tuple("U", MQT_NAMED_BUILDER(mlir::qc::u), + MQT_NAMED_BUILDER(mlir::qco::u), false), + std::tuple("SingleControlledU", + MQT_NAMED_BUILDER(mlir::qc::singleControlledU), + MQT_NAMED_BUILDER(mlir::qco::singleControlledU), false), + std::tuple("MultipleControlledU", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledU), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledU), false), + std::tuple("X", MQT_NAMED_BUILDER(mlir::qc::x), + MQT_NAMED_BUILDER(mlir::qco::x), false), + std::tuple("SingleControlledX", + MQT_NAMED_BUILDER(mlir::qc::singleControlledX), + MQT_NAMED_BUILDER(mlir::qco::singleControlledX), false), + std::tuple("MultipleControlledX", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledX), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledX), false), + std::tuple("RepeatedControlledX", + MQT_NAMED_BUILDER(mlir::qc::repeatedControlledX), + MQT_NAMED_BUILDER(mlir::qco::repeatedControlledX), false), + std::tuple("XXMinusYY", MQT_NAMED_BUILDER(mlir::qc::xxMinusYY), + MQT_NAMED_BUILDER(mlir::qco::xxMinusYY), false), + std::tuple("SingleControlledXXMinusYY", + MQT_NAMED_BUILDER(mlir::qc::singleControlledXxMinusYY), + MQT_NAMED_BUILDER(mlir::qco::singleControlledXxMinusYY), false), + std::tuple("MultipleControlledXXMinusYY", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledXxMinusYY), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledXxMinusYY), + false), + std::tuple("XXPlusYY", MQT_NAMED_BUILDER(mlir::qc::xxPlusYY), + MQT_NAMED_BUILDER(mlir::qco::xxPlusYY), false), + std::tuple("SingleControlledXXPlusYY", + MQT_NAMED_BUILDER(mlir::qc::singleControlledXxPlusYY), + MQT_NAMED_BUILDER(mlir::qco::singleControlledXxPlusYY), false), + std::tuple("MultipleControlledXXPlusYY", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledXxPlusYY), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledXxPlusYY), false), + std::tuple("Y", MQT_NAMED_BUILDER(mlir::qc::y), + MQT_NAMED_BUILDER(mlir::qco::y), false), + std::tuple("SingleControlledY", + MQT_NAMED_BUILDER(mlir::qc::singleControlledY), + MQT_NAMED_BUILDER(mlir::qco::singleControlledY), false), + std::tuple("MultipleControlledY", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledY), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledY), false), + std::tuple("Z", MQT_NAMED_BUILDER(mlir::qc::z), + MQT_NAMED_BUILDER(mlir::qco::z), false), + std::tuple("SingleControlledZ", + MQT_NAMED_BUILDER(mlir::qc::singleControlledZ), + MQT_NAMED_BUILDER(mlir::qco::singleControlledZ), false), + std::tuple("MultipleControlledZ", + MQT_NAMED_BUILDER(mlir::qc::multipleControlledZ), + MQT_NAMED_BUILDER(mlir::qco::multipleControlledZ), false), + std::tuple("SingleMeasurementToSingleBit", + MQT_NAMED_BUILDER(mlir::qc::singleMeasurementToSingleBit), + MQT_NAMED_BUILDER(mlir::qco::singleMeasurementToSingleBit), + false), + std::tuple("RepeatedMeasurementToSameBit", + MQT_NAMED_BUILDER(mlir::qc::repeatedMeasurementToSameBit), + MQT_NAMED_BUILDER(mlir::qco::repeatedMeasurementToSameBit), + false), + std::tuple("RepeatedMeasurementToDifferentBits", + MQT_NAMED_BUILDER(mlir::qc::repeatedMeasurementToDifferentBits), + MQT_NAMED_BUILDER(mlir::qco::repeatedMeasurementToDifferentBits), + false), + std::tuple( + "MultipleClassicalRegistersAndMeasurements", + MQT_NAMED_BUILDER(mlir::qc::multipleClassicalRegistersAndMeasurements), + MQT_NAMED_BUILDER(mlir::qco::multipleClassicalRegistersAndMeasurements), + false), + std::tuple("PartialMeasurementToRegister", + MQT_NAMED_BUILDER(mlir::qc::partialMeasurementToRegister), + MQT_NAMED_BUILDER(mlir::qco::partialMeasurementToRegister), + false), + std::tuple("DynamicallyIndexedMeasurement", + MQT_NAMED_BUILDER(mlir::qc::dynamicallyIndexedMeasurement), + MQT_NAMED_BUILDER(mlir::qco::dynamicallyIndexedMeasurement), + true), + std::tuple("MeasurementWithoutRegisters", + MQT_NAMED_BUILDER(mlir::qc::measurementWithoutRegisters), + MQT_NAMED_BUILDER(mlir::qco::measurementWithoutRegisters), + false), + std::tuple("ResetQubitAfterSingleOp", + MQT_NAMED_BUILDER(mlir::qc::resetQubitAfterSingleOp), + MQT_NAMED_BUILDER(mlir::qco::resetQubitAfterSingleOp), false), + std::tuple("ResetMultipleQubitsAfterSingleOp", + MQT_NAMED_BUILDER(mlir::qc::resetMultipleQubitsAfterSingleOp), + MQT_NAMED_BUILDER(mlir::qco::resetMultipleQubitsAfterSingleOp), + false), + std::tuple("NestedIfOpForLoop", + MQT_NAMED_BUILDER(mlir::qc::nestedIfOpForLoop), + MQT_NAMED_BUILDER(mlir::qco::nestedIfOpForLoop), true), + std::tuple("SimpleWhile", MQT_NAMED_BUILDER(mlir::qc::simpleWhileReset), + MQT_NAMED_BUILDER(mlir::qco::simpleWhileReset), false), + std::tuple("SimpleDoWhile", MQT_NAMED_BUILDER(mlir::qc::simpleDoWhileReset), + MQT_NAMED_BUILDER(mlir::qco::simpleDoWhileReset), false), + std::tuple("SimpleForLoop", MQT_NAMED_BUILDER(mlir::qc::simpleForLoop), + MQT_NAMED_BUILDER(mlir::qco::simpleForLoop), true), + std::tuple("NestedForLoopIfOp", + MQT_NAMED_BUILDER(mlir::qc::nestedForLoopIfOp), + MQT_NAMED_BUILDER(mlir::qco::nestedForLoopIfOp), true), + std::tuple( + "NestedForLoopCtrlOpWithSeparateQubit", + MQT_NAMED_BUILDER(mlir::qc::nestedForLoopCtrlOpWithSeparateQubit), + MQT_NAMED_BUILDER(mlir::qco::nestedForLoopCtrlOpWithSeparateQubit), + true), +}; + +} // namespace mqt::test diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 6dc21d472e..abd29e5e7b 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -20,6 +20,7 @@ #include "mqt/Dialect/QTensor/IR/QTensorDialect.h" #include "mqt/Support/Passes.h" +#include "Conversion/ConversionTestCases.h" #include "Support/IRVerification.h" #include "TestCaseUtils.h" #include "qc_programs.h" @@ -984,506 +985,35 @@ TEST_P(QCOToQCTest, ProgramEquivalence) { areModulesEquivalentWithPermutations(program.get(), reference.get())); } -// QCOToQC/QubitManagement/QubitManagement.cpp +static SmallVector commonConversionCases() { + SmallVector cases; + for (const auto& testCase : ::mqt::test::COMMON_QC_QCO_CONVERSION_CASES) { + cases.push_back({ + .name = std::get<0>(testCase), + .programBuilder = std::get<2>(testCase), + .referenceBuilder = std::get<1>(testCase), + }); + } + return cases; +} + +INSTANTIATE_TEST_SUITE_P(CommonCases, QCOToQCTest, + testing::ValuesIn(commonConversionCases())); + INSTANTIATE_TEST_SUITE_P( - QCOQubitManagementTest, QCOToQCTest, + SpecialCases, QCOToQCTest, testing::Values( - QCOToQCTestCase{"StaticQubits", MQT_NAMED_BUILDER(qco::staticQubits), - MQT_NAMED_BUILDER(qc::staticQubits)}, - QCOToQCTestCase{"StaticQubitsWithOps", - MQT_NAMED_BUILDER(qco::staticQubitsWithOps), - MQT_NAMED_BUILDER(qc::staticQubitsWithOps)}, - QCOToQCTestCase{"StaticQubitsWithParametricOps", - MQT_NAMED_BUILDER(qco::staticQubitsWithParametricOps), - MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps)}, - QCOToQCTestCase{"StaticQubitsWithTwoTargetOps", - MQT_NAMED_BUILDER(qco::staticQubitsWithTwoTargetOps), - MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps)}, - QCOToQCTestCase{"StaticQubitsWithCtrl", - MQT_NAMED_BUILDER(qco::staticQubitsWithCtrl), - MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl)}, - QCOToQCTestCase{"StaticQubitsWithInv", - MQT_NAMED_BUILDER(qco::staticQubitsWithInv), - MQT_NAMED_BUILDER(qc::staticQubitsWithInv)}, QCOToQCTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qco::allocSinkPair), - MQT_NAMED_BUILDER(qc::allocDeallocPair)})); - -// QCOToQC/Modifiers/PowOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOPowOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"CtrlPowSx", - MQT_NAMED_BUILDER(qco::ctrlPowSx), - MQT_NAMED_BUILDER(qc::ctrlPowSx)}, - QCOToQCTestCase{"PowTwo", MQT_NAMED_BUILDER(qco::powTwo), - MQT_NAMED_BUILDER(qc::powTwo)})); - -// QCOToQC/Modifiers/CtrlOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOCtrlOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"CtrlTwo", MQT_NAMED_BUILDER(qco::ctrlTwo), - MQT_NAMED_BUILDER(qc::ctrlTwo)}, - QCOToQCTestCase{"CtrlTwoMixed", - MQT_NAMED_BUILDER(qco::ctrlTwoMixed), - MQT_NAMED_BUILDER(qc::ctrlTwoMixed)}, - QCOToQCTestCase{"CtrlInvTwo", - MQT_NAMED_BUILDER(qco::ctrlInvTwo), - MQT_NAMED_BUILDER(qc::ctrlInvTwo)})); - -// QCOToQC/Modifiers/InvOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOInvOpTest, QCOToQCTest, - testing::Values( - // iSWAP cannot be inverted with current canonicalization - QCOToQCTestCase{"InverseiSWAP", MQT_NAMED_BUILDER(qco::inverseIswap), - MQT_NAMED_BUILDER(qc::inverseIswap)}, - QCOToQCTestCase{"InverseMultipleControllediSWAP", - MQT_NAMED_BUILDER(qco::inverseMultipleControlledIswap), - MQT_NAMED_BUILDER(qc::inverseMultipleControlledIswap)}, - // Inverse DCX is not canonicalized in QCO + MQT_NAMED_BUILDER(qc::allocDeallocPair)}, QCOToQCTestCase{"InverseDCX", MQT_NAMED_BUILDER(qco::inverseDcx), MQT_NAMED_BUILDER(qc::dcx)}, QCOToQCTestCase{"InverseMultipleControlledDCX", MQT_NAMED_BUILDER(qco::inverseMultipleControlledDcx), MQT_NAMED_BUILDER(qc::multipleControlledDcx)}, - QCOToQCTestCase{"InvTwo", MQT_NAMED_BUILDER(qco::invTwo), - MQT_NAMED_BUILDER(qc::invTwo)})); - -// QCOToQC/Operations/StandardGates/BarrierOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOBarrierOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"Barrier", MQT_NAMED_BUILDER(qco::barrier), - MQT_NAMED_BUILDER(qc::barrier)}, - QCOToQCTestCase{"BarrierTwoQubits", - MQT_NAMED_BUILDER(qco::barrierTwoQubits), - MQT_NAMED_BUILDER(qc::barrierTwoQubits)}, - QCOToQCTestCase{ - "BarrierMultipleQubits", - MQT_NAMED_BUILDER(qco::barrierMultipleQubits), - MQT_NAMED_BUILDER(qc::barrierMultipleQubits)})); - -// QCOToQC/Operations/StandardGates/DcxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCODCXOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"DCX", MQT_NAMED_BUILDER(qco::dcx), - MQT_NAMED_BUILDER(qc::dcx)}, - QCOToQCTestCase{"SingleControlledDCX", - MQT_NAMED_BUILDER(qco::singleControlledDcx), - MQT_NAMED_BUILDER(qc::singleControlledDcx)}, - QCOToQCTestCase{ - "MultipleControlledDCX", - MQT_NAMED_BUILDER(qco::multipleControlledDcx), - MQT_NAMED_BUILDER(qc::multipleControlledDcx)})); - -// QCOToQC/Operations/StandardGates/EcrOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOECROpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"ECR", MQT_NAMED_BUILDER(qco::ecr), - MQT_NAMED_BUILDER(qc::ecr)}, - QCOToQCTestCase{"SingleControlledECR", - MQT_NAMED_BUILDER(qco::singleControlledEcr), - MQT_NAMED_BUILDER(qc::singleControlledEcr)}, - QCOToQCTestCase{ - "MultipleControlledECR", - MQT_NAMED_BUILDER(qco::multipleControlledEcr), - MQT_NAMED_BUILDER(qc::multipleControlledEcr)})); - -// QCOToQC/Operations/StandardGates/GphaseOp.cpp -INSTANTIATE_TEST_SUITE_P(QCOGPhaseOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{ - "GlobalPhase", MQT_NAMED_BUILDER(qco::globalPhase), - MQT_NAMED_BUILDER(qc::globalPhase)})); - -// QCOToQC/Operations/StandardGates/HOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOHOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"H", MQT_NAMED_BUILDER(qco::h), - MQT_NAMED_BUILDER(qc::h)}, - QCOToQCTestCase{"SingleControlledH", - MQT_NAMED_BUILDER(qco::singleControlledH), - MQT_NAMED_BUILDER(qc::singleControlledH)}, - QCOToQCTestCase{"MultipleControlledH", - MQT_NAMED_BUILDER(qco::multipleControlledH), - MQT_NAMED_BUILDER(qc::multipleControlledH)}, - QCOToQCTestCase{"HWithoutRegister", - MQT_NAMED_BUILDER(qco::hWithoutRegister), - MQT_NAMED_BUILDER(qc::hWithoutRegister)})); - -// QCOToQC/Operations/StandardGates/IswapOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOiSWAPOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"iSWAP", MQT_NAMED_BUILDER(qco::iswap), - MQT_NAMED_BUILDER(qc::iswap)}, - QCOToQCTestCase{"SingleControllediSWAP", - MQT_NAMED_BUILDER(qco::singleControlledIswap), - MQT_NAMED_BUILDER(qc::singleControlledIswap)}, - QCOToQCTestCase{"MultipleControllediSWAP", - MQT_NAMED_BUILDER(qco::multipleControlledIswap), - MQT_NAMED_BUILDER(qc::multipleControlledIswap)})); - -// QCOToQC/Operations/StandardGates/POp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOPOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"P", MQT_NAMED_BUILDER(qco::p), - MQT_NAMED_BUILDER(qc::p)}, - QCOToQCTestCase{"SingleControlledP", - MQT_NAMED_BUILDER(qco::singleControlledP), - MQT_NAMED_BUILDER(qc::singleControlledP)}, - QCOToQCTestCase{ - "MultipleControlledP", - MQT_NAMED_BUILDER(qco::multipleControlledP), - MQT_NAMED_BUILDER(qc::multipleControlledP)})); - -// QCOToQC/Operations/StandardGates/RCCXOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORCCXOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"RCCX", MQT_NAMED_BUILDER(qco::rccx), - MQT_NAMED_BUILDER(qc::rccx)}, - QCOToQCTestCase{"SingleControlledRCCX", - MQT_NAMED_BUILDER(qco::singleControlledRccx), - MQT_NAMED_BUILDER(qc::singleControlledRccx)}, - QCOToQCTestCase{"MultipleControlledRCCX", - MQT_NAMED_BUILDER(qco::multipleControlledRccx), - MQT_NAMED_BUILDER(qc::multipleControlledRccx)})); - -// QCOToQC/Operations/StandardGates/ROp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOROpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"R", MQT_NAMED_BUILDER(qco::r), - MQT_NAMED_BUILDER(qc::r)}, - QCOToQCTestCase{"SingleControlledR", - MQT_NAMED_BUILDER(qco::singleControlledR), - MQT_NAMED_BUILDER(qc::singleControlledR)}, - QCOToQCTestCase{ - "MultipleControlledR", - MQT_NAMED_BUILDER(qco::multipleControlledR), - MQT_NAMED_BUILDER(qc::multipleControlledR)})); - -// QCOToQC/Operations/StandardGates/RxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORXOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RX", MQT_NAMED_BUILDER(qco::rx), - MQT_NAMED_BUILDER(qc::rx)}, - QCOToQCTestCase{"SingleControlledRX", - MQT_NAMED_BUILDER(qco::singleControlledRx), - MQT_NAMED_BUILDER(qc::singleControlledRx)}, - QCOToQCTestCase{ - "MultipleControlledRX", - MQT_NAMED_BUILDER(qco::multipleControlledRx), - MQT_NAMED_BUILDER(qc::multipleControlledRx)})); - -// QCOToQC/Operations/StandardGates/RxxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORXXOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RXX", MQT_NAMED_BUILDER(qco::rxx), - MQT_NAMED_BUILDER(qc::rxx)}, - QCOToQCTestCase{"SingleControlledRXX", - MQT_NAMED_BUILDER(qco::singleControlledRxx), - MQT_NAMED_BUILDER(qc::singleControlledRxx)}, - QCOToQCTestCase{ - "MultipleControlledRXX", - MQT_NAMED_BUILDER(qco::multipleControlledRxx), - MQT_NAMED_BUILDER(qc::multipleControlledRxx)})); - -// QCOToQC/Operations/StandardGates/RyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORYOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RY", MQT_NAMED_BUILDER(qco::ry), - MQT_NAMED_BUILDER(qc::ry)}, - QCOToQCTestCase{"SingleControlledRY", - MQT_NAMED_BUILDER(qco::singleControlledRy), - MQT_NAMED_BUILDER(qc::singleControlledRy)}, - QCOToQCTestCase{ - "MultipleControlledRY", - MQT_NAMED_BUILDER(qco::multipleControlledRy), - MQT_NAMED_BUILDER(qc::multipleControlledRy)})); - -// QCOToQC/Operations/StandardGates/RyyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORYYOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RYY", MQT_NAMED_BUILDER(qco::ryy), - MQT_NAMED_BUILDER(qc::ryy)}, - QCOToQCTestCase{"SingleControlledRYY", - MQT_NAMED_BUILDER(qco::singleControlledRyy), - MQT_NAMED_BUILDER(qc::singleControlledRyy)}, - QCOToQCTestCase{ - "MultipleControlledRYY", - MQT_NAMED_BUILDER(qco::multipleControlledRyy), - MQT_NAMED_BUILDER(qc::multipleControlledRyy)})); - -// QCOToQC/Operations/StandardGates/RzOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORZOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RZ", MQT_NAMED_BUILDER(qco::rz), - MQT_NAMED_BUILDER(qc::rz)}, - QCOToQCTestCase{"SingleControlledRZ", - MQT_NAMED_BUILDER(qco::singleControlledRz), - MQT_NAMED_BUILDER(qc::singleControlledRz)}, - QCOToQCTestCase{ - "MultipleControlledRZ", - MQT_NAMED_BUILDER(qco::multipleControlledRz), - MQT_NAMED_BUILDER(qc::multipleControlledRz)})); - -// QCOToQC/Operations/StandardGates/RzxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORZXOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RZX", MQT_NAMED_BUILDER(qco::rzx), - MQT_NAMED_BUILDER(qc::rzx)}, - QCOToQCTestCase{"SingleControlledRZX", - MQT_NAMED_BUILDER(qco::singleControlledRzx), - MQT_NAMED_BUILDER(qc::singleControlledRzx)}, - QCOToQCTestCase{ - "MultipleControlledRZX", - MQT_NAMED_BUILDER(qco::multipleControlledRzx), - MQT_NAMED_BUILDER(qc::multipleControlledRzx)})); - -// QCOToQC/Operations/StandardGates/RzzOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCORZZOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"RZZ", MQT_NAMED_BUILDER(qco::rzz), - MQT_NAMED_BUILDER(qc::rzz)}, - QCOToQCTestCase{"SingleControlledRZZ", - MQT_NAMED_BUILDER(qco::singleControlledRzz), - MQT_NAMED_BUILDER(qc::singleControlledRzz)}, - QCOToQCTestCase{ - "MultipleControlledRZZ", - MQT_NAMED_BUILDER(qco::multipleControlledRzz), - MQT_NAMED_BUILDER(qc::multipleControlledRzz)})); - -// QCOToQC/Operations/StandardGates/SOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOSOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"S", MQT_NAMED_BUILDER(qco::s), - MQT_NAMED_BUILDER(qc::s)}, - QCOToQCTestCase{"SingleControlledS", - MQT_NAMED_BUILDER(qco::singleControlledS), - MQT_NAMED_BUILDER(qc::singleControlledS)}, - QCOToQCTestCase{ - "MultipleControlledS", - MQT_NAMED_BUILDER(qco::multipleControlledS), - MQT_NAMED_BUILDER(qc::multipleControlledS)})); - -// QCOToQC/Operations/StandardGates/SdgOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOSdgOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"Sdg", MQT_NAMED_BUILDER(qco::sdg), - MQT_NAMED_BUILDER(qc::sdg)}, - QCOToQCTestCase{"SingleControlledSdg", - MQT_NAMED_BUILDER(qco::singleControlledSdg), - MQT_NAMED_BUILDER(qc::singleControlledSdg)}, - QCOToQCTestCase{ - "MultipleControlledSdg", - MQT_NAMED_BUILDER(qco::multipleControlledSdg), - MQT_NAMED_BUILDER(qc::multipleControlledSdg)})); - -// QCOToQC/Operations/StandardGates/SwapOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOSWAPOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"SWAP", MQT_NAMED_BUILDER(qco::swap), - MQT_NAMED_BUILDER(qc::swap)}, - QCOToQCTestCase{"SingleControlledSWAP", - MQT_NAMED_BUILDER(qco::singleControlledSwap), - MQT_NAMED_BUILDER(qc::singleControlledSwap)}, - QCOToQCTestCase{"MultipleControlledSWAP", - MQT_NAMED_BUILDER(qco::multipleControlledSwap), - MQT_NAMED_BUILDER(qc::multipleControlledSwap)})); - -// QCOToQC/Operations/StandardGates/SxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOSXOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"SX", MQT_NAMED_BUILDER(qco::sx), - MQT_NAMED_BUILDER(qc::sx)}, - QCOToQCTestCase{"SingleControlledSX", - MQT_NAMED_BUILDER(qco::singleControlledSx), - MQT_NAMED_BUILDER(qc::singleControlledSx)}, - QCOToQCTestCase{ - "MultipleControlledSX", - MQT_NAMED_BUILDER(qco::multipleControlledSx), - MQT_NAMED_BUILDER(qc::multipleControlledSx)})); - -// QCOToQC/Operations/StandardGates/SxdgOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOSXdgOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"SXdg", MQT_NAMED_BUILDER(qco::sxdg), - MQT_NAMED_BUILDER(qc::sxdg)}, - QCOToQCTestCase{"SingleControlledSXdg", - MQT_NAMED_BUILDER(qco::singleControlledSxdg), - MQT_NAMED_BUILDER(qc::singleControlledSxdg)}, - QCOToQCTestCase{"MultipleControlledSXdg", - MQT_NAMED_BUILDER(qco::multipleControlledSxdg), - MQT_NAMED_BUILDER(qc::multipleControlledSxdg)})); - -// QCOToQC/Operations/StandardGates/TOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOTOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"T", MQT_NAMED_BUILDER(qco::t_), - MQT_NAMED_BUILDER(qc::t_)}, - QCOToQCTestCase{"SingleControlledT", - MQT_NAMED_BUILDER(qco::singleControlledT), - MQT_NAMED_BUILDER(qc::singleControlledT)}, - QCOToQCTestCase{ - "MultipleControlledT", - MQT_NAMED_BUILDER(qco::multipleControlledT), - MQT_NAMED_BUILDER(qc::multipleControlledT)})); - -// QCOToQC/Operations/StandardGates/TdgOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOTdgOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"Tdg", MQT_NAMED_BUILDER(qco::tdg), - MQT_NAMED_BUILDER(qc::tdg)}, - QCOToQCTestCase{"SingleControlledTdg", - MQT_NAMED_BUILDER(qco::singleControlledTdg), - MQT_NAMED_BUILDER(qc::singleControlledTdg)}, - QCOToQCTestCase{ - "MultipleControlledTdg", - MQT_NAMED_BUILDER(qco::multipleControlledTdg), - MQT_NAMED_BUILDER(qc::multipleControlledTdg)})); - -// QCOToQC/Operations/StandardGates/U2Op.cpp -INSTANTIATE_TEST_SUITE_P( - QCOU2OpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"U2", MQT_NAMED_BUILDER(qco::u2), - MQT_NAMED_BUILDER(qc::u2)}, - QCOToQCTestCase{"SingleControlledU2", - MQT_NAMED_BUILDER(qco::singleControlledU2), - MQT_NAMED_BUILDER(qc::singleControlledU2)}, - QCOToQCTestCase{ - "MultipleControlledU2", - MQT_NAMED_BUILDER(qco::multipleControlledU2), - MQT_NAMED_BUILDER(qc::multipleControlledU2)})); - -// QCOToQC/Operations/StandardGates/UOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOUOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"U", MQT_NAMED_BUILDER(qco::u), - MQT_NAMED_BUILDER(qc::u)}, - QCOToQCTestCase{"SingleControlledU", - MQT_NAMED_BUILDER(qco::singleControlledU), - MQT_NAMED_BUILDER(qc::singleControlledU)}, - QCOToQCTestCase{ - "MultipleControlledU", - MQT_NAMED_BUILDER(qco::multipleControlledU), - MQT_NAMED_BUILDER(qc::multipleControlledU)})); - -// QCOToQC/Operations/StandardGates/XOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOXOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"X", MQT_NAMED_BUILDER(qco::x), - MQT_NAMED_BUILDER(qc::x)}, - QCOToQCTestCase{"SingleControlledX", - MQT_NAMED_BUILDER(qco::singleControlledX), - MQT_NAMED_BUILDER(qc::singleControlledX)}, - QCOToQCTestCase{"MultipleControlledX", - MQT_NAMED_BUILDER(qco::multipleControlledX), - MQT_NAMED_BUILDER(qc::multipleControlledX)}, - QCOToQCTestCase{ - "RepeatedControlledX", - MQT_NAMED_BUILDER(qco::repeatedControlledX), - MQT_NAMED_BUILDER(qc::repeatedControlledX)})); - -// QCOToQC/Operations/StandardGates/XxMinusYyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOXXMinusYYOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"XXMinusYY", MQT_NAMED_BUILDER(qco::xxMinusYY), - MQT_NAMED_BUILDER(qc::xxMinusYY)}, - QCOToQCTestCase{"SingleControlledXXMinusYY", - MQT_NAMED_BUILDER(qco::singleControlledXxMinusYY), - MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY)}, - QCOToQCTestCase{"MultipleControlledXXMinusYY", - MQT_NAMED_BUILDER(qco::multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY)})); - -// QCOToQC/Operations/StandardGates/XxPlusYyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOXXPlusYYOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"XXPlusYY", MQT_NAMED_BUILDER(qco::xxPlusYY), - MQT_NAMED_BUILDER(qc::xxPlusYY)}, - QCOToQCTestCase{"SingleControlledXXPlusYY", - MQT_NAMED_BUILDER(qco::singleControlledXxPlusYY), - MQT_NAMED_BUILDER(qc::singleControlledXxPlusYY)}, - QCOToQCTestCase{"MultipleControlledXXPlusYY", - MQT_NAMED_BUILDER(qco::multipleControlledXxPlusYY), - MQT_NAMED_BUILDER(qc::multipleControlledXxPlusYY)})); - -// QCOToQC/Operations/StandardGates/YOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOYOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"Y", MQT_NAMED_BUILDER(qco::y), - MQT_NAMED_BUILDER(qc::y)}, - QCOToQCTestCase{"SingleControlledY", - MQT_NAMED_BUILDER(qco::singleControlledY), - MQT_NAMED_BUILDER(qc::singleControlledY)}, - QCOToQCTestCase{ - "MultipleControlledY", - MQT_NAMED_BUILDER(qco::multipleControlledY), - MQT_NAMED_BUILDER(qc::multipleControlledY)})); - -// QCOToQC/Operations/StandardGates/ZOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOZOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"Z", MQT_NAMED_BUILDER(qco::z), - MQT_NAMED_BUILDER(qc::z)}, - QCOToQCTestCase{"SingleControlledZ", - MQT_NAMED_BUILDER(qco::singleControlledZ), - MQT_NAMED_BUILDER(qc::singleControlledZ)}, - QCOToQCTestCase{ - "MultipleControlledZ", - MQT_NAMED_BUILDER(qco::multipleControlledZ), - MQT_NAMED_BUILDER(qc::multipleControlledZ)})); - -// QCOToQC/Operations/MeasureOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOMeasureOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"SingleMeasurementToSingleBit", - MQT_NAMED_BUILDER(qco::singleMeasurementToSingleBit), - MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit)}, - QCOToQCTestCase{"RepeatedMeasurementToSameBit", - MQT_NAMED_BUILDER(qco::repeatedMeasurementToSameBit), - MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit)}, - QCOToQCTestCase{ - "RepeatedMeasurementToDifferentBits", - MQT_NAMED_BUILDER(qco::repeatedMeasurementToDifferentBits), - MQT_NAMED_BUILDER(qc::repeatedMeasurementToDifferentBits)}, - QCOToQCTestCase{ - "MultipleClassicalRegistersAndMeasurements", - MQT_NAMED_BUILDER(qco::multipleClassicalRegistersAndMeasurements), - MQT_NAMED_BUILDER(qc::multipleClassicalRegistersAndMeasurements)}, - QCOToQCTestCase{"PartialMeasurementToRegister", - MQT_NAMED_BUILDER(qco::partialMeasurementToRegister), - MQT_NAMED_BUILDER(qc::partialMeasurementToRegister)}, - QCOToQCTestCase{"DynamicallyIndexedMeasurement", - MQT_NAMED_BUILDER(qco::dynamicallyIndexedMeasurement), - MQT_NAMED_BUILDER(qc::dynamicallyIndexedMeasurement)}, - QCOToQCTestCase{"MeasurementWithoutRegisters", - MQT_NAMED_BUILDER(qco::measurementWithoutRegisters), - MQT_NAMED_BUILDER(qc::measurementWithoutRegisters)})); - -// QCOToQC/Operations/ResetOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOResetOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"ResetQubitAfterSingleOp", - MQT_NAMED_BUILDER(qco::resetQubitAfterSingleOp), - MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp)}, - QCOToQCTestCase{ - "ResetMultipleQubitsAfterSingleOp", - MQT_NAMED_BUILDER(qco::resetMultipleQubitsAfterSingleOp), - MQT_NAMED_BUILDER(qc::resetMultipleQubitsAfterSingleOp)}, QCOToQCTestCase{"RepeatedResetAfterSingleOp", MQT_NAMED_BUILDER(qco::repeatedResetAfterSingleOp), - MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp)})); - -// QCOToQC/Operations/IfOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOIfOpTest, QCOToQCTest, - testing::Values( + MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp)}, QCOToQCTestCase{"SimpleIfOp", MQT_NAMED_BUILDER(qco::simpleIf), MQT_NAMED_BUILDER(qc::simpleIf)}, QCOToQCTestCase{"IfElse", MQT_NAMED_BUILDER(qco::ifElse), @@ -1495,50 +1025,18 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::ifWithMeasurement)}, QCOToQCTestCase{"IfWithCreg", MQT_NAMED_BUILDER(qco::ifWithCreg), MQT_NAMED_BUILDER(qc::ifWithCreg)}, - QCOToQCTestCase{"NestedIfOpForLoop", - MQT_NAMED_BUILDER(qco::nestedIfOpForLoop), - MQT_NAMED_BUILDER(qc::nestedIfOpForLoop)})); - -// QCOToQC/Operations/IndexSwitchOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOIndexSwitchOpTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"SimpleIndexSwitchOp", - MQT_NAMED_BUILDER(qco::simpleIndexSwitch), - MQT_NAMED_BUILDER(qc::simpleIndexSwitch)}, - QCOToQCTestCase{ - "IndexSwitchMultiCase", + QCOToQCTestCase{"SimpleIndexSwitchOp", + MQT_NAMED_BUILDER(qco::simpleIndexSwitch), + MQT_NAMED_BUILDER(qc::simpleIndexSwitch)}, + QCOToQCTestCase{"IndexSwitchMultiCase", MQT_NAMED_BUILDER(qco::indexSwitchMultiCase), - MQT_NAMED_BUILDER(qc::indexSwitchMultiCase)})); - -// QCOToQC/Operations/WhileOp.cpp -INSTANTIATE_TEST_SUITE_P( - SCFWhileOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"SimpleWhile", MQT_NAMED_BUILDER(qco::simpleWhileReset), - MQT_NAMED_BUILDER(qc::simpleWhileReset)}, - QCOToQCTestCase{"SimpleDoWhile", - MQT_NAMED_BUILDER(qco::simpleDoWhileReset), - MQT_NAMED_BUILDER(qc::simpleDoWhileReset)})); - -// QCOToQC/Operations/ForOp.cpp -INSTANTIATE_TEST_SUITE_P( - SCFForOpTest, QCOToQCTest, - testing::Values( - QCOToQCTestCase{"SimpleForLoop", MQT_NAMED_BUILDER(qco::simpleForLoop), - MQT_NAMED_BUILDER(qc::simpleForLoop)}, - QCOToQCTestCase{"NestedForLoopIfOp", - MQT_NAMED_BUILDER(qco::nestedForLoopIfOp), - MQT_NAMED_BUILDER(qc::nestedForLoopIfOp)}, + MQT_NAMED_BUILDER(qc::indexSwitchMultiCase)}, QCOToQCTestCase{"NestedForLoopWhileOp", MQT_NAMED_BUILDER(qco::nestedForLoopWhileOp), MQT_NAMED_BUILDER(qc::nestedForLoopWhileOp)}, QCOToQCTestCase{"NestedForLoopSwitchOp", MQT_NAMED_BUILDER(qco::nestedForLoopSwitchOp), MQT_NAMED_BUILDER(qc::nestedForLoopSwitchOp)}, - QCOToQCTestCase{ - "NestedForLoopCtrlOpWithSeparateQubit", - MQT_NAMED_BUILDER(qco::nestedForLoopCtrlOpWithSeparateQubit), - MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithSeparateQubit)}, QCOToQCTestCase{ "NestedForLoopCtrlOpWithExtractedQubit", MQT_NAMED_BUILDER(qco::nestedForLoopCtrlOpWithExtractedQubit), diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index a80782170d..0d6325ba87 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -28,6 +28,7 @@ #include "mqt/Dialect/QTensor/IR/QTensorOps.h" #include "mqt/Support/Passes.h" +#include "Conversion/ConversionTestCases.h" #include "Support/IRVerification.h" #include "TestCaseUtils.h" #include "qc_programs.h" @@ -44,6 +45,7 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/DialectRegistry.h" +#include "mlir/IR/Location.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/Matchers.h" #include "mlir/IR/OperationSupport.h" @@ -81,7 +83,6 @@ struct QCToQCOTestCase { ::mqt::test::NamedMLIRBuilder programBuilder; ::mqt::test::NamedMLIRBuilder referenceBuilder; bool expectsCompleteTensorState = false; - bool skipReferenceComparison = false; friend std::ostream& operator<<(std::ostream& os, const QCToQCOTestCase& info); @@ -315,6 +316,17 @@ module { EXPECT_TRUE(destination.getBody().empty()); } +TEST_F(QCToQCORegressionTest, RejectsYieldOutsideModifier) { + auto moduleOp = parseSourceString( + "module { func.func @main(%x: i64) { qc.yield } }", &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + + ScopedDiagnosticHandler handler( + &context, [](Diagnostic& /*diagnostic*/) { return success(); }); + EXPECT_TRUE(failed(runQCToQCOConversion(*moduleOp))); +} + TEST_F(QCToQCORegressionTest, PreservesForResultsWithQuantumState) { constexpr llvm::StringLiteral source = R"mlir( module { @@ -788,6 +800,8 @@ module { %value = arith.constant 42 : i64 %result = func.call @reset(%true, %q, %value) : (i1, !qc.qubit, i64) -> i1 + %next = func.call @reset(%result, %q, %value) + : (i1, !qc.qubit, i64) -> i1 memref.dealloc %reg : memref<1x!qc.qubit> return } @@ -800,13 +814,14 @@ module { ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); ASSERT_TRUE(succeeded(verify(*moduleOp))); EXPECT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); - auto call = *mlir::mqt::getEntryPoint(*moduleOp) - .getBody() - .getOps() - .begin(); + auto calls = llvm::to_vector( + mlir::mqt::getEntryPoint(*moduleOp).getOps()); + ASSERT_EQ(calls.size(), 2U); + auto call = calls.front(); ASSERT_EQ(call.getNumOperands(), 3U); EXPECT_TRUE(isa(call.getOperand(1).getType())); ASSERT_EQ(call.getNumResults(), 2U); + EXPECT_EQ(calls.back().getOperand(0), call.getResult(0)); EXPECT_TRUE(isa(call.getResult(1).getType())); EXPECT_TRUE(call.getOperand(1).getDefiningOp()); ASSERT_TRUE(call.getResult(1).hasOneUse()); @@ -941,6 +956,173 @@ TEST_F(QCToQCORegressionTest, ThreadsReferencesThroughExternalCalls) { EXPECT_TRUE(llvm::equal(calls[1].getOperands(), calls[0].getResults())); } +TEST_F(QCToQCORegressionTest, + RoundTripsInterleavedArgumentsAndOwnedCallResults) { + auto moduleOp = parseSourceString(R"mlir( + module { + func.func @main(%angle: f64, %flag: i1) attributes {mqt.entry_point} { + %q = qc.alloc : !qc.qubit + %r = qc.alloc : !qc.qubit + %theta, %owned, %bit = func.call @helper(%angle, %q, %flag, %r) + : (f64, !qc.qubit, i1, !qc.qubit) -> (f64, !qc.qubit, i1) + qc.ry(%theta) %owned : !qc.qubit + qc.swap %q, %owned : !qc.qubit, !qc.qubit + scf.if %bit { qc.x %r : !qc.qubit } + return + } + func.func private @helper(%a: f64, %q: !qc.qubit, %b: i1, %r: !qc.qubit) + -> (f64, !qc.qubit, i1) { + qc.x %q : !qc.qubit + qc.ry(%a) %r : !qc.qubit + %new = func.call @factory() : () -> !qc.qubit + return %a, %new, %b : f64, !qc.qubit, i1 + } + func.func private @factory() -> !qc.qubit + })mlir", + &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + auto helper = moduleOp->lookupSymbol("helper"); + const auto originalType = helper.getFunctionType(); + ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); + ASSERT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + expectNoQCOperations(*moduleOp); + ASSERT_EQ(helper.getNumArguments(), 4U); + ASSERT_EQ(helper.getNumResults(), 5U); + ASSERT_TRUE(succeeded(runQCOToQCConversion(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + EXPECT_EQ(helper.getFunctionType(), originalType); + auto x = *helper.getOps().begin(); + auto ry = *helper.getOps().begin(); + EXPECT_EQ(x->getOperand(0), helper.getArgument(1)); + EXPECT_EQ(ry->getOperand(0), helper.getArgument(3)); + auto ret = cast(helper.getBody().front().back()); + EXPECT_EQ(ret.getOperand(0), helper.getArgument(0)); + EXPECT_EQ(ret.getOperand(2), helper.getArgument(2)); + EXPECT_TRUE(ret.getOperand(1).getDefiningOp()); +} + +TEST_F(QCToQCORegressionTest, PreservesWideInterleavedSignatures) { + constexpr unsigned numQubits = 1024; + std::string source = "module { func.func private @wide("; + for (unsigned i = 0; i < numQubits; ++i) { + if (i != 0) { + source += ", "; + } + const auto suffix = std::to_string(i); + source.append("%a") + .append(suffix) + .append(": f64 {test.position = ") + .append(std::to_string(2 * i)) + .append(" : i32}, %q") + .append(suffix) + .append(": !qc.qubit {test.position = ") + .append(std::to_string(2 * i + 1)) + .append(" : i32}"); + } + source += ") -> (f64, f64) { return %a0, %a" + std::to_string(numQubits - 1) + + " : f64, f64 } }"; + auto moduleOp = parseSourceString(source, &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + auto function = moduleOp->lookupSymbol("wide"); + const auto originalType = function.getFunctionType(); + const auto originalAttrs = function.getAllArgAttrs(); + SmallVector originalLocations; + for (auto argument : function.getArguments()) { + originalLocations.push_back(argument.getLoc()); + } + + ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + ASSERT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + ASSERT_EQ(function.getNumArguments(), 2 * numQubits); + ASSERT_EQ(function.getNumResults(), numQubits + 2); + EXPECT_EQ(function.getAllArgAttrs(), originalAttrs); + auto ret = cast(function.getBody().front().getTerminator()); + EXPECT_EQ(ret.getOperand(0), function.getArgument(0)); + EXPECT_EQ(ret.getOperand(1), function.getArgument(2 * (numQubits - 1))); + for (unsigned i = 0; i < numQubits; ++i) { + EXPECT_TRUE(function.getArgument(2 * i).getType().isF64()); + EXPECT_TRUE(isa(function.getArgument(2 * i + 1).getType())); + EXPECT_EQ(ret.getOperand(i + 2), function.getArgument(2 * i + 1)); + } + for (auto [argument, location] : + llvm::zip_equal(function.getArguments(), originalLocations)) { + EXPECT_EQ(argument.getLoc(), location); + } + ASSERT_TRUE(succeeded(runQCOToQCConversion(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + EXPECT_EQ(function.getFunctionType(), originalType); + EXPECT_EQ(function.getAllArgAttrs(), originalAttrs); +} + +TEST_F(QCToQCORegressionTest, RemapsRegisterIndexArguments) { + auto moduleOp = parseSourceString(R"mlir( + module { + func.func private @helper(%i: index, %reg: memref<4x!qc.qubit>, + %angle: f64, %q: !qc.qubit) -> index { + %slot = memref.load %reg[%i] : memref<4x!qc.qubit> + qc.ry(%angle) %slot : !qc.qubit + qc.swap %slot, %q : !qc.qubit, !qc.qubit + return %i : index + } + })mlir", + &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + auto function = moduleOp->lookupSymbol("helper"); + const auto originalType = function.getFunctionType(); + ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + ASSERT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + const auto extracts = llvm::to_vector(function.getOps()); + ASSERT_FALSE(extracts.empty()); + for (auto extract : extracts) { + EXPECT_EQ(extract.getIndex(), function.getArgument(0)); + } + ASSERT_TRUE(succeeded(runQCOToQCConversion(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + EXPECT_EQ(function.getFunctionType(), originalType); + auto ret = cast(function.getBody().front().getTerminator()); + EXPECT_EQ(ret.getOperand(0), function.getArgument(0)); +} + +TEST_F(QCToQCORegressionTest, RoundTripsUnitaryCallInNestedRegions) { + auto moduleOp = parseSourceString(R"mlir( + module { + func.func @main(%condition: i1) attributes {mqt.entry_point} { + %q = qc.alloc : !qc.qubit + scf.if %condition { + qc.inv (%a = %q) { + qc.call @flip(%a) : !qc.qubit + qc.yield + } : !qc.qubit + } + qc.x %q : !qc.qubit + return + } + func.func private @flip(%q: !qc.qubit) attributes {mqt.unitary} { + qc.x %q : !qc.qubit + return + } + })mlir", + &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + ASSERT_TRUE(succeeded(runQCToQCOConversion(*moduleOp))); + ASSERT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + ASSERT_TRUE(succeeded(runQCOToQCConversion(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + auto function = moduleOp->lookupSymbol("main"); + auto conditional = *function.getOps().begin(); + auto inverse = *conditional.getThenRegion().getOps().begin(); + auto call = *inverse.getRegion().getOps().begin(); + EXPECT_EQ(call->getOperand(0), inverse.getRegion().front().getArgument(0)); + EXPECT_TRUE(mlir::mqt::isUnitaryFunction( + moduleOp->lookupSymbol("flip"))); +} + TEST_F(QCToQCORegressionTest, ConvertsQubitFunctionArgumentsToTrailingResults) { constexpr llvm::StringLiteral source = R"mlir( module { @@ -1247,6 +1429,40 @@ module { } } +TEST_F(QCToQCORegressionTest, RejectsNestedQuantumAllocationsBeforeRewriting) { + for (const auto* allocation : { + "%q = qc.alloc : !qc.qubit\nqc.dealloc %q : !qc.qubit", + "%q = memref.alloc() : memref<1x!qc.qubit>\n" + "memref.dealloc %q : memref<1x!qc.qubit>", + "%q = memref.alloca() : memref<1x!qc.qubit>", + }) { + SCOPED_TRACE(allocation); + const auto source = std::string(R"mlir(module { + func.func @main(%condition: i1) { + %outer = qc.alloc : !qc.qubit + scf.if %condition { + )mlir") + allocation + + "\n} qc.dealloc %outer : !qc.qubit\nreturn } }"; + auto moduleOp = parseSourceString(source, &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + OwningOpRef original = moduleOp->clone(); + bool diagnosed = false; + ScopedDiagnosticHandler handler(&context, [&](Diagnostic& diagnostic) { + diagnosed |= + StringRef(diagnostic.str()) + .contains( + "quantum allocations must be in a function's entry block"); + return success(); + }); + EXPECT_TRUE(failed(runQCToQCOConversion(*moduleOp))); + EXPECT_TRUE(diagnosed); + EXPECT_TRUE(OperationEquivalence::isEquivalentTo( + moduleOp->getOperation(), original->getOperation(), + OperationEquivalence::Flags::None)); + } +} + TEST_F(QCToQCORegressionTest, CapturesQubitsUsedByPowInsideFor) { constexpr llvm::StringLiteral source = R"mlir( module { @@ -1404,9 +1620,45 @@ module { EXPECT_TRUE(sawExpectedDiagnostic); } +TEST_F(QCToQCORegressionTest, RejectsRegisterAliasingExposedByNormalization) { + for (const auto* indices : { + "%lhs = arith.addi %one, %one : index\n" + "%rhs = arith.constant 2 : index", + "%lhs = arith.addi %i, %one : index\n" + "%rhs = arith.addi %i, %one : index", + }) { + SCOPED_TRACE(indices); + const auto source = std::string(R"mlir( + module { + func.func @main(%i: index) attributes {mqt.entry_point} { + %reg = memref.alloc() : memref<4x!qc.qubit> + %one = arith.constant 1 : index + )mlir") + indices + R"mlir( + %q0 = memref.load %reg[%lhs] : memref<4x!qc.qubit> + %q1 = memref.load %reg[%rhs] : memref<4x!qc.qubit> + qc.swap %q0, %q1 : !qc.qubit, !qc.qubit + memref.dealloc %reg : memref<4x!qc.qubit> + return + } + })mlir"; + auto moduleOp = parseSourceString(source, &context); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + + bool sawExpectedDiagnostic = false; + ScopedDiagnosticHandler handler(&context, [&](Diagnostic& diagnostic) { + sawExpectedDiagnostic |= + StringRef(diagnostic.str()).contains("distinct qubit operands"); + return success(); + }); + EXPECT_TRUE(failed(runQCToQCOConversion(*moduleOp))); + EXPECT_TRUE(sawExpectedDiagnostic); + } +} + TEST_P(QCToQCOTest, ProgramConversion) { - const auto& [_, programBuilder, referenceBuilder, expectsCompleteTensorState, - skipReferenceComparison] = GetParam(); + const auto& [_, programBuilder, referenceBuilder, + expectsCompleteTensorState] = GetParam(); const auto name = " (" + GetParam().name + ")"; ::mqt::test::DeferredPrinter printer; @@ -1432,524 +1684,48 @@ TEST_P(QCToQCOTest, ProgramConversion) { printer.record(program.get(), "Canonicalized Converted QCO IR" + name); EXPECT_TRUE(verify(*program).succeeded()); - if (!skipReferenceComparison) { - auto reference = - ::mqt::test::buildMLIRProgram(context.get(), referenceBuilder); - ASSERT_TRUE(reference); - printer.record(reference.get(), "Reference QCO IR" + name); - EXPECT_TRUE(verify(*reference).succeeded()); + auto reference = + ::mqt::test::buildMLIRProgram(context.get(), referenceBuilder); + ASSERT_TRUE(reference); + printer.record(reference.get(), "Reference QCO IR" + name); + EXPECT_TRUE(verify(*reference).succeeded()); - EXPECT_TRUE(runQCOCleanupPipeline(reference.get()).succeeded()); - printer.record(reference.get(), "Canonicalized Reference QCO IR" + name); - EXPECT_TRUE(verify(*reference).succeeded()); + EXPECT_TRUE(runQCOCleanupPipeline(reference.get()).succeeded()); + printer.record(reference.get(), "Canonicalized Reference QCO IR" + name); + EXPECT_TRUE(verify(*reference).succeeded()); - EXPECT_TRUE( - areModulesEquivalentWithPermutations(program.get(), reference.get())); + EXPECT_TRUE( + areModulesEquivalentWithPermutations(program.get(), reference.get())); +} + +static SmallVector commonConversionCases() { + SmallVector cases; + for (const auto& [name, qcBuilder, qcoBuilder, completeTensorState] : + ::mqt::test::COMMON_QC_QCO_CONVERSION_CASES) { + cases.push_back({ + .name = name, + .programBuilder = qcBuilder, + .referenceBuilder = qcoBuilder, + .expectsCompleteTensorState = completeTensorState, + }); } + return cases; } -// QCToQCO/QubitManagement/StaticOp.cpp +INSTANTIATE_TEST_SUITE_P(CommonCases, QCToQCOTest, + testing::ValuesIn(commonConversionCases())); + INSTANTIATE_TEST_SUITE_P( - QCStaticOpTest, QCToQCOTest, + SpecialCases, QCToQCOTest, testing::Values( - QCToQCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), - MQT_NAMED_BUILDER(qco::staticQubits)}, - QCToQCOTestCase{"StaticQubitsWithOps", - MQT_NAMED_BUILDER(qc::staticQubitsWithOps), - MQT_NAMED_BUILDER(qco::staticQubitsWithOps)}, - QCToQCOTestCase{"StaticQubitsWithParametricOps", - MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps), - MQT_NAMED_BUILDER(qco::staticQubitsWithParametricOps)}, - QCToQCOTestCase{"StaticQubitsWithTwoTargetOps", - MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps), - MQT_NAMED_BUILDER(qco::staticQubitsWithTwoTargetOps)}, - QCToQCOTestCase{"StaticQubitsWithCtrl", - MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl), - MQT_NAMED_BUILDER(qco::staticQubitsWithCtrl)}, - QCToQCOTestCase{"StaticQubitsWithInv", - MQT_NAMED_BUILDER(qc::staticQubitsWithInv), - MQT_NAMED_BUILDER(qco::staticQubitsWithInv)}, QCToQCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), - MQT_NAMED_BUILDER(qco::emptyQCO)})); - -// QCToQCO/Modifiers/PowOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCPowOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"CtrlPowSx", - MQT_NAMED_BUILDER(qc::ctrlPowSx), - MQT_NAMED_BUILDER(qco::ctrlPowSx)}, - QCToQCOTestCase{"PowTwo", MQT_NAMED_BUILDER(qc::powTwo), - MQT_NAMED_BUILDER(qco::powTwo)})); - -// QCToQCO/Modifiers/CtrlOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCCtrlOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"CtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), - MQT_NAMED_BUILDER(qco::ctrlTwo)}, - QCToQCOTestCase{"CtrlTwoMixed", - MQT_NAMED_BUILDER(qc::ctrlTwoMixed), - MQT_NAMED_BUILDER(qco::ctrlTwoMixed)}, - QCToQCOTestCase{"CtrlInvTwo", - MQT_NAMED_BUILDER(qc::ctrlInvTwo), - MQT_NAMED_BUILDER(qco::ctrlInvTwo)})); - -// QCToQCO/Modifiers/InvOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCInvOpTest, QCToQCOTest, - testing::Values( - // iSWAP cannot be inverted with current canonicalization - QCToQCOTestCase{"InverseiSWAP", MQT_NAMED_BUILDER(qc::inverseIswap), - MQT_NAMED_BUILDER(qco::inverseIswap)}, - QCToQCOTestCase{"InverseMultipleControllediSWAP", - MQT_NAMED_BUILDER(qc::inverseMultipleControlledIswap), - MQT_NAMED_BUILDER(qco::inverseMultipleControlledIswap)}, - QCToQCOTestCase{"InvTwo", MQT_NAMED_BUILDER(qc::invTwo), - MQT_NAMED_BUILDER(qco::invTwo)})); - -// QCToQCO/Operations/StandardGates/BarrierOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCBarrierOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"Barrier", MQT_NAMED_BUILDER(qc::barrier), - MQT_NAMED_BUILDER(qco::barrier)}, - QCToQCOTestCase{"BarrierTwoQubits", - MQT_NAMED_BUILDER(qc::barrierTwoQubits), - MQT_NAMED_BUILDER(qco::barrierTwoQubits)}, - QCToQCOTestCase{ - "BarrierMultipleQubits", - MQT_NAMED_BUILDER(qc::barrierMultipleQubits), - MQT_NAMED_BUILDER(qco::barrierMultipleQubits)})); - -// QCToQCO/Operations/StandardGates/DcxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCDCXOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"DCX", MQT_NAMED_BUILDER(qc::dcx), - MQT_NAMED_BUILDER(qco::dcx)}, - QCToQCOTestCase{"SingleControlledDCX", - MQT_NAMED_BUILDER(qc::singleControlledDcx), - MQT_NAMED_BUILDER(qco::singleControlledDcx)}, - QCToQCOTestCase{"MultipleControlledDCX", - MQT_NAMED_BUILDER(qc::multipleControlledDcx), - MQT_NAMED_BUILDER(qco::multipleControlledDcx)})); - -// QCToQCO/Operations/StandardGates/EcrOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCECROpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"ECR", MQT_NAMED_BUILDER(qc::ecr), - MQT_NAMED_BUILDER(qco::ecr)}, - QCToQCOTestCase{"SingleControlledECR", - MQT_NAMED_BUILDER(qc::singleControlledEcr), - MQT_NAMED_BUILDER(qco::singleControlledEcr)}, - QCToQCOTestCase{"MultipleControlledECR", - MQT_NAMED_BUILDER(qc::multipleControlledEcr), - MQT_NAMED_BUILDER(qco::multipleControlledEcr)})); - -// QCToQCO/Operations/StandardGates/GphaseOp.cpp -INSTANTIATE_TEST_SUITE_P(QCGPhaseOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{ - "GlobalPhase", MQT_NAMED_BUILDER(qc::globalPhase), - MQT_NAMED_BUILDER(qco::globalPhase)})); - -// QCToQCO/Operations/StandardGates/HOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCHOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"H", MQT_NAMED_BUILDER(qc::h), - MQT_NAMED_BUILDER(qco::h)}, - QCToQCOTestCase{"SingleControlledH", - MQT_NAMED_BUILDER(qc::singleControlledH), - MQT_NAMED_BUILDER(qco::singleControlledH)}, - QCToQCOTestCase{ - "MultipleControlledH", - MQT_NAMED_BUILDER(qc::multipleControlledH), - MQT_NAMED_BUILDER(qco::multipleControlledH)}, - QCToQCOTestCase{"HWithoutRegister", - MQT_NAMED_BUILDER(qc::hWithoutRegister), - MQT_NAMED_BUILDER(qco::hWithoutRegister)})); - -// QCToQCO/Operations/StandardGates/IdOp.cpp -INSTANTIATE_TEST_SUITE_P(QCIDOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{ - "Identity", MQT_NAMED_BUILDER(qc::identity), - MQT_NAMED_BUILDER(qco::alloc1QubitRegister)})); - -// QCToQCO/Operations/StandardGates/IswapOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCiSWAPOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"iSWAP", MQT_NAMED_BUILDER(qc::iswap), - MQT_NAMED_BUILDER(qco::iswap)}, - QCToQCOTestCase{"SingleControllediSWAP", - MQT_NAMED_BUILDER(qc::singleControlledIswap), - MQT_NAMED_BUILDER(qco::singleControlledIswap)}, - QCToQCOTestCase{"MultipleControllediSWAP", - MQT_NAMED_BUILDER(qc::multipleControlledIswap), - MQT_NAMED_BUILDER(qco::multipleControlledIswap)})); - -// QCToQCO/Operations/StandardGates/POp.cpp -INSTANTIATE_TEST_SUITE_P( - QCPOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"P", MQT_NAMED_BUILDER(qc::p), - MQT_NAMED_BUILDER(qco::p)}, - QCToQCOTestCase{"SingleControlledP", - MQT_NAMED_BUILDER(qc::singleControlledP), - MQT_NAMED_BUILDER(qco::singleControlledP)}, - QCToQCOTestCase{ - "MultipleControlledP", - MQT_NAMED_BUILDER(qc::multipleControlledP), - MQT_NAMED_BUILDER(qco::multipleControlledP)})); - -// QCToQCO/Operations/StandardGates/RCCXOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRCCXOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"RCCX", MQT_NAMED_BUILDER(qc::rccx), - MQT_NAMED_BUILDER(qco::rccx)}, - QCToQCOTestCase{"SingleControlledRCCX", - MQT_NAMED_BUILDER(qc::singleControlledRccx), - MQT_NAMED_BUILDER(qco::singleControlledRccx)}, - QCToQCOTestCase{"MultipleControlledRCCX", - MQT_NAMED_BUILDER(qc::multipleControlledRccx), - MQT_NAMED_BUILDER(qco::multipleControlledRccx)})); - -// QCToQCO/Operations/StandardGates/ROp.cpp -INSTANTIATE_TEST_SUITE_P( - QCROpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"R", MQT_NAMED_BUILDER(qc::r), - MQT_NAMED_BUILDER(qco::r)}, - QCToQCOTestCase{"SingleControlledR", - MQT_NAMED_BUILDER(qc::singleControlledR), - MQT_NAMED_BUILDER(qco::singleControlledR)}, - QCToQCOTestCase{ - "MultipleControlledR", - MQT_NAMED_BUILDER(qc::multipleControlledR), - MQT_NAMED_BUILDER(qco::multipleControlledR)})); - -// QCToQCO/Operations/StandardGates/RxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRXOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"RX", MQT_NAMED_BUILDER(qc::rx), - MQT_NAMED_BUILDER(qco::rx)}, - QCToQCOTestCase{"SingleControlledRX", - MQT_NAMED_BUILDER(qc::singleControlledRx), - MQT_NAMED_BUILDER(qco::singleControlledRx)}, - QCToQCOTestCase{ - "MultipleControlledRX", - MQT_NAMED_BUILDER(qc::multipleControlledRx), - MQT_NAMED_BUILDER(qco::multipleControlledRx)})); - -// QCToQCO/Operations/StandardGates/RxxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRXXOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"RXX", MQT_NAMED_BUILDER(qc::rxx), - MQT_NAMED_BUILDER(qco::rxx)}, - QCToQCOTestCase{"SingleControlledRXX", - MQT_NAMED_BUILDER(qc::singleControlledRxx), - MQT_NAMED_BUILDER(qco::singleControlledRxx)}, - QCToQCOTestCase{"MultipleControlledRXX", - MQT_NAMED_BUILDER(qc::multipleControlledRxx), - MQT_NAMED_BUILDER(qco::multipleControlledRxx)})); - -// QCToQCO/Operations/StandardGates/RyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRYOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"RY", MQT_NAMED_BUILDER(qc::ry), - MQT_NAMED_BUILDER(qco::ry)}, - QCToQCOTestCase{"SingleControlledRY", - MQT_NAMED_BUILDER(qc::singleControlledRy), - MQT_NAMED_BUILDER(qco::singleControlledRy)}, - QCToQCOTestCase{ - "MultipleControlledRY", - MQT_NAMED_BUILDER(qc::multipleControlledRy), - MQT_NAMED_BUILDER(qco::multipleControlledRy)})); - -// QCToQCO/Operations/StandardGates/RyyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRYYOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"RYY", MQT_NAMED_BUILDER(qc::ryy), - MQT_NAMED_BUILDER(qco::ryy)}, - QCToQCOTestCase{"SingleControlledRYY", - MQT_NAMED_BUILDER(qc::singleControlledRyy), - MQT_NAMED_BUILDER(qco::singleControlledRyy)}, - QCToQCOTestCase{"MultipleControlledRYY", - MQT_NAMED_BUILDER(qc::multipleControlledRyy), - MQT_NAMED_BUILDER(qco::multipleControlledRyy)})); - -// QCToQCO/Operations/StandardGates/RzOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRZOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"RZ", MQT_NAMED_BUILDER(qc::rz), - MQT_NAMED_BUILDER(qco::rz)}, - QCToQCOTestCase{"SingleControlledRZ", - MQT_NAMED_BUILDER(qc::singleControlledRz), - MQT_NAMED_BUILDER(qco::singleControlledRz)}, - QCToQCOTestCase{ - "MultipleControlledRZ", - MQT_NAMED_BUILDER(qc::multipleControlledRz), - MQT_NAMED_BUILDER(qco::multipleControlledRz)})); - -// QCToQCO/Operations/StandardGates/RzxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRZXOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"RZX", MQT_NAMED_BUILDER(qc::rzx), - MQT_NAMED_BUILDER(qco::rzx)}, - QCToQCOTestCase{"SingleControlledRZX", - MQT_NAMED_BUILDER(qc::singleControlledRzx), - MQT_NAMED_BUILDER(qco::singleControlledRzx)}, - QCToQCOTestCase{"MultipleControlledRZX", - MQT_NAMED_BUILDER(qc::multipleControlledRzx), - MQT_NAMED_BUILDER(qco::multipleControlledRzx)})); - -// QCToQCO/Operations/StandardGates/RzzOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCRZZOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"RZZ", MQT_NAMED_BUILDER(qc::rzz), - MQT_NAMED_BUILDER(qco::rzz)}, - QCToQCOTestCase{"SingleControlledRZZ", - MQT_NAMED_BUILDER(qc::singleControlledRzz), - MQT_NAMED_BUILDER(qco::singleControlledRzz)}, - QCToQCOTestCase{"MultipleControlledRZZ", - MQT_NAMED_BUILDER(qc::multipleControlledRzz), - MQT_NAMED_BUILDER(qco::multipleControlledRzz)})); - -// QCToQCO/Operations/StandardGates/SOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCSOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"S", MQT_NAMED_BUILDER(qc::s), - MQT_NAMED_BUILDER(qco::s)}, - QCToQCOTestCase{"SingleControlledS", - MQT_NAMED_BUILDER(qc::singleControlledS), - MQT_NAMED_BUILDER(qco::singleControlledS)}, - QCToQCOTestCase{ - "MultipleControlledS", - MQT_NAMED_BUILDER(qc::multipleControlledS), - MQT_NAMED_BUILDER(qco::multipleControlledS)})); - -// QCToQCO/Operations/StandardGates/SdgOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCSdgOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"Sdg", MQT_NAMED_BUILDER(qc::sdg), - MQT_NAMED_BUILDER(qco::sdg)}, - QCToQCOTestCase{"SingleControlledSdg", - MQT_NAMED_BUILDER(qc::singleControlledSdg), - MQT_NAMED_BUILDER(qco::singleControlledSdg)}, - QCToQCOTestCase{"MultipleControlledSdg", - MQT_NAMED_BUILDER(qc::multipleControlledSdg), - MQT_NAMED_BUILDER(qco::multipleControlledSdg)})); - -// QCToQCO/Operations/StandardGates/SwapOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCSWAPOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"SWAP", MQT_NAMED_BUILDER(qc::swap), - MQT_NAMED_BUILDER(qco::swap)}, - QCToQCOTestCase{"SingleControlledSWAP", - MQT_NAMED_BUILDER(qc::singleControlledSwap), - MQT_NAMED_BUILDER(qco::singleControlledSwap)}, - QCToQCOTestCase{"MultipleControlledSWAP", - MQT_NAMED_BUILDER(qc::multipleControlledSwap), - MQT_NAMED_BUILDER(qco::multipleControlledSwap)})); - -// QCToQCO/Operations/StandardGates/SxOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCSXOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"SX", MQT_NAMED_BUILDER(qc::sx), - MQT_NAMED_BUILDER(qco::sx)}, - QCToQCOTestCase{"SingleControlledSX", - MQT_NAMED_BUILDER(qc::singleControlledSx), - MQT_NAMED_BUILDER(qco::singleControlledSx)}, - QCToQCOTestCase{ - "MultipleControlledSX", - MQT_NAMED_BUILDER(qc::multipleControlledSx), - MQT_NAMED_BUILDER(qco::multipleControlledSx)})); - -// QCToQCO/Operations/StandardGates/SxdgOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCSXdgOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"SXdg", MQT_NAMED_BUILDER(qc::sxdg), - MQT_NAMED_BUILDER(qco::sxdg)}, - QCToQCOTestCase{"SingleControlledSXdg", - MQT_NAMED_BUILDER(qc::singleControlledSxdg), - MQT_NAMED_BUILDER(qco::singleControlledSxdg)}, - QCToQCOTestCase{"MultipleControlledSXdg", - MQT_NAMED_BUILDER(qc::multipleControlledSxdg), - MQT_NAMED_BUILDER(qco::multipleControlledSxdg)})); - -// QCToQCO/Operations/StandardGates/TOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCTOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"T", MQT_NAMED_BUILDER(qc::t_), - MQT_NAMED_BUILDER(qco::t_)}, - QCToQCOTestCase{"SingleControlledT", - MQT_NAMED_BUILDER(qc::singleControlledT), - MQT_NAMED_BUILDER(qco::singleControlledT)}, - QCToQCOTestCase{ - "MultipleControlledT", - MQT_NAMED_BUILDER(qc::multipleControlledT), - MQT_NAMED_BUILDER(qco::multipleControlledT)})); - -// QCToQCO/Operations/StandardGates/TdgOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCTdgOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"Tdg", MQT_NAMED_BUILDER(qc::tdg), - MQT_NAMED_BUILDER(qco::tdg)}, - QCToQCOTestCase{"SingleControlledTdg", - MQT_NAMED_BUILDER(qc::singleControlledTdg), - MQT_NAMED_BUILDER(qco::singleControlledTdg)}, - QCToQCOTestCase{"MultipleControlledTdg", - MQT_NAMED_BUILDER(qc::multipleControlledTdg), - MQT_NAMED_BUILDER(qco::multipleControlledTdg)})); - -// QCToQCO/Operations/StandardGates/U2Op.cpp -INSTANTIATE_TEST_SUITE_P( - QCU2OpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"U2", MQT_NAMED_BUILDER(qc::u2), - MQT_NAMED_BUILDER(qco::u2)}, - QCToQCOTestCase{"SingleControlledU2", - MQT_NAMED_BUILDER(qc::singleControlledU2), - MQT_NAMED_BUILDER(qco::singleControlledU2)}, - QCToQCOTestCase{ - "MultipleControlledU2", - MQT_NAMED_BUILDER(qc::multipleControlledU2), - MQT_NAMED_BUILDER(qco::multipleControlledU2)})); - -// QCToQCO/Operations/StandardGates/UOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCUOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"U", MQT_NAMED_BUILDER(qc::u), - MQT_NAMED_BUILDER(qco::u)}, - QCToQCOTestCase{"SingleControlledU", - MQT_NAMED_BUILDER(qc::singleControlledU), - MQT_NAMED_BUILDER(qco::singleControlledU)}, - QCToQCOTestCase{ - "MultipleControlledU", - MQT_NAMED_BUILDER(qc::multipleControlledU), - MQT_NAMED_BUILDER(qco::multipleControlledU)})); - -// QCToQCO/Operations/StandardGates/XOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCXOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"X", MQT_NAMED_BUILDER(qc::x), - MQT_NAMED_BUILDER(qco::x)}, - QCToQCOTestCase{"SingleControlledX", - MQT_NAMED_BUILDER(qc::singleControlledX), - MQT_NAMED_BUILDER(qco::singleControlledX)}, - QCToQCOTestCase{"MultipleControlledX", - MQT_NAMED_BUILDER(qc::multipleControlledX), - MQT_NAMED_BUILDER(qco::multipleControlledX)}, - QCToQCOTestCase{"RepeatedControlledX", - MQT_NAMED_BUILDER(qc::repeatedControlledX), - MQT_NAMED_BUILDER(qco::repeatedControlledX)})); - -// QCToQCO/Operations/StandardGates/XxMinusYyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCXXMinusYYOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"XXMinusYY", MQT_NAMED_BUILDER(qc::xxMinusYY), - MQT_NAMED_BUILDER(qco::xxMinusYY)}, - QCToQCOTestCase{"SingleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY), - MQT_NAMED_BUILDER(qco::singleControlledXxMinusYY)}, - QCToQCOTestCase{"MultipleControlledXXMinusYY", - MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(qco::multipleControlledXxMinusYY)})); - -// QCToQCO/Operations/StandardGates/XxPlusYyOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCXXPlusYYOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"XXPlusYY", MQT_NAMED_BUILDER(qc::xxPlusYY), - MQT_NAMED_BUILDER(qco::xxPlusYY)}, - QCToQCOTestCase{"SingleControlledXXPlusYY", - MQT_NAMED_BUILDER(qc::singleControlledXxPlusYY), - MQT_NAMED_BUILDER(qco::singleControlledXxPlusYY)}, - QCToQCOTestCase{"MultipleControlledXXPlusYY", - MQT_NAMED_BUILDER(qc::multipleControlledXxPlusYY), - MQT_NAMED_BUILDER(qco::multipleControlledXxPlusYY)})); - -// QCToQCO/Operations/StandardGates/YOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCYOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"Y", MQT_NAMED_BUILDER(qc::y), - MQT_NAMED_BUILDER(qco::y)}, - QCToQCOTestCase{"SingleControlledY", - MQT_NAMED_BUILDER(qc::singleControlledY), - MQT_NAMED_BUILDER(qco::singleControlledY)}, - QCToQCOTestCase{ - "MultipleControlledY", - MQT_NAMED_BUILDER(qc::multipleControlledY), - MQT_NAMED_BUILDER(qco::multipleControlledY)})); - -// QCToQCO/Operations/StandardGates/ZOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCZOpTest, QCToQCOTest, - testing::Values(QCToQCOTestCase{"Z", MQT_NAMED_BUILDER(qc::z), - MQT_NAMED_BUILDER(qco::z)}, - QCToQCOTestCase{"SingleControlledZ", - MQT_NAMED_BUILDER(qc::singleControlledZ), - MQT_NAMED_BUILDER(qco::singleControlledZ)}, - QCToQCOTestCase{ - "MultipleControlledZ", - MQT_NAMED_BUILDER(qc::multipleControlledZ), - MQT_NAMED_BUILDER(qco::multipleControlledZ)})); - -// QCToQCO/Operations/MeasureOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCMeasureOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"SingleMeasurementToSingleBit", - MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit), - MQT_NAMED_BUILDER(qco::singleMeasurementToSingleBit)}, - QCToQCOTestCase{"RepeatedMeasurementToSameBit", - MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit), - MQT_NAMED_BUILDER(qco::repeatedMeasurementToSameBit)}, - QCToQCOTestCase{ - "RepeatedMeasurementToDifferentBits", - MQT_NAMED_BUILDER(qc::repeatedMeasurementToDifferentBits), - MQT_NAMED_BUILDER(qco::repeatedMeasurementToDifferentBits)}, - QCToQCOTestCase{ - "MultipleClassicalRegistersAndMeasurements", - MQT_NAMED_BUILDER(qc::multipleClassicalRegistersAndMeasurements), - MQT_NAMED_BUILDER(qco::multipleClassicalRegistersAndMeasurements)}, - QCToQCOTestCase{"PartialMeasurementToRegister", - MQT_NAMED_BUILDER(qc::partialMeasurementToRegister), - MQT_NAMED_BUILDER(qco::partialMeasurementToRegister)}, - QCToQCOTestCase{"DynamicallyIndexedMeasurement", - MQT_NAMED_BUILDER(qc::dynamicallyIndexedMeasurement), - MQT_NAMED_BUILDER(qco::dynamicallyIndexedMeasurement), - true}, - QCToQCOTestCase{"MeasurementWithoutRegisters", - MQT_NAMED_BUILDER(qc::measurementWithoutRegisters), - MQT_NAMED_BUILDER(qco::measurementWithoutRegisters)})); - -// QCToQCO/Operations/ResetOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCResetOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"ResetQubitAfterSingleOp", - MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp), - MQT_NAMED_BUILDER(qco::resetQubitAfterSingleOp)}, - QCToQCOTestCase{ - "ResetMultipleQubitsAfterSingleOp", - MQT_NAMED_BUILDER(qc::resetMultipleQubitsAfterSingleOp), - MQT_NAMED_BUILDER(qco::resetMultipleQubitsAfterSingleOp)}, + MQT_NAMED_BUILDER(qco::emptyQCO)}, + QCToQCOTestCase{"Identity", MQT_NAMED_BUILDER(qc::identity), + MQT_NAMED_BUILDER(qco::alloc1QubitRegister)}, QCToQCOTestCase{"RepeatedResetAfterSingleOp", MQT_NAMED_BUILDER(qc::repeatedResetAfterSingleOp), - MQT_NAMED_BUILDER(qco::resetQubitAfterSingleOp)})); - -// QCToQCO/Operations/IfOp.cpp -INSTANTIATE_TEST_SUITE_P( - SCFIfOpTest, QCToQCOTest, - testing::Values( + MQT_NAMED_BUILDER(qco::resetQubitAfterSingleOp)}, QCToQCOTestCase{"SimpleIfOp", MQT_NAMED_BUILDER(qc::simpleIf), MQT_NAMED_BUILDER(qco::simpleIfCompleteTensorState), true}, @@ -1965,41 +1741,13 @@ INSTANTIATE_TEST_SUITE_P( QCToQCOTestCase{"IfWithCreg", MQT_NAMED_BUILDER(qc::ifWithCreg), MQT_NAMED_BUILDER(qco::ifWithCregCompleteTensorState), true}, - QCToQCOTestCase{"NestedIfOpForLoop", - MQT_NAMED_BUILDER(qc::nestedIfOpForLoop), - MQT_NAMED_BUILDER(qco::nestedIfOpForLoop), true})); - -// QCToQCO/Operations/IndexSwitchOp.cpp -INSTANTIATE_TEST_SUITE_P( - QCOIndexSwitchOpTest, QCToQCOTest, - testing::Values( QCToQCOTestCase{ "SimpleIndexSwitchOp", MQT_NAMED_BUILDER(qc::simpleIndexSwitch), MQT_NAMED_BUILDER(qco::simpleIndexSwitchCompleteTensorState), true}, QCToQCOTestCase{ "IndexSwitchMultiCase", MQT_NAMED_BUILDER(qc::indexSwitchMultiCase), MQT_NAMED_BUILDER(qco::indexSwitchMultiCaseCompleteTensorState), - true})); - -// QCToQCO/Operations/WhileOp.cpp -INSTANTIATE_TEST_SUITE_P( - SCFWhileOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"SimpleWhile", MQT_NAMED_BUILDER(qc::simpleWhileReset), - MQT_NAMED_BUILDER(qco::simpleWhileReset)}, - QCToQCOTestCase{"SimpleDoWhile", - MQT_NAMED_BUILDER(qc::simpleDoWhileReset), - MQT_NAMED_BUILDER(qco::simpleDoWhileReset)})); - -// QCToQCO/Operations/ForOp.cpp -INSTANTIATE_TEST_SUITE_P( - SCFForOpTest, QCToQCOTest, - testing::Values( - QCToQCOTestCase{"SimpleForLoop", MQT_NAMED_BUILDER(qc::simpleForLoop), - MQT_NAMED_BUILDER(qco::simpleForLoop), true}, - QCToQCOTestCase{"NestedForLoopIfOp", - MQT_NAMED_BUILDER(qc::nestedForLoopIfOp), - MQT_NAMED_BUILDER(qco::nestedForLoopIfOp), true}, + true}, QCToQCOTestCase{ "NestedForLoopWhileOp", MQT_NAMED_BUILDER(qc::nestedForLoopWhileOp), MQT_NAMED_BUILDER(qco::nestedForLoopWhileOpCompleteTensorState), @@ -2009,10 +1757,6 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::nestedForLoopSwitchOp), MQT_NAMED_BUILDER(qco::nestedForLoopSwitchOpCompleteTensorState), true}, - QCToQCOTestCase{ - "NestedForLoopCtrlOpWithSeparateQubit", - MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithSeparateQubit), - MQT_NAMED_BUILDER(qco::nestedForLoopCtrlOpWithSeparateQubit), true}, QCToQCOTestCase{ "NestedForLoopCtrlOpWithExtractedQubit", MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit), diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 5b0df25b3c..8d3deb6bb2 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -367,7 +367,7 @@ TEST_F(QCTest, BuilderSupportsIndependentClassicalRegisterInitialization) { "undefined"); } -TEST_F(QCTest, BuilderDeallocatesDynamicResourcesDeterministically) { +TEST_F(QCTest, BuilderDisposesOnlyUnreturnedResourcesInAllocationOrder) { QCProgramBuilder builder(context.get()); builder.initialize(); @@ -378,8 +378,17 @@ TEST_F(QCTest, BuilderDeallocatesDynamicResourcesDeterministically) { allocatedRegisters.push_back(builder.allocQubitRegisterStorage(1)); } - auto moduleOp = builder.finalize(); + SmallVector returned{ + allocatedQubits.pop_back_val(), + allocatedRegisters.pop_back_val(), + }; + builder.retype(ValueRange(returned).getTypes()); + auto moduleOp = builder.finalize(returned); ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + auto mainFunc = mlir::mqt::getEntryPoint(*moduleOp); + auto terminator = cast(mainFunc.getBody().front().back()); + EXPECT_TRUE(llvm::equal(terminator.getOperands(), returned)); SmallVector deallocatedQubits; SmallVector deallocatedRegisters; diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index a3179f7a61..8d910e7a06 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -693,6 +693,62 @@ TEST_F(QCOTest, BuilderRejectsIncompleteRegisterBoundaries) { } } +TEST_F(QCOTest, BuilderRejectsQuantumCapturesAndEscapes) { + for (unsigned scenario = 0; scenario < 4; ++scenario) { + SCOPED_TRACE(scenario); + EXPECT_DEATH( + ([&] { + QCOProgramBuilder builder(context.get()); + builder.initialize(); + auto qubit = builder.allocQubit(); + auto tensor = builder.qtensorAlloc(1); + if (scenario < 2) { + builder.qcoIf(true, ValueRange{}, [&](ValueRange) { + if (scenario == 0) { + builder.h(qubit); + } else { + builder.qtensorExtract(tensor, 0); + } + return SmallVector{}; + }); + } else if (scenario == 2) { + builder.qcoIf(true, tensor, [&](Value argument) { + return builder.qtensorExtract(argument, 0).first; + }); + builder.finalize(); + } else { + builder.inv(qubit, [&](Value argument) { + builder.qtensorExtract(tensor, 0); + return argument; + }); + } + }()), + "Quantum values must be passed explicitly through region arguments"); + } +} + +TEST_F(QCOTest, BuilderRejectsChangedTensorSlots) { + for (unsigned scenario = 0; scenario < 3; ++scenario) { + SCOPED_TRACE(scenario); + EXPECT_DEATH( + ([&] { + QCOProgramBuilder builder(context.get()); + builder.initialize(); + auto [tensor, qubit] = + builder.qtensorExtract(builder.qtensorAlloc(2), 0); + if (scenario == 1) { + tensor = builder.qtensorAlloc(2); + } else if (scenario == 2) { + qubit = builder.allocQubit(); + } + builder.qtensorInsert(qubit, tensor, scenario == 0 ? 1 : 0); + }()), + scenario == 0 + ? "Inserted qubit must return to its original slot" + : "Inserted qubit must belong to the destination register"); + } +} + TEST_F(QCOTest, BuilderRejectsChangedTensorRegisterAssociations) { EXPECT_DEATH(([&] { QCOProgramBuilder builder(context.get()); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp b/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp index d992300442..0afbfb786c 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp @@ -355,22 +355,35 @@ TEST_F(WireIteratorFixture, UnitaryCallContinuesWire) { QCOProgramBuilder builder(context.get()); builder.initialize(); auto flip = builder.createUnitaryFunction( - "flip", TypeRange{QubitType::get(context.get())}, + "flip", + TypeRange{ + builder.getF64Type(), + QubitType::get(context.get()), + QubitType::get(context.get()), + }, [&](ValueRange arguments) { - return SmallVector{builder.x(arguments[0])}; + return SmallVector{ + builder.rx(arguments[0], arguments[1]), + builder.x(arguments[2]), + }; }); - Value input = builder.allocQubit(); - Value output = builder.call(flip, input).front(); - builder.sink(output); + SmallVector inputs{builder.allocQubit(), builder.allocQubit()}; + auto outputs = + builder.call(flip, {builder.floatConstant(0.5), inputs[0], inputs[1]}); + for (auto output : outputs) { + builder.sink(output); + } auto moduleOp = builder.finalize(); ASSERT_TRUE(moduleOp); - WireIterator iterator(input); - ++iterator; - EXPECT_TRUE(isa(iterator.operation())); - EXPECT_EQ(iterator.qubit(), output); - --iterator; - EXPECT_EQ(iterator.qubit(), input); + for (size_t i = 0; i < inputs.size(); ++i) { + WireIterator iterator(inputs[i]); + ++iterator; + EXPECT_TRUE(isa(iterator.operation())); + EXPECT_EQ(iterator.qubit(), outputs[i]); + --iterator; + EXPECT_EQ(iterator.qubit(), inputs[i]); + } } TEST_F(WireIteratorFixture, GenericCallIsWireBoundary) { diff --git a/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp b/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp index 1f9830cece..e0d7041f4f 100644 --- a/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp +++ b/mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp @@ -336,6 +336,7 @@ TEST_F(QTensorTest, NestedRegisterAccessInsideModifierFailsVerification) { namespace { enum class AdjacentIndexKind : std::uint8_t { EqualConstants, + DifferentConstants, IdenticalDynamicValue, PotentiallyAliasingDynamicValues, }; @@ -358,9 +359,11 @@ buildAdjacentInsertExtractProgram(MLIRContext* context, Value insertIndex; Value extractIndex; - if (indexKind == AdjacentIndexKind::EqualConstants) { + if (indexKind == AdjacentIndexKind::EqualConstants || + indexKind == AdjacentIndexKind::DifferentConstants) { insertIndex = arith::ConstantIndexOp::create(b, 0); - extractIndex = arith::ConstantIndexOp::create(b, 0); + extractIndex = arith::ConstantIndexOp::create( + b, indexKind == AdjacentIndexKind::EqualConstants ? 0 : 1); } else { insertIndex = block->getArgument(0); extractIndex = indexKind == AdjacentIndexKind::IdenticalDynamicValue @@ -394,11 +397,7 @@ static LogicalResult canonicalize(ModuleOp moduleOp) { static OwningOpRef buildTwoQubitInsertChainProgram(MLIRContext* context, - const bool reverseInsertOrder, - const bool swapInsertTargets) { - const int64_t q0Target = swapInsertTargets ? 1 : 0; - const int64_t q1Target = swapInsertTargets ? 0 : 1; - + const bool reverseInsertOrder) { QCOProgramBuilder builder(context); builder.initialize(); @@ -410,11 +409,11 @@ buildTwoQubitInsertChainProgram(MLIRContext* context, std::tie(tensor, q1) = builder.qtensorExtract(tensor, 1); if (reverseInsertOrder) { - tensor = builder.qtensorInsert(q1, tensor, q1Target); - tensor = builder.qtensorInsert(q0, tensor, q0Target); + tensor = builder.qtensorInsert(q1, tensor, 1); + tensor = builder.qtensorInsert(q0, tensor, 0); } else { - tensor = builder.qtensorInsert(q0, tensor, q0Target); - tensor = builder.qtensorInsert(q1, tensor, q1Target); + tensor = builder.qtensorInsert(q0, tensor, 0); + tensor = builder.qtensorInsert(q1, tensor, 1); } return builder.finalize(); @@ -468,41 +467,31 @@ buildResetWithSameIndexInsertProgram(MLIRContext* context, namespace { -TEST_F(QTensorTest, AdjacentInsertExtractFoldsEqualConstants) { - auto moduleOp = buildAdjacentInsertExtractProgram( - context.get(), AdjacentIndexKind::EqualConstants); - ASSERT_TRUE(moduleOp); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - ASSERT_TRUE(succeeded(canonicalize(*moduleOp))); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - EXPECT_EQ(countOps(*moduleOp), 1U); - EXPECT_EQ(countOps(*moduleOp), 1U); -} - -TEST_F(QTensorTest, AdjacentInsertExtractFoldsIdenticalDynamicValue) { - auto moduleOp = buildAdjacentInsertExtractProgram( - context.get(), AdjacentIndexKind::IdenticalDynamicValue); - ASSERT_TRUE(moduleOp); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - ASSERT_TRUE(succeeded(canonicalize(*moduleOp))); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - EXPECT_EQ(countOps(*moduleOp), 1U); - EXPECT_EQ(countOps(*moduleOp), 1U); -} - -TEST_F(QTensorTest, AdjacentInsertExtractKeepsPotentialDynamicAlias) { - auto moduleOp = buildAdjacentInsertExtractProgram( - context.get(), AdjacentIndexKind::PotentiallyAliasingDynamicValues); - ASSERT_TRUE(moduleOp); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - ASSERT_TRUE(succeeded(canonicalize(*moduleOp))); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - EXPECT_EQ(countOps(*moduleOp), 2U); - EXPECT_EQ(countOps(*moduleOp), 2U); +TEST_F(QTensorTest, AdjacentInsertExtractFoldsOnlyEqualIndices) { + for (const auto& [name, indexKind, expectedCount] : { + std::tuple{"equal constants", AdjacentIndexKind::EqualConstants, 1U}, + std::tuple{"different constants", + AdjacentIndexKind::DifferentConstants, 2U}, + std::tuple{"same dynamic value", + AdjacentIndexKind::IdenticalDynamicValue, 1U}, + std::tuple{"potential dynamic alias", + AdjacentIndexKind::PotentiallyAliasingDynamicValues, 2U}, + }) { + SCOPED_TRACE(name); + auto moduleOp = buildAdjacentInsertExtractProgram(context.get(), indexKind); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + ASSERT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + ASSERT_TRUE(succeeded(canonicalize(*moduleOp))); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + EXPECT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + EXPECT_EQ(countOps(*moduleOp), expectedCount); + EXPECT_EQ(countOps(*moduleOp), expectedCount); + } } TEST_F(QTensorTest, InsertChainCanonicalizationRemainsLocal) { - auto program = buildTwoQubitInsertChainProgram(context.get(), false, false); + auto program = buildTwoQubitInsertChainProgram(context.get(), false); ASSERT_TRUE(program); ASSERT_TRUE(verify(*program).succeeded()); ASSERT_TRUE(succeeded(qco::verifyLinearity(*program))); @@ -510,7 +499,7 @@ TEST_F(QTensorTest, InsertChainCanonicalizationRemainsLocal) { ASSERT_TRUE(verify(*program).succeeded()); ASSERT_TRUE(succeeded(qco::verifyLinearity(*program))); - auto reference = buildTwoQubitInsertChainProgram(context.get(), true, false); + auto reference = buildTwoQubitInsertChainProgram(context.get(), true); ASSERT_TRUE(reference); ASSERT_TRUE(verify(*reference).succeeded()); ASSERT_TRUE(succeeded(qco::verifyLinearity(*reference))); @@ -522,23 +511,6 @@ TEST_F(QTensorTest, InsertChainCanonicalizationRemainsLocal) { EXPECT_EQ(countOps(*reference), 0U); } -TEST_F(QTensorTest, InsertChainDifferentAssignmentsNotEquivalent) { - auto program = buildTwoQubitInsertChainProgram(context.get(), false, false); - ASSERT_TRUE(program); - EXPECT_TRUE(verify(*program).succeeded()); - EXPECT_TRUE(runQCOCleanupPipeline(program.get()).succeeded()); - EXPECT_TRUE(verify(*program).succeeded()); - - auto reference = buildTwoQubitInsertChainProgram(context.get(), true, true); - ASSERT_TRUE(reference); - EXPECT_TRUE(verify(*reference).succeeded()); - EXPECT_TRUE(runQCOCleanupPipeline(reference.get()).succeeded()); - EXPECT_TRUE(verify(*reference).succeeded()); - - EXPECT_FALSE( - areModulesEquivalentWithPermutations(program.get(), reference.get())); -} - TEST_F(QTensorTest, ResetAfterExtractThroughCommutingInsertIsEliminated) { auto program = buildResetWithCommutingInsertProgram(context.get(), true); ASSERT_TRUE(program); @@ -851,18 +823,10 @@ INSTANTIATE_TEST_SUITE_P( "QTensorExtractInsertSameIndex", MQT_NAMED_BUILDER(qtensorExtractInsertSameIndex), MQT_NAMED_BUILDER(qtensorAlloc)}, - QTensorIntegrationTestCase{ - "QTensorExtractInsertIndexMismatch", - MQT_NAMED_BUILDER(qtensorExtractInsertIndexMismatch), - MQT_NAMED_BUILDER(qtensorExtractInsertIndexMismatch)}, QTensorIntegrationTestCase{ "QTensorInsertExtractSameIndex", MQT_NAMED_BUILDER(qtensorInsertExtractSameIndex), MQT_NAMED_BUILDER(qtensorInsert)}, - QTensorIntegrationTestCase{ - "QTensorInsertExtractIndexMismatch", - MQT_NAMED_BUILDER(qtensorInsertExtractIndexMismatch), - MQT_NAMED_BUILDER(qtensorInsertExtractIndexMismatch)}, QTensorIntegrationTestCase{"QTensorAlternativeInsertChain", MQT_NAMED_BUILDER(qtensorAlternativeChain), MQT_NAMED_BUILDER(qtensorChain)})); diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index bd506917bb..0bebb65b4b 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -11,6 +11,8 @@ #include "mqt/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mqt/Dialect/QCO/IR/QCODialect.h" #include "mqt/Dialect/QCO/IR/QCOOps.h" +#include "mqt/Dialect/QCO/QCOUtils.h" +#include "mqt/Dialect/QCO/Utils/WireIterator.h" #include "mqt/Dialect/QTensor/IR/QTensorDialect.h" #include "mqt/Dialect/QTensor/IR/QTensorOps.h" #include "mqt/Dialect/QTensor/Utils/TensorIterator.h" @@ -378,79 +380,63 @@ TEST_F(TensorIteratorTest, TraversesMixedResultConditionals) { } TEST_F(TensorIteratorTest, TraversesWhileCarriedTensors) { - QCOProgramBuilder builder(context.get()); - builder.initialize(); - - auto scalar0 = builder.floatConstant(1.0); - auto tensor0 = builder.qtensorAlloc(2); - auto tensor1 = builder.qtensorAlloc(3); - auto loop = scf::WhileOp::create( - builder, builder.getLoc(), - TypeRange{builder.getI64Type(), tensor1.getType(), tensor0.getType()}, - ValueRange{scalar0, tensor0, tensor1}); - const SmallVector locations(3, builder.getLoc()); - auto* before = builder.createBlock( - &loop.getBefore(), {}, ValueRange{scalar0, tensor0, tensor1}.getTypes(), - locations); - builder.setInsertionPointToStart(before); - auto scalar1 = builder.intConstant(1); - scf::ConditionOp::create( - builder, builder.getLoc(), builder.boolConstant(false), - ValueRange{scalar1, before->getArgument(2), before->getArgument(1)}); - auto* after = builder.createBlock(&loop.getAfter(), {}, loop.getResultTypes(), - locations); - builder.setInsertionPointToStart(after); - scf::YieldOp::create(builder, builder.getLoc(), - ValueRange{ - builder.floatConstant(2.0), - after->getArgument(2), - after->getArgument(1), - }); - builder.setInsertionPointAfter(loop); - auto tensor0Result = loop.getResult(2); - auto tensor1Result = loop.getResult(1); - qtensor::DeallocOp::create(builder, builder.getLoc(), tensor0Result); - qtensor::DeallocOp::create(builder, builder.getLoc(), tensor1Result); - ASSERT_TRUE(succeeded(verify(loop))); - - TensorIterator beforeRegionIterator( - cast>(before->getArgument(1))); - ++beforeRegionIterator; - ASSERT_TRUE(isa(beforeRegionIterator.operation())); - ASSERT_EQ(beforeRegionIterator.tensor(), nullptr); - - TensorIterator iterator(cast>(tensor0)); - ASSERT_EQ(iterator.operation(), tensor0.getDefiningOp()); - ASSERT_EQ(iterator.tensor(), tensor0); - - ++iterator; - ASSERT_TRUE(isa(iterator.operation())); - ASSERT_EQ(iterator.tensor(), tensor0Result); - - ++iterator; - ASSERT_TRUE(isa(iterator.operation())); - ASSERT_EQ(iterator.tensor(), nullptr); - - ++iterator; - ASSERT_EQ(iterator, std::default_sentinel); - - --iterator; - ASSERT_TRUE(isa(iterator.operation())); - ASSERT_EQ(iterator.tensor(), nullptr); - - --iterator; - ASSERT_TRUE(isa(iterator.operation())); - ASSERT_EQ(iterator.tensor(), tensor0Result); - - --iterator; - ASSERT_EQ(iterator.operation(), tensor0.getDefiningOp()); - ASSERT_EQ(iterator.tensor(), tensor0); - - TensorIterator swapped(cast>(tensor1)); - ++swapped; - ASSERT_TRUE(isa(swapped.operation())); - ASSERT_EQ(swapped.tensor(), tensor1Result); - --swapped; - ASSERT_EQ(swapped.operation(), tensor1.getDefiningOp()); - ASSERT_EQ(swapped.tensor(), tensor1); + auto moduleOp = parseSourceString(R"mlir( + func.func private @touch(%t: tensor<2x!qco.qubit>) -> tensor<2x!qco.qubit> { + return %t : tensor<2x!qco.qubit> + } + func.func @main() { + %c2 = arith.constant 2 : index + %c3 = arith.constant 3 : index + %zero = arith.constant 0.0 : f64 + %false = arith.constant false + %t0 = qtensor.alloc(%c2) : tensor<2x!qco.qubit> + %q = qco.alloc : !qco.qubit + %t1 = qtensor.alloc(%c3) : tensor<3x!qco.qubit> + %out:4 = scf.while (%c = %zero, %a = %t0, %b = %q, %d = %t1) + : (f64, tensor<2x!qco.qubit>, !qco.qubit, tensor<3x!qco.qubit>) + -> (tensor<2x!qco.qubit>, !qco.qubit, i1, tensor<3x!qco.qubit>) { + %called = func.call @touch(%a) : (tensor<2x!qco.qubit>) -> tensor<2x!qco.qubit> + scf.condition(%false) %called, %b, %false, %d + : tensor<2x!qco.qubit>, !qco.qubit, i1, tensor<3x!qco.qubit> + } do { + ^bb0(%a: tensor<2x!qco.qubit>, %b: !qco.qubit, %c: i1, %d: tensor<3x!qco.qubit>): + scf.yield %zero, %a, %b, %d + : f64, tensor<2x!qco.qubit>, !qco.qubit, tensor<3x!qco.qubit> + } + qtensor.dealloc %out#0 : tensor<2x!qco.qubit> + qco.sink %out#1 : !qco.qubit + qtensor.dealloc %out#3 : tensor<3x!qco.qubit> + return + } + )mlir", + context.get()); + ASSERT_TRUE(moduleOp); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + ASSERT_TRUE(succeeded(qco::verifyLinearity(*moduleOp))); + scf::WhileOp loop; + moduleOp->walk([&](scf::WhileOp op) { loop = op; }); + ASSERT_TRUE(loop); + qco::WireIterator wire(loop.getInits()[2]); + ++wire; + EXPECT_EQ(wire.qubit(), loop.getResult(1)); + --wire; + EXPECT_EQ(wire.qubit(), loop.getInits()[2]); + for (auto [input, output] : {std::pair{1U, 0U}, std::pair{3U, 3U}}) { + auto tensor = cast>(loop.getInits()[input]); + TensorIterator iterator(tensor); + ++iterator; + EXPECT_EQ(iterator.operation(), loop.getOperation()); + EXPECT_EQ(iterator.tensor(), loop.getResult(output)); + ++iterator; + EXPECT_TRUE(isa(iterator.operation())); + EXPECT_EQ(iterator.tensor(), nullptr); + ++iterator; + EXPECT_EQ(iterator, std::default_sentinel); + --iterator; + --iterator; + EXPECT_EQ(iterator.tensor(), loop.getResult(output)); + --iterator; + EXPECT_EQ(iterator.tensor(), tensor); + EXPECT_EQ(iterator.operation(), tensor.getDefiningOp()); + } } diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 0ee7133358..37f9c62049 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -4530,13 +4530,6 @@ Value qtensorInsert(QCOProgramBuilder& b) { return measureAndReturn(b, {}); } -Value qtensorExtractInsertIndexMismatch(QCOProgramBuilder& b) { - auto qtensor = b.qtensorAlloc(3); - auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); - (void)b.qtensorInsert(q0, extractOutTensor, 1); - return measureAndReturn(b, {}); -} - Value qtensorExtractInsertSameIndex(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); @@ -4544,16 +4537,6 @@ Value qtensorExtractInsertSameIndex(QCOProgramBuilder& b) { return measureAndReturn(b, {}); } -Value qtensorInsertExtractIndexMismatch(QCOProgramBuilder& b) { - auto qtensor = b.qtensorAlloc(3); - auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); - auto q1 = b.h(q0); - auto insertOutTensor = b.qtensorInsert(q1, extractOutTensor, 0); - auto [extractOutTensor1, q2] = b.qtensorExtract(insertOutTensor, 1); - (void)b.qtensorInsert(q2, extractOutTensor1, 0); - return measureAndReturn(b, {}); -} - Value qtensorInsertExtractSameIndex(QCOProgramBuilder& b) { auto qtensor = b.qtensorAlloc(3); auto [extractOutTensor, q0] = b.qtensorExtract(qtensor, 0); diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index 3550463107..837d60fc0a 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -1544,17 +1544,9 @@ Value qtensorExtract(QCOProgramBuilder& b); /// Inserts a qubit into a tensor. Value qtensorInsert(QCOProgramBuilder& b); -/// Extracts a qubit from a tensor and inserts it immediately at a different -/// index. -Value qtensorExtractInsertIndexMismatch(QCOProgramBuilder& b); - /// Extracts a qubit from a tensor and inserts it immediately at the same index. Value qtensorExtractInsertSameIndex(QCOProgramBuilder& b); -/// Inserts a qubit into a tensor and extracts it immediately at a different -/// index. -Value qtensorInsertExtractIndexMismatch(QCOProgramBuilder& b); - /// Inserts a qubit into a tensor and extracts it immediately at the same index. Value qtensorInsertExtractSameIndex(QCOProgramBuilder& b);