From 3a6f2c8770499e021466151c4a453e3594410b76 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:29:05 +0200 Subject: [PATCH] fix: key the opcode cache by content --- scripts/profile_sync.py | 16 +++++++++++----- tests/test_profile_sync.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 12d6be4f..c31b36d7 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -723,22 +723,28 @@ def _map_index(opcodes, index: int) -> int | None: return None -_OPCODE_CACHE: dict[tuple[int, int], list] = {} +_OPCODE_CACHE: dict[tuple[tuple[str, ...], tuple[str, ...]], list] = {} +_OPCODE_CACHE_SIZE = 8 def _opcodes(pin: list[str], head: list[str]) -> list: """Opcodes for one pair of revisions, computed once per run. Every ref citing the same file would otherwise redo the same diff, and a - driver of thirty thousand lines takes seconds each time. + driver of thirty thousand lines takes seconds each time. Keyed by the + lines themselves: anchor_block hands over lists it builds and frees, and + an id() key served one file's opcodes to the next pair at the same + addresses. """ - key = (id(pin), id(head)) - cached = _OPCODE_CACHE.get(key) + key = (tuple(pin), tuple(head)) + cached = _OPCODE_CACHE.pop(key, None) if cached is None: cached = difflib.SequenceMatcher( None, pin, head, autojunk=False ).get_opcodes() - _OPCODE_CACHE[key] = cached + _OPCODE_CACHE[key] = cached + while len(_OPCODE_CACHE) > _OPCODE_CACHE_SIZE: + del _OPCODE_CACHE[next(iter(_OPCODE_CACHE))] return cached diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index d31adf31..145a688a 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -3397,6 +3397,23 @@ class TestWrappedProse(unittest.TestCase): self.assertEqual(self.path.read_text(), self.TEXT) +class TestOpcodeCache(unittest.TestCase): + """The diff cache was keyed by id() of lists anchor_block builds and frees + on return: a later pair at the same addresses read another file's + opcodes, and a CHANGED ref could be mapped onto the wrong line.""" + + def test_each_pair_gets_its_own_diff(self): + import difflib + + for n in range(200): + pin = [f"line {n} {i}" for i in range(n % 7 + 2)] + head = [f"line {n} {i}" for i in range(n % 5 + 1)] + [f"new {n}"] + expected = difflib.SequenceMatcher( + None, pin, head, autojunk=False + ).get_opcodes() + self.assertEqual(profile_sync._opcodes(list(pin), list(head)), expected) + + class TestCommentCitations(unittest.TestCase): """The loader drops comments: zesarux's `# rom_load switch, src/cpu.c:3212-3451` stayed on the old lines under a new pin."""