Skip to content

goto: a labelled block resolves only its own labels; a computed goto checks its target - #4475

Merged
borisbat merged 5 commits into
masterfrom
bbatkin/lookibed-goto
Nov 7, 2026
Merged

borisbat merged 5 commits into
masterfrom
bbatkin/lookibed-goto

Conversation

@borisbat

@borisbat borisbat commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Behavior change: a computed goto whose target no label of the function declares now throws invalid label index on every tier; a goto to a label in an enclosing block, or to a label that ends a block, now reaches it in the interpreter.

Why. Five of lookibed's goto reports. A labelled block or loop body resolved every jump from its own table, padded with -1 for the numbers it does not hold: SIGSEGV from a loop body (#4451), jump to label N failed from a plain block (#4453) and for a label standing last in a block (#4454). A computed goto's bad target fell silently out of the function. -aot wrote a C++ goto past an initialized local, which clang refuses (#4450).

What changes.

  • A labelled block takes a jump only to a label it holds, finishes when that label ends the block, and otherwise leaves the flag for the enclosing block; one ownedLabel read replaces the open-coded table checks in the block evals and the loop macro.
  • A computed goto node carries the set of labels its function (or the expression it was simulated for) declares and throws for any other target; a block literal's goto takes the literal's own labels, not the enclosing function's; the table survives relocateCode.
  • The AOT emitter declares every local of a labelled block at the block top through its moved-variable path, and block folding keeps a declaration inside an unreachable region after an unconditional goto.
  • Four copyNode bodies share one copyNodeArray helper.
  • The coverage pass skips global initializers: a block literal in one sits outside any function, and instrumenting it dereferenced a null current-function pointer (the nightly coverage lane failed on this PR's test).

Observable behavior.

  • goto label 1 from a loop body to an outer label numbered below the body's labels: SIGSEGV -> runs.
  • goto label 1 from a labelled non-loop block to an outer label: jump to label 1 failed -> runs.
  • A label as the last statement of a block: jump to label 3 failed -> the block finishes.
  • goto expr with a target no label declares: garbage return -> invalid label index.
  • goto expr naming a label only a block literal inside the function declares: falls out of the function -> invalid label index; a block literal's own goto expr still lands on its own label.
  • -aot of a goto past var g = n * 2: cannot jump from this goto statement to its label -> compiles.

Where to look. SimNode_BlockWithLabels::eval and DAS_PROCESS_LOOP_FLAGS_LABELED in include/daScript/simulate/simulate.h; SimNode_Goto in simulate_nodes.h; preVisitExprLetVariable in daslib/aot_cpp.das.

Fixes #4451, fixes #4453, fixes #4454, fixes #4450, fixes #4466.

Validation, claims, ledger

Validation

  • Full AOT sweep (preflight --only tests-aot, the test_aot build and sweep over tests/): passed on the final code, 770 s; the only commit after it rewraps one comment. Every other gate and lane (lint, review-md, md-ascii, hash-refs, cpp-syntax, ast-verify, compile-sweep, tests-cpp, tests-interp, tests-jit, utils-tests) passed on the same tree.
  • Every branch the diff adds has a test that fails when it is mutated (negative-controlled: the label-table copy only under ASan, which CI's sanitizer lane runs over tests/language).
  • The hot-path addition (the computed goto's label check) is ledgered under ARCHITECTURE.md#sanctioned-hot-path-additions. Measured on a four-way computed-goto dispatch loop (goto (i & 3) + 100, 20M iterations, best of 5 in-process rounds, 5 alternating process runs per binary, M5 Max, interpreter): 5.32 ns -> 5.58 ns per dispatch, +4.8%; run-to-run cv 0.4% and 1.5%.
  • SimNode_Goto gains two data members and a copyNode override: a GitHub code search finds the type only in copies of daScript's own sources (DagorEngine's vendored tree and forks), nothing else constructs it.
  • test_aot_subset passed with daslib/fio.das in its closure.
  • Codex review round at the tip: one finding, rejected with a probe - it assumed a goto can target a label inside a nested block, which the front end refuses (can't find label, error 30819).
  • The compile-sweep gate in the review tree needed the utils/find-dupe daspkg install and the dasSQLITE/dasCrypto modules enabled; both are environment, not diff.

Claims - stated, not tested

  • A debugger-stepped run takes the same jump rules: test_goto_out_of_a_labelled_block.das runs under options debugger, the plain nodes take the same shapes in test_goto_to_a_label_at_block_end.das.

Not done

#nightly - the diff touches src/ and include/, and the full AOT sweep is a nightly lane.

Copilot AI balanced review requested due to automatic review settings November 7, 2026 07:52
@borisbat
borisbat force-pushed the bbatkin/lookibed-goto branch from 1802ca7 to bbffec7 Compare November 7, 2026 07:55

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

The change still has unresolved moderate issues in the computed-goto simulation around incorrect label collection across closures and stale label-root/known-label state across multiple initializer simulations, plus a minor test-style inconsistency noted in the new test.

3 open issues
What changed in this PR

This pull request corrects goto semantics across interpreter/JIT/AOT by tightening labelled-block label ownership (blocks only resolve their own labels and otherwise propagate the jump outward), making computed goto validate its target (throwing invalid label index when invalid), and adjusting AOT emission/block folding so goto does not produce invalid C++ control flow around declarations. It also adds regression tests and minor refactors/documentation updates.

Changes:

  • Fix labelled block/loop jump handling so only “owned” labels are resolved locally; outward jumps and block-end labels behave correctly in the interpreter.
  • Add computed-goto target validation using the function/expression’s declared label set, preserved across code relocation.
  • Update AOT emission and AST folding to keep/move declarations into C++-legal positions when bypassed by unconditional goto.
  • Add regression tests for the reported cases; refactor repeated node-array copy logic.

Key review notes to address (from final comments):

  • Moderate: FunctionLabelCollector should not treat labels inside nested block literals/closures (ExprMakeBlock) as “known” labels for the outer function; also remove the unnecessary const_cast on labelRoot.
  • Moderate: SimulateVisitor::simulateExpression must reset labelRoot (and the cached known-label set) per entrypoint call to avoid validating later initializer subtrees against the first subtree’s labels.
  • Nits: Minor grammar fixes in docs and minor test-style/comment-style consistency tweaks in the new tests.

This pull request requires changes.

File Summary
tests/​language/​test_goto_to_a_label_at_block_end.das Adds regression coverage for jumping to a label at block end and related outward-jump behavior.
tests/​language/​test_goto_past_declaration.das Adds regression coverage for -aot correctness when goto bypasses local declarations/unreachable regions.
tests/​language/​test_goto_out_of_a_loop_body.das Adds regression coverage ensuring loop bodies don’t incorrectly swallow outward gotos.
tests/​language/​test_goto_out_of_a_labelled_block.das Adds regression coverage for outward gotos from labelled non-loop blocks; includes comment-style nit to adjust.
tests/​language/​test_computed_goto_in_while.das Adds regression coverage for computed goto validity/invalid-target throwing; includes test-idiom nit to align assertion ordering.
src/​simulate/​simulate.cpp Updates labelled-block evaluation to resolve only owned labels and propagate jumps outward; includes node-copy refactor usage.
src/​simulate/​simulate_visit.cpp Ensures computed-goto metadata survives relocation via SimNode_Goto::copyNode.
src/​builtin/​REVIEW.md Documentation wording updates; includes minor grammar nit.
src/​ast/​REVIEW.md Documentation clarifications around cache/versioning and related rules.
src/​ast/​ast_simulate.cpp Collects “known labels” and wires them into computed-goto simulation; has two moderate correctness issues to fix (closure traversal + labelRoot reset/caching).
src/​ast/​ast_block_folding.cpp Adjusts folding to retain declarations after unconditional gotos so AOT has required declarations available.
include/​daScript/​simulate/​simulate.h Adds copyNodeArray helper and updates labelled-loop handling to use owned-label lookup.
include/​daScript/​simulate/​simulate_nodes.h Extends SimNode_Goto to validate computed targets against known labels and support relocation-safe copying.
include/​daScript/​simulate/​REVIEW.md Documentation wording updates; includes minor grammar nit.
include/​daScript/​simulate/​ARCHITECTURE.md Records the computed-goto validation as a sanctioned hot-path addition; includes minor grammar nit.
include/​daScript/​ast/​REVIEW.md Documentation clarifications around AST/JIT review guidance.
include/​daScript/​ast/​ast_serializer.h Bumps serialization/versioning to invalidate stale caches after AST/folding behavior changes.
daslib/​REVIEW.md Documentation clarifications for compile-time visitor/routing scope.
daslib/​aot_cpp.das Updates AOT variable/declaration handling for labelled blocks so generated C++ remains valid with goto.

Review effort: Balanced

Comment thread src/ast/ast_simulate.cpp Resolved
Comment thread src/ast/ast_simulate.cpp Resolved
Copilot AI balanced review requested due to automatic review settings November 7, 2026 08:02

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🔵 Needs a closer look

A critical open issue remains in src/ast/ast_simulate.cpp where sv_collectKnownLabels() incorrectly records labels from nested closure blocks, potentially letting computed goto target an inner-closure label and bypass the intended runtime safety check.

3 open issues

Review effort: Balanced

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

Approval is blocked because there are still unresolved nit findings affecting clarity and repo style conventions, including an unnecessary const_cast in ast_simulate.cpp and a misplaced top-level //! comment in a language test.

1 open issue
3 resolved since last review

Review effort: Balanced

@borisbat
borisbat force-pushed the bbatkin/lookibed-goto branch from c100efc to 69005d1 Compare November 7, 2026 10:02
Copilot AI balanced review requested due to automatic review settings November 7, 2026 10:02

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

There are unresolved findings, most notably that the public-header copyNodeArray uses an unconstrained memcpy that can be misused with non-trivially-copyable types, along with a smaller const-correctness concern in sv_collectKnownLabels due to an unnecessary const_cast.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread include/daScript/simulate/simulate.h Resolved
A block literal in a global initializer sits outside any function, so
the coverage pass reached its block instrumentation with a null (or the
previous function's) `func` and threw. The pass now skips global
variables the way it skips template structures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🔵 Needs a closer look

A remaining review finding notes an unnecessary const_cast on a const Expression pointer in src/ast/ast_simulate.cpp that should be removed to preserve const-correctness and avoid implying AST mutation.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Comment thread src/ast/ast_simulate.cpp Resolved
@borisbat
borisbat force-pushed the bbatkin/lookibed-goto branch from bdd2577 to 5abeea9 Compare November 7, 2026 11:45
Copilot AI balanced review requested due to automatic review settings November 7, 2026 11:45

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

Approval is blocked because a moderate active finding reports potential signed-overflow undefined behavior in src/ast/ast_simulate.cpp when computing maxLabel + 1 in int32_t, requiring a type-safe cast/clamp fix.

1 open issue

Review effort: Balanced

Comment thread src/ast/ast_simulate.cpp Resolved
borisbat and others added 2 commits November 7, 2026 03:54
A labelled block or loop body padded its label table with -1 for every number
it does not hold and jumped there anyway: SIGSEGV from a loop body, 'jump to
label N failed' from a plain block, and a label standing last in a block
indexed one past its body and failed the same way. The block now takes a jump
only to a label it holds, finishes when that label ends the block, and
otherwise leaves the flag set for the enclosing block.

Fixes #4451

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The innermost labelled block used to throw for a label index outside its
table; now that a block hands an unowned jump outward, the goto node itself
carries the set of labels the function declares and throws 'invalid label
index' for any other value, so a bad computed target never falls silently out
of the function. The test also pins the computed-goto-into-a-while shape,
which answers right on this master.

Fixes #4466

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
borisbat and others added 2 commits November 7, 2026 03:54
C++ refuses a goto that jumps past an initialized declaration. The emitter now
hoists every local of a block holding labels through its moved-variable path,
the way it already does for a block with a finally section, so the declaration
precedes every goto and label of the block and the site becomes an assignment.
Block folding keeps a declaration inside an unreachable region after an
unconditional goto, so the uses after the label still see it.

Fixes #4450

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The simulate checklist defines the hot path once in its opening, gives the ABI
sweep its own rule with a layout change defined (data members, virtuals, a
base) over every header it ships, lists the ledger entry's content as the work
itself and splits the PR-measurement duty off it. The daslib checklist names
the AOT emitter's visitors in its macro routing and leads its pair-record rule
with the trigger. The src/builtin checklist takes the annotation-name duty: a
bind's registered name, the name string a Managed*Annotation is constructed
with included, bumps the JIT codegen version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

The change still has open review findings—specifically a few identified grammar/wording issues in the ARCHITECTURE.md and REVIEW.md documents—that need to be addressed before approval.

3 open issues
1 resolved since last review

Review effort: Balanced

Comment thread include/daScript/simulate/REVIEW.md Resolved
Comment thread src/builtin/REVIEW.md Resolved
@borisbat

borisbat commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator Author

The one red lane, extended_checks (linux, all), fails llvm_jit_global_lookup.das, a test #4472 added: it counts a hoisted lookup in the x86 vectorizer's run-once iter.check block as inside the loop. Nothing in this PR touches the JIT; #4479 fixes the count. Every other lane is green.

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