Skip to content

gen-verilog: an early return inside a loop does not leave the loop -- 84 guard sites in 34 files, and no corpus file can demonstrate it #2989

Description

@gHashTag

An early return inside a loop sets a flag the loop never reads, so the loop keeps going -- and one of them does not terminate

The Verilog backend lowers return inside a loop body as "assign the function's
return variable, set __t27_ret", and guards only the FUNCTION-TAIL return with
if (!__t27_ret). Nothing guards the loop. Verbatim from
gen-verilog specs/isa/ternary_search.t27:

while ((lo < hi)) begin
    mid = (lo + ((hi - lo) / 2));
    if ((data[mid] == target)) begin
        binary_search = $signed(mid);
        __t27_ret = 1'b1;            // <-- flag set, loop NOT exited,
    end else if ((data[mid] < target)) begin   //     lo and hi unchanged
        lo = (mid + 1);
    end else begin
        hi = mid;
    end
end
if (!__t27_ret) begin
    binary_search = -1;
    __t27_ret = 1'b1;
end

On a hit neither lo nor hi moves, so while ((lo < hi)) spins forever.
On a miss it terminates and is correct.

linear_search in the same file has the terminating variant of the same defect:
it runs to the end of the array and returns the LAST match where the source says
the first.

Population

55 sites across 24 of the 581 generated .v files (a scan tracking begin/end
depth from each loop header and counting __t27_ret = 1'b1; inside). Largest:
specs_math_property_test_template.v 11, specs_compiler_stdlib.v 8,
specs_isa_ternary_hash.v 5, specs_pins_ir.v 4.

55 is the size of the CLASS, not 55 proven-wrong values. An early return in a
loop is only wrong when the loop can iterate again afterwards; the detector
over-counts by construction and this number should not be quoted as defects.
Two functions were read end to end and are demonstrably wrong.

C, Rust and Zig all return the first match on the same source.

Why nothing reported it

All 24 files fail iverilog today for unrelated reasons, and the one deep ruler
that would run them has had zero targets -- #2987. A non-terminating simulation
is also invisible to a compile-only gate by construction.

The repair is the same missing machinery as #2988: a named block per loop, so
the flag can disable it. Filing them separately because the populations differ
and the wrong-value consequence here is silent rather than a no-op.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions