Issue draft — NVIDIA/cuda-quantum (not filed)
Title: kernel.apply_call raises AttributeError for any value-returning @cudaq.kernel
Labels: applied automatically by the form (triage)
Required prerequisites
Describe the bug
Passing a @cudaq.kernel that returns a value to kernel_builder's apply_call always fails with an AttributeError from inside the builder. Kernels that return nothing work fine, so the failure depends only on whether the callee has a return type.
The closure that wraps the decorator kernel is built in PyKernel.resolve_callable_arg. In the branch that handles a callee with a result it takes .result off the func.CallOp and then asks that OpResult for .results:
# python/cudaq/kernel/kernel_builder.py:1481-1483 (on main @ 228f099a0)
if funcTy.results:
call = func.CallOp(fn, vs).result
cc.ReturnOp(call.results)
func.CallOp(...).result is an OpResult, which has no .results, so the line is unreachable-in-practice code that was never exercised — there is no test in python/tests where a builder apply_calls a value-returning decorator kernel.
There is a second defect hiding behind the first, which is why this is not a one-token fix. Once the closure genuinely returns a value, the lambda's !cc.callable signature has a result, but __applyControlOrAdjoint hardcodes an empty result list on the call:
# python/cudaq/kernel/kernel_builder.py:729
cc.CallCallableOp([], otherFuncCloned, mlirValues)
cudaq::cc::CallCallableOp::verify() (lib/Optimizer/Dialect/CC/CCOps.cpp:2544-2551) checks coarity, so fixing only the AttributeError swaps the crash for an invalid module (see below). Both spots need the fix.
Steps to reproduce the bug
import cudaq
@cudaq.kernel
def flipAndMeasure(q: cudaq.qubit) -> bool:
x(q)
return mz(q)
flipAndMeasure.compile()
kernel = cudaq.make_kernel()
q = kernel.qalloc()
kernel.apply_call(flipAndMeasure, q)
kernel.mz(q)
print(cudaq.sample(kernel))
Output on CUDA-Q 0.15.1 (macOS arm64, cuda-quantum-cu13 wheel):
Traceback (most recent call last):
File "repro.py", line 14, in <module>
kernel.apply_call(flipAndMeasure, q)
File ".../cudaq/kernel/kernel_builder.py", line 1428, in apply_call
target = self.resolve_callable_arg(self.insertPoint,
DecoratorCapture(target))
File ".../cudaq/kernel/kernel_builder.py", line 1481, in resolve_callable_arg
cc.ReturnOp(call.results)
^^^^^^^^^^^^
AttributeError: 'cudaq.mlir._mlir_libs._mlir.ir.OpResult' object has no attribute 'results'
Dropping the return annotation (def flipAndMeasure(q: cudaq.qubit):) makes the same program work, which isolates the trigger to the callee's return type.
For the second defect: changing only func.CallOp(fn, vs).result to func.CallOp(fn, vs) gets past the AttributeError and then produces a module that does not verify:
error: 'cc.call_callable' op call has incorrect coarity
RuntimeError: pass pipeline failed
Expected behavior
kernel.apply_call(valueReturningKernel, ...) should apply the callee just like any other kernel. The callee's return value is not observable through apply_call (it returns None, and the plain func.call path for builder-to-builder calls already discards the callee result the same way), but the call itself must be emitted and the module must verify.
A related API question for the maintainers, which I have deliberately left out of the fix: should apply_call return a QuakeValue for the callee's result so the builder can consume it? That would be a new API surface rather than a bug fix, so the fix below keeps apply_call returning None and simply stops the crash / invalid IR. Happy to follow up if that is wanted.
Is this a regression? If it is, put the last known working version (or commit) here.
Not a regression as far as I can tell. apply_call learned to accept @cudaq.kernel decorators in #3667 / #3693, and this branch of the closure builder appears never to have worked.
Environment
- CUDA-Q version: 0.15.1 (
cuda-quantum-cu13 wheel, commit aca5853); code inspected at 228f099a0 on main
- Python version: 3.13
- C++ compiler: n/a (pip wheel)
- Operating system: macOS 15 (arm64)
Suggestions
Two changes in python/cudaq/kernel/kernel_builder.py:
-
In resolve_callable_arg, keep the op and forward its results:
call = func.CallOp(fn, vs)
cc.ReturnOp(call.results)
-
In __applyControlOrAdjoint, carry the callable's result types over to the call so cc.call_callable verifies:
lambdaTy = FunctionType(TypeAttr(target.attributes['function_type']).value)
otherFTy = lambdaTy.inputs
otherResTy = lambdaTy.results
...
cc.CallCallableOp(otherResTy, otherFuncCloned, mlirValues)
Only apply_call reaches the cc.create_lambda / cc.call_callable path — adjoint and control pass their target straight to __cloneOrGetFunction — so this does not affect those.
I have a PR ready with these two changes plus a regression test in
python/tests/builder/test_kernel_builder.py.
Issue draft — NVIDIA/cuda-quantum (not filed)
Title:
kernel.apply_callraisesAttributeErrorfor any value-returning@cudaq.kernelLabels: applied automatically by the form (
triage)Required prerequisites
Describe the bug
Passing a
@cudaq.kernelthat returns a value tokernel_builder'sapply_callalways fails with anAttributeErrorfrom inside the builder. Kernels that return nothing work fine, so the failure depends only on whether the callee has a return type.The closure that wraps the decorator kernel is built in
PyKernel.resolve_callable_arg. In the branch that handles a callee with a result it takes.resultoff thefunc.CallOpand then asks thatOpResultfor.results:func.CallOp(...).resultis anOpResult, which has no.results, so the line is unreachable-in-practice code that was never exercised — there is no test inpython/testswhere a builderapply_calls a value-returning decorator kernel.There is a second defect hiding behind the first, which is why this is not a one-token fix. Once the closure genuinely returns a value, the lambda's
!cc.callablesignature has a result, but__applyControlOrAdjointhardcodes an empty result list on the call:cudaq::cc::CallCallableOp::verify()(lib/Optimizer/Dialect/CC/CCOps.cpp:2544-2551) checks coarity, so fixing only theAttributeErrorswaps the crash for an invalid module (see below). Both spots need the fix.Steps to reproduce the bug
Output on CUDA-Q 0.15.1 (macOS arm64,
cuda-quantum-cu13wheel):Dropping the return annotation (
def flipAndMeasure(q: cudaq.qubit):) makes the same program work, which isolates the trigger to the callee's return type.For the second defect: changing only
func.CallOp(fn, vs).resulttofunc.CallOp(fn, vs)gets past theAttributeErrorand then produces a module that does not verify:Expected behavior
kernel.apply_call(valueReturningKernel, ...)should apply the callee just like any other kernel. The callee's return value is not observable throughapply_call(it returnsNone, and the plainfunc.callpath for builder-to-builder calls already discards the callee result the same way), but the call itself must be emitted and the module must verify.A related API question for the maintainers, which I have deliberately left out of the fix: should
apply_callreturn aQuakeValuefor the callee's result so the builder can consume it? That would be a new API surface rather than a bug fix, so the fix below keepsapply_callreturningNoneand simply stops the crash / invalid IR. Happy to follow up if that is wanted.Is this a regression? If it is, put the last known working version (or commit) here.
Not a regression as far as I can tell.
apply_calllearned to accept@cudaq.kerneldecorators in #3667 / #3693, and this branch of the closure builder appears never to have worked.Environment
cuda-quantum-cu13wheel, commitaca5853); code inspected at228f099a0onmainSuggestions
Two changes in
python/cudaq/kernel/kernel_builder.py:In
resolve_callable_arg, keep the op and forward its results:In
__applyControlOrAdjoint, carry the callable's result types over to the call socc.call_callableverifies:Only
apply_callreaches thecc.create_lambda/cc.call_callablepath —adjointandcontrolpass their target straight to__cloneOrGetFunction— so this does not affect those.I have a PR ready with these two changes plus a regression test in
python/tests/builder/test_kernel_builder.py.