Skip to content

Commit 409a587

Browse files
committed
Sort switch cases by value; coalesce identical-body cases
Three composing changes to the optimized Ok() switch generator: 1. Case-label sort. Each switch arm's labels are sorted by the underlying integer/enum value before emit. _case_sort_key() returns the int for sorting. Sorted cases give older embedded GCCs (the ones shipped with microblaze-elf and many bare-metal arm-none-eabi toolchains) a better shot at emitting a dense jump table rather than an if-ladder. 2. Identical-body coalescing. Cases whose rendered body text is identical (same field set in the same order) are merged into a single arm with multiple \`case X:\` labels. The C++ compiler emits one body for the whole arm — a real text-size win once a later PR (disjunction matching) starts producing such pairs. 3. Multi-field per case. When two conditional fields share a discriminant + case value (\`if tag == 0: a\` and \`if tag == 0: b\`), they're now bundled into the same case arm rather than the second falling back to a separate if-statement. Each field's validation becomes one line of the case body. The ok_method_switch_case template becomes ok_method_switch_arm, taking pre-formatted \${case_labels} and \${case_body} strings. Single-label single-field arms render identically to the old template, so golden churn is limited to the f0_copy field in testdata/many_conditionals.emb folding into case 0 of the LargeConditionals switch.
1 parent 507136e commit 409a587

3 files changed

Lines changed: 81 additions & 43 deletions

File tree

compiler/back_end/cpp/generated_code_templates

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,8 @@ ${switch_cases}
412412
}
413413

414414

415-
// ** ok_method_switch_case ** /////////////////////////////////////////////////
416-
case ${case_value}:
417-
if (!${field}().Ok()) return false;
418-
break;
415+
// ** ok_method_switch_arm ** //////////////////////////////////////////////////
416+
${case_labels}${case_body} break;
419417

420418

421419

compiler/back_end/cpp/header_generator.py

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,22 @@ def _render_case_label(expression, ir):
13641364
assert False, "Unsupported switch case type"
13651365

13661366

1367+
def _case_sort_key(expression):
1368+
"""Returns the underlying integer value of a switch case expression.
1369+
1370+
Used to sort case labels within a switch so the emitted C++ presents cases
1371+
in monotonic order. Compilers (particularly the older GCCs in many
1372+
embedded toolchains) are more likely to generate a dense jump table when
1373+
cases are sorted.
1374+
"""
1375+
if expression.type.which_type == "integer":
1376+
return int(expression.type.integer.modular_value)
1377+
elif expression.type.which_type == "enumeration":
1378+
return int(expression.type.enumeration.value)
1379+
else:
1380+
assert False, "Unsupported switch case type"
1381+
1382+
13671383
def _get_switch_candidate(expression, ir):
13681384
"""Returns (discriminant_expr, case_value_expr) or (None, None)."""
13691385
if not _is_equality_check(expression):
@@ -1470,27 +1486,14 @@ def _generate_optimized_ok_method_body(fields, ir, subexpressions):
14701486
groups[key] = {
14711487
"type": "switch",
14721488
"discrim_rendered": discrim_rendered,
1473-
"cases": [],
1474-
"seen_cases": set(),
1489+
"cases_by_label": {},
14751490
}
14761491
ordered_keys.append(key)
1477-
14781492
case_str = _render_case_label(case_expr, ir)
1479-
if case_str not in groups[key]["seen_cases"]:
1480-
groups[key]["seen_cases"].add(case_str)
1481-
groups[key]["cases"].append((case_str, field))
1482-
else:
1483-
cond_res = _render_expression(
1484-
field.existence_condition, ir, subexpressions=None
1485-
)
1486-
if_key = "IF:" + cond_res.rendered
1487-
if if_key not in groups:
1488-
groups[if_key] = {
1489-
"type": "if",
1490-
"fields": [],
1491-
}
1492-
ordered_keys.append(if_key)
1493-
groups[if_key]["fields"].append(field)
1493+
case_entry = groups[key]["cases_by_label"].setdefault(
1494+
case_str, {"sort_key": _case_sort_key(case_expr), "fields": []}
1495+
)
1496+
case_entry["fields"].append(field)
14941497
else:
14951498
cond_res = _render_expression(
14961499
field.existence_condition, ir, subexpressions=None
@@ -1508,24 +1511,7 @@ def _generate_optimized_ok_method_body(fields, ir, subexpressions):
15081511
for key in ordered_keys:
15091512
group = groups[key]
15101513
if group["type"] == "switch":
1511-
# Generate switch cases using template
1512-
switch_cases = []
1513-
for case_val, field in group["cases"]:
1514-
switch_cases.append(
1515-
code_template.format_template(
1516-
_TEMPLATES.ok_method_switch_case,
1517-
case_value=case_val,
1518-
field=_cpp_field_name(field.name.name.text),
1519-
)
1520-
)
1521-
1522-
blocks.append(
1523-
code_template.format_template(
1524-
_TEMPLATES.ok_method_switch_block,
1525-
discriminant=group["discrim_rendered"],
1526-
switch_cases="".join(switch_cases),
1527-
)
1528-
)
1514+
blocks.append(_emit_switch_block(group))
15291515
else:
15301516
for field in group["fields"]:
15311517
blocks.append(
@@ -1538,6 +1524,62 @@ def _generate_optimized_ok_method_body(fields, ir, subexpressions):
15381524
return "".join(blocks)
15391525

15401526

1527+
def _render_case_body(fields):
1528+
"""Renders the body of a single switch arm — the field validation tests."""
1529+
return "".join(
1530+
" if (!{}().Ok()) return false;\n".format(
1531+
_cpp_field_name(f.name.name.text)
1532+
)
1533+
for f in fields
1534+
)
1535+
1536+
1537+
def _emit_switch_block(group):
1538+
"""Emits a complete switch block from a collected switch group.
1539+
1540+
Performs case-label sorting and identical-body coalescing:
1541+
1542+
* Case labels within an arm are sorted by underlying numeric value, so
1543+
the C++ compiler is presented with monotonic case sequences and is
1544+
more likely to emit a dense jump table on embedded targets.
1545+
* Distinct case values whose bodies are textually identical (which
1546+
happens when several disjuncts of `||` guard the same field, or when
1547+
multiple fields share an existence condition) are merged into a
1548+
single multi-label arm, so the compiler emits one body for all of
1549+
them rather than duplicating per case.
1550+
"""
1551+
body_to_labels = {}
1552+
body_first_seen = {}
1553+
for case_str, case_entry in group["cases_by_label"].items():
1554+
body = _render_case_body(case_entry["fields"])
1555+
body_to_labels.setdefault(body, []).append((case_entry["sort_key"], case_str))
1556+
if body not in body_first_seen:
1557+
body_first_seen[body] = case_entry["sort_key"]
1558+
1559+
arms = []
1560+
for body, labels in body_to_labels.items():
1561+
labels.sort() # by (sort_key, case_str)
1562+
arms.append((body_first_seen[body], labels, body))
1563+
arms.sort(key=lambda arm: arm[0])
1564+
1565+
rendered_arms = []
1566+
for _, labels, body in arms:
1567+
case_labels = "".join(" case {}:\n".format(cs) for _, cs in labels)
1568+
rendered_arms.append(
1569+
code_template.format_template(
1570+
_TEMPLATES.ok_method_switch_arm,
1571+
case_labels=case_labels,
1572+
case_body=body,
1573+
)
1574+
)
1575+
1576+
return code_template.format_template(
1577+
_TEMPLATES.ok_method_switch_block,
1578+
discriminant=group["discrim_rendered"],
1579+
switch_cases="".join(rendered_arms),
1580+
)
1581+
1582+
15411583
def _generate_structure_definition(type_ir, ir, config: Config):
15421584
"""Generates C++ for an Emboss structure (struct or bits).
15431585

testdata/golden_cpp/many_conditionals.emb.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class GenericLargeConditionalsView final {
9797
switch (emboss_reserved_switch_discrim.ValueOrDefault()) {
9898
case static_cast</**/ ::std::int32_t>(0LL):
9999
if (!f0().Ok()) return false;
100+
if (!f0_copy().Ok()) return false;
100101
break;
101102

102103
case static_cast</**/ ::std::int32_t>(1LL):
@@ -500,9 +501,6 @@ class GenericLargeConditionalsView final {
500501
}
501502
}
502503

503-
if (!has_f0_copy().Known()) return false;
504-
if (has_f0_copy().ValueOrDefault() && !f0_copy().Ok()) return false;
505-
506504
return true;
507505
}
508506
Storage BackingStorage() const { return backing_; }

0 commit comments

Comments
 (0)