Eliminate redundant residual re-read in optimized Ok() switch arms#272
Open
AaronWebster wants to merge 1 commit into
Open
Eliminate redundant residual re-read in optimized Ok() switch arms#272AaronWebster wants to merge 1 commit into
AaronWebster wants to merge 1 commit into
Conversation
Lever A of the Ok() rework: in a conditional-switch arm whose field carries a residual predicate beyond the discriminant equality (e.g. `if tag == K && outer == 1`), gate the field's Ok() check on the residual alone rather than re-deriving the full has_<field>() existence condition. Inside `case K:` the switch has already decoded and (provably, or via the emitted Known() guard) established the discriminant, so `has_<field>() <=> residual`. The prior form re-invoked has_<field>(), which redundantly re-reads and re-compares the discriminant on every arm; the residual (a single hoisted, shared subexpression) drops that per-arm work. Implementation: preserve the residual conjunct list through _generate_optimized_ok_method_body instead of collapsing it to a bool, thread (ir, subexpressions) into _emit_switch_block/_render_case_body, and render the residual at emit time (so demoted groups leak no dead subexpression). Multiple conjuncts fold into one synthesized boolean AND so the residual renders once. The gate is required for correctness, not just size: <field>() returns a null view when absent, so an unguarded <field>().Ok() would wrongly fail when the residual is false; we also bail when the residual is not Known. Adds testdata OuterGuardedUnion (100 tag arms each guarded by a shared `outer == 1` residual) to exercise and measure the change; flat LargeConditionals (all bare arms) is the unaffected control. Measured OuterGuardedUnion::Ok() reader symbol @ -Os vs the pre-Lever-A generator (schema held fixed): MicroBlaze LE/Vitis (authoritative) -18.7%, MicroBlaze BE -21.5%, ARM Cortex-M4 -17.0%, x86-64 gcc -9.5% / clang -50.7%. Retired guest instructions (OuterGuardedUnion hot loop): MicroBlaze BE -3.2%, MicroBlaze LE -1.6%, ARM -1.1%. Flat LargeConditionals codegen is byte-identical (0 symbol delta on every target); x86 wall-clock unchanged. Golden diff is confined to the residual-arm schemas (condition.emb.h, many_conditionals.emb.h); every pre-existing struct is byte-identical. Full compiler+runtime suite (205 tests) and the #266 condition_test oracle stay green.
This was referenced Jul 19, 2026
robrussell
approved these changes
Jul 22, 2026
| case_entry["entries"].append((field, bool(residual))) | ||
| # Keep the residual conjunct list itself (not just a bool): | ||
| # a residual arm gates its Ok() check on the residual alone | ||
| # at emit time (Lever A), so the concrete IR must survive. |
Member
There was a problem hiding this comment.
This comment seems unnecessary.
| not residual | ||
| for case_entry in group["cases_by_label"].values() | ||
| for (_field, has_residual) in case_entry["entries"] | ||
| for (_field, residual) in case_entry["entries"] |
Member
There was a problem hiding this comment.
for (_, residual) in case_entry["entries"]
| this case. When the list is empty the arm is bare and the case body is | ||
| a single direct Ok() check. | ||
|
|
||
| When there is a residual, we gate the Ok() check on the residual *alone* |
Member
There was a problem hiding this comment.
"When the list of residuals is not empty..."
| then trusted to fold the now-trivially-true discriminant comparison | ||
| inside the has_${field}() call (it's inlined and the case label has | ||
| pinned the discriminant value). | ||
| Each entry is `(field, residual)` where `residual` is the list of |
| """ | ||
| parts = [] | ||
| for field, has_residual in entries: | ||
| for field, residual in entries: |
| `has_${field}()` internally and returns a null view (whose Ok() is false) | ||
| when the field is absent, so an unguarded `${field}().Ok()` would wrongly | ||
| fail whenever the residual is false. We first bail if the residual is not | ||
| Known (e.g. an out-of-bounds read), matching the has_${field}()-based |
Member
There was a problem hiding this comment.
"matching the has_${field}()-based check it replaces." refers to code that no longer exists after this PR.
The explanation is somewhat useful but overly verbose and includes a lot of implementation details about this function and others which may not hold true in future revisions.
| for field, residual in entries: | ||
| name = _cpp_field_name(field.name.name.text) | ||
| if has_residual: | ||
| if residual: |
Member
There was a problem hiding this comment.
These branches can be simplified, something like:
for field, residuals in entries:
name = _cpp_field_name(field.name.name.text)
if len(residuals) == 0:
parts.append(" if (!{}().Ok()) return false;\n".format(name))
continue
if len(residuals) == 1:
residual = residuals[0]
else:
residual = ir_data.Expression(
function=ir_data.Function(
function=ir_data.FunctionMapping.AND,
args=residuals,
),
type=ir_data.ExpressionType(boolean=ir_data.BooleanType()),
)
rendered = _render_expression(
residual, ir, subexpressions=subexpressions
).rendered
parts.append(
" if (!({0}).Known()) return false;\n if (({1}).ValueOrDefault() && !{2}().Ok()) return false;\n"
.format(rendered, rendered, name
)
)
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.
Based on #271 (the guard-demote correctness precursor); review/merge that first.
What
In the optimized
Ok()switch, each residual-guarded arm previously re-read andre-compared the discriminant that the
switchhad already decoded, then recomputedhas_X(). This gates each residual arm on the residual alone:dropping the redundant discriminant re-read. For more than one conjunct the residual is
rendered once as a synthesized boolean-
ANDexpression (shared subexpressions). The gateis required, not merely an optimization:
X()internally re-checkshas_X()and returnsa null view when the field is absent, so an unguarded
X().Ok()would wrongly fail.Soundness rests on the #271 invariant: every switch reaching
_emit_switch_blockhas thediscriminant
Knowninsidecase K:(provably, or via the emittedKnown()guard), sohas_X() ⟺ residual.Measurement
Adds a residual-heavy
OuterGuardedUnionschema totestdata/many_conditionals.emb(always-present
outer, ~100 arms guarded ontag == K && outer == 1) so Lever Aactually fires and the committed harness (#270) can measure it. The flat
LargeConditionalsunion is all bare arms and is the unaffected control.OuterGuardedUnion::Ok()reader symbol @-Os, vs the #271 baseline:Retired instructions (OuterGuardedUnion hot loop): MB-BE −3.2%, MB-LE −1.6%, ARM −1.1%
(small — only the one active arm runs per call).
Control
LargeConditionals: byte-identical codegen, 0 symbol delta on every target.(The plan predicted x86 size unchanged; Lever A in fact helps x86 too.)
Verification
condition.emb.h+many_conditionals.emb.h; all pre-existingstructs byte-identical.
bazel test //...green (206/206), includingcondition_test's Guard conditional switch discriminants in optimized Ok() #266 oracle.