Skip to content

Tail-form return in terminal switches#258

Open
AaronWebster wants to merge 1 commit into
emboss/drop-known-guardfrom
emboss/tail-form-return
Open

Tail-form return in terminal switches#258
AaronWebster wants to merge 1 commit into
emboss/drop-known-guardfrom
emboss/tail-form-return

Conversation

@AaronWebster
Copy link
Copy Markdown
Collaborator

When the optimized Ok() switch is the function's tail — last group emitted, no [requires] clause after — arms that validate exactly one field with no residual collapse from

case K:
  if (!field().Ok()) return false;
  break;

to

case K:
  return field().Ok();

Saves one conditional branch per qualifying arm. On Thumb-2 / MicroBlaze (where -Os is conservative about merging cmp+beq sequences) and on older compilers that don't see through the if/return-false/break shape, this trims a few bytes per case across large tagged-union schemas.

Per-arm decision: any arm that doesn't qualify (multi-field, residual, or the switch isn't terminal) keeps the break-form body. Mixing forms within one switch is safe — the default fallthrough lands on the function's return true; either way.

Golden churn: many_conditionals.emb.h shrinks substantially (the 99 single-field cases all collapse to tail-form); condition.emb.h and parameters.emb.h see smaller similar collapses.

Size impact (cumulative vs. master)

Target Metric Master PR Delta
ARM Thumb-2 TU .text 18962 17974 −988 (−5.2%)
ARM Thumb-2 LargeConditionals::Ok() 5382 4930 −452 (−8.4%)
ARM Thumb-2 DisjunctionConditionals::Ok() 358 262 −96 (−26.8%)
MicroBlaze TU .text 43640 35796 −7844 (−18.0%)
MicroBlaze LargeConditionals::Ok() 14824 8308 −6516 (−44.0%)
MicroBlaze DisjunctionConditionals::Ok() 640 532 −108 (−16.9%)
Host x86-64 TU .text 29166 28242 −924 (−3.2%)
Host x86-64 LargeConditionals::Ok() 3948 3040 −908 (−23.0%)
Host x86-64 DisjunctionConditionals::Ok() 431 354 −77 (−17.9%)

Note: LargeConditionals::Ok() measured by nm grows slightly on Thumb-2 and MicroBlaze for this PR alone (+302 / +604 bytes), but TU .text still shrinks. The compiler is inlining tail-form arms into callers more aggressively, so reduced overall code lives in Ok()'s callers rather than Ok() itself.

Stacked on #257.

When the optimized Ok() switch is the function's tail — last group
emitted, no [requires] clause after — arms that validate exactly one
field with no residual collapse from

    case K:
      if (!field().Ok()) return false;
      break;

to

    case K:
      return field().Ok();

Saves one conditional branch per qualifying arm. On Thumb-2 /
MicroBlaze (where -Os is conservative about merging cmp+beq
sequences) and on older compilers that don't see through the
if/return-false/break shape, this trims a few bytes per case across
large tagged-union schemas.

Per-arm decision: any arm that doesn't qualify (multi-field,
residual, or the switch isn't terminal) keeps the break-form body.
Mixing forms within one switch is safe — the default fallthrough
lands on the function's \`return true;\` either way.

Golden churn: many_conditionals.emb.h shrinks substantially (the 99
single-field cases all collapse to tail-form); condition.emb.h and
parameters.emb.h see smaller similar collapses.
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.

2 participants