[Python] Measurement handle type#4439
Draft
khalatepradnya wants to merge 10 commits intoNVIDIA:mainfrom
Draft
Conversation
CI Summary — ❌ failedRun #25239067615 · trigger ❌ Failed or cancelled
Top-level jobs (13)
⏩ Skipped jobs (7) — intentionally skipped on PR builds; run on merge_group / workflow_dispatch
All sub-jobs (50) — every matrix leg, with links
|
| Required check | Status | Link |
|---|---|---|
| Build and test (amd64, clang16, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (amd64, clang16, openmpi) / Dev environment (Python) | ❌ failure | view |
| Build and test (amd64, gcc11, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (amd64, gcc11, openmpi) / Dev environment (Python) | ❌ failure | view |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Python) | ❌ failure | view |
| Build and test (arm64, clang16, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (arm64, clang16, openmpi) / Dev environment (Python) | ❌ failure | view |
Widen the `expand-measurements` pass to lower vector-form `quake.mz`/`mx`/`my` whose result is `!cc.stdvec<!cc.measure_handle>`, mirroring the legacy `!cc.stdvec<!quake.measure>` path. Adds a secondary `ExpandStdvecHandleDiscriminate` pattern for the post-SSA-boundary case where `quake.discriminate` consumes a handle vector that the bridge has stored to / loaded from memory. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
Implements the C++ runtime half of the measure_handle spec (cudaq-spec/proposals/measure_handle.bs). measure_handle is the new return type of mz / mx / my; the legacy `measure_result` spelling is preserved via Option C — under MLIR mode `measure_result` is a `using` alias for `measure_handle`, so existing callers compile unchanged but gain the new handle semantics. Library mode keeps the legacy `class measure_result` block untouched, including the `__nvqpp__MeasureResultBoolConversion` adaptive-feedback hook. Spec invariant: mz / mx / my are __qpu__-only entry points (Kernel Signature Rule). MLIR-mode inline bodies trap with `std::abort()` so host-scope misuse fails loudly instead of computing on a meaningless value. measure_handle::operator bool() also aborts: the bridge intercepts every legitimate bool coercion at AST time and emits quake.discriminate, so reaching the host body means a bridge interception path was missed and the program would otherwise compute on a meaningless `bool`. Files - runtime/cudaq/qis/measure_handle.h (new): class declaration gated by `#ifndef CUDAQ_LIBRARY_MODE`. Tag-dispatched `measure_handle(handle_index, idx)` constructor reserved for runtime use; default constructor produces an unbound handle whose `index` carries a `numeric_limits<int64_t>::max()` sentinel. `static_assert`s pin the i64 payload width / trivially copyable / standard layout invariants the bridge marshalling relies on. - runtime/cudaq/qis/execution_manager.h: under `#ifdef CUDAQ_LIBRARY_MODE` the existing `class measure_result` is unchanged; under `#else` the previous `using measure_result = bool` becomes `using measure_result = measure_handle`. - runtime/cudaq/qis/qubit_qis.h: MLIR-mode bodies of `measureZ` / `measureX` / `measureY` and the bulk `to_bools` overload trap with `std::abort()`. Library-mode behavior is preserved verbatim. Bridge wiring, byte-size machinery, QIR conversion gap fix, boundary verifier, test migration, and docs updates follow as separate commits in this PR. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
…nery, and QIR conversion
Consolidates the bridge-side, type-system, and QIR-conversion work for
the measure_handle PR stack. The runtime API arrived in the previous
commit; this commit makes the AST bridge produce !cc.measure_handle
SSA values, teaches the verifier to reject handles at the host-device
boundary, fills the byte-size and marshaling gaps, and patches the QIR
conversion so handle pointer/stdvec ops survive --convert-to-qir-api.
Type-system support
- include/cudaq/Optimizer/Dialect/CC/CCTypes.h, CCTypes.cpp:
containsMeasureHandle (value-shape check) and
containsMeasureHandleAtBoundary (recursive into callable signatures
and bare function types). The boundary variant is required so
`std::function<void(measure_handle)>` and `cudaq::qkernel<...>`
parameters are caught at entry-point classification.
- lib/Optimizer/Dialect/CC/CCOps.cpp:
MeasureHandleType case in getByteSizeOfType returning a constant
8 bytes, the IR-mode width of a class with a single std::int64_t
field. Without this, a pure-device kernel returning
std::vector<measure_handle> aborts in ConvertStmt.cpp with
"unhandled vector element type" because __nvqpp_vectorCopyCtor
cannot get a constant element size for the heap-copy prologue.
AST bridge
- lib/Frontend/nvqpp/ConvertType.cpp, ConvertDecl.cpp:
cudaq::measure_handle maps to !cc.measure_handle;
std::vector<measure_handle> is recognised in measurement
register-name handling.
- lib/Frontend/nvqpp/ConvertExpr.cpp: the central rewire.
* mz / mx / my emit !cc.measure_handle (scalar) or
!cc.stdvec<!cc.measure_handle> (range/variadic) directly.
* CK_UserDefinedConversion at measure_handle::operator bool inserts
quake.discriminate at every spec-mandated bool-coercion site.
* The discriminate-insertion path runs an isBoundHandle check that
walks through cc.compute_ptr / cc.cast to the base alloca and,
on the scalar-handle alloca shape, requires that a binding store
dominate the load (mlir::DominanceInfo, computed lazily once per
coercion site). Conditional-store shapes that previously emitted
a discriminate over an uninitialized i64 payload now diagnose.
* cudaq::to_bools is intercepted by name and lowered to a vectorized
quake.discriminate on the entire handle stdvec; it is the bulk
counterpart to operator bool.
* cudaq::to_integer rejects vector<measure_handle> with a
spec-named diagnostic (per measure_handle.bs §C++ API): the
silent auto-insert that hid the bulk-discrimination API is gone.
* measure_handle copy/move construction and operator= are
intercepted as value-typed aliasing of the sub-i64 stack value;
chained `h3 = h2 = h;` works because the dispatch drops the
callee value the visitor pushed.
* default-construct produces only the storage slot (cc.alloca);
VisitVarDecl binds it directly so any read at a discriminate
site is statically diagnosed by the unbound-handle path.
- lib/Frontend/nvqpp/ASTBridge.cpp: __qpu__ entry-point classification
rejects functor operator() shapes whose signature transitively
mentions measure_handle, the only disambiguable spec violation at
AST time.
Marshaling and QIR conversion
- lib/Optimizer/Builder/Marshal.cpp:
hasLegalType extends the entry-point predicate to reject
measure_handle alongside qubit-typed parameters/results.
lookupHostEntryPointFunc early-returns for device-only kernels
whose signature cannot cross the host boundary, so the host-side
rewriter skips them entirely.
- lib/Optimizer/CodeGen/ConvertToQIRAPI.cpp:
The TypeConverter rewrites !cc.measure_handle to i64, but
cc.compute_ptr / cc.stdvec_data / cc.stdvec_init / cc.stdvec_size
carrying handle pointer or stdvec types had no patterns and no
dynamic-legality predicates, so the framework left them
legal-by-default and inserted unrealized_conversion_casts that
applyPartialConversion could not resolve. Add OpInterfacePattern
instantiations and extend the legality predicate so all four ops
participate in the same operand/result-type rewrite the existing
CC pointer ops already use.
LLVM 22 idiom
- All 15 op-creation sites added by this commit in ConvertExpr.cpp
use the LLVM 22 form Op::create(builder, loc, ...). Two of those
sites are arith::ConstantIntOp poison-result fallbacks (the
unbound-handle and to_integer-rejection paths) and additionally
use the LLVM 22 (builder, loc, type, value) signature.
Tests, runtime helpers, and docs follow as the next commit in this
PR. The dialect type itself (PR NVIDIA#4403) and the QIR conversion's
TypeConverter entry for !cc.measure_handle (PR NVIDIA#4404) are already on
main.
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
Closes the test surface for the measure_handle PR stack: every existing
AST-Quake / Transforms / targettests / docs site that observed the
old `!quake.measure` SSA shape now matches the new `!cc.measure_handle`
shape, and the new test files locking down spec-mandated behavior land
alongside.
New tests
- test/AST-Quake/measure_handle.cpp: scalar-handle bridge shape lock,
including the bind-store / load-with-dominance round-trip and the
`quake.discriminate` insertion at every CK_UserDefinedConversion site
the bridge intercepts.
- test/AST-Quake/measure_handle_qir.cpp: end-to-end QIR conversion
shape for a kernel whose handle escapes a basic block, exercising
the cc.compute_ptr / cc.stdvec_data / cc.stdvec_init / cc.stdvec_size
patterns added to ConvertToQIRAPI in the previous commit.
- test/AST-error/measure_handle.cpp: spec-named diagnostics for the
unbound-handle path, the host-boundary `std::vector<measure_handle>`
rejection, and the `cudaq::to_integer(mz(qvec))` rejection.
- test/Transforms/qir_api_measure_handle.qke: lit-replay of the QIR
conversion's measure_handle paths against hand-rolled IR, so a
bridge-side regression cannot mask a conversion-side regression.
- test/Transforms/cast_fold.qke: extends the cast-fold checks to the
no-op case where a discriminate result is immediately re-cast.
- test/AST-Quake/qir_profiles.cpp: rename of base_profile-1.cpp; the
rename is the only PR-3b contribution to that file -- LLVM 22 had
already removed the `read_result` lines this PR originally targeted.
CHECK migrations (all unchanged shape, just the type string)
- 33 test/AST-Quake/*.cpp + 11 targettests/{Kernel,execution}/*.cpp +
3 test/Transforms/*.qke + 3 docs/sphinx/examples/cpp/**/*.cpp
switched from `!quake.measure` to `!cc.measure_handle` and, where
the bridge inserts the bound-handle round-trip, picked up the
matching `cc.alloca` / `cc.store` / `cc.load` CHECK lines.
LLVM 22 conflict resolutions
- test/AST-Quake/bug_3270.cpp: layered PR 3b's type change on LLVM 22's
loosened `result%{{.*}}` regex form; the unused SSA captures from
the pre-LLVM-22 form are dropped (no downstream CHECK referenced
them).
- test/AST-Quake/if.cpp: PR 3b's `kernel_short_circuit_or` CHECK form
is preserved verbatim (alloca/store/load + discriminate + cmpi ne
against `arith.constant false`); the `arith.constant false` CHECK
line LLVM 22 dropped is re-added because PR 3b's bool coercion still
emits a `cmpi ne, x, false`.
- test/AST-Quake/to_qir.cpp: layered PR 3b's load-delay intent (move
the `load i1, ptr %VAL_9` from before the second mz to inside the
successor block of `br i1 %VAL_12`) on top of LLVM 22's opaque-ptr
form; the typed-pointer / `bitcast %Result*` CHECK form from the
pre-LLVM-22 base is dropped, and basic-block label captures stay on
LLVM 22's loose `{{[0-9]+}}` form.
- test/AST-Quake/qir_profiles.cpp: PR 3b's only intent vs the OLD base
was removing 6 `__quantum__qis__read_result__body` CHECK lines;
LLVM 22 had already removed the same six lines (and additionally
added an `array_record_output` line and the typed-to-opaque pointer
conversion). The rename is the remaining PR 3b contribution.
- test/AST-Quake/cudaq_run.cpp, test/AST-Quake/qalloc_initialization.cpp,
test/Transforms/qir_base_profile.qke: 3-way merge resolved cleanly;
LLVM 22's `!llvm.ptr` / opaque-pointer churn lives alongside PR 3b's
type-name updates without conflict.
Dropped
- unittests/CMakeLists.txt: PR 3b's directory-scope
`add_compile_definitions(CUDAQ_LIBRARY_MODE)` is already on main as
of PR NVIDIA#4427 (which split out the same workaround). PR 3b's wording
reword would be a drive-by; main's wording stays.
Docs and examples
- docs/sphinx/examples/cpp/measuring_kernels.cpp,
docs/sphinx/examples/cpp/sample_to_run_migration.cpp,
docs/sphinx/examples/cpp/basics/mid_circuit_measurement.cpp:
reflect the spec-mandated `cudaq::to_bools(mz(...))` and
`bool` return-type form so the examples compile under measure_handle.
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
ba032ee to
f0887b9
Compare
CI Summary (
|
| Job | Result | Link |
|---|---|---|
build_and_test |
❌ failure | view |
Top-level jobs (13)
| Job | Result |
|---|---|
binaries |
⏩ skipped |
build_and_test |
❌ failure |
config_devdeps |
✅ success |
config_source_build |
⏩ skipped |
config_wheeldeps |
✅ success |
devdeps |
✅ success |
docker_image |
⏩ skipped |
gen_code_coverage |
⏩ skipped |
metadata |
✅ success |
python_metapackages |
⏩ skipped |
python_wheels |
⏩ skipped |
source_build |
⏩ skipped |
wheeldeps |
✅ success |
⏩ Skipped jobs (7) — intentionally skipped on PR builds; run on merge_group / workflow_dispatch
| Job |
|---|
binaries |
config_source_build |
docker_image |
gen_code_coverage |
python_metapackages |
python_wheels |
source_build |
All sub-jobs (40) — every matrix leg, with links
| Job | Status | Link |
|---|---|---|
| Build and test (amd64, llvm, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (amd64, llvm, openmpi) / Dev environment (Python) | ❌ failure | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Python) | ❌ failure | view |
| CI Summary | ❔ in_progress | view |
| Configure build (devdeps) | ✅ success | view |
| Configure build (source_build) | ⏩ skipped | view |
| Configure build (wheeldeps) | ✅ success | view |
| Create CUDA Quantum installer | ⏩ skipped | view |
| Create Docker images | ⏩ skipped | view |
| Create Python metapackages | ⏩ skipped | view |
| Create Python wheels | ⏩ skipped | view |
| Gen code coverage | ⏩ skipped | view |
| Load dependencies (amd64, gcc12) / Caching | ✅ success | view |
| Load dependencies (amd64, gcc12) / Finalize | ✅ success | view |
| Load dependencies (amd64, gcc12) / Metadata | ✅ success | view |
| Load dependencies (amd64, llvm) / Caching | ✅ success | view |
| Load dependencies (amd64, llvm) / Finalize | ✅ success | view |
| Load dependencies (amd64, llvm) / Metadata | ✅ success | view |
| Load dependencies (arm64, gcc12) / Caching | ✅ success | view |
| Load dependencies (arm64, gcc12) / Finalize | ✅ success | view |
| Load dependencies (arm64, gcc12) / Metadata | ✅ success | view |
| Load dependencies (arm64, llvm) / Caching | ✅ success | view |
| Load dependencies (arm64, llvm) / Finalize | ✅ success | view |
| Load dependencies (arm64, llvm) / Metadata | ✅ success | view |
| Load source build cache | ⏩ skipped | view |
| Load wheel dependencies (amd64, 12.6) / Caching | ✅ success | view |
| Load wheel dependencies (amd64, 12.6) / Finalize | ✅ success | view |
| Load wheel dependencies (amd64, 12.6) / Metadata | ✅ success | view |
| Load wheel dependencies (amd64, 13.0) / Caching | ✅ success | view |
| Load wheel dependencies (amd64, 13.0) / Finalize | ✅ success | view |
| Load wheel dependencies (amd64, 13.0) / Metadata | ✅ success | view |
| Load wheel dependencies (arm64, 12.6) / Caching | ✅ success | view |
| Load wheel dependencies (arm64, 12.6) / Finalize | ✅ success | view |
| Load wheel dependencies (arm64, 12.6) / Metadata | ✅ success | view |
| Load wheel dependencies (arm64, 13.0) / Caching | ✅ success | view |
| Load wheel dependencies (arm64, 13.0) / Finalize | ✅ success | view |
| Load wheel dependencies (arm64, 13.0) / Metadata | ✅ success | view |
| Prepare cache clean-up | ❔ in_progress | view |
| Retrieve PR info | ✅ success | view |
⚠️ Required checks (0/6) — 6 missing — declared in .github/required-checks.yml for push
| Required check | Status | Link |
|---|---|---|
| Build and test (amd64, llvm, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (amd64, llvm, openmpi) / Dev environment (Python) | ❌ failure | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Debug) | ❌ failure | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Python) | ❌ failure | view |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Debug) | ❔ missing | |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Python) | ❔ missing |
Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
…pkhalate/measure-handle-pr3b-cpp-frontend Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
08404ed to
3ef9232
Compare
Python-side counterpart to PR 3b's C++ frontend rewire, per spec
cudaq-spec/proposals/measure_handle.bs (Python API; Operational
Semantics; IR Representation).
Bindings and host stubs
- python/runtime/mlir/py_register_dialects.cpp: bind
`!cc.measure_handle` so the AST bridge can construct, check, and
emit the type from Python.
- python/cudaq/kernel_types.py: `cudaq.measure_handle` kernel-type
stub (mirrors `qubit` / `qvector` / `qview`); host-scope
construction raises a device-only RuntimeError.
- python/cudaq/__init__.py: re-export `cudaq.measure_handle` and add
a host stub for `cudaq.to_bools(handles)` raising the same
device-only error -- kernel-side calls are intercepted by the AST
bridge.
- python/cudaq/kernel/utils.py: `containsMeasureHandle(ty)`,
mirroring `cudaq::cc::containsMeasureHandle` from PR 3b
(`lib/Optimizer/Dialect/CC/CCTypes.cpp`); used by the bridge's
boundary check.
AST bridge (`PyASTBridge` in `python/cudaq/kernel/ast_bridge.py`)
- mz / mx / my emit `quake.{mz,mx,my}` producing
`!cc.measure_handle` (scalar) or `!cc.stdvec<!cc.measure_handle>`
(vector). Discriminate insertion is deferred to coercion sites.
- `__discriminateIfMeasureHandle` inserts `quake.discriminate` at
every spec-listed coercion site: arithmetic-to-bool (if / while /
not / and / or), `changeOperandToType` (i1 + i8 + stdvec<i1>),
Compare (==/!=), explicit `bool(...)`, IfExp test, Assert test;
surfaces the `discriminating an unbound measure_handle`
diagnostic for the default-constructed pattern.
- `cudaq.measure_handle()` -> `cc.UndefOp(!cc.measure_handle)` so
the unbound-handle diagnostic has a recognizable source.
- `cudaq.to_bools(handles)` -> vectorized `quake.discriminate` on
`!cc.stdvec<!cc.measure_handle>`; legacy
`cudaq.to_integer(mz(qvec))` is rejected with a targeted
diagnostic matching PR 3b's C++ rejection.
- Boundary check at entry-point creation: walk the kernel signature
with `containsMeasureHandle` and reject any parameter / return
position transitively containing a `measure_handle`. Diagnostic
matches PR 3b's spec-canonical wording.
Tests
- python/tests/kernel/test_measure_handle.py: new spec-coverage
suite (host-scope rejections, scalar/vector emission shape, every
bool-coercion site, `to_bools` lowering, `to_integer(to_bools(...))`
composition, unbound-handle diagnostic, boundary diagnostic).
- python/tests/mlir/{bug_1775,bug_1777,bug_1875,call_qpu}.py:
type-rename and structural CHECK refresh for the new bridge IR
shape.
- python/tests/kernel/test_{assignments,kernel_features,run_kernel,
to_integer,kernel_shift_operators}.py: pytest migrations off
the implicit `mz`-returns-bool assumption.
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Pradnya Khalate <pkhalate@nvidia.com>
3ef9232 to
12c9308
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cudaq::measure_handle#4409.cudaq.measure_handle, rewires the Python AST bridge somz/mx/myemit handles directly, lowers every Python bool-coercion site toquake.discriminate, surfacescudaq.to_boolsfor bulk discrimination, and rejectsmeasure_handlein entry-point kernel signatures with the spec-canonical diagnostic.What Changed
cudaq.measure_handleis a kernel-only stub that raises on host instantiation;!cc.measure_handleregistered as a Python-visible MLIR type.mz/mx/myproduce!cc.measure_handle(scalar) or!cc.stdvec<!cc.measure_handle>(vector);__discriminateIfMeasureHandleis inserted at every bool-coercion site (if/while/not/bool()/==/!=/return,and/orshort-circuit RHS, list-of-bool element stores viai8storage);cudaq.to_boolslowers to vectorizedquake.discriminate;cudaq.to_integer(list[measure_handle])is rejected and requires explicitto_integer(to_bools(...))composition.measure_handle(direct or transitive) in the signature get the spec-canonical 'measure_handle cannot cross the host-device boundary; entry-point kernels must discriminate first' diagnostic, mirroring the C++ wording for cross-frontend consistency.Breaking Changes
mzreturningbooldirectly (e.g.,if mz(q):used to type-check asbool; now type-checks asmeasure_handleand is implicitly discriminated by the new bridge path). User-visible behavior in the common case is unchanged, but deeper integrations that introspect the return type will see the new class.(mz(q), 1)returning a tuple) is a known gap; documented asFIXMEinline in the affected test and tracked infollowups.md. Users hit a clear bridge diagnostic, not a silent miscompile.Dependency
cudaq::measure_handle#4409