✨ Add Dead Gate Elimination Pattern#1755
Conversation
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
📝 WalkthroughWalkthroughThis PR adds isOutput tracking to QASM3 declarations and an addBarrier flag to measureAll, marks QCO operations Pure with canonicalization enabled, reworks QC/QCO/QIR program builders to support explicit return types/values, adds selective QIR measurement output recording, introduces a dead-gate elimination canonicalization pattern, adds IfOp qubit input/output mapping helpers, and updates the entire test/program-fixture suite accordingly. ChangesQASM3 Outputs, measureAll Barrier, and QCO Op Traits
Estimated code review effort: 4 (Complex) | ~75 minutes Program Builder Return-Value API and Measurement Recording
Estimated code review effort: 5 (Critical) | ~150 minutes Sequence Diagram(s)sequenceDiagram
participant QCProgramBuilder
participant QCToQIRBase
participant QIRCommon
participant stripReturnedMeasurements
QCProgramBuilder->>QCProgramBuilder: build(callback) -> (values, types)
QCProgramBuilder->>QCProgramBuilder: retype(types)
QCProgramBuilder->>QCProgramBuilder: finalize(values)
QCToQIRBase->>stripReturnedMeasurements: strip qc.measure operands from func.return
stripReturnedMeasurements->>QCToQIRBase: state.returnedMeasurements updated
QCToQIRBase->>QIRCommon: lower qc.measure with shouldRecord flag
QIRCommon->>QIRCommon: addOutputRecording (only for recorded indices/arrays)
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mlir/include/mlir/Dialect/QCO/QCOUtils.h`:
- Line 252: Update the comment to use the dialect's terminology: replace
"deallocs" with "sinks" or "SinkOp" so it matches the check against SinkOp in
the code; e.g., modify the comment above the removal check to read something
like "If the operation is only used by sinks (SinkOp), we can safely remove it."
Ensure the comment near the function that inspects SinkOp usage (referencing
SinkOp) is updated for consistency.
In `@mlir/lib/Dialect/QCO/IR/QCOOps.cpp`:
- Around line 38-54: Add a Doxygen-style comment block for the
DeadGateElimination struct describing its purpose and behavior; place a brief
/** ... */ above the struct declaration for DeadGateElimination, mention that it
is an OpInterfaceRewritePattern for UnitaryOpInterface, summarize that
matchAndRewrite skips ops with no uses (while excluding GPhase/inverse) and
delegates removal to checkAndRemoveDeadGate, and keep the description concise
per repository guidelines.
In `@mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp`:
- Around line 239-241: Update the Doxygen summary for the DeadIfRemoval pass to
accurately describe its purpose: replace the misleading "Remove dead resets"
summary with a concise Doxygen-style line such as "Canonicalize and remove dead
IfOp patterns" (or "Remove dead IfOps / canonicalize IfOp patterns") so that the
comment above the DeadIfRemoval class/function reflects that it canonicalizes
IfOps; ensure the comment uses proper Doxygen syntax and is placed directly
above the DeadIfRemoval symbol.
- Around line 245-247: Guard the removal of IfOp in matchAndRewrite by ensuring
branch regions contain no side-effecting operations before calling
checkAndRemoveDeadGate; specifically, before erasing op in matchAndRewrite (or
inside checkAndRemoveDeadGate) inspect op.thenRegion() and op.elseRegion() (or
the region bodies) and bail out if any contained Operation
mayHaveSideEffects()/hasTrait<OpTrait::HasSideEffects>() or reports
MemoryEffects—only allow deletion when both regions are empty or provably
side-effect-free. Ensure you reference IfOp, matchAndRewrite,
checkAndRemoveDeadGate, thenRegion/elseRegion and use the Operation side-effect
query APIs to make the decision.
In `@mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp`:
- Around line 117-148: Extend the test to also exercise dead-gate elimination
for measurement and conditional ops by adding a measurement (MeasureOp) whose
result is unused and an IfOp that produces an output which is never consumed,
then sinking those unused outputs and ensuring the cleaned module matches a
reference without those ops; specifically, in the TEST_F(QCOTest,
CheckDeadGateElimination) case, use QCOProgramBuilder methods that create a
measurement (e.g., builder.measure or MeasureOp via builder) and an
if/conditional op (e.g., builder.ifOp/IfOp) producing qubits/bits, do not
consume their results (call builder.sink on those outputs), finalize module and
create a reference program that omits the measurement and if op, then run
verify, runQCOCleanupPipeline, verify again and assert
areModulesEquivalentWithPermutations(module.get(), ref.get()) to validate the
dead-gate paths for MeasureOp and IfOp.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0906695c-971e-4416-bb67-64f8691df3f6
📒 Files selected for processing (9)
CHANGELOG.mdmlir/include/mlir/Dialect/QCO/IR/QCODialect.tdmlir/include/mlir/Dialect/QCO/IR/QCOOps.tdmlir/include/mlir/Dialect/QCO/QCOUtils.hmlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cppmlir/lib/Dialect/QCO/IR/Operations/ResetOp.cppmlir/lib/Dialect/QCO/IR/QCOOps.cppmlir/lib/Dialect/QCO/IR/SCF/IfOp.cppmlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
mlir/lib/Dialect/QCO/IR/QCOOps.cpp (1)
44-46:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix the Doxygen summary for
DeadGateElimination.The comment says "Remove dead measurements" but this pattern operates on
UnitaryOpInterface, notMeasureOp. The description should match the pattern's scope.✏️ Suggested fix
/** - * `@brief` Remove dead measurements. + * `@brief` Remove dead unitary operations (gates). */🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mlir/lib/Dialect/QCO/IR/QCOOps.cpp` around lines 44 - 46, Update the Doxygen summary for DeadGateElimination to accurately describe its scope: replace "Remove dead measurements" with a brief line stating that DeadGateElimination removes dead gates (or eliminates operations) on UnitaryOpInterface instances rather than referring to MeasureOp; ensure the summary references DeadGateElimination and UnitaryOpInterface (and remove or avoid mentioning MeasureOp) so the comment matches the pattern's actual target.mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp (1)
245-248:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winGuard dead
IfOpelimination against side effects inside the branches.
IfOphasRecursiveMemoryEffects, so its memory effects are derived from child operations. Without checkingisMemoryEffectFree(op), this pattern can incorrectly remove anIfOpwhose branches contain side-effecting operations (e.g.,GPhaseOpwithMemWrite).The analogous
DeadGateEliminationpattern inQCOOps.cppcorrectly guards with!isMemoryEffectFree(op).🐛 Proposed fix to add memory-effect guard
struct DeadIfRemoval final : OpRewritePattern<IfOp> { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(IfOp op, PatternRewriter& rewriter) const override { + if (!isMemoryEffectFree(op)) { + return failure(); + } return checkAndRemoveDeadGate(op, rewriter); } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp` around lines 245 - 248, The matchAndRewrite implementation for IfOp currently always calls checkAndRemoveDeadGate(op, rewriter) and can remove IfOp with side-effecting branch ops; update the pattern to first check isMemoryEffectFree(op) (like DeadGateElimination in QCOOps.cpp) and only call checkAndRemoveDeadGate when the check passes (i.e., skip removal if !isMemoryEffectFree(op)). Ensure you reference the IfOp instance passed into matchAndRewrite and use the existing isMemoryEffectFree utility to guard the removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mlir/include/mlir/Dialect/QCO/QCOUtils.h`:
- Around line 250-251: Replace the use of std::all_of with llvm::all_of for the
users check: change the call that currently reads
std::all_of(op->getUsers().begin(), op->getUsers().end(), [](Operation* user){
return isa<SinkOp>(user); }) to use llvm::all_of with the same range and lambda
(i.e., llvm::all_of(op->getUsers(), [](Operation *user) { return
isa<SinkOp>(user); }) or llvm::all_of(op->getUsers().begin(),
op->getUsers().end(), ...)); keep the op->getUsers() range and the isa<SinkOp>
predicate intact and ensure the appropriate LLVM header is available.
---
Duplicate comments:
In `@mlir/lib/Dialect/QCO/IR/QCOOps.cpp`:
- Around line 44-46: Update the Doxygen summary for DeadGateElimination to
accurately describe its scope: replace "Remove dead measurements" with a brief
line stating that DeadGateElimination removes dead gates (or eliminates
operations) on UnitaryOpInterface instances rather than referring to MeasureOp;
ensure the summary references DeadGateElimination and UnitaryOpInterface (and
remove or avoid mentioning MeasureOp) so the comment matches the pattern's
actual target.
In `@mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp`:
- Around line 245-248: The matchAndRewrite implementation for IfOp currently
always calls checkAndRemoveDeadGate(op, rewriter) and can remove IfOp with
side-effecting branch ops; update the pattern to first check
isMemoryEffectFree(op) (like DeadGateElimination in QCOOps.cpp) and only call
checkAndRemoveDeadGate when the check passes (i.e., skip removal if
!isMemoryEffectFree(op)). Ensure you reference the IfOp instance passed into
matchAndRewrite and use the existing isMemoryEffectFree utility to guard the
removal.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: bcefdaa8-d6db-49c7-8ec3-d1552d3f463a
📒 Files selected for processing (6)
mlir/include/mlir/Dialect/QCO/IR/QCOOps.tdmlir/include/mlir/Dialect/QCO/QCOUtils.hmlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cppmlir/lib/Dialect/QCO/IR/QCOOps.cppmlir/lib/Dialect/QCO/IR/SCF/IfOp.cppmlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp
|
@burgholzer the implementation itself should be done now, but the tests will still fail until we fixed the ordering issue in the tester that we discussed internally. I guess this PR can either be reviewed now already or once that fix has been merged in here. |
Thanks! I'll try to review this asap. Let's see if that turns out to be before or after the fix. |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h (1)
408-418: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the stale
measure(qubit, bit)example.The overload now returns
{output_qubit, measurement_result}, but the example still assigns the pair to a singleValue.📝 Proposed doc fix
- * q0 = builder.measure(q0, c[0]); + * auto [q0_out, result] = builder.measure(q0, c[0]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h` around lines 408 - 418, Update the stale documentation for QCOProgramBuilder::measure(Value qubit, const Bit& bit) so the example matches the current return type; the method now returns a pair of output qubit and measurement result, so fix the C++ and MLIR examples to show destructuring or separate bindings instead of assigning to a single Value. Keep the wording aligned with the measure overload signature and its documented pair return.mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp (2)
358-412: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAvoid duplicate measurement side effects for cached result slots.
Both overloads reuse cached result pointers but still emit a fresh
__quantum__qis__mz__bodycall for the same classical slot. Repeated measurement into the same bit should return the cached result without another measurement side effect.Add a measurement-side-effect cache separate from
resultPtrs/loadedResults, because those maps can contain allocated-but-not-yet-measured pointers. Based on learnings, “ensure a cache check prevents duplicate measurements … return early to avoid generating multiple mz calls for the same classical bit index.” <retrieved_learnings>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp` around lines 358 - 412, The measure overloads in QIRProgramBuilder::measure still emit a new __quantum__qis__mz__body call even when the same classical slot has already been measured; add a dedicated measurement-result cache keyed by the classical result index/Bit and check it before creating the call. Update both measure(Value, int64_t, bool) and measure(Value, const Bit&, bool) so they return the previously recorded result early when the slot was already measured, without reusing resultPtrs/loadedResults as the only guard.Source: Learnings
1014-1062: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftDo not release result storage before returning a result pointer.
build()can now pass the callback result directly tofinalize(result). If that result is produced bymeasure(), adaptive finalization releases allresultPtrs/resultArraysbefore the terminator, so the function returns a released runtime result pointer. Either return an owned scalar value, or skip releasing result storage that is returned frommain.Also applies to: 1078-1087
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp` around lines 1014 - 1062, `QIRProgramBuilder::finalize(Value returnValue)` is releasing adaptive result storage before the function returns, which can invalidate a runtime result pointer produced by `measure()`. Update the adaptive cleanup in `finalize` so that any `resultPtrs` or `resultArrays` that may be returned from `main` are not freed before the `LLVM::ReturnOp` executes, or convert the callback path to return an owned scalar instead of a raw runtime pointer. Use the `finalize`, `generateOutputRecording`, and `resultPtrs`/`resultArrays` logic to ensure returned values remain valid through function exit.mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h (1)
313-345: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAlign the docs with the new conditional recording and general return-value semantics.
These comments still say measurements are recorded unconditionally and describe
returnValueas an exit code, but the API now supportsrecord = falseand arbitrary return values.Proposed documentation cleanup
- * The output is recorded via `__quantum__rt__result_record_output` during - * `finalize()`. + * When `record` is true, the output is recorded via + * `__quantum__rt__result_record_output` during `finalize()`. - * The output is recorded via `__quantum__rt__result_array_record_output` - * during `finalize()`. + * When `record` is true, the output is recorded via + * `__quantum__rt__result_array_record_output` during `finalize()`. - * `@brief` Finalize the program with a given exit code and return the + * `@brief` Finalize the program with a given return value and return the ... - * `@param` returnValue The value representing the exit code to return + * `@param` returnValue The value returned by the main function ... - * adds a return statement with a given exit code, + * adds a return statement with the given return value,Also applies to: 347-382, 1100-1117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h` around lines 313 - 345, Update the documentation for QIRProgramBuilder methods like measure and the return-value handling APIs to match the current behavior: describe that measurement recording is conditional via the record parameter (including the false case) rather than always happening, and replace any language that treats returnValue as an exit code with wording that it can carry arbitrary return values. Keep the comments/examples in sync across the affected declarations so the semantics described in the docs match the implementation.src/qasm3/Parser.cpp (1)
169-189: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
outputis accepted on quantum declarations (qubit/qreg), which is invalid per OpenQASM 3.The
outputbranch callsparseDeclaration(false, true)without restricting the following type. SinceparseType()acceptsQubit/Qreg,output qubit q;parses successfully withisOutput=true, even though the OpenQASM 3 spec documentsoutputonly for classical variables (e.g.output bit result;). Consider rejectingoutputwhen the parsed type is a quantum type.🛡️ Proposed fix sketch (exact API for quantum-type check needs verification)
auto statement = std::make_shared<DeclarationStatement>(DeclarationStatement{ makeDebugInfo(tBegin, tEnd), isConst, isOutput, type, name, expression}); + + if (isOutput && /* type represents a quantum type, e.g. Qubit/Qreg */) { + error(tBegin, "The 'output' modifier is only valid for classical declarations"); + }Also applies to: 565-599
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/qasm3/Parser.cpp` around lines 169 - 189, The Parser::parseDeclaration path currently allows output declarations for quantum types because the Output branch unconditionally calls parseDeclaration(false, true). Update the declaration parsing flow so that when isOutput is true, ParseType/parseType rejects Qubit and Qreg (or the caller validates the returned type) and emits a parse error for quantum output declarations. Keep the existing Const/Output handling in Parser::parseStatement/parseDeclaration, but add a type check tied to parseType so only classical types can be marked output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h`:
- Around line 1166-1182: Update the Doxygen for
QCProgramBuilder::finalize(ValueRange) to describe generic main-function return
values instead of an exit code: revise the brief, the `@param` returnValues
description, and the details text so they consistently say the method finalizes
the module using the provided return values, while still noting the values must
match the main function signature. Keep the wording aligned with the
finalize(ValueRange) overload and its support for arbitrary return types.
In `@mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h`:
- Around line 1474-1506: The Doxygen for QCOProgramBuilder::finalize and
QCOProgramBuilder::build is outdated: finalize(ValueRange) should be described
as finalizing the module with the provided return values rather than an exit
code, and the build(...) documentation should remove any mention of a
returnTypes parameter and instead explain that the buildFunc result supplies
both values and types used for finalization. Update the comments on these two
API symbols so they match the current signatures and behavior.
In `@mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp`:
- Around line 1239-1242: Update the Doxygen conversion example near the
QCOToJeff rewrite to match the current behavior: the `convertFuncFuncOp`-style
pattern only strips the `passthrough` attribute and no longer rewrites `@main`
to `-> ()` with an empty return. Edit the nearby example/comments so they
describe preserving the existing returns and reflect the actual transformation
performed by `rewriter.startOpModification`, `op->removeAttr("passthrough")`,
and `rewriter.finalizeOpModification`.
In `@mlir/lib/Conversion/QCToQIR/QIRCommon/QIRCommon.cpp`:
- Around line 474-503: The measurement-return stripping in
stripReturnedMeasurements currently rewrites every func::FuncOp, which can
change helper function return types without fixing callers. Restrict this logic
to the entry-point function only (using the function identified by the lowering
state), or else update all call sites and any dependent signatures consistently
when return operands/types are rewritten. Keep the returnedMeasurements tracking
tied to that entry-point-only handling.
In `@mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp`:
- Around line 232-247: The output collection in
TranslateQASM3ToQC::translateQASM3ToQC only checks the measured bits present in
bitValues for each register, so a partial prefix can pass unnoticed; update the
outputRegisters loop to compare the number of measured bits against the declared
register size before appending returnValues/returnTypes. Use the existing
bitValues lookup for each regName, but verify the full output bit[ ] declaration
is covered and reject any missing suffix bits with the same error path if the
measured count is smaller than the register’s expected size.
In `@mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp`:
- Around line 888-890: In the finalization path of the quantum-to-QC
translation, reject any classical bits that were never populated by a
measurement before calling builder.finalize(state.results). Update the logic
around the state.results assembly so it validates every entry is non-null for
getNcbits() > 0, and only pass the results vector to builder.finalize once all
classical bits have been measured; otherwise handle the invalid computation
explicitly instead of finalizing with null Values.
In `@mlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cpp`:
- Around line 197-202: The test in buildAndCanonicalize only calls qtensorAlloc,
so it never creates the DeallocOp that the assertion is supposed to eliminate.
Update the callback in test_qtensor_ir.cpp to explicitly build a matching
deallocation for the allocated tensor using the relevant QCOProgramBuilder
helpers (qtensorAlloc and the corresponding dealloc op creation path), so the
canonicalization is exercised on a real alloc/dealloc pair instead of passing
vacuously.
In `@mlir/unittests/programs/quantum_computation_programs.cpp`:
- Around line 118-122: The multipleControlledIdentity fixture in
QuantumComputation currently uses the wrong control/target mapping and only
measures one qubit, so update multipleControlledIdentity to match the
three-qubit contract used by the QC/QASM fixtures: keep the same
QuantumComputation setup but call mci with controls 0 and 1 targeting 2, and add
measurements for all three qubits into the classical register so translation and
equivalence tests see the same signature and operation mapping.
---
Outside diff comments:
In `@mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h`:
- Around line 408-418: Update the stale documentation for
QCOProgramBuilder::measure(Value qubit, const Bit& bit) so the example matches
the current return type; the method now returns a pair of output qubit and
measurement result, so fix the C++ and MLIR examples to show destructuring or
separate bindings instead of assigning to a single Value. Keep the wording
aligned with the measure overload signature and its documented pair return.
In `@mlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.h`:
- Around line 313-345: Update the documentation for QIRProgramBuilder methods
like measure and the return-value handling APIs to match the current behavior:
describe that measurement recording is conditional via the record parameter
(including the false case) rather than always happening, and replace any
language that treats returnValue as an exit code with wording that it can carry
arbitrary return values. Keep the comments/examples in sync across the affected
declarations so the semantics described in the docs match the implementation.
In `@mlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cpp`:
- Around line 358-412: The measure overloads in QIRProgramBuilder::measure still
emit a new __quantum__qis__mz__body call even when the same classical slot has
already been measured; add a dedicated measurement-result cache keyed by the
classical result index/Bit and check it before creating the call. Update both
measure(Value, int64_t, bool) and measure(Value, const Bit&, bool) so they
return the previously recorded result early when the slot was already measured,
without reusing resultPtrs/loadedResults as the only guard.
- Around line 1014-1062: `QIRProgramBuilder::finalize(Value returnValue)` is
releasing adaptive result storage before the function returns, which can
invalidate a runtime result pointer produced by `measure()`. Update the adaptive
cleanup in `finalize` so that any `resultPtrs` or `resultArrays` that may be
returned from `main` are not freed before the `LLVM::ReturnOp` executes, or
convert the callback path to return an owned scalar instead of a raw runtime
pointer. Use the `finalize`, `generateOutputRecording`, and
`resultPtrs`/`resultArrays` logic to ensure returned values remain valid through
function exit.
In `@src/qasm3/Parser.cpp`:
- Around line 169-189: The Parser::parseDeclaration path currently allows output
declarations for quantum types because the Output branch unconditionally calls
parseDeclaration(false, true). Update the declaration parsing flow so that when
isOutput is true, ParseType/parseType rejects Qubit and Qreg (or the caller
validates the returned type) and emits a parse error for quantum output
declarations. Keep the existing Const/Output handling in
Parser::parseStatement/parseDeclaration, but add a type check tied to parseType
so only classical types can be marked output.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 40253feb-8ff4-4273-8720-e53c23ffbc1d
📒 Files selected for processing (48)
CHANGELOG.mdbindings/ir/register_quantum_computation.cppinclude/mqt-core/ir/QuantumComputation.hppinclude/mqt-core/qasm3/Parser.hppinclude/mqt-core/qasm3/Statement.hppmlir/include/mlir/Conversion/QCToQIR/QIRCommon/QIRCommon.hmlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.hmlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.hmlir/include/mlir/Dialect/QCO/IR/QCOOps.tdmlir/include/mlir/Dialect/QIR/Builder/QIRProgramBuilder.hmlir/lib/Conversion/QCOToJeff/QCOToJeff.cppmlir/lib/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.cppmlir/lib/Conversion/QCToQIR/QIRBase/QCToQIRBase.cppmlir/lib/Conversion/QCToQIR/QIRCommon/QIRCommon.cppmlir/lib/Dialect/QC/Builder/QCProgramBuilder.cppmlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cppmlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cppmlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cppmlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cppmlir/lib/Dialect/QCO/IR/SCF/IfOp.cppmlir/lib/Dialect/QIR/Builder/QIRProgramBuilder.cppmlir/unittests/Compiler/test_compiler_pipeline.cppmlir/unittests/Conversion/JeffRoundTrip/test_jeff_round_trip.cppmlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cppmlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cppmlir/unittests/Conversion/QCToQIR/QCToQIRAdaptive/test_qc_to_qir_adaptive.cppmlir/unittests/Conversion/QCToQIR/QCToQIRBase/test_qc_to_qir_base.cppmlir/unittests/Dialect/QC/IR/test_qc_ir.cppmlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cppmlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cppmlir/unittests/Dialect/QCO/IR/test_qco_ir.cppmlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cppmlir/unittests/Dialect/QCO/Transforms/Decomposition/test_euler_decomposition.cppmlir/unittests/Dialect/QIR/IR/test_qir_ir.cppmlir/unittests/Dialect/QTensor/IR/test_qtensor_ir.cppmlir/unittests/TestCaseUtils.hmlir/unittests/programs/qasm_programs.cppmlir/unittests/programs/qc_programs.cppmlir/unittests/programs/qc_programs.hmlir/unittests/programs/qco_programs.cppmlir/unittests/programs/qco_programs.hmlir/unittests/programs/qir_programs.cppmlir/unittests/programs/qir_programs.hmlir/unittests/programs/quantum_computation_programs.cppnoxfile.pypython/mqt/core/ir/__init__.pyisrc/ir/QuantumComputation.cppsrc/qasm3/Parser.cpp
…ults Signed-off-by: burgholzer <burgholzer@me.com>
Avoids accessing QCO code from QC
Signed-off-by: burgholzer <burgholzer@me.com>
Signed-off-by: burgholzer <burgholzer@me.com>
Signed-off-by: burgholzer <burgholzer@me.com>
Signed-off-by: burgholzer <burgholzer@me.com>
Signed-off-by: burgholzer <burgholzer@me.com>
2bf7f84 to
0cd289a
Compare
burgholzer
left a comment
There was a problem hiding this comment.
Thanks @DRovara for the Herculean effort here!
I went through almost everything here. In the process, I significantly simplified the signatures of the tests by automatically deducing the types and providing a couple of shortcuts.
This brought the diff down by 1.5k lines or so.
I still have to go through the tests once more to make sure they are fine as they are. However, there was one thing in the main code that will require changes (if I understood everything correctly). See the inline comment. This should also allow us to revert quite some of the changes in the qasm_programs.cpp file.
Could you please take a look at that? (and at the other changes as well; to check whether they make sense to you as well)
| if ((stmt->isOutput || openQASM2CompatMode) && | ||
| sizedType->type == qasm3::Bit) { | ||
| // We return `output` bits in QASM3, or all named bits in QASM2. | ||
| outputRegisters.push_back(id); | ||
| } |
There was a problem hiding this comment.
This is not entirely spec compliant. If an OpenQASM 3 program does not contain any output statements.
The spec states
Similarly, the output modifier can be used to indicate that one or more variables are to be provided as an explicit output of the quantum procedure. Note that OpenQASM 2 did not allow the programmer to specify that only a subset of its variables should be returned as output, and so it would return all classical variables (which were all creg variables) as output. For compatibility, OpenQASM 3 does not require an output declaration to be provided: in this case it assumes that all of the declared variables are to be returned as output. If the programmer provides one or more output declarations, then only those variables described as outputs will be returned as an output of the quantum process. A variable may not be marked as both input and output.
|
Thanks a lot for the updates @burgholzer! Automatically inferring the return types is smart, no idea how I missed that. Regarding the built-in method to get corresponding inputs/outputs: I saw that too, but I didn't know we can just cast Also, regarding the OPENQASM 3 output thing: I can fix that, shouldn't be too much effort. And yeah, that should enable us to rever most |
This is mainly following the syntax and wording of the corresponding SCF operation. because the
Would be great 👍🏼 I can take it from there again. |
Update is ready. |
Description
This PR implements Dead Gate Elimination for unused operations on qubits.
An operation is considered unused if all of its outputs' users are dealloc operations.
This means a measurement can also be dead if its classical outcome is not used.
Similarly,
resetandifoperations can also be considered unused.This PR marks all quantum gates as
Pure(and marks theRecursiveMemoryEffectsoperations asPureas well, as otherwise, any recursive memory effects would be overwritten by the compiler not knowing whether these region operations are pure to begin with). This allows us to simply check for memory effects before removing region operations.Currently, this only considers individual qubits, rather than
qtensorcases to keep this PR contained.An extension to qtensors should also be possible, though it may require some additional assumptions to maintain overview on what specific qubits end up going unused.
The following new features/changes also had to be implemented to make this PR work with the rest of the framework:
Return values for builders and tests
Without return values, most quantum programs constructed by the builder would basically be "dead". Therefore, the
QCandQCOProgramBuilders now require all build functions to also indicate what values and types they return. Theinitialize(...)method now takes a function signature for themainfunction as an argument, while thefinalize(...)method takes the actual return values. Similarly, the newretype(...)changes the type of themainfunction during the build process.Translations between IRs:
As
QCandQCOprograms now have return values, other IRs also need some notion to represent that concept and ways to translate between them. We have adapted the conversion/translation methods so that:recordQIR methods to indicate the output of measurement outcomes. (During translation fromQCtoQIR, return values have to be stripped out if they are direct measurement outcomes)outputkeyword to be returned.OpenQASM Parsing:
Consequently, handling for the
outputkeyword was added to the OpenQASM parser code. Similarly, the translation from OpenQASM to QC was adapted. Note that theoutputkeyword is ignored when parsing intoQuantumComputationobjects, rather than into theQCdialect.QuantumComputation
For convenience (to keep the tests minimal), the
measureAllmethod in theQuantumComputationwas adapted to also take theaddBarrierargument. If it is not set to true, barriers will no longer be inserted between the main circuit body and the measurements.QIR Builder
To no longer make the
QIRProgramBuilderconsider all measurement outcomes for reporting, themeasuremethod now also takes therecordflag as an argument. Outcomes will only be reported if the flag is set totrue.Other
Various further helper methods have been added to make working with more complex programs easier.
AI Usage note: Some design decisions were taken after discussions with Gemini 3.1 Pro. Parts of the code were written with help from Gemini 3.1 Pro and Claude Opus 4.6.
Checklist
If PR contains AI-assisted content:
Assisted-by: [Model Name] via [Tool Name]footer.