Skip to content

Reverse switches on the stored condition instead of a control-flow tape - #1918

Merged
vgvassilev merged 1 commit into
masterfrom
switch-cond-reverse
Jul 21, 2026
Merged

Reverse switches on the stored condition instead of a control-flow tape#1918
vgvassilev merged 1 commit into
masterfrom
switch-cond-reverse

Conversation

@vgvassilev

Copy link
Copy Markdown
Owner

Reverse-mode differentiation of a switch statement recorded, in a second control-flow tape, which case a break exited from, then re-dispatched the reverse sweep by popping that tape. The information is redundant: the switch condition is already stored (_cond), and every case guard already compares against it (if (v == _cond) break). The extra tape -- and the BreakContStmtHandler state backing it -- only duplicated what the condition carries.

Drop the control-flow tape for switches and re-switch on the stored condition directly. Each fall-through group's reverse entry is now labelled with its original case values rather than a synthesized counter, and the trailing group (closed by the switch end rather than a break) is labelled in VisitSwitchStmt. The per-case guards are unchanged. Loops keep their control-flow tape, where a break's iteration genuinely cannot be recovered from a condition.

This is behaviour-preserving: all Switch.C/SwitchInit.C execution results are unchanged; only the generated code -- forward (the counter pushes are gone) and reverse -- and its FileCheck baselines change, with one fewer tape. A switch whose cases return rather than break is added to Switch.C to cover the returning-case shape.

@vgvassilev
vgvassilev force-pushed the switch-cond-reverse branch from 897a4d6 to e8e9dfe Compare July 21, 2026 19:14

@github-actions github-actions Bot 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.

clang-tidy made some suggestions

Sema::ConditionResult revCondRes =
m_Sema.ActOnCondition(getCurrentScope(), noLoc, CloneNode(condExpr),
Sema::ConditionKind::Switch);
SwitchStmt* reverseSS =

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.

warning: use auto when initializing with a template cast to avoid duplicating the type name [modernize-use-auto]

Suggested change
SwitchStmt* reverseSS =
auto* reverseSS =

++i) {
SwitchCase* rev = nullptr;
if (isa<DefaultStmt>(SSData.cases[i])) {
rev = new (m_Context) DefaultStmt(noLoc, noLoc, inner);

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.

warning: assigning newly created 'gsl::owner<>' to non-owner 'SwitchCase *' [cppcoreguidelines-owning-memory]

        rev = new (m_Context) DefaultStmt(noLoc, noLoc, inner);
        ^

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Reverse-mode differentiation of a switch statement recorded, in a second
control-flow tape, which case a `break` exited from, then re-dispatched the
reverse sweep by popping that tape. The information is redundant: the switch
condition is already stored (`_cond`), and every case guard already compares
against it (`if (v == _cond) break`). The extra tape -- and the
BreakContStmtHandler state backing it -- only duplicated what the condition
carries.

Drop the control-flow tape for switches and re-switch on the stored condition
directly. Each fall-through group's reverse entry is now labelled with its
original case values rather than a synthesized counter, and the trailing
group (closed by the switch end rather than a break) is labelled in
VisitSwitchStmt. The per-case guards are unchanged. Loops keep their
control-flow tape, where a break's iteration genuinely cannot be recovered
from a condition.

This is behaviour-preserving: all Switch.C/SwitchInit.C execution results are
unchanged; only the generated code -- forward (the counter pushes are gone)
and reverse -- and its FileCheck baselines change, with one fewer tape. A
switch whose cases return rather than break is added to Switch.C to cover the
returning-case shape.

SwitchInit.C no longer needs its Valgrind XFAIL: the control-flow tape it
tripped on under memcheck is gone.
@vgvassilev
vgvassilev force-pushed the switch-cond-reverse branch from e8e9dfe to b6d553b Compare July 21, 2026 21:16
@vgvassilev
vgvassilev merged commit 53ebd00 into master Jul 21, 2026
40 checks passed
@vgvassilev
vgvassilev deleted the switch-cond-reverse branch July 21, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant