added more extensive peephole optimizations
This commit is contained in:
261
main.py
261
main.py
@@ -3631,32 +3631,79 @@ class Assembler:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _peephole_optimize_definition(self, definition: Definition) -> None:
|
def _peephole_optimize_definition(self, definition: Definition) -> None:
|
||||||
# Rewrite short stack-manipulation sequences into canonical forms.
|
# Word-only rewrite rules: pattern → replacement (both tuples of
|
||||||
rules: List[Tuple[Tuple[str, ...], Tuple[str, ...]]] = [
|
# word names). The engine scans left-to-right and applies the
|
||||||
|
# longest matching rule at each position.
|
||||||
|
word_rules: List[Tuple[Tuple[str, ...], Tuple[str, ...]]] = [
|
||||||
|
# --- stack no-ops (cancellation) ---
|
||||||
|
(("dup", "drop"), ()),
|
||||||
|
(("swap", "swap"), ()),
|
||||||
|
(("over", "drop"), ()),
|
||||||
|
(("dup", "nip"), ()),
|
||||||
|
(("2dup", "2drop"), ()),
|
||||||
|
(("2swap", "2swap"), ()),
|
||||||
|
(("rot", "rot", "rot"), ()),
|
||||||
|
(("rot", "-rot"), ()),
|
||||||
|
(("-rot", "rot"), ()),
|
||||||
|
(("drop", "drop"), ("2drop",)),
|
||||||
|
(("over", "over"), ("2dup",)),
|
||||||
|
(("inc", "dec"), ()),
|
||||||
|
(("dec", "inc"), ()),
|
||||||
|
(("neg", "neg"), ()),
|
||||||
|
(("not", "not"), ()),
|
||||||
|
(("bitnot", "bitnot"), ()),
|
||||||
|
(("bnot", "bnot"), ()),
|
||||||
|
(("abs", "abs"), ("abs",)),
|
||||||
|
# --- canonicalizations that merge into single ops ---
|
||||||
(("swap", "drop"), ("nip",)),
|
(("swap", "drop"), ("nip",)),
|
||||||
# Stack no-ops
|
|
||||||
(("dup", "drop"), tuple()),
|
|
||||||
(("swap", "swap"), tuple()),
|
|
||||||
(("over", "drop"), tuple()),
|
|
||||||
(("dup", "nip"), tuple()),
|
|
||||||
(("2dup", "2drop"), tuple()),
|
|
||||||
(("2swap", "2swap"), tuple()),
|
|
||||||
(("rot", "rot", "rot"), tuple()),
|
|
||||||
# Canonicalizations
|
|
||||||
(("swap", "over"), ("tuck",)),
|
(("swap", "over"), ("tuck",)),
|
||||||
(("swap", "nip"), ("drop",)),
|
(("swap", "nip"), ("drop",)),
|
||||||
(("nip", "drop"), ("2drop",)),
|
(("nip", "drop"), ("2drop",)),
|
||||||
(("tuck", "drop"), ("swap",)),
|
(("tuck", "drop"), ("swap",)),
|
||||||
|
# --- commutative ops: swap before them is a no-op ---
|
||||||
|
(("swap", "+"), ("+",)),
|
||||||
|
(("swap", "*"), ("*",)),
|
||||||
|
(("swap", "=="), ("==",)),
|
||||||
|
(("swap", "!="), ("!=",)),
|
||||||
|
(("swap", "band"), ("band",)),
|
||||||
|
(("swap", "bor"), ("bor",)),
|
||||||
|
(("swap", "bxor"), ("bxor",)),
|
||||||
|
(("swap", "and"), ("and",)),
|
||||||
|
(("swap", "or"), ("or",)),
|
||||||
|
(("swap", "min"), ("min",)),
|
||||||
|
(("swap", "max"), ("max",)),
|
||||||
|
# --- dup + self-idempotent binary → identity ---
|
||||||
|
(("dup", "bor"), ()), # x | x == x
|
||||||
|
(("dup", "band"), ()), # x & x == x
|
||||||
|
(("dup", "bxor"), ("drop", "literal_0")), # x ^ x == 0
|
||||||
|
(("dup", "=="), ("drop", "literal_1")), # x == x always true
|
||||||
|
(("dup", "-"), ("drop", "literal_0")), # x - x == 0
|
||||||
]
|
]
|
||||||
|
|
||||||
max_pat_len = max(len(pattern) for pattern, _ in rules)
|
# Filter out placeholder rules whose replacements contain
|
||||||
|
# pseudo-words; expand them into proper Op sequences later.
|
||||||
|
_PLACEHOLDER_RULES: Dict[Tuple[str, ...], Tuple[str, ...]] = {}
|
||||||
|
clean_rules: List[Tuple[Tuple[str, ...], Tuple[str, ...]]] = []
|
||||||
|
for pat, repl in word_rules:
|
||||||
|
if any(r.startswith("literal_") for r in repl):
|
||||||
|
_PLACEHOLDER_RULES[pat] = repl
|
||||||
|
else:
|
||||||
|
clean_rules.append((pat, repl))
|
||||||
|
word_rules = clean_rules
|
||||||
|
|
||||||
# Build index: first word -> list of (pattern, replacement)
|
max_pat_len = max(len(p) for p, _ in word_rules) if word_rules else 0
|
||||||
rule_index: Dict[str, List[Tuple[Tuple[str, ...], Tuple[str, ...]]]] = {}
|
rule_index: Dict[str, List[Tuple[Tuple[str, ...], Tuple[str, ...]]]] = {}
|
||||||
for pattern, repl in rules:
|
for pattern, repl in word_rules:
|
||||||
rule_index.setdefault(pattern[0], []).append((pattern, repl))
|
rule_index.setdefault(pattern[0], []).append((pattern, repl))
|
||||||
|
|
||||||
nodes = definition.body
|
nodes = definition.body
|
||||||
|
|
||||||
|
# Outer loop: keeps re-running all passes until nothing changes.
|
||||||
|
any_changed = True
|
||||||
|
while any_changed:
|
||||||
|
any_changed = False
|
||||||
|
|
||||||
|
# ---------- Pass 1: word-only pattern rewriting ----------
|
||||||
changed = True
|
changed = True
|
||||||
while changed:
|
while changed:
|
||||||
changed = False
|
changed = False
|
||||||
@@ -3666,7 +3713,35 @@ class Assembler:
|
|||||||
node = nodes[idx]
|
node = nodes[idx]
|
||||||
matched = False
|
matched = False
|
||||||
if node._opcode == OP_WORD:
|
if node._opcode == OP_WORD:
|
||||||
candidates = rule_index.get(str(node.data))
|
word_name = str(node.data)
|
||||||
|
|
||||||
|
# --- placeholder rules (produce literals) ---
|
||||||
|
for pat, repl in _PLACEHOLDER_RULES.items():
|
||||||
|
plen = len(pat)
|
||||||
|
if pat[0] != word_name:
|
||||||
|
continue
|
||||||
|
if idx + plen > len(nodes):
|
||||||
|
continue
|
||||||
|
seg = nodes[idx:idx + plen]
|
||||||
|
if any(n._opcode != OP_WORD for n in seg):
|
||||||
|
continue
|
||||||
|
if tuple(str(n.data) for n in seg) == pat:
|
||||||
|
base_loc = seg[0].loc
|
||||||
|
for r in repl:
|
||||||
|
if r.startswith("literal_"):
|
||||||
|
val = int(r[len("literal_"):])
|
||||||
|
optimized.append(Op(op="literal", data=val, loc=base_loc))
|
||||||
|
else:
|
||||||
|
optimized.append(Op(op="word", data=r, loc=base_loc))
|
||||||
|
idx += plen
|
||||||
|
changed = True
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
if matched:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# --- normal word-only rules ---
|
||||||
|
candidates = rule_index.get(word_name)
|
||||||
if candidates:
|
if candidates:
|
||||||
for window in range(min(max_pat_len, len(nodes) - idx), 1, -1):
|
for window in range(min(max_pat_len, len(nodes) - idx), 1, -1):
|
||||||
segment = nodes[idx:idx + window]
|
segment = nodes[idx:idx + window]
|
||||||
@@ -3691,51 +3766,122 @@ class Assembler:
|
|||||||
continue
|
continue
|
||||||
optimized.append(nodes[idx])
|
optimized.append(nodes[idx])
|
||||||
idx += 1
|
idx += 1
|
||||||
|
if changed:
|
||||||
|
any_changed = True
|
||||||
nodes = optimized
|
nodes = optimized
|
||||||
|
|
||||||
# Literal-aware algebraic identities and redundant unary chains.
|
# ---------- Pass 2: literal + word algebraic identities ----------
|
||||||
|
# String literals push TWO values (pointer + length), so
|
||||||
|
# most literal-aware rewrites must be restricted to scalars.
|
||||||
|
def _is_scalar_literal(node: Op) -> bool:
|
||||||
|
return node._opcode == OP_LITERAL and not isinstance(node.data, str)
|
||||||
|
|
||||||
changed = True
|
changed = True
|
||||||
while changed:
|
while changed:
|
||||||
changed = False
|
changed = False
|
||||||
optimized = []
|
optimized = []
|
||||||
idx = 0
|
idx = 0
|
||||||
|
|
||||||
while idx < len(nodes):
|
while idx < len(nodes):
|
||||||
# Redundant unary pairs.
|
# -- Redundant unary pairs (word word) --
|
||||||
if idx + 1 < len(nodes):
|
if idx + 1 < len(nodes):
|
||||||
a = nodes[idx]
|
a, b = nodes[idx], nodes[idx + 1]
|
||||||
b = nodes[idx + 1]
|
|
||||||
if a._opcode == OP_WORD and b._opcode == OP_WORD:
|
if a._opcode == OP_WORD and b._opcode == OP_WORD:
|
||||||
wa = str(a.data)
|
wa, wb = str(a.data), str(b.data)
|
||||||
wb = str(b.data)
|
|
||||||
if (wa, wb) in {
|
if (wa, wb) in {
|
||||||
("not", "not"),
|
("not", "not"), ("neg", "neg"),
|
||||||
("neg", "neg"),
|
("bitnot", "bitnot"), ("bnot", "bnot"),
|
||||||
|
("inc", "dec"), ("dec", "inc"),
|
||||||
}:
|
}:
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
# abs is idempotent
|
||||||
|
if wa == "abs" and wb == "abs":
|
||||||
|
optimized.append(a)
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
# Binary op identities where right operand is a literal.
|
# -- scalar literal + dup → literal literal --
|
||||||
if idx + 1 < len(nodes):
|
if idx + 1 < len(nodes):
|
||||||
lit = nodes[idx]
|
a, b = nodes[idx], nodes[idx + 1]
|
||||||
op = nodes[idx + 1]
|
if _is_scalar_literal(a) and b._opcode == OP_WORD and str(b.data) == "dup":
|
||||||
|
optimized.append(a)
|
||||||
|
optimized.append(Op(op="literal", data=a.data, loc=a.loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# -- scalar literal + drop → (nothing) --
|
||||||
|
if idx + 1 < len(nodes):
|
||||||
|
a, b = nodes[idx], nodes[idx + 1]
|
||||||
|
if _is_scalar_literal(a) and b._opcode == OP_WORD and str(b.data) == "drop":
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# -- scalar literal scalar literal + 2drop → (nothing) --
|
||||||
|
if idx + 2 < len(nodes):
|
||||||
|
a, b, c = nodes[idx], nodes[idx + 1], nodes[idx + 2]
|
||||||
|
if _is_scalar_literal(a) and _is_scalar_literal(b) and c._opcode == OP_WORD and str(c.data) == "2drop":
|
||||||
|
idx += 3
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# -- scalar literal scalar literal + swap → swapped --
|
||||||
|
if idx + 2 < len(nodes):
|
||||||
|
a, b, c = nodes[idx], nodes[idx + 1], nodes[idx + 2]
|
||||||
|
if _is_scalar_literal(a) and _is_scalar_literal(b) and c._opcode == OP_WORD and str(c.data) == "swap":
|
||||||
|
optimized.append(Op(op="literal", data=b.data, loc=b.loc))
|
||||||
|
optimized.append(Op(op="literal", data=a.data, loc=a.loc))
|
||||||
|
idx += 3
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# -- Binary op identities: literal K + word --
|
||||||
|
if idx + 1 < len(nodes):
|
||||||
|
lit, op = nodes[idx], nodes[idx + 1]
|
||||||
if lit._opcode == OP_LITERAL and isinstance(lit.data, int) and op._opcode == OP_WORD:
|
if lit._opcode == OP_LITERAL and isinstance(lit.data, int) and op._opcode == OP_WORD:
|
||||||
k = int(lit.data)
|
k = int(lit.data)
|
||||||
w = str(op.data)
|
w = str(op.data)
|
||||||
base_loc = lit.loc or op.loc
|
base_loc = lit.loc or op.loc
|
||||||
|
|
||||||
|
# Identity elements
|
||||||
if (w == "+" and k == 0) or (w == "-" and k == 0) or (w == "*" and k == 1) or (w == "/" and k == 1):
|
if (w == "+" and k == 0) or (w == "-" and k == 0) or (w == "*" and k == 1) or (w == "/" and k == 1):
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Absorbing elements
|
||||||
|
if w == "*" and k == 0:
|
||||||
|
optimized.append(Op(op="word", data="drop", loc=base_loc))
|
||||||
|
optimized.append(Op(op="literal", data=0, loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if w == "band" and k == 0:
|
||||||
|
optimized.append(Op(op="word", data="drop", loc=base_loc))
|
||||||
|
optimized.append(Op(op="literal", data=0, loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if w == "bor" and k == -1:
|
||||||
|
optimized.append(Op(op="word", data="drop", loc=base_loc))
|
||||||
|
optimized.append(Op(op="literal", data=-1, loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Negate
|
||||||
if w == "*" and k == -1:
|
if w == "*" and k == -1:
|
||||||
optimized.append(Op(op="word", data="neg", loc=base_loc))
|
optimized.append(Op(op="word", data="neg", loc=base_loc))
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Modulo 1 → always 0
|
||||||
if w == "%" and k == 1:
|
if w == "%" and k == 1:
|
||||||
optimized.append(Op(op="word", data="drop", loc=base_loc))
|
optimized.append(Op(op="word", data="drop", loc=base_loc))
|
||||||
optimized.append(Op(op="literal", data=0, loc=base_loc))
|
optimized.append(Op(op="literal", data=0, loc=base_loc))
|
||||||
@@ -3743,31 +3889,86 @@ class Assembler:
|
|||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 0 == → not
|
||||||
if w == "==" and k == 0:
|
if w == "==" and k == 0:
|
||||||
optimized.append(Op(op="word", data="not", loc=base_loc))
|
optimized.append(Op(op="word", data="not", loc=base_loc))
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# No-op bitwise
|
||||||
if (w == "bor" and k == 0) or (w == "bxor" and k == 0):
|
if (w == "bor" and k == 0) or (w == "bxor" and k == 0):
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if w == "band" and k == -1:
|
if w == "band" and k == -1:
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if w in {"shl", "shr", "sar"} and k == 0:
|
if w in {"shl", "shr", "sar"} and k == 0:
|
||||||
idx += 2
|
idx += 2
|
||||||
changed = True
|
changed = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Strength reduction: multiply by power of 2 → shl
|
||||||
|
if w == "*" and k > 1 and (k & (k - 1)) == 0:
|
||||||
|
shift = k.bit_length() - 1
|
||||||
|
optimized.append(Op(op="literal", data=shift, loc=base_loc))
|
||||||
|
optimized.append(Op(op="word", data="shl", loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# +1 → inc, -1 → dec, +(-1) → dec, -(−1) → inc
|
||||||
|
if w == "+" and k == 1:
|
||||||
|
optimized.append(Op(op="word", data="inc", loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
if w == "+" and k == -1:
|
||||||
|
optimized.append(Op(op="word", data="dec", loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
if w == "-" and k == 1:
|
||||||
|
optimized.append(Op(op="word", data="dec", loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
if w == "-" and k == -1:
|
||||||
|
optimized.append(Op(op="word", data="inc", loc=base_loc))
|
||||||
|
idx += 2
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
optimized.append(nodes[idx])
|
optimized.append(nodes[idx])
|
||||||
idx += 1
|
idx += 1
|
||||||
|
if changed:
|
||||||
|
any_changed = True
|
||||||
nodes = optimized
|
nodes = optimized
|
||||||
|
|
||||||
|
# ---------- Pass 3: dead-code after unconditional jump/end ----------
|
||||||
|
# Opcodes that prevent fall-through.
|
||||||
|
_TERMINATORS = {OP_JUMP}
|
||||||
|
new_nodes: List[Op] = []
|
||||||
|
dead = False
|
||||||
|
for node in nodes:
|
||||||
|
kind = node._opcode
|
||||||
|
if dead:
|
||||||
|
# A label ends the dead region.
|
||||||
|
if kind == OP_LABEL:
|
||||||
|
dead = False
|
||||||
|
new_nodes.append(node)
|
||||||
|
else:
|
||||||
|
any_changed = True
|
||||||
|
continue
|
||||||
|
new_nodes.append(node)
|
||||||
|
if kind in _TERMINATORS:
|
||||||
|
dead = True
|
||||||
|
if len(new_nodes) != len(nodes):
|
||||||
|
any_changed = True
|
||||||
|
nodes = new_nodes
|
||||||
|
|
||||||
definition.body = nodes
|
definition.body = nodes
|
||||||
|
|
||||||
def _fold_constants_in_definition(self, definition: Definition) -> None:
|
def _fold_constants_in_definition(self, definition: Definition) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user