Noticed this while working on TypedCallable.
We use this equivalence predicate to check whether an invokee CodeInstance can be swapped out for another in the cache before codegen:
|
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(workqueue.interp))::CodeInstance |
|
if cached === callee |
|
# make sure callee is gc-rooted and cached, as required by jl_add_codeinsts_to_jit |
|
code_cache(workqueue.interp)[mi] = callee |
|
else |
|
# use an existing CI from the cache, if there is available one that is compatible |
|
callee === ci && (ci = cached) |
|
callee = cached |
|
end |
and then to support invoking the cached "equivalent" CI, our AOT / JIT codegen has a very similar notion of "equivalence" to choose a target CI:
|
return jl_atomic_load_relaxed(&CI2->min_world) <= MinWorld && |
|
jl_atomic_load_relaxed(&CI2->max_world) >= MaxWorld && |
|
jl_egal(CI2->def, Def) && jl_egal(CI2->owner, Owner) && |
|
jl_egal(CI2->rettype, RetType); |
but notably this does not replace the CodeInstance in the :invoke or .edges of the CI.
IPO-safety guarantees us that this replacement is sound even if the exct, ipo_purity_bits, etc. from the new CodeInstance are wide compared to the CI originally inferred against, but this guarantee only applies point-wise in world age - it requires that we continue to respect the world / invalidation bounds for the invoked CI. My concern is that if later the "equivalent" CI is invalidated but your original CI in your .edges was not, it seems that this can become a stale invalidation bug.
Even before then, it appears that the world bounds for the produced CI can be invalid if the "equivalent" CI has a more narrow world range.
If reachable this bug is quite hard to hit in practice, since it relies on two different runs for the same MethodInstance yielding appreciably different inference results, which for the time being ~usually only happens due to cache effects, etc.
I put Claude 🤖 on reproducing, but we've gotten sidetracked after discovering other missing edges.
Noticed this while working on TypedCallable.
We use this equivalence predicate to check whether an invokee CodeInstance can be swapped out for another in the cache before codegen:
julia/Compiler/src/typeinfer.jl
Lines 1723 to 1731 in f89b539
and then to support invoking the cached "equivalent" CI, our AOT / JIT codegen has a very similar notion of "equivalence" to choose a target CI:
julia/src/jitlayers.cpp
Lines 2331 to 2334 in f89b539
but notably this does not replace the CodeInstance in the
:invokeor.edgesof the CI.IPO-safety guarantees us that this replacement is sound even if the
exct,ipo_purity_bits, etc. from the new CodeInstance are wide compared to the CI originally inferred against, but this guarantee only applies point-wise in world age - it requires that we continue to respect the world / invalidation bounds for the invoked CI. My concern is that if later the "equivalent" CI is invalidated but your original CI in your.edgeswas not, it seems that this can become a stale invalidation bug.Even before then, it appears that the world bounds for the produced CI can be invalid if the "equivalent" CI has a more narrow world range.
If reachable this bug is quite hard to hit in practice, since it relies on two different runs for the same
MethodInstanceyielding appreciably different inference results, which for the time being ~usually only happens due to cache effects, etc.I put Claude 🤖 on reproducing, but we've gotten sidetracked after discovering other missing edges.