Skip to content

Commit 5e1894c

Browse files
committed
Tail-form return in terminal switches
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.
1 parent 449a73a commit 5e1894c

4 files changed

Lines changed: 181 additions & 555 deletions

File tree

compiler/back_end/cpp/header_generator.py

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,7 +1554,9 @@ def _extract_switch_arms(expression, ir):
15541554
return None, None
15551555

15561556

1557-
def _generate_optimized_ok_method_body(fields, ir, subexpressions):
1557+
def _generate_optimized_ok_method_body(
1558+
fields, ir, subexpressions, allow_tail_form=False
1559+
):
15581560
"""Generates optimized C++ code for the Ok() method body.
15591561
15601562
This function optimizes validation logic for structures with conditional
@@ -1698,6 +1700,19 @@ def _generate_optimized_ok_method_body(fields, ir, subexpressions):
16981700
if total_entries < 2:
16991701
group["type"] = "demoted_to_if"
17001702

1703+
# Find the last surviving switch group; only that one is eligible for
1704+
# tail-form case rewrites, since tail-form turns the switch into the
1705+
# function's exit and any later block would be unreachable.
1706+
last_switch_key = None
1707+
if allow_tail_form:
1708+
for key in reversed(ordered_keys):
1709+
if groups[key]["type"] == "switch":
1710+
last_switch_key = key
1711+
break
1712+
# Any non-switch block after a switch means that switch is
1713+
# not the last statement in Ok(); abandon tail-form.
1714+
break
1715+
17011716
blocks = []
17021717
for key in ordered_keys:
17031718
group = groups[key]
@@ -1711,6 +1726,7 @@ def _generate_optimized_ok_method_body(fields, ir, subexpressions):
17111726
group["known_check_required"] = not _is_discriminant_provably_known(
17121727
group["discrim_expr"], fields
17131728
)
1729+
group["tail_form"] = key == last_switch_key
17141730
blocks.append(_emit_switch_block(group))
17151731
elif group["type"] == "demoted_to_if":
17161732
for field in group["encounter_order"]:
@@ -1765,7 +1781,8 @@ def _render_case_body(entries):
17651781
def _emit_switch_block(group):
17661782
"""Emits a complete switch block from a collected switch group.
17671783
1768-
Performs case-label sorting and identical-body coalescing:
1784+
Performs case-label sorting, identical-body coalescing, and (when the
1785+
switch is the last statement in Ok()) tail-form arm rewriting:
17691786
17701787
* Case labels within an arm are sorted by underlying numeric value, so
17711788
the C++ compiler is presented with monotonic case sequences and is
@@ -1775,31 +1792,63 @@ def _emit_switch_block(group):
17751792
multiple fields share an existence condition) are merged into a
17761793
single multi-label arm, so the compiler emits one body for all of
17771794
them rather than duplicating per case.
1795+
* When the switch is the function's tail and an arm validates a single
1796+
field with no residual, the arm body becomes `return field().Ok();`
1797+
instead of `if (!field().Ok()) return false; break;`. Trims one
1798+
conditional branch per qualifying arm — adds up across large
1799+
tagged-union schemas on Thumb-2 / MicroBlaze.
17781800
"""
1779-
body_to_labels = {}
1780-
body_first_seen = {}
1801+
tail_form_enabled = group.get("tail_form", False)
1802+
1803+
# Group cases by rendered body (for identical-body coalescing). Track
1804+
# the original entries so we can decide tail-form eligibility per arm.
1805+
body_to_arm = {}
17811806
for case_str, case_entry in group["cases_by_label"].items():
17821807
body = _render_case_body(case_entry["entries"])
1783-
body_to_labels.setdefault(body, []).append((case_entry["sort_key"], case_str))
1784-
if body not in body_first_seen:
1785-
body_first_seen[body] = case_entry["sort_key"]
1808+
arm = body_to_arm.setdefault(
1809+
body,
1810+
{
1811+
"labels": [],
1812+
"entries": case_entry["entries"],
1813+
"first_sort_key": case_entry["sort_key"],
1814+
},
1815+
)
1816+
arm["labels"].append((case_entry["sort_key"], case_str))
1817+
if case_entry["sort_key"] < arm["first_sort_key"]:
1818+
arm["first_sort_key"] = case_entry["sort_key"]
17861819

1787-
arms = []
1788-
for body, labels in body_to_labels.items():
1789-
labels.sort() # by (sort_key, case_str)
1790-
arms.append((body_first_seen[body], labels, body))
1791-
arms.sort(key=lambda arm: arm[0])
1820+
sorted_arms = sorted(body_to_arm.values(), key=lambda a: a["first_sort_key"])
1821+
for arm in sorted_arms:
1822+
arm["labels"].sort()
17921823

17931824
rendered_arms = []
1794-
for _, labels, body in arms:
1795-
case_labels = "".join(" case {}:\n".format(cs) for _, cs in labels)
1796-
rendered_arms.append(
1797-
code_template.format_template(
1798-
_TEMPLATES.ok_method_switch_arm,
1799-
case_labels=case_labels,
1800-
case_body=body,
1801-
)
1825+
for arm in sorted_arms:
1826+
case_labels = "".join(
1827+
" case {}:\n".format(cs) for _, cs in arm["labels"]
18021828
)
1829+
# Tail-form is eligible when the arm has a single bare-equality
1830+
# entry (one field, no residual). In that case the case body
1831+
# collapses to a single Ok() call we can return directly.
1832+
if (
1833+
tail_form_enabled
1834+
and len(arm["entries"]) == 1
1835+
and not arm["entries"][0][1] # no residual
1836+
):
1837+
field = arm["entries"][0][0]
1838+
rendered_arms.append(
1839+
"{0} return {1}().Ok();\n".format(
1840+
case_labels, _cpp_field_name(field.name.name.text)
1841+
)
1842+
)
1843+
else:
1844+
body = _render_case_body(arm["entries"])
1845+
rendered_arms.append(
1846+
code_template.format_template(
1847+
_TEMPLATES.ok_method_switch_arm,
1848+
case_labels=case_labels,
1849+
case_body=body,
1850+
)
1851+
)
18031852

18041853
if group.get("known_check_required", True):
18051854
known_check = (
@@ -1979,6 +2028,11 @@ def _generate_structure_definition(type_ir, ir, config: Config):
19792028
],
19802029
ir,
19812030
ok_subexpressions,
2031+
# When the final group emitted is a switch and there is nothing
2032+
# after it (no [requires] clause), each case body's
2033+
# `if (!X().Ok()) return false; break;` can be rewritten as
2034+
# `return X().Ok();` — one fewer branch per case.
2035+
allow_tail_form=(requires_check == ""),
19822036
)
19832037

19842038
class_forward_declarations = code_template.format_template(

testdata/golden_cpp/condition.emb.h

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16544,17 +16544,9 @@ class GenericConditionalInlineView final {
1654416544
const auto emboss_reserved_switch_discrim = emboss_reserved_local_ok_subexpr_2;
1654516545
switch (emboss_reserved_switch_discrim.ValueOrDefault()) {
1654616546
case static_cast</**/::std::int32_t>(0LL):
16547-
if (!type_0().Ok()) return false;
16548-
break;
16549-
16550-
16551-
16547+
return type_0().Ok();
1655216548
case static_cast</**/::std::int32_t>(1LL):
16553-
if (!type_1().Ok()) return false;
16554-
break;
16555-
16556-
16557-
16549+
return type_1().Ok();
1655816550

1655916551
}
1656016552
}

0 commit comments

Comments
 (0)