Something cool that Julia does is method invalidation. When a method is compiled to a MethodInstance
upon a call, assumptions like what methods are dispatched to and compile-time constants are baked in. When a function is edited by adding or replacing a method, some of those assumptions will no longer be valid, so Julia invalidates the affected MethodInstance
s to allow future calls to recompile. Moreover, MethodInstance
s store backedges (links) to dependent MethodInstances
, so those will also be invalidated (if needed).
Minimal example of how replacing a callee method causes an invalidation of the callee and a caller.
julia> f() = 0
f (generic function with 1 method)
julia> callf() = f()
callf (generic function with 1 method)
julia> callf()
0
julia> f() = 1
f (generic function with 1 method)
julia> callf()
1
Analogous macro counterexample.
julia> macro f2() :(0) end
@f2 (macro with 1 method)
julia> callf2() = @f2
callf2 (generic function with 1 method)
julia> callf2()
0
julia> macro f2() :(1) end
@f2 (macro with 1 method)
julia> callf2()
0
Analogous module counterexample. Note that you can accomplish the intended effect by editing the function in the module instead of replacing the module.
julia> module F export f3; f3() = 0 end; using .F
julia> callf3() = f3()
callf3 (generic function with 1 method)
julia> callf3()
0
julia> module F export f3; f3() = 1 end; using .F
WARNING: replacing module F.
WARNING: using F.f3 in module Main conflicts with an existing identifier.
julia> callf3()
0
julia> f3(), F.f3()
(0, 1)
Analogous invalidation of callers via backedges doesn’t happen when macros are redefined or when modules are replaced. Of course, the things that would be “invalidated” are completely different from compiled MethodInstance
s; in the macro case, the caller method itself would need to be reevaluated, and in the module case, the entire Main
module and its constants would need to be reevaluated.
But is there a reason this isn’t done? The number of module definitions and macro calls don’t seem to be significantly larger than method calls, so making backedges from macros and modules to methods and other modules seems plausible. Reevaluating modules can also replace global instances after modifying type definitions (though unlike macros, I’m not sure if it’s necessary to reevaluate dependent methods because I don’t know if the actual type or just the symbol is stored for calls in the method body or annotations of the arguments). Although a lot can be reevaluated, it seems less than or equal to a full restart. There has to be a hazard to reevaluation I’m not seeing.