Conversation
`ValidateReturnStatements.all_paths_return` treats a `for`/`while` loop
as guaranteeing a return whenever its body alone always returns,
regardless of whether the loop is ever entered:
if isinstance(stmt, (ast.For, ast.While)):
if all_paths_return(stmt.body) or all_paths_return(stmt.orelse):
return True
For a loop with a runtime-dependent trip count (`for i in range(n):`,
`while i < n:`) this is unsound: if the loop executes zero times,
control falls off the end of the function with no return statement
having run, and the compiler never raises "functions with return type
annotations must have a return statement." Reproduced end to end: a
kernel `def k(n: int) -> int: for i in range(n): return 1` compiles
successfully and, called with `n=0`, returns whatever the backend's
`cc.UndefOp` fallback happens to produce instead of erroring.
Only tighten the two shapes that are provably unsound from the syntax
alone: a `while` whose condition is not a statically-true comparison,
and a `for` over `range(...)` whose bound is not a compile-time
constant with a non-empty span. Iteration over anything else (a plain
variable, a list literal, a qvector, ...) keeps the existing lenient
treatment, since this file's own test suite already relies on it for
unrelated reasons in exactly that case, and there is no static
argument for or against it either way.
The pre-existing `while <var> <op> <literal>:` idiom used throughout
this file's own test suite (`i = 0` then `while i < 6:`) is preserved
by tracking compile-time-constant integer variables assigned earlier
in the same statement list.
Signed-off-by: Udaya Tejas <udayatejas2004@gmail.com>
Collaborator
|
In the C++ bridge, a loop that will definitely execute will still produce a warning for a missing return. This PR should probably go the easier route of rejecting programs without a return after the loop and not look too closely at the loop condition. @schweitzpgi, does that sound reasonable to you? Produces |
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.
Fixes #5369
Bug
ValidateReturnStatements.all_paths_return(python/cudaq/kernel/analysis.py:191-206) treats afor/whileloop as guaranteeing a return whenever its body alone always returns, regardless ofwhether the loop ever executes:
For a loop with a runtime-dependent trip count (
for i in range(n):,while i < n:), this isunsound: a zero-trip loop falls through with no return statement having run, and
cudaq.kernel functions with return type annotations must have a return statement.never fires.The kernel compiles and the backend's
cc.UndefOpfallback silently manufactures the returnedvalue instead of erroring. The sibling
ast.Ifcheck just above requires both branches toreturn (
and); this is the same soundness rule, just not applied to loops.Fix
Only tighten the two shapes that are provably unsound from the syntax alone: a
whilewhosecondition is not a statically-true comparison, and a
foroverrange(...)whose bound is not acompile-time constant with a non-empty span. Any other iterable (a plain variable, a list literal,
a qvector, ...) keeps the existing lenient treatment, since it is a compile-time unknown either way
and this file's own test suite already relies on that leniency for unrelated reasons in exactly
that case. The pre-existing
while <var> <op> <literal>:idiom used throughout this file's owntests (
i = 0thenwhile i < 6:) is preserved by tracking compile-time-constant integervariables assigned earlier in the same statement list.
Verification
Not a regression from a deliberate choice: introduced with the validator itself in
cb16d1e59(#3148, "Handling return from loop variations in a kernel") with no comment addressing the
and/orasymmetry with theIfcase just above — confirmed viagit log -S. Unchanged since:git log <wheel-sha>..main -- python/cudaq/kernel/analysis.pyis empty, and the file isbyte-identical between the 0.15.1 wheel and current
main.Negative control, both directions, run directly against the installed wheel (this box has no
local LLVM/MLIR build;
analysis.pyis pure Python with no compiled-extension dependency, sothis file is not subject to that limitation — the worktree's file was copied into the wheel's
site-packagesbefore each run):Also ran the full existing
test_run_kernel.pysuite with the fix applied: 80 passed, 4 skipped,no regressions — including every
test_return_from_*/test_return_with_*case that alreadyexercises this validator (54 tests), specifically
test_return_from_if_and_else_loop_with_true_condition_in_for_loopand..._in_while_loop, whoserange(6)/i = 0; while i < 6:bodies are exactly the shapes thisfix must keep accepting.
Small diff; touches only the loop-soundness branch of one validator and adds three regression
tests.
Signed-off-by: Udaya Tejas udayatejas2004@gmail.com