215 lines
7.4 KiB
Python
215 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
from .formatting import h16, label_for
|
|
from .model import Instruction
|
|
|
|
|
|
def summarize_timing(
|
|
instructions: dict[int, Instruction],
|
|
labels: dict[int, str] | None = None,
|
|
call_graph: dict[str, object] | None = None,
|
|
) -> dict[str, list[dict[str, object]]]:
|
|
labels = labels or {}
|
|
addresses = sorted(instructions)
|
|
if not addresses:
|
|
return {"blocks": [], "loops": []}
|
|
|
|
starts = _block_starts(instructions, addresses, labels, call_graph)
|
|
blocks = [_summarize_block(start, instructions, labels, starts) for start in sorted(starts)]
|
|
loops = _summarize_loops(instructions, labels)
|
|
return {
|
|
"blocks": [block for block in blocks if block["instruction_count"]],
|
|
"loops": loops,
|
|
}
|
|
|
|
|
|
def cycle_bounds(cycles: dict[str, object] | None) -> tuple[int, int] | None:
|
|
if not cycles:
|
|
return None
|
|
if "cycles" in cycles:
|
|
value = int(cycles["cycles"])
|
|
return value, value
|
|
if "cycles_min" in cycles and "cycles_max" in cycles:
|
|
return int(cycles["cycles_min"]), int(cycles["cycles_max"])
|
|
if "not_taken" in cycles and "taken" in cycles:
|
|
values = [int(cycles["not_taken"]), int(cycles["taken"])]
|
|
return min(values), max(values)
|
|
if "trap_not_taken" in cycles and "trap_taken" in cycles:
|
|
values = [int(cycles["trap_not_taken"]), int(cycles["trap_taken"])]
|
|
return min(values), max(values)
|
|
if "false" in cycles and "count_minus_1" in cycles and "taken" in cycles:
|
|
values = [int(cycles["false"]), int(cycles["count_minus_1"]), int(cycles["taken"])]
|
|
return min(values), max(values)
|
|
return None
|
|
|
|
|
|
def _block_starts(
|
|
instructions: dict[int, Instruction],
|
|
addresses: list[int],
|
|
labels: dict[int, str],
|
|
call_graph: dict[str, object] | None,
|
|
) -> set[int]:
|
|
address_set = set(addresses)
|
|
starts = {addresses[0]}
|
|
starts.update(address for address in labels if address in address_set)
|
|
|
|
for node in (call_graph or {}).get("nodes", []):
|
|
start = int(node["start"])
|
|
if start in address_set:
|
|
starts.add(start)
|
|
|
|
for ins in instructions.values():
|
|
starts.update(target for target in ins.targets if target in address_set)
|
|
if ins.kind in {"branch", "jump", "return", "rte", "sleep", "invalid"}:
|
|
next_address = ins.address + max(ins.size, 1)
|
|
if next_address in address_set:
|
|
starts.add(next_address)
|
|
return starts
|
|
|
|
|
|
def _summarize_block(
|
|
start: int,
|
|
instructions: dict[int, Instruction],
|
|
labels: dict[int, str],
|
|
starts: set[int],
|
|
) -> dict[str, object]:
|
|
addresses = []
|
|
pc = start
|
|
cycles_min = 0
|
|
cycles_max = 0
|
|
unknown_cycles = 0
|
|
terminator: Instruction | None = None
|
|
|
|
while pc in instructions:
|
|
if addresses and pc in starts:
|
|
break
|
|
ins = instructions[pc]
|
|
addresses.append(pc)
|
|
bounds = cycle_bounds(ins.cycles)
|
|
if bounds is None:
|
|
unknown_cycles += 1
|
|
else:
|
|
cycles_min += bounds[0]
|
|
cycles_max += bounds[1]
|
|
terminator = ins
|
|
next_pc = pc + max(ins.size, 1)
|
|
if ins.kind in {"branch", "jump", "return", "rte", "sleep", "invalid"}:
|
|
break
|
|
if next_pc in starts:
|
|
break
|
|
pc = next_pc
|
|
|
|
end = addresses[-1] if addresses else start
|
|
return {
|
|
"start": start,
|
|
"end": end,
|
|
"label": labels.get(start, label_for(start)),
|
|
"instruction_count": len(addresses),
|
|
"cycles_min": cycles_min,
|
|
"cycles_max": cycles_max,
|
|
"unknown_cycles": unknown_cycles,
|
|
"terminator": terminator.text if terminator else "",
|
|
"targets": list(terminator.targets if terminator else []),
|
|
}
|
|
|
|
|
|
def _summarize_loops(
|
|
instructions: dict[int, Instruction],
|
|
labels: dict[int, str],
|
|
) -> list[dict[str, object]]:
|
|
loops: list[dict[str, object]] = []
|
|
for ins in sorted(instructions.values(), key=lambda item: item.address):
|
|
for target in ins.targets:
|
|
if target > ins.address or target not in instructions:
|
|
continue
|
|
body = [
|
|
address
|
|
for address in sorted(instructions)
|
|
if target <= address <= ins.address
|
|
]
|
|
if not body:
|
|
continue
|
|
cycles_min = 0
|
|
cycles_max = 0
|
|
unknown_cycles = 0
|
|
has_call = False
|
|
for address in body:
|
|
body_ins = instructions[address]
|
|
has_call = has_call or body_ins.kind == "call"
|
|
bounds = cycle_bounds(body_ins.cycles)
|
|
if bounds is None:
|
|
unknown_cycles += 1
|
|
else:
|
|
cycles_min += bounds[0]
|
|
cycles_max += bounds[1]
|
|
loops.append(
|
|
{
|
|
"start": target,
|
|
"end": ins.address,
|
|
"label": labels.get(target, label_for(target)),
|
|
"back_edge": ins.address,
|
|
"back_edge_text": ins.text,
|
|
"instruction_count": len(body),
|
|
"cycles_min": cycles_min,
|
|
"cycles_max": cycles_max,
|
|
"unknown_cycles": unknown_cycles,
|
|
"has_call": has_call,
|
|
"kind": _loop_kind(ins, has_call),
|
|
},
|
|
)
|
|
return loops
|
|
|
|
|
|
def _loop_kind(instruction: Instruction, has_call: bool) -> str:
|
|
if instruction.mnemonic.startswith("SCB/"):
|
|
return "counter_delay_loop" if not has_call else "counter_loop"
|
|
if instruction.mnemonic in {"BRA", "BRN"}:
|
|
return "unconditional_loop" if not has_call else "loop_with_call"
|
|
return "delay_loop_candidate" if not has_call else "loop_with_call"
|
|
|
|
|
|
def format_timing_summary(summary: dict[str, list[dict[str, object]]], *, max_items: int = 40) -> list[str]:
|
|
lines: list[str] = []
|
|
blocks = summary.get("blocks", [])
|
|
loops = summary.get("loops", [])
|
|
if not blocks and not loops:
|
|
return lines
|
|
|
|
lines.append("; Timing Summary")
|
|
if blocks:
|
|
lines.append("; Straight-line blocks")
|
|
for block in blocks[:max_items]:
|
|
lines.append(
|
|
"; block "
|
|
f"{h16(int(block['start']))}-{h16(int(block['end']))} "
|
|
f"{str(block['label']):<20} "
|
|
f"ins={block['instruction_count']:<3} "
|
|
f"cycles={_cycle_range(block)} "
|
|
f"unknown={block['unknown_cycles']}",
|
|
)
|
|
if len(blocks) > max_items:
|
|
lines.append(f"; ... {len(blocks) - max_items} more blocks")
|
|
if loops:
|
|
lines.append("; Backward-branch loop candidates")
|
|
for loop in loops[:max_items]:
|
|
lines.append(
|
|
"; loop "
|
|
f"{h16(int(loop['start']))}-{h16(int(loop['end']))} "
|
|
f"{str(loop['label']):<20} "
|
|
f"{loop['kind']:<22} "
|
|
f"cycles/iteration={_cycle_range(loop)} "
|
|
f"back_edge={h16(int(loop['back_edge']))}",
|
|
)
|
|
if len(loops) > max_items:
|
|
lines.append(f"; ... {len(loops) - max_items} more loops")
|
|
lines.append("")
|
|
return lines
|
|
|
|
|
|
def _cycle_range(item: dict[str, object]) -> str:
|
|
minimum = int(item["cycles_min"])
|
|
maximum = int(item["cycles_max"])
|
|
if minimum == maximum:
|
|
return str(minimum)
|
|
return f"{minimum}-{maximum}"
|