add helpers for backedge updates on CI equivalence swaps and copies#62217
add helpers for backedge updates on CI equivalence swaps and copies#62217vtjnash wants to merge 6 commits into
Conversation
Do you mean #62021 (comment) ? I never claimed any reason for #62022, which is a distinct MWE FWIW I think this was hit in the wild in timholy/Revise.jl#994, if that helps generate an MWE |
| } | ||
| } | ||
| // after storing new backedges, use cached if it is still legal (covers wider world range) | ||
| return jl_atomic_load_relaxed(&cached->max_world) >= jl_atomic_load_relaxed(&callee->max_world); |
There was a problem hiding this comment.
It is somewhat awkward that this leaves your max_world potentially incoherent with your forward edges
That seems likely to mislead us, e.g. we can now serialize a CI thinking (correctly) that it is still valid (.max_world == typemax) and then discover during de-serialization that it ends up invalid (despite loading in exactly the same method / binding state)
There was a problem hiding this comment.
That's fair. Maybe we should throw this method in the world_lock, so that ordering isn't so important here (and also prevents concurrent update attempts to edges).
topolarity
left a comment
There was a problem hiding this comment.
This code seems to have a number of serious bugs / confusion still:
srccan end up coming fromcalleebut compiled and attached tocachedif!ci_has_invoke(cached)which is quite illegal. We also forget to updatecode_cache(workqueue.interp)[mi]iflink_ci_equiv!fails validationcollectinvokes!seems to be confused about whether it should be walking the old or re-linked:invokegraph (JIT does not need to walk the re-linked graph, but IIUC AOT does)jl_link_ci_equivdoes not check thatcached->min_world <= callee->min_worldso this can still create invalid-to-invoke code at lower world agescachedis likely to fail validation often in bounded-world environments for reasons unrelated to concurrency, sincejl_get_ci_equivdoes not check thatcached->max_world >= callee->max_world
It also doesn't resolve the MWE in #62021 (which I'm quite sure is this bug)
FWIW having no great test strategy for bounded-world inference / IPO divergence seems like a real problem, although not one we have to fix right away.
The testability barrier here seems to mean we can only catch cache bugs like the above by inspection or by crowd-sourcing them to our users which is a problem we should try to fix with some smart design eventually I think, like the ability to mock the cache perhaps.
Oooh, right. We should perhaps do 2 lookups here (first one try one that can replace the invoke, but if that fails for AOT, opportunistically compile something else)? That code in jl_get_ci_equiv was removed in a8216e0, but could be restored.
Why is that illegal? Do you just mean we might have set up the edges inaccurately? We intended to show the CI are equivalent, but originally from 608a06c might never have done that |
Yeah, that might be better. I've also been wondering if it might be worth handling this "re-linking" as part of the re-inference / re-validation that we do when loading pkgimages? That might make it possible to implement this as an atomic edge update, rather than having to preserve
Yeah essentially - |
Okay, that's fine then. The premise under which I was going to add that was wrong anyways (this is just preparing a dissociated list of compilation jobs--there's no implied connection until linking, and JIT/AOT already has the logic to add the edge when it is actually needed) |
Hmm not sure I understand My concern is that if |
233d557 to
31d3acc
Compare
31d3acc to
8f4fa11
Compare
…ence swaps When a CodeInstance `callee` is replaced by an equivalent `cached` CI (via `jl_get_ci_equiv` in `add_codeinsts_to_jit!` and `compile!`) or when a new CI is created as a copy (via `copy_to_mi_cache`), the edges/backedges were not updated. As a result, any caller whose edges svec contained `callee` by identity would not be invalidated when `cached` was invalidated, since `callee` was not registered in any MethodInstance's backedges. Fix by introducing `link_ci_equiv!` on the Julia side, which: 1. Appends `cached` to `callee.edges` so that `_invalidate_backedges`'s pointer-identity check (`edge == replaced_ci`) matches when `cached` is being invalidated. 2. Registers `callee` in `cached`'s MI's backedges (via `store_backedges`) so the invalidation walk can reach `callee` when `cached` is invalidated. 3. Promotes `callee` to the current world via `jl_promote_ci_to_current` so it participates in the normal validity regime. For `copy_to_mi_cache` on the C side, resolve the long-standing TODO by adding `store_backedges_c` (a C mirror of the Julia `store_backedges` function that parses the forward-edges svec format) and calling `jl_promote_ci_to_current` on the newly created CI. Also add `jl_codeinst_set_edges` as an exported C helper to atomically update a CodeInstance's edges field with the proper write barrier. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend the CI equivalence edge/backedge fix to the C-side sites where equivalent CodeInstances are found and substituted. Adds jl_link_ci_equiv, a C-level mirror of link_ci_equiv! from typeinfer.jl, and calls it from: - aot_link_output (get_ci_equiv_compiled): AOT call-target routing - linkOutput (findCompatibleCI): JIT call-target routing In both cases, when a callee CI is routed through a cached/equiv CI, jl_link_ci_equiv ensures the callee ends up in the equiv's MI backedges and that the equiv appears in the callee's forward edges. Without this, invalidation of the equiv would not propagate to the callee. The jl_link_ci_equiv call in linkOutput is made with LinkerMutex temporarily released, since jl_alloc_svec may GC. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
warning: this make sysimage 160 -> 190 MB, which needs fixing (due to bad edges list)
8f4fa11 to
6e41e8b
Compare
No longer includes that part of the change, only some of the helper methods for re-visiting this later after some other concurrent work is resolved
No description provided.