From 32922ca71264187d014bcdf0c0eea55356c1d4cd Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 1 Sep 2026 07:04:44 -0500 Subject: [PATCH] Preserve both subtrees when merging mt_backedges `join_invalidations!` keeps the node already present in `list`, so `join_branches!` must merge the incoming node into that one. Merging in the opposite direction discarded the incoming callers, so `invalidation_trees` omitted invalidations that `consolidate=false` reported. In the `:deleting` consolidation loop, `covered` now resets per edge, and the edge is wrapped in a `BackedgeMT` vector to match `join_invalidations!`. Fixes #364 Assisted-by: Claude Opus 5 --- src/invalidations.jl | 9 +++++---- test/snoop_invalidations.jl | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/invalidations.jl b/src/invalidations.jl index ae281074..325bb2c7 100644 --- a/src/invalidations.jl +++ b/src/invalidations.jl @@ -616,14 +616,14 @@ function invalidation_trees(list::InvalidationLists; consolidate::Bool=true, kwa if etree.reason === :deleting @assert isempty(etree.backedges) # should not have any backedges # Determine whether any of the deleted methods cover this - covered = false for (edge, node) in etree.mt_backedges + covered = false for mtree in mtrees mtree.reason === :deleting || continue isnothing(mtree.method) && continue mtree.method.sig <: edge || continue # This edge is covered by the deleted method - join_invalidations!(mtree.mt_backedges, edge => node) + join_invalidations!(mtree.mt_backedges, BackedgeMT[edge => node]) covered = true end covered && continue @@ -785,8 +785,9 @@ function join_invalidations!(list::AbstractVector{<:Pair}, items::AbstractVector key2 == key || continue mi2 = root2.mi if mi2 == mi - # Find the first branch that isn't shared - join_branches!(node, root2) + # Merge into the node already in `list`, which is the one that is kept + # (issue #364) + join_branches!(root2, node) found = true break end diff --git a/test/snoop_invalidations.jl b/test/snoop_invalidations.jl index bedfd9a0..16f1022f 100644 --- a/test/snoop_invalidations.jl +++ b/test/snoop_invalidations.jl @@ -315,6 +315,27 @@ end Pkg.activate(cproj) end +@testset "Merging mt_backedges" begin + # issue #364: when two invalidation trees share a signature and root + # MethodInstance, the merged tree must retain the callers of both. + c = Any[1] + SnooprTests.callapplyf(c) + SnooprTests.mccc1(c, 1) + root = methodinstance(SnooprTests.applyf, (Vector{Any},)) + childa = methodinstance(SnooprTests.callapplyf, (Vector{Any},)) + childb = methodinstance(SnooprTests.mccc1, (Vector{Any}, Int)) + sig = Tuple{typeof(SnooprTests.f), Any} + function mt_backedge(childmi) + rootnode = SnoopCompile.InstanceNode(root, 0) + SnoopCompile.InstanceNode(childmi, rootnode) + return SnoopCompile.BackedgeMT[sig => rootnode] + end + list = mt_backedge(childa) + SnoopCompile.join_invalidations!(list, mt_backedge(childb)) + _, merged = only(list) + @test Set(child.mi for child in merged.children) == Set((childa, childb)) +end + @testset "Unknown-tree attribution via logmeths cross-reference" begin # When a package is loaded and its precompiled CIs are already C-level invalid # (max_world=0), verify_method returns early without emitting an