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}"