Skip to content

✨ Extend QCO DD construction/simulation beyond static unitaries - #1973

Draft
simon1hofmann wants to merge 18 commits into
mainfrom
feat/qco-dd-functionality-gaps
Draft

✨ Extend QCO DD construction/simulation beyond static unitaries#1973
simon1hofmann wants to merge 18 commits into
mainfrom
feat/qco-dd-functionality-gaps

Conversation

@simon1hofmann

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖

Summary

Extends QCO decision-diagram APIs beyond static unitaries (#1915) so programs with mid-circuit dynamics and concrete classical control can be simulated and sampled.

  • C++ (mlir::qco): RNG simulate for measure/reset; concrete qco.if / index_switch; richer classical SSA (cmpi, arith, shifts, select, casts); scf.for (concrete bounds, ≤10000 trips); non-recursive single-block func.call; static 1-D memref<?xi1> classical registers; dense embed for k>3 unitaries (≤12 qubits); sample and sampleWithClassics (mid-circuit bits in encounter order).
  • Python: build_functionality, simulate, sample, sample_with_classics, SampleResult on mqt.core.mlir, reusing mqt.core.dd package/DD types; QCOProgram.entry_func support via entryFunc().
  • Out of scope: symbolic parameters, qtensors, multi-block CFG, scf.while, noise/density matrix.

Checklist

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

If PR contains AI-assisted content:

  • Any agent that created, edited, or submitted GitHub content was explicitly authorized for that scope, as required by our AI Usage Guidelines.
  • Every agent-authored or agent-edited public text body begins with the visible disclosure 🤖 *AI text below* 🤖 (titles are exempt).
  • I have disclosed AI assistance in the PR description.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

…ements, resets, and control-flow operations. Added new simulation functions and updated existing functionality to handle classical branching and multi-shot sampling.
…, resets, and advanced control-flow operations. Introduced new simulation functions and updated existing ones to accommodate classical branching and multi-shot sampling capabilities. Updated changelog and added Python bindings for new functionalities.
… in QCO DD simulation. Updated functionality to handle allocation, storage, and loading of classical registers, enhancing the simulation capabilities. Changelog updated to reflect these changes.
@simon1hofmann simon1hofmann self-assigned this Jul 30, 2026
@simon1hofmann simon1hofmann added enhancement Improvement of existing feature DD Anything related to the DD package c++ Anything related to C++ code MLIR Anything related to MLIR labels Jul 30, 2026
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.30849% with 112 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp 83.1% 112 Missing ⚠️

📢 Thoughts on this report? Let us know!

simon1hofmann and others added 3 commits July 30, 2026 16:11
Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com>
@simon1hofmann

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@simon1hofmann, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 3 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 905252b7-068b-47e6-99f8-410e0280e930

📥 Commits

Reviewing files that changed from the base of the PR and between 60ad6c0 and ed17d52.

📒 Files selected for processing (4)
  • bindings/mlir/register_mlir.cpp
  • mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h
  • mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp
  • python/mqt/core/mlir.pyi
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Python APIs for decision-diagram-based QCO simulation, unitary construction, and sampling.
    • Added support for measurements, resets, classical control flow, registers, function calls, and bounded loops.
    • Added final quantum-state and mid-circuit classical outcome histograms.
    • Added seeded and nondeterministic simulation and sampling.
    • Expanded support for dense, non-contiguous wire layouts and dynamic QCO programs.
  • Documentation

    • Updated the unreleased changelog to describe the expanded QCO functionality.

Walkthrough

This PR adds measurement-aware QCO decision-diagram simulation, sampling, classical control flow, memref-backed registers, public C++ APIs, Python bindings, and extensive C++ and Python tests.

Changes

QCO DD simulation and sampling

Layer / File(s) Summary
Public contracts and entry selection
mlir/include/mlir/Compiler/Programs.h, mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h, mlir/lib/Compiler/Programs.cpp, mlir/lib/Dialect/QCO/Utils/CMakeLists.txt, mlir/unittests/Compiler/test_compiler_pipeline.cpp
Adds entry-function lookup, RNG-based simulation, sampling, classical-result types, and required MLIR linkage.
Simulation and sampling engine
mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp
Adds shared quantum and classical state, measurement, reset, classical operations, control flow, function calls, dense-wire embedding, and dynamic sampling.
Python DD API wiring
bindings/mlir/*, python/mqt/core/mlir.pyi
Exposes DD construction, simulation, sampling, SampleResult, seed handling, lifetime preservation, GIL release, and Python error conversion.
Behavior validation and release notes
mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp, test/python/test_qco_dd.py, CHANGELOG.md
Adds coverage for simulation, sampling, classical control flow, registers, rejection cases, entry selection, Python APIs, and the changelog entry.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Python
  participant QCOProgram
  participant DDFunctionality
  participant DDPackage
  Python->>QCOProgram: provide QCO program
  QCOProgram->>DDFunctionality: select entry FuncOp
  DDFunctionality->>DDPackage: build, simulate, or sample DD state
  DDPackage-->>DDFunctionality: return quantum and classical histograms
  DDFunctionality-->>Python: return VectorDD, histogram, or SampleResult
Loading

Possibly related PRs

Suggested labels: python

Suggested reviewers: burgholzer, denialhaag

Poem

A rabbit measures, loops, and hops,
While DD sampling never stops.
Seeded paths align just right,
Classical bits record the flight.
Python bindings join the quest—
Tests guide every fluffy step. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.98% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: extending QCO decision-diagram construction and simulation beyond static unitaries.
Description check ✅ Passed The description provides the change summary, scope, out-of-scope items, and checklist, but it does not state dependencies or a linked issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/qco-dd-functionality-gaps

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mlir/lib/Dialect/QCO/Utils/CMakeLists.txt (1)

63-69: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Link MLIRArithDialect in MLIRQCODDFunctionality.

MLIRQCODialect declares MLIRArithDialect only as a PRIVATE dependency, while DDFunctionality.cpp directly uses mlir/Dialect/Arith/IR/Arith.h and instantiates arith::* types. Add MLIRArithDialect to MLIRQCODDFunctionality’s LINK_LIBS.

🤖 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/Utils/CMakeLists.txt` around lines 63 - 69, Update the
MLIRQCODDFunctionality LINK_LIBS list in the CMake configuration to include
MLIRArithDialect alongside the existing public dependencies, ensuring
DDFunctionality.cpp’s direct arith::* usage is linked explicitly.
🤖 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 `@bindings/mlir/register_mlir.cpp`:
- Around line 612-618: Add a nanobind call guard releasing the GIL to both QCO
sampling bindings around the lambdas invoking mlir::qco::sample, including the
second binding noted at lines 636-642. Confirm the sampled dd::Package is not
shared across threads before applying the guard, and keep the existing sampling
and error propagation unchanged.
- Around line 559-566: Add the nanobind keep-alive policy <nb::keep_alive<0,
1>()> to the `build_functionality` binding, matching the existing DD package
bindings, so the returned DD objects retain `ddPackage` for their lifetime.
- Around line 123-130: Update takeFailureOr and its call sites to capture MLIR
diagnostics with a ScopedDiagnosticHandler on the relevant program context,
accumulating emitted messages into a std::string before checking the FailureOr
result. When failure occurs, append the collected diagnostics to the supplied
message in the raised nb::value_error, while preserving the existing successful
return behavior.
- Around line 610-656: Update the sample and sample_with_classics bindings to
accept an optional uint64 seed with a None/empty default, matching simulate
semantics; invoke makeRng only for an explicitly provided seed and otherwise use
nondeterministic RNG initialization. Revise both docstrings to document None as
nondeterministic rather than 0, and regenerate the Python stub to reflect the
optional seed signatures.

In `@mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h`:
- Around line 63-66: Update the non-RNG simulate documentation near simulateImpl
to remove the claim that loops and multi-block function bodies are unsupported,
and accurately state that concrete-bound scf.for loops and non-recursive
single-block func.call operations are supported independently of RNG; retain the
existing limitations for unsupported cases and the reference-consumption
behavior.
- Around line 149-167: Move the sampling `@brief/`@details documentation from
SampleResult to the two sampleWithClassics overloads, documenting their shared
behavior while leaving SampleResult without that function-level description.
Update both overload signatures to use std::size_t consistently with the
existing sample overloads.

In `@mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp`:
- Around line 1129-1145: Adjust the callee traversal around walkFunction so
func::ReturnOp is not subjected to the entry-function-only validateReturn
canonical wire-order rule. Preserve return/result arity validation and bind the
callee return operands to call results, while retaining canonical return
validation for the top-level simulated function.
- Around line 686-735: Update the cmpi evaluation around the i1 handling and
signed predicate cases so boolean true is interpreted with arith’s
sign-extension semantics as -1 rather than 1 for signed comparisons. Preserve
unsigned comparisons as 0/1 and keep equality/inequality behavior unchanged,
using the existing cmp.getPredicate() and result calculation.
- Around line 628-657: Update the arith::AddIOp, arith::SubIOp, and
arith::MulIOp handlers using applyBinaryIndex to perform wrapping uint64_t
arithmetic before storing the resulting index, avoiding signed-overflow UB. In
the arith::ShLIOp handler, preserve the existing shift-range validation, convert
the left operand to uint64_t before shifting, and store the wrapped result
without signed-shift UB.
- Around line 1000-1028: In the IndexSwitchOp handler, validate the default
block and yield obtained from getDefaultBlock() and getDefaultYield() before
dereferencing them, matching the existing IfOp guard behavior. Emit an operation
diagnostic and return failure for an absent or empty default region; otherwise
preserve the case-selection and applyRegionBranch flow.
- Around line 1296-1332: Update requiresDynamicSampling to account for MeasureOp
and ResetOp in functions invoked through func.call, either by recursively
visiting callees or conservatively treating calls as dynamically sampled. Ensure
sampleImpl selects the dynamic per-shot simulation path whenever a reachable
callee contains measurement or reset, avoiding simulateImpl with a null RNG for
these programs.

In `@mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp`:
- Around line 784-798: Add a companion QCODDFunctionalityTest using scfFor with
exactly 10000 trips, retaining the identity iterArgs body and sink setup from
RejectsScfForTripCountLimit, then assert module construction and simulation
succeed. Keep the existing 10001-trip rejection test unchanged so both sides of
the boundary are covered.

In `@test/python/test_qco_dd.py`:
- Around line 33-51: Expand test_unitary_x_build_simulate_and_sample and add
focused Python binding tests covering documented ValueError paths: simulate
without a seed must reject mid-circuit measure/reset, while supplying a seed
must allow it. Also add coverage for a QCO program without a func.func to verify
entryFunc validation and exception translation; remove the vacuous matrix is not
None assertion while retaining valid cleanup and success-path checks.

---

Outside diff comments:
In `@mlir/lib/Dialect/QCO/Utils/CMakeLists.txt`:
- Around line 63-69: Update the MLIRQCODDFunctionality LINK_LIBS list in the
CMake configuration to include MLIRArithDialect alongside the existing public
dependencies, ensuring DDFunctionality.cpp’s direct arith::* usage is linked
explicitly.
🪄 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 Plus

Run ID: 6290089f-62d0-45cf-ac89-59611528846a

📥 Commits

Reviewing files that changed from the base of the PR and between b91a0bf and 60f76e8.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • bindings/mlir/CMakeLists.txt
  • bindings/mlir/register_mlir.cpp
  • mlir/include/mlir/Compiler/Programs.h
  • mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h
  • mlir/lib/Compiler/Programs.cpp
  • mlir/lib/Dialect/QCO/Utils/CMakeLists.txt
  • mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp
  • mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp
  • python/mqt/core/mlir.pyi
  • test/python/test_qco_dd.py

Comment thread bindings/mlir/register_mlir.cpp Outdated
Comment thread bindings/mlir/register_mlir.cpp
Comment thread bindings/mlir/register_mlir.cpp
Comment thread bindings/mlir/register_mlir.cpp
Comment thread mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h Outdated
Comment thread mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp
Comment thread mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp Outdated
Comment thread mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp Outdated
Comment thread mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp
Comment thread test/python/test_qco_dd.py
@mergify mergify Bot added the conflict label Jul 30, 2026
simon1hofmann and others added 2 commits July 31, 2026 10:23
Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com>
@mergify mergify Bot removed the conflict label Jul 31, 2026
@simon1hofmann

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
bindings/mlir/register_mlir.cpp (2)

613-630: ⚠️ Potential issue | 🟠 Major

Retain dd_package for the simulated result.

🤖 AI text below 🤖

mlir::qco::simulate returns a dd::VectorDD backed by ddPackage. Unlike build_functionality, this binding does not retain argument 3. If Python releases dd_package, the returned VectorDD can retain invalid package-owned storage. Add nb::keep_alive<0, 3>(). Nanobind uses index 0 for the return value and indexes free-function arguments from 1. (nanobind.readthedocs.io)

Proposed fix
       "program"_a, "initial_state"_a, "dd_package"_a, "seed"_a = nb::none(),
+      nb::keep_alive<0, 3>(),
       R"pb(Simulate a QCO program on a DD state.
#!/bin/bash
set -euo pipefail

sed -n '579,645p' bindings/mlir/register_mlir.cpp

echo "== Existing DD lifetime policies =="
fd -i 'register_*dd*.cpp' bindings -x rg -n -C2 'keep_alive|reference_internal' {}

echo "== Python lifetime regression coverage =="
rg -n -C3 'simulate\(|build_functionality\(|gc\.collect|del package' test/python
🤖 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 `@bindings/mlir/register_mlir.cpp` around lines 613 - 630, Add the nanobind
lifetime policy keep_alive<0, 3>() to the simulate binding after its argument
declarations, ensuring the returned VectorDD keeps ddPackage alive. Do not alter
the simulation logic or seed handling.

109-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use API-neutral error text for entryFunc.

🤖 AI text below 🤖

entryFunc also serves build_functionality, sample, and sample_with_classics. The text "to simulate" is incorrect for those APIs. Use an API-neutral message such as "QCO program has no func.func entry point".

Proposed fix
-    throw nb::value_error("QCO program has no func.func to simulate");
+    throw nb::value_error("QCO program has no func.func entry point");
🤖 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 `@bindings/mlir/register_mlir.cpp` around lines 109 - 116, Update the
missing-function error in entryFunc to use API-neutral wording, replacing the
simulation-specific phrase with a message indicating that the QCO program has no
func.func entry point. Preserve the existing validation and exception type.
mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp (1)

1082-1144: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard scf.for trip-count computation against signed-overflow UB.

*ub - *lb - 1 operates on int64_t values that come from arbitrary classical index SSA values (constants, prior arithmetic, or measurement-derived indices). When lb and ub are far apart (for example lb = INT64_MIN, ub = INT64_MAX), the subtraction overflows before the trips > maxTrips check runs. Signed-integer overflow is undefined behavior in C++, even under the project's C++20 target (C++20 guarantees two's-complement representation, not that overflow is defined). A UBSan build aborts on this input instead of producing the "trip count exceeds ... limit" diagnostic.

Compute the span and trip count in uint64_t, and compare against maxTrips before casting back to int64_t, the same way this file already avoids overflow in the arith.addi/arith.subi/arith.muli handlers.

🛡️ Suggested fix
-          const int64_t trips = (*ub > *lb) ? ((*ub - *lb - 1) / *step) + 1 : 0;
           constexpr int64_t maxTrips = 10000;
-          if (trips > maxTrips) {
-            return forOp.emitError()
-                   << "scf.for trip count exceeds QCO DD simulation limit of "
-                   << maxTrips;
-          }
+          int64_t trips = 0;
+          if (*ub > *lb) {
+            const auto span = static_cast<uint64_t>(*ub) -
+                              static_cast<uint64_t>(*lb);
+            const uint64_t tripsU =
+                (span - 1) / static_cast<uint64_t>(*step) + 1;
+            if (tripsU > static_cast<uint64_t>(maxTrips)) {
+              return forOp.emitError()
+                     << "scf.for trip count exceeds QCO DD simulation limit "
+                        "of "
+                     << maxTrips;
+            }
+            trips = static_cast<int64_t>(tripsU);
+          }
🤖 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/Utils/DDFunctionality.cpp` around lines 1082 - 1144,
Update the trip-count calculation in the scf::ForOp handler to avoid signed
overflow when computing the bound span: perform the bound comparison and
span/trip arithmetic in uint64_t, including the nonnegative lower-to-upper
difference, then compare the resulting trip count against maxTrips before
converting it to int64_t for the existing loop. Preserve zero trips for ub <= lb
and the current limit diagnostic for oversized counts.
mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp (1)

69-74: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard mainFunc against an empty function range.

mainFunc dereferences mod.getBody()->getOps<func::FuncOp>().begin() without checking that the range is non-empty. If a future test parses or builds a module with no func::FuncOp, this dereferences an end iterator, which is undefined behavior. Add an assertion so a missing function fails with a clear message instead of an ambiguous crash.

🛡️ Proposed fix
   [[nodiscard]] static func::FuncOp mainFunc(ModuleOp mod) {
     if (auto main = mod.lookupSymbol<func::FuncOp>("main")) {
       return main;
     }
-    return *mod.getBody()->getOps<func::FuncOp>().begin();
+    auto funcs = mod.getBody()->getOps<func::FuncOp>();
+    assert(funcs.begin() != funcs.end() && "module must contain a func.func");
+    return *funcs.begin();
   }
🤖 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/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp` around lines 69 -
74, Update mainFunc to capture the func::FuncOp range and assert that it is
non-empty before dereferencing begin(), using a clear failure message for
modules without a function. Preserve the existing lookup of the "main" symbol
and return the first function when the range contains one.
🤖 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.

Outside diff comments:
In `@bindings/mlir/register_mlir.cpp`:
- Around line 613-630: Add the nanobind lifetime policy keep_alive<0, 3>() to
the simulate binding after its argument declarations, ensuring the returned
VectorDD keeps ddPackage alive. Do not alter the simulation logic or seed
handling.
- Around line 109-116: Update the missing-function error in entryFunc to use
API-neutral wording, replacing the simulation-specific phrase with a message
indicating that the QCO program has no func.func entry point. Preserve the
existing validation and exception type.

In `@mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp`:
- Around line 1082-1144: Update the trip-count calculation in the scf::ForOp
handler to avoid signed overflow when computing the bound span: perform the
bound comparison and span/trip arithmetic in uint64_t, including the nonnegative
lower-to-upper difference, then compare the resulting trip count against
maxTrips before converting it to int64_t for the existing loop. Preserve zero
trips for ub <= lb and the current limit diagnostic for oversized counts.

In `@mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp`:
- Around line 69-74: Update mainFunc to capture the func::FuncOp range and
assert that it is non-empty before dereferencing begin(), using a clear failure
message for modules without a function. Preserve the existing lookup of the
"main" symbol and return the first function when the range contains one.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6128b128-c640-4ace-9dac-2034df0715b0

📥 Commits

Reviewing files that changed from the base of the PR and between 60f76e8 and 238ef0f.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • bindings/mlir/register_mlir.cpp
  • mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h
  • mlir/lib/Dialect/QCO/Utils/CMakeLists.txt
  • mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp
  • mlir/unittests/Compiler/test_compiler_pipeline.cpp
  • mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp
  • python/mqt/core/mlir.pyi
  • test/python/test_qco_dd.py

@simon1hofmann

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/Utils/DDFunctionality.h (1)

60-62: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the classical-register type in the API contract.

🤖 AI text below 🤖

Line 62 and Line 91 state that memref<?xi1> is supported. That type has a dynamic dimension. isStaticI1MemRef and applyMemRefAlloc accept only static-shape registers and reject dynamic allocation. Document memref<Nxi1> for a concrete static N, or use equivalent static-shape wording.

Proposed documentation fix
- * `memref<?xi1>` classical registers (`alloc`/`store`/`load`/`dealloc`).
+ * static-shape 1-D `memref<Nxi1>` classical registers
+ * (`alloc`/`store`/`load`/`dealloc`).

Also applies to: 90-92

🤖 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/Utils/DDFunctionality.h` around lines 60 - 62,
Update the API contract documentation near the classical-register description
and the corresponding lines around the allocation APIs to describe only
statically shaped 1-D i1 memrefs, using memref<Nxi1> for a concrete static N
instead of memref<?xi1>. Align the wording with the static-shape requirements
enforced by isStaticI1MemRef and applyMemRefAlloc.
mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp (1)

1099-1128: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Compute scf.for induction values with unsigned arithmetic.

🤖 AI text below 🤖

Line 1128 still evaluates *lb + (t * *step) in signed int64_t. Valid extreme bounds can overflow this intermediate expression even when the loop result is in range. For lb = INT64_MIN, ub = INT64_MAX, and step = 3074457345618258603, the induction value at t = 3 should be 1, but signed overflow from t * step makes the stored value invalid.

Use uint64_t for the offset and cast the sum back to int64_t, matching the trip-count arithmetic.

Proposed fix
           for (int64_t t = 0; t < trips; ++t) {
-            walk.classical.indices[body.getArgument(0)] = *lb + (t * *step);
+            const auto offset = static_cast<uint64_t>(t) *
+                                static_cast<uint64_t>(*step);
+            walk.classical.indices[body.getArgument(0)] =
+                static_cast<int64_t>(static_cast<uint64_t>(*lb) + offset);
🤖 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/Utils/DDFunctionality.cpp` around lines 1099 - 1128,
Update the induction-value assignment in the scf.for simulation loop around
body.getArgument(0) to compute t * *step and the lower-bound sum using uint64_t,
then cast the final two’s-complement result to int64_t before storing it.
Preserve the existing trip-count logic and loop behavior.
bindings/mlir/register_mlir.cpp (3)

581-586: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the project’s raw-string form for Python docstrings.

🤖 AI text below 🤖

The new SampleResult and property docstrings use ordinary C++ string literals. Use R"pb(...)pb" for these Python-facing docstrings, consistent with the binding convention.

Proposed change
-                                      "Histograms from QCO DD sampling.")
+                                      R"pb(Histograms from QCO DD sampling.)pb")

Based on learnings, nanobind bindings under bindings/ should define Python docstrings in the binding code with R"pb(...)pb" syntax.

🤖 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 `@bindings/mlir/register_mlir.cpp` around lines 581 - 586, Update the
Python-facing docstrings in the SampleResult binding, including the class
description and the shots and classical property descriptions, to use the
project’s R"pb(...)pb" raw-string syntax instead of ordinary C++ string
literals.

Source: Learnings


601-611: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the build_functionality argument documentation.

🤖 AI text below 🤖

The program argument says that its entry func.func is simulated. build_functionality builds a matrix DD; it does not simulate the program.

-    program: A QCO program whose entry ``func.func`` is simulated.
+    program: A QCO program whose entry ``func.func`` is used to build a matrix DD.
🤖 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 `@bindings/mlir/register_mlir.cpp` around lines 601 - 611, Update the
build_functionality documentation for the program argument to describe that its
entry func.func is used to construct the program’s functionality or matrix DD,
not simulated. Leave the remaining argument, return, and exception documentation
unchanged.

648-676: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Document that dd_package is not thread-safe.

sample and sample_with_classics release the GIL while using a mutable dd::Package whose tables and memory manager are not locked. Add explicit ownership/GIL-free constraints to both docstrings.

🤖 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 `@bindings/mlir/register_mlir.cpp` around lines 648 - 676, Update the `sample`
and `sample_with_classics` docstrings to explicitly state that the
caller-provided `dd_package` is mutable and not thread-safe, and must not be
used concurrently, including while the GIL is released. Keep the existing
argument documentation and behavior unchanged.
🤖 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.

Outside diff comments:
In `@bindings/mlir/register_mlir.cpp`:
- Around line 581-586: Update the Python-facing docstrings in the SampleResult
binding, including the class description and the shots and classical property
descriptions, to use the project’s R"pb(...)pb" raw-string syntax instead of
ordinary C++ string literals.
- Around line 601-611: Update the build_functionality documentation for the
program argument to describe that its entry func.func is used to construct the
program’s functionality or matrix DD, not simulated. Leave the remaining
argument, return, and exception documentation unchanged.
- Around line 648-676: Update the `sample` and `sample_with_classics` docstrings
to explicitly state that the caller-provided `dd_package` is mutable and not
thread-safe, and must not be used concurrently, including while the GIL is
released. Keep the existing argument documentation and behavior unchanged.

In `@mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h`:
- Around line 60-62: Update the API contract documentation near the
classical-register description and the corresponding lines around the allocation
APIs to describe only statically shaped 1-D i1 memrefs, using memref<Nxi1> for a
concrete static N instead of memref<?xi1>. Align the wording with the
static-shape requirements enforced by isStaticI1MemRef and applyMemRefAlloc.

In `@mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp`:
- Around line 1099-1128: Update the induction-value assignment in the scf.for
simulation loop around body.getArgument(0) to compute t * *step and the
lower-bound sum using uint64_t, then cast the final two’s-complement result to
int64_t before storing it. Preserve the existing trip-count logic and loop
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a7678d2c-06fa-4b42-a000-c2da05f8738c

📥 Commits

Reviewing files that changed from the base of the PR and between 238ef0f and 60ad6c0.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • bindings/mlir/register_mlir.cpp
  • mlir/include/mlir/Dialect/QCO/Utils/DDFunctionality.h
  • mlir/lib/Dialect/QCO/Utils/DDFunctionality.cpp
  • mlir/unittests/Dialect/QCO/Utils/test_dd_functionality.cpp

@simon1hofmann

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mergify mergify Bot added the conflict label Aug 3, 2026
Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com>
@mergify mergify Bot added conflict and removed conflict labels Aug 4, 2026
@mergify mergify Bot removed the conflict label Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Anything related to C++ code DD Anything related to the DD package enhancement Improvement of existing feature MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant