Body normalisation: per-method similarity score + leaf delta
Turns the dispatch axis from a binary changed/unchanged into a "how much" measure
of code change — the original goal. ams.normalize compares two body fingerprints
(the ordered leaf-call anchors) with difflib after collapsing consecutive-duplicate
anchors (a load-twice codegen artefact), yielding a 0-100 similarity and the exact
leaves that appeared/vanished.
Every dispatch `changed` entry now carries body={similarity, added, removed}, and the
block carries a summary={shared, identical, changed, mean_similarity}.
Golden pair (cross-compiler): 470 shared bodies, 131 identical, mean 66% similar;
Animo SHOW/HIDE/PAUSE/RESUME come out 100% despite MSVC6 vs MSVC8, LOAD 50% with the
swapped leaves spelled out.
- normalize.py: canonical / body_similarity / body_delta
- diff: _dispatch_diff enriches changed with body + adds summary
- render: METHOD BODIES shows %, leaf delta, summary line
- UI: similarity % + leaf delta + axis summary
- tests: 5 new -> 34/34
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
30
ams/diff.py
30
ams/diff.py
@@ -85,6 +85,33 @@ def _dispatch_with_names(snap: Snapshot) -> list[Item]:
|
||||
return out
|
||||
|
||||
|
||||
def _dispatch_diff(old: Snapshot, new: Snapshot) -> dict[str, Any]:
|
||||
"""Dispatch axis with body-level normalisation: every `changed` entry carries a `body`
|
||||
{similarity, added, removed} from ams.normalize, and the block gets a `summary` measuring
|
||||
how much the shared bodies changed overall (mean similarity, identical/changed counts)."""
|
||||
from .normalize import body_delta, body_similarity
|
||||
|
||||
do = _dispatch_with_names(old)
|
||||
dn = _dispatch_with_names(new)
|
||||
block = keyed_diff(do, dn, _dispatch_key, ["impl", "calls"])
|
||||
|
||||
old_calls = {_dispatch_key(r): r.get("calls", []) for r in do}
|
||||
new_calls = {_dispatch_key(r): r.get("calls", []) for r in dn}
|
||||
for ch in block["changed"]:
|
||||
k = _dispatch_key(ch["item"])
|
||||
ch["body"] = body_delta(old_calls.get(k, []), new_calls.get(k, []))
|
||||
|
||||
shared = set(old_calls) & set(new_calls)
|
||||
sims = [body_similarity(old_calls[k], new_calls[k]) for k in shared]
|
||||
block["summary"] = {
|
||||
"shared": len(shared),
|
||||
"identical": sum(1 for s in sims if s == 100),
|
||||
"changed": sum(1 for s in sims if s < 100),
|
||||
"mean_similarity": int(round(sum(sims) / len(sims))) if sims else 100,
|
||||
}
|
||||
return block
|
||||
|
||||
|
||||
def compute_diff(old: Snapshot, new: Snapshot) -> dict[str, Any]:
|
||||
return {
|
||||
"binary": {"from": old.binary, "to": new.binary},
|
||||
@@ -94,8 +121,7 @@ def compute_diff(old: Snapshot, new: Snapshot) -> dict[str, Any]:
|
||||
"fields": keyed_diff(old.fields, new.fields, _owner_name_key, ["type"]),
|
||||
"struct_layout": keyed_diff(old.struct_layout, new.struct_layout, _layout_key,
|
||||
["size", "is_vtable"]),
|
||||
"method_dispatch": keyed_diff(_dispatch_with_names(old), _dispatch_with_names(new),
|
||||
_dispatch_key, ["impl", "calls"]),
|
||||
"method_dispatch": _dispatch_diff(old, new),
|
||||
"method_inheritance": keyed_diff(old.method_inheritance, new.method_inheritance,
|
||||
lambda x: x["runner"], ["base_runner"]),
|
||||
"field_inheritance": keyed_diff(old.field_inheritance, new.field_inheritance,
|
||||
|
||||
Reference in New Issue
Block a user