Files
BFM-decomp/tools/permuter_weights.py
T
Drew T 0eadf313ee feat(phase-24): T5 — §31-directed permuter mutation + grinder input-changed gating
- tools/permuter_weights.py (NEW): classify(klass,where) -> regalloc|schedule|cse|None
  + render_settings_toml emitting the [weight_overrides] table decomp-permuter merges over
  the gcc defaults (main.py:336 / helpers.py per-key replace). Biases pass-selection toward
  each class's §31 levers (perm_reorder_decls RC-1/3, perm_reorder_stmts RC-2/S1,
  perm_temp_for_expr S2, perm_commutative cse); value/type noise -> ~0.1. NO submodule edit.
- p16_permute.setup(..., klass=, where=) writes it (+--klass, backlog auto-lookup);
  grinder.py auto-threads klass/where_stuck. klass=None -> gcc defaults (undirected superset).
- grinder fix (R14): replace the blind tried.clear() idle churn with input-changed gating
  (draft_sig=(best_draft mtime, closeness) — re-open a fn only when the worker improved it).
- VALIDATED: regalloc profile drove func_8014E048 masked 36->29 (match_one -drz 35->28) where
  the undirected search stalled; re-logged (28, improved draft promoted). Flagship S11/RC-6
  count-exact seeds improve but do not gate (intrinsic, §31 "two probes dont grind") -> T5b fuel.
- distill: cookbook §3b + SETUP inventory (R16/R30). No build-input changed -> 136/136 untouched.
2026-07-03 00:14:29 -06:00

176 lines
9.4 KiB
Python

#!/usr/bin/env python3
"""permuter_weights.py — §31-directed decomp-permuter mutation (Phase 24 T5).
Turns the permuter's RANDOM walk into a DIRECTED search over the gcc-2.7.2 lever space
(docs/gcc-2.7.2-map/{regalloc,sched,cse_expr}.md). A near-miss draft's residual has a KNOWN
class (the wave agent already diagnosed it — the `klass`/`where_stuck` fields in the backlog,
the `@class:`/`@stuck:` headers on .run/wave/*.c). §31 says which C-level lever moves each
class; each lever is exercised by a specific decomp-permuter `perm_*` randomization pass. So
biasing the pass-selection weights toward the class's levers (and away from value/type noise
that a COUNT-EXACT register/schedule permutation can never use) makes every mutation land on
the residual instead of a random subexpression.
Mechanism (NO decomp-permuter submodule edit — R3/R20): the permuter reads a top-level
`weight_overrides` table from the scratch dir's settings.toml (src/main.py:336), merges it over
the compiler-type defaults per-key (helpers.py:43 merge_randomization_weights REPLACES a key's
weight; unknown keys are ignored, all base keys survive), and the Randomizer picks a pass with
`random_weighted(methods)` (randomizer.py:2467). So a partial {pass: weight} override reshapes
the mutation distribution. This module emits that table from a residual class.
The whole-binary byte-gate (tools/harvest_verify.py) remains the SOLE arbiter (G3/P9): a biased
search only reaches the SAME output-0 candidates faster/at-all — a permuter output-0 is a strong
candidate to gate, never a bank.
Pass→class map (grounded in the §31 catalog; keys are the exact perm_* method names in
tools/decomp-permuter/src/randomizer.py / default_weights.toml [base]):
perm_reorder_decls RC-1 spill-slot order · RC-3 callee-saved creation-order tie (K1)
perm_reorder_stmts RC-2 live-range shaping · S1 LUID order · D1 delay-slot adjacency
perm_temp_for_expr S2 birthing-boost (fresh single-set local) · RC-2 (the workhorse)
perm_expand_expr S2 kill (inline a temp) · S3 re-decompose a chain
perm_split_assignment set-count/position → S2 / RC-2
perm_duplicate_assignment 2nd SET → kills the S2 boost / defeats RC-7 REG_EQUIV
perm_add_self_assignment 2nd SET (as above)
perm_commutative operand order of a commutative op → cse/combine mult-canon (RC-cse)
perm_factor_mult/shift product re-decomposition → cse/S3
perm_pad_var_decl nudges K1 pseudo-creation order → RC-1
perm_struct_ref a->b ↔ (*a).b access SHAPE → RC-1 (spill vs temp vs MEM)
perm_ins_block scoping → RC-5 pin/var range
(value/type noise, down-weighted for count-exact residuals): perm_add_mask, perm_xor_zero,
perm_mult_zero, perm_add_sub, perm_inequalities, perm_cast_simple, perm_refer_to_var,
perm_float_literal, perm_condition, perm_dummy_comma_expr, perm_compound_assignment,
perm_randomize_{internal,external,function}_type, perm_empty_stmt, perm_remove_ast.
"""
import re
# --- The class profiles ------------------------------------------------------------------
# Each maps perm_* pass -> weight (REPLACES the gcc default for that key). Absent keys keep
# their gcc default (default_weights.toml [base]+[gcc]). Noise passes are pushed to ~0.1 (not
# a hard 0 — keep a sliver of diversity) so the search spends its budget on the class's levers.
_NOISE = {
"perm_add_mask": 0.1, "perm_xor_zero": 0.1, "perm_mult_zero": 0.1,
"perm_add_sub": 0.1, "perm_inequalities": 0.1, "perm_cast_simple": 0.3,
"perm_refer_to_var": 0.2, "perm_float_literal": 0.1, "perm_condition": 0.3,
"perm_dummy_comma_expr": 0.1, "perm_compound_assignment": 0.3,
"perm_randomize_internal_type": 1.0, "perm_randomize_external_type": 0.2,
"perm_randomize_function_type": 0.2, "perm_empty_stmt": 0.5, "perm_remove_ast": 0.5,
"perm_inline": 1.0,
}
# REGALLOC — RC-1/2/3, S7, S11 register-permutation: decl-order + range-shaping + boost, under
# the draft's hand-applied pins holding the callee-saved core stable (§31 S11 protocol).
_REGALLOC = {
"perm_reorder_decls": 40.0, # RC-1 slot order, RC-3 tie order — THE regalloc lever
"perm_reorder_stmts": 40.0, # RC-2 range shaping / S11 LUID
"perm_temp_for_expr": 60.0, # S2 boost / fresh single-set (the workhorse)
"perm_split_assignment": 20.0,
"perm_expand_expr": 15.0,
"perm_duplicate_assignment": 8.0,
"perm_add_self_assignment": 8.0,
"perm_struct_ref": 8.0, # access SHAPE (RC-1)
"perm_pad_var_decl": 5.0, # K1 creation-order nudge
"perm_ins_block": 6.0, # RC-5 scope
"perm_sameline": 4.0,
"perm_commutative": 2.0,
"perm_chain_assignment": 2.0, "perm_long_chain_assignment": 1.0,
**_NOISE,
}
# SCHEDULE — S1..S5, D1..D4: statement reorder dominates (LUID), plus S2 boost + S4 fillers.
_SCHEDULE = {
"perm_reorder_stmts": 60.0, # THE schedule lever (LUID order)
"perm_temp_for_expr": 60.0, # S2 boost
"perm_expand_expr": 25.0, # S2 kill / re-decompose
"perm_reorder_decls": 15.0,
"perm_split_assignment": 15.0,
"perm_duplicate_assignment": 8.0, "perm_add_self_assignment": 8.0,
"perm_ins_block": 10.0, "perm_empty_stmt": 5.0, # S4 filler (empty_stmt un-suppressed here)
"perm_sameline": 6.0,
"perm_commutative": 4.0,
"perm_struct_ref": 5.0,
**{k: v for k, v in _NOISE.items() if k != "perm_empty_stmt"},
}
# CSE / COMBINE — operand-order + re-decomposition canonicalization (func_80148094 mult-order).
_CSE = {
"perm_commutative": 40.0, # mult/add operand order — the cse-canon residual
"perm_expand_expr": 40.0, # re-decompose a folded expression
"perm_split_assignment": 30.0,
"perm_temp_for_expr": 60.0,
"perm_factor_mult": 10.0, "perm_factor_shift": 8.0, # product re-decomposition
"perm_reorder_stmts": 15.0, "perm_reorder_decls": 10.0,
"perm_add_sub": 4.0,
**{k: v for k, v in _NOISE.items()
if k not in ("perm_add_sub",)},
}
PROFILES = {"regalloc": _REGALLOC, "schedule": _SCHEDULE, "cse": _CSE}
# --- The classifier ----------------------------------------------------------------------
# Precedence: the `klass` TAG is the primary bucket (a seed tagged `regalloc-order` gets the
# regalloc profile even though its `where` text mentions LUID/schedule mechanism), EXCEPT a cse
# residual (mult-canon / operand-order) overrides — it wants the commutative-heavy profile no
# matter the tag (func_80148094 is tagged regalloc-order but its residual is a cse mult-order).
# A generic tag (WAVE/GIANT/none) falls back to the longer `where` diagnosis. Unknown -> None
# (broad gcc default: a safe superset — never worse than today's undirected search).
_CSE_RE = re.compile(r"cse|combine|mult-?canon|canonicali|operand-?order|neg\w*first|mem-?first", re.I)
_REGALLOC_TAG = re.compile(r"regalloc|reg-?alloc|remat|spill|coalesc|pressure|prologue|-order\b|\brc-?\d", re.I)
_SCHED_TAG = re.compile(r"sched|\bs[1-9]\b|\bd[1-9]\b|delay|birthing|chain-?prior|dbr", re.I)
# `where`-text fallbacks (only consulted when the klass tag is generic): note S11/S10 must NOT
# read as the S1 schedule token, so the schedule fallback excludes a following digit.
_SCHED_WHERE = re.compile(r"schedul|\bs[1-9](?![0-9])\b|\bd[1-9]\b|delay-?slot|birthing|chain-?prior|dbr|luid", re.I)
_REGALLOC_WHERE = re.compile(r"regalloc|reg-?alloc|\brc-?\d|s1[01]\b|s7\b|spill|callee-?saved|coalesc|pressure|prologue-?weave|allocno", re.I)
def classify(klass, where=""):
"""residual class string(s) -> profile name in {'cse','schedule','regalloc'} or None.
`klass` = the short class TAG (backlog `klass` / .c `@class:`); `where` = the longer
diagnosis text (backlog `where_stuck`). The tag decides the primary bucket; cse residuals
override; a generic tag falls back to the where text.
"""
kt = klass or ""
wt = where or ""
if _CSE_RE.search(kt) or _CSE_RE.search(wt): # residual-level override
return "cse"
if _REGALLOC_TAG.search(kt):
return "regalloc"
if _SCHED_TAG.search(kt):
return "schedule"
# generic tag (WAVE / GIANT / None) -> use the diagnosis text
if _SCHED_WHERE.search(wt):
return "schedule"
if _REGALLOC_WHERE.search(wt):
return "regalloc"
return None
def weight_overrides_for(klass, where=""):
"""residual class -> the {perm_*: weight} override dict (empty if unknown/None)."""
prof = classify(klass, where)
return dict(PROFILES.get(prof, {}))
def render_settings_toml(fn, compiler_type="gcc", klass=None, where=""):
"""the full settings.toml body: func_name + compiler_type + (optional) [weight_overrides].
Empty overrides => no table => decomp-permuter uses the plain gcc defaults (unchanged)."""
prof = classify(klass, where)
ov = dict(PROFILES.get(prof, {}))
lines = [f'func_name = "{fn}"', 'compiler_type = "%s"' % compiler_type]
if ov:
lines.append("")
lines.append(f"# §31-directed mutation (Phase 24 T5): profile={prof} for class={klass!r}")
lines.append("[weight_overrides]")
for k in sorted(ov):
lines.append(f"{k} = {float(ov[k])}")
return "\n".join(lines) + "\n", prof
if __name__ == "__main__":
import sys
klass = sys.argv[1] if len(sys.argv) > 1 else "regalloc-order"
where = sys.argv[2] if len(sys.argv) > 2 else ""
body, prof = render_settings_toml("func_TEST", klass=klass, where=where)
print(f"# classify({klass!r}, {where!r}) -> profile={prof}\n")
print(body)