diff --git a/src/legacy_loading.jl b/src/legacy_loading.jl index e310f22f..725f3496 100644 --- a/src/legacy_loading.jl +++ b/src/legacy_loading.jl @@ -156,7 +156,8 @@ function parse_pkg_files(id::PkgId) # To reduce compiler latency, use runtime dispatch for `queue_includes!`. # `queue_includes!` requires compilation of the whole parsing/expression-splitting infrastructure, # and it's better to wait to compile it until we actually need it. - Base.invokelatest(queue_includes!, pkgdata, id) + #worldage[] = Base.get_world_counter() + invoke_revisefunc(queue_includes!, pkgdata, id) return pkgdata end diff --git a/src/loading.jl b/src/loading.jl index db8d0b22..79b7b7d7 100644 --- a/src/loading.jl +++ b/src/loading.jl @@ -48,7 +48,7 @@ function parse_pkg_files(id::PkgId) # To reduce compiler latency, use runtime dispatch for `queue_includes!`. # `queue_includes!` requires compilation of the whole parsing/expression-splitting infrastructure, # and it's better to wait to compile it until we actually need it. - Base.invokelatest(queue_includes!, pkgdata, id) + invoke_revisefunc(queue_includes!, pkgdata, id) return pkgdata end diff --git a/src/lowered.jl b/src/lowered.jl index e754c6bb..bbc65010 100644 --- a/src/lowered.jl +++ b/src/lowered.jl @@ -128,6 +128,8 @@ function methods_by_execution(mod::Module, ex::Expr; kwargs...) return methodinfo, docexprs, frame end +_lower(m::Module, ex, world::UInt) = ccall(:jl_expand_in_world, Any, (Any, Module, Cstring, Cint, Csize_t), ex, m, "none", 0, world) + """ methods_by_execution!(recurse=JuliaInterpreter.Compiled(), methodinfo, docexprs, mod::Module, ex::Expr; mode=:eval, disablebp=true, skip_include=mode!==:eval, always_rethrow=false) @@ -175,7 +177,7 @@ The other keyword arguments are more straightforward: function methods_by_execution!(@nospecialize(recurse), methodinfo, docexprs, mod::Module, ex::Expr; mode::Symbol=:eval, disablebp::Bool=true, always_rethrow::Bool=false, kwargs...) mode ∈ (:sigs, :eval, :evalmeth, :evalassign) || error("unsupported mode ", mode) - lwr = Meta.lower(mod, ex) + lwr = lower_in_reviseworld(mod, ex) isa(lwr, Expr) || return nothing, nothing if lwr.head === :error || lwr.head === :incomplete error("lowering returned an error, ", lwr) diff --git a/src/packagedef.jl b/src/packagedef.jl index 441c25ac..8526b067 100644 --- a/src/packagedef.jl +++ b/src/packagedef.jl @@ -198,6 +198,15 @@ const silence_pkgs = Set{Symbol}() const depsdir = joinpath(dirname(@__DIR__), "deps") const silencefile = Ref(joinpath(depsdir, "silence.txt")) # Ref so that tests don't clobber +""" + Revise.worldage + +The world age Revise was started in. Needed so that Revise doesn't delete methods +from under itself. +""" +const worldage = Ref{Union{Nothing,UInt}}(nothing) +#using CodeTracking: worldage + ## ## The inputs are sets of expressions found in each file. ## Some of those expressions will generate methods which are identified via their signatures. @@ -262,10 +271,13 @@ function delete_missing!(exs_sigs_old::ExprsSigs, exs_sigs_new) @debug "DeleteMethod" _group="Action" time=time() deltainfo=(sig, MethodSummary(m)) # Delete the corresponding methods for p in workers() - try # guard against serialization errors if the type isn't defined on the worker - remotecall(Core.eval, p, Main, :(delete_method_by_sig($sig))) - catch - end + #try # guard against serialization errors if the type isn't defined on the worker + future = remotecall(Core.eval, p, Main, :(delete_method_by_sig($sig))) + finalizer(future) do f + invoke_revisefunc(Distributed.finalize_ref, f) + end + #catch + #end end Base.delete_method(m) # Remove the entries from CodeTracking data @@ -627,6 +639,7 @@ function handle_deletions(pkgdata, file) topmod = first(keys(mexsold)) fileok = file_exists(filep) mexsnew = fileok ? parse_source(filep, topmod) : ModuleExprsSigs(topmod) + worldage[] = Base.get_world_counter() if mexsnew !== nothing delete_missing!(mexsold, mexsnew) end @@ -720,6 +733,7 @@ function revise(; throw=false) # Do all the deletion first. This ensures that a method that moved from one file to another # won't get redefined first and deleted second. + @show worldage[] = Base.get_world_counter() revision_errors = [] queue = sort!(collect(revision_queue); lt=pkgfileless) finished = eltype(revision_queue)[] @@ -752,6 +766,7 @@ function revise(; throw=false) mode ∈ (:sigs, :eval, :evalmeth, :evalassign) || error("unsupported mode ", mode) exsold = get(fi.modexsigs, mod, empty_exs_sigs) for rex in keys(exsnew) + @show Base.get_world_counter() sigs, includes = eval_rex(rex, exsold, mod; mode=mode) if sigs !== nothing exsnew[rex] = sigs @@ -1174,11 +1189,34 @@ function maybe_set_prompt_color(color) return nothing end +if VERSION < v"1.6.0-DEV.1162" + const invoke_revisefunc = Base.invokelatest + const lower_in_reviseworld = Meta.lower +else + function invoke_revisefunc(f, args...; kwargs...) + #@show worldage[] + #@show Base.get_world_counter() + #Base.show_backtrace(stdout, backtrace()[1:4]) + #println() + return Base.invoke_in_world(worldage[], f, args...; kwargs...) + end + function lower_in_reviseworld(m::Module, @nospecialize(ex)) + #@show worldage[] + #@show Base.get_world_counter() + #Base.show_backtrace(stdout, backtrace()[1:1]) + #println() + return ccall(:jl_expand_in_world, Any, + (Any, Ref{Module}, Cstring, Cint, Csize_t), + ex, m, "none", 0, worldage[], + ) + end +end + # On Julia 1.5.0-DEV.282 and higher, we can just use an AST transformation # This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime. # This allows `revise_first` to be compiled without compiling `revise` itself, and greatly # reduces the overhead of using Revise. -revise_first(ex) = Expr(:toplevel, :(isempty($revision_queue) || Base.invokelatest($revise)), ex) +revise_first(ex) = Expr(:toplevel, :(isempty($revision_queue) || (#=worldage[] = Base.get_world_counter(); =#invoke_revisefunc($revise))), ex) @noinline function run_backend(backend) while true @@ -1277,6 +1315,7 @@ function init_worker(p) end function __init__() + worldage[] = Base.get_world_counter() run_on_worker = get(ENV, "JULIA_REVISE_WORKER_ONLY", "0") if !(myid() == 1 || run_on_worker == "1") return nothing @@ -1328,6 +1367,8 @@ function __init__() id = PkgId(nothing, "@REPL") pkgdatas[id] = pkgdata = PkgData(id, nothing) # Set the lookup callbacks + CodeTracking.method_lookup_callback[] = x -> (worldage[] = Base.get_world_counter(); invoke_revisefunc(get_def, x)) + CodeTracking.expressions_callback[] = x -> (worldage[] = Base.get_world_counter(); invoke_revisefunc(get_expressions, x)) CodeTracking.method_lookup_callback[] = get_def CodeTracking.expressions_callback[] = get_expressions diff --git a/src/pkgs.jl b/src/pkgs.jl index 3b1cc929..15df6157 100644 --- a/src/pkgs.jl +++ b/src/pkgs.jl @@ -171,6 +171,7 @@ function maybe_add_includes_to_pkgdata!(pkgdata::PkgData, file::AbstractString, parse_source!(fi.modexsigs, fullfile, mod) if eval_now # Use runtime dispatch to reduce latency + #Base.invoke_in_world(worldage[], instantiate_sigs!, fi.modexsigs; mode=:eval) Base.invokelatest(instantiate_sigs!, fi.modexsigs; mode=:eval) end end @@ -237,6 +238,7 @@ function _add_require(sourcefile, modcaller, idmod, modname, expr) end end if complex + #Base.invoke_in_world(worldage[], eval_require_now, pkgdata, fileidx, filekey, sourcefile, modcaller, expr) Base.invokelatest(eval_require_now, pkgdata, fileidx, filekey, sourcefile, modcaller, expr) end finally diff --git a/src/utils.jl b/src/utils.jl index ec4aae51..49396899 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -193,7 +193,7 @@ This is used to make stacktraces obtained with Revise more similar to those obta without Revise, while retaining one entry to reveal Revise's involvement. """ function trim_toplevel!(bt) - # return bt # uncomment this line if you're debugging Revise itself + return bt n = itoplevel = length(bt) for (i, t) in enumerate(bt) sfs = StackTraces.lookup(t)