diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 68668b0ac2c91..9a5b19709e697 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -113,6 +113,10 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(fun π•ƒβ‚š, 𝕃ᡒ = ipo_lattice(interp), typeinf_lattice(interp) βŠ‘β‚š, β‹€β‚š, βŠ”β‚š, βŠ”α΅’ = partialorder(π•ƒβ‚š), strictneqpartialorder(π•ƒβ‚š), join(π•ƒβ‚š), join(𝕃ᡒ) argtypes = arginfo.argtypes + if si.saw_latestworld + add_remark!(interp, sv, "Cannot infer call, because we previously saw :latestworld") + return Future(CallMeta(Any, Any, Effects(), NoCallInfo())) + end matches = find_method_matches(interp, argtypes, atype; max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) @@ -321,7 +325,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(fun if csig !== nothing && (!seenall || csig !== sig) # corresponds to whether the first look already looked at this, so repeating abstract_call_method is not useful sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), csig, method.sig)::SimpleVector if match.sparams === sp_[2] - mresult = abstract_call_method(interp, method, csig, match.sparams, multiple_matches, StmtInfo(false), sv)::Future + mresult = abstract_call_method(interp, method, csig, match.sparams, multiple_matches, StmtInfo(false, false), sv)::Future isready(mresult) || return false # wait for mresult Future to resolve off the callstack before continuing end end @@ -1585,7 +1589,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n @assert !isvarargtype(itertype) iterateresult = Future{AbstractIterationResult}() - call1future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true), sv)::Future + call1future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true, false), sv)::Future function inferiterate(interp, sv) call1 = call1future[] stateordonet = call1.rt @@ -1641,7 +1645,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n valtype = getfield_tfunc(𝕃ᡒ, stateordonet, Const(1)) push!(ret, valtype) statetype = nstatetype - call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv)::Future + call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true, false), sv)::Future if !isready(call2future) nextstate = 0x1 return false @@ -1683,7 +1687,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv)::Future + call2future = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true, false), sv)::Future if !isready(call2future) nextstate = 0x2 return false @@ -2292,7 +2296,7 @@ end function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState) if length(argtypes) == 3 finalizer_argvec = Any[argtypes[2], argtypes[3]] - call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false), sv, #=max_methods=#1)::Future + call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false, false), sv, #=max_methods=#1)::Future return Future{CallMeta}(call, interp, sv) do call, interp, sv return CallMeta(Nothing, Any, Effects(), FinalizerInfo(call.info, call.effects)) end @@ -2331,12 +2335,12 @@ function abstract_throw_methoderror(interp::AbstractInterpreter, argtypes::Vecto end const generic_getglobal_effects = Effects(EFFECTS_THROWS, consistent=ALWAYS_FALSE, inaccessiblememonly=ALWAYS_FALSE) -function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s)) +function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, @nospecialize(M), @nospecialize(s)) βŠ‘ = partialorder(typeinf_lattice(interp)) if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol - return CallMeta(abstract_eval_globalref(interp, GlobalRef(M, s), sv), NoCallInfo()) + return CallMeta(abstract_eval_globalref(interp, GlobalRef(M, s), saw_latestworld, sv), NoCallInfo()) end return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol) @@ -2354,17 +2358,17 @@ function merge_exct(cm::CallMeta, @nospecialize(exct)) return cm end -function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(order)) +function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, @nospecialize(M), @nospecialize(s), @nospecialize(order)) goe = global_order_exct(order, #=loading=#true, #=storing=#false) - cm = abstract_eval_getglobal(interp, sv, M, s) + cm = abstract_eval_getglobal(interp, sv, saw_latestworld, M, s) return merge_exct(cm, goe) end -function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) +function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) if length(argtypes) == 3 - return abstract_eval_getglobal(interp, sv, argtypes[2], argtypes[3]) + return abstract_eval_getglobal(interp, sv, saw_latestworld, argtypes[2], argtypes[3]) elseif length(argtypes) == 4 - return abstract_eval_getglobal(interp, sv, argtypes[2], argtypes[3], argtypes[4]) + return abstract_eval_getglobal(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) elseif !isvarargtype(argtypes[end]) || length(argtypes) > 5 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else @@ -2411,11 +2415,11 @@ end const setglobal!_effects = Effects(EFFECTS_TOTAL; effect_free=ALWAYS_FALSE, nothrow=false, inaccessiblememonly=ALWAYS_FALSE) -function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(v)) +function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, @nospecialize(M), @nospecialize(s), @nospecialize(v)) if isa(M, Const) && isa(s, Const) M, s = M.val, s.val if M isa Module && s isa Symbol - exct = global_assignment_exct(interp, sv, GlobalRef(M, s), v) + exct = global_assignment_exct(interp, sv, saw_latestworld, GlobalRef(M, s), v) return CallMeta(v, exct, Effects(setglobal!_effects, nothrow=exct===Bottom), NoCallInfo()) end return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) @@ -2429,17 +2433,17 @@ function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, return CallMeta(v, Union{TypeError, ErrorException}, setglobal!_effects, NoCallInfo()) end -function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(v), @nospecialize(order)) +function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, @nospecialize(M), @nospecialize(s), @nospecialize(v), @nospecialize(order)) goe = global_order_exct(order, #=loading=#false, #=storing=#true) - cm = abstract_eval_setglobal!(interp, sv, M, s, v) + cm = abstract_eval_setglobal!(interp, sv, saw_latestworld, M, s, v) return merge_exct(cm, goe) end -function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) +function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) if length(argtypes) == 4 - return abstract_eval_setglobal!(interp, sv, argtypes[2], argtypes[3], argtypes[4]) + return abstract_eval_setglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) elseif length(argtypes) == 5 - return abstract_eval_setglobal!(interp, sv, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) + return abstract_eval_setglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) elseif !isvarargtype(argtypes[end]) || length(argtypes) > 6 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else @@ -2447,9 +2451,9 @@ function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, end end -function abstract_eval_setglobalonce!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) +function abstract_eval_setglobalonce!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) if length(argtypes) in (4, 5, 6) - cm = abstract_eval_setglobal!(interp, sv, argtypes[2], argtypes[3], argtypes[4]) + cm = abstract_eval_setglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) if length(argtypes) >= 5 goe = global_order_exct(argtypes[5], #=loading=#true, #=storing=#true) cm = merge_exct(cm, goe) @@ -2466,7 +2470,7 @@ function abstract_eval_setglobalonce!(interp::AbstractInterpreter, sv::AbsIntSta end end -function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) +function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) if length(argtypes) in (5, 6, 7) (M, s, x, v) = argtypes[2], argtypes[3], argtypes[4], argtypes[5] @@ -2485,7 +2489,7 @@ function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntSta effects = merge_effects(rte.effects, Effects(setglobal!_effects, nothrow=exct===Bottom)) sg = CallMeta(Any, exct, effects, NoCallInfo()) else - sg = abstract_eval_setglobal!(interp, sv, M, s, v) + sg = abstract_eval_setglobal!(interp, sv, saw_latestworld, M, s, v) end if length(argtypes) >= 6 goe = global_order_exct(argtypes[6], #=loading=#true, #=storing=#true) @@ -2539,15 +2543,15 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif f === Core.throw_methoderror return abstract_throw_methoderror(interp, argtypes, sv) elseif f === Core.getglobal - return Future(abstract_eval_getglobal(interp, sv, argtypes)) + return Future(abstract_eval_getglobal(interp, sv, si.saw_latestworld, argtypes)) elseif f === Core.setglobal! - return Future(abstract_eval_setglobal!(interp, sv, argtypes)) + return Future(abstract_eval_setglobal!(interp, sv, si.saw_latestworld, argtypes)) elseif f === Core.setglobalonce! - return Future(abstract_eval_setglobalonce!(interp, sv, argtypes)) + return Future(abstract_eval_setglobalonce!(interp, sv, si.saw_latestworld, argtypes)) elseif f === Core.replaceglobal! - return Future(abstract_eval_replaceglobal!(interp, sv, argtypes)) + return Future(abstract_eval_replaceglobal!(interp, sv, si.saw_latestworld, argtypes)) elseif f === Core.getfield && argtypes_are_actually_getglobal(argtypes) - return Future(abstract_eval_getglobal(interp, sv, argtypes)) + return Future(abstract_eval_getglobal(interp, sv, si.saw_latestworld, argtypes)) elseif f === Core.isdefined && argtypes_are_actually_getglobal(argtypes) exct = Bottom if length(argtypes) == 4 @@ -2561,6 +2565,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), interp, GlobalRef((argtypes[2]::Const).val::Module, (argtypes[3]::Const).val::Symbol), + si.saw_latestworld, sv), NoCallInfo()), exct)) elseif f === Core.get_binding_type @@ -2815,8 +2820,8 @@ function sp_type_rewrap(@nospecialize(T), mi::MethodInstance, isreturn::Bool) return unwraptv(T) end -function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) - f = abstract_eval_value(interp, e.args[2], vtypes, sv) +function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) + f = abstract_eval_value(interp, e.args[2], sstate, sv) # rt = sp_type_rewrap(e.args[3], sv.linfo, true) atv = e.args[4]::SimpleVector at = Vector{Any}(undef, length(atv) + 1) @@ -2828,18 +2833,18 @@ function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::U # this may be the wrong world for the call, # but some of the result is likely to be valid anyways # and that may help generate better codegen - abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false), sv)::Future + abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false, false), sv)::Future rt = e.args[1] isa(rt, Type) || (rt = Any) return RTEffects(rt, Any, EFFECTS_UNKNOWN) end -function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState) +function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), sstate::StatementState, sv::AbsIntState) if isa(e, SSAValue) return RTEffects(abstract_eval_ssavalue(e, sv), Union{}, EFFECTS_TOTAL) elseif isa(e, SlotNumber) - if vtypes !== nothing - vtyp = vtypes[slot_id(e)] + if sstate.vtypes !== nothing + vtyp = sstate.vtypes[slot_id(e)] if !vtyp.undef return RTEffects(vtyp.typ, Union{}, EFFECTS_TOTAL) end @@ -2847,14 +2852,14 @@ function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize( end return RTEffects(Any, UndefVarError, EFFECTS_THROWS) elseif isa(e, Argument) - if vtypes !== nothing - return RTEffects(vtypes[slot_id(e)].typ, Union{}, EFFECTS_TOTAL) + if sstate.vtypes !== nothing + return RTEffects(sstate.vtypes[slot_id(e)].typ, Union{}, EFFECTS_TOTAL) else @assert isa(sv, IRInterpretationState) return RTEffects(sv.ir.argtypes[e.n], Union{}, EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion end elseif isa(e, GlobalRef) - return abstract_eval_globalref(interp, e, sv) + return abstract_eval_globalref(interp, e, sstate.saw_latestworld, sv) end if isa(e, QuoteNode) e = e.value @@ -2878,21 +2883,21 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, sv::AbsI return Any end -function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState) +function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), sstate::StatementState, sv::AbsIntState) if isa(e, Expr) return abstract_eval_value_expr(interp, e, sv) else - (;rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv) + (;rt, effects) = abstract_eval_special_value(interp, e, sstate, sv) merge_effects!(interp, sv, effects) return collect_limitations!(rt, sv) end end -function collect_argtypes(interp::AbstractInterpreter, ea::Vector{Any}, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) +function collect_argtypes(interp::AbstractInterpreter, ea::Vector{Any}, sstate::StatementState, sv::AbsIntState) n = length(ea) argtypes = Vector{Any}(undef, n) @inbounds for i = 1:n - ai = abstract_eval_value(interp, ea[i], vtypes, sv) + ai = abstract_eval_value(interp, ea[i], sstate, sv) if ai === Bottom return nothing end @@ -2915,12 +2920,12 @@ end CallMeta(rte::RTEffects, info::CallInfo) = CallMeta(rte.rt, rte.exct, rte.effects, info, rte.refinements) -function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) +function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sstate::StatementState, sv::InferenceState) unused = call_result_unused(sv, sv.currpc) if unused add_curr_ssaflag!(sv, IR_FLAG_UNUSED) end - si = StmtInfo(!unused) + si = StmtInfo(!unused, sstate.saw_latestworld) call = abstract_call(interp, arginfo, si, sv)::Future Future{Any}(call, interp, sv) do call, interp, sv # this only is needed for the side-effect, sequenced before any task tries to consume the return value, @@ -2931,25 +2936,26 @@ function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::Infere return call end -function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_call(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) ea = e.args - argtypes = collect_argtypes(interp, ea, vtypes, sv) + argtypes = collect_argtypes(interp, ea, sstate, sv) if argtypes === nothing return Future(RTEffects(Bottom, Any, Effects())) end arginfo = ArgInfo(ea, argtypes) - call = abstract_call(interp, arginfo, sv)::Future + call = abstract_call(interp, arginfo, sstate, sv)::Future return Future{RTEffects}(call, interp, sv) do call, interp, sv (; rt, exct, effects, refinements) = call return RTEffects(rt, exct, effects, refinements) end end -function abstract_eval_new(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + +function abstract_eval_new(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) 𝕃ᡒ = typeinf_lattice(interp) - rt, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) + rt, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], sstate, sv), true) ut = unwrap_unionall(rt) exct = Union{ErrorException,TypeError} if isa(ut, DataType) && !isabstracttype(ut) @@ -2976,7 +2982,7 @@ function abstract_eval_new(interp::AbstractInterpreter, e::Expr, vtypes::Union{V local anyrefine = false local allconst = true for i = 1:nargs - at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv)) + at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], sstate, sv)) ft = fieldtype(rt, i) nothrow && (nothrow = βŠ‘(𝕃ᡒ, at, ft)) at = tmeet(𝕃ᡒ, at, ft) @@ -3018,13 +3024,13 @@ function abstract_eval_new(interp::AbstractInterpreter, e::Expr, vtypes::Union{V return RTEffects(rt, exct, effects) end -function abstract_eval_splatnew(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_splatnew(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) 𝕃ᡒ = typeinf_lattice(interp) - rt, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) + rt, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], sstate, sv), true) nothrow = false if length(e.args) == 2 && isconcretedispatch(rt) && !ismutabletype(rt) - at = abstract_eval_value(interp, e.args[2], vtypes, sv) + at = abstract_eval_value(interp, e.args[2], sstate, sv) n = fieldcount(rt) if (isa(at, Const) && isa(at.val, Tuple) && n == length(at.val::Tuple) && (let t = rt, at = at @@ -3048,14 +3054,14 @@ function abstract_eval_splatnew(interp::AbstractInterpreter, e::Expr, vtypes::Un return RTEffects(rt, Any, effects) end -function abstract_eval_new_opaque_closure(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_new_opaque_closure(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) 𝕃ᡒ = typeinf_lattice(interp) rt = Union{} effects = Effects() # TODO if length(e.args) >= 5 ea = e.args - argtypes = collect_argtypes(interp, ea, vtypes, sv) + argtypes = collect_argtypes(interp, ea, sstate, sv) if argtypes === nothing rt = Bottom effects = EFFECTS_THROWS @@ -3073,7 +3079,7 @@ function abstract_eval_new_opaque_closure(interp::AbstractInterpreter, e::Expr, argtypes = most_general_argtypes(rt) pushfirst!(argtypes, rt.env) callinfo = abstract_call_opaque_closure(interp, rt, - ArgInfo(nothing, argtypes), StmtInfo(true), sv, #=check=#false)::Future + ArgInfo(nothing, argtypes), StmtInfo(true, false), sv, #=check=#false)::Future Future{Any}(callinfo, interp, sv) do callinfo, interp, sv sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo) nothing @@ -3084,10 +3090,10 @@ function abstract_eval_new_opaque_closure(interp::AbstractInterpreter, e::Expr, return Future(RTEffects(rt, Any, effects)) end -function abstract_eval_copyast(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_copyast(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) effects = EFFECTS_UNKNOWN - rt = abstract_eval_value(interp, e.args[1], vtypes, sv) + rt = abstract_eval_value(interp, e.args[1], sstate, sv) if rt isa Const && rt.val isa Expr # `copyast` makes copies of Exprs rt = Expr @@ -3095,11 +3101,11 @@ function abstract_eval_copyast(interp::AbstractInterpreter, e::Expr, vtypes::Uni return RTEffects(rt, Any, effects) end -function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) sym = e.args[1] - if isa(sym, SlotNumber) && vtypes !== nothing - vtyp = vtypes[slot_id(sym)] + if isa(sym, SlotNumber) && sstate.vtypes !== nothing + vtyp = sstate.vtypes[slot_id(sym)] if vtyp.typ === Bottom rt = Const(false) # never assigned previously elseif !vtyp.undef @@ -3109,16 +3115,16 @@ function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, vtyp end return RTEffects(rt, Union{}, EFFECTS_TOTAL) end - return abstract_eval_isdefined(interp, sym, sv) + return abstract_eval_isdefined(interp, sym, sstate.saw_latestworld, sv) end -function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), sv::AbsIntState) +function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), saw_latestworld::Bool, sv::AbsIntState) rt = Bool effects = EFFECTS_TOTAL exct = Union{} isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym)) if isa(sym, GlobalRef) - rte = abstract_eval_globalref(interp, sym, sv) + rte = abstract_eval_globalref(interp, sym, saw_latestworld, sv) if rte.exct == Union{} rt = Const(true) elseif rte.rt === Union{} && rte.exct === UndefVarError @@ -3142,8 +3148,8 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym) return RTEffects(rt, exct, effects) end -function abstract_eval_throw_undef_if_not(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) - condt = abstract_eval_value(interp, e.args[2], vtypes, sv) +function abstract_eval_throw_undef_if_not(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) + condt = abstract_eval_value(interp, e.args[2], sstate, sv) condval = maybe_extract_const_bool(condt) rt = Nothing exct = UndefVarError @@ -3183,32 +3189,32 @@ function abstract_eval_static_parameter(::AbstractInterpreter, e::Expr, sv::AbsI return RTEffects(rt, exct, effects) end -function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState)::Future{RTEffects} ehead = e.head if ehead === :call - return abstract_eval_call(interp, e, vtypes, sv) + return abstract_eval_call(interp, e, sstate, sv) elseif ehead === :new - return abstract_eval_new(interp, e, vtypes, sv) + return abstract_eval_new(interp, e, sstate, sv) elseif ehead === :splatnew - return abstract_eval_splatnew(interp, e, vtypes, sv) + return abstract_eval_splatnew(interp, e, sstate, sv) elseif ehead === :new_opaque_closure - return abstract_eval_new_opaque_closure(interp, e, vtypes, sv) + return abstract_eval_new_opaque_closure(interp, e, sstate, sv) elseif ehead === :foreigncall - return abstract_eval_foreigncall(interp, e, vtypes, sv) + return abstract_eval_foreigncall(interp, e, sstate, sv) elseif ehead === :cfunction - return abstract_eval_cfunction(interp, e, vtypes, sv) + return abstract_eval_cfunction(interp, e, sstate, sv) elseif ehead === :method rt = (length(e.args) == 1) ? Any : Nothing return RTEffects(rt, Any, EFFECTS_UNKNOWN) elseif ehead === :copyast - return abstract_eval_copyast(interp, e, vtypes, sv) + return abstract_eval_copyast(interp, e, sstate, sv) elseif ehead === :invoke || ehead === :invoke_modify error("type inference data-flow error: tried to double infer a function") elseif ehead === :isdefined - return abstract_eval_isdefined_expr(interp, e, vtypes, sv) + return abstract_eval_isdefined_expr(interp, e, sstate, sv) elseif ehead === :throw_undef_if_not - return abstract_eval_throw_undef_if_not(interp, e, vtypes, sv) + return abstract_eval_throw_undef_if_not(interp, e, sstate, sv) elseif ehead === :boundscheck return RTEffects(Bool, Union{}, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) elseif ehead === :the_exception @@ -3245,16 +3251,16 @@ function refine_partial_type(@nospecialize t) return t end -function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) +function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) mi = frame_instance(sv) t = sp_type_rewrap(e.args[2], mi, true) for i = 3:length(e.args) - if abstract_eval_value(interp, e.args[i], vtypes, sv) === Bottom + if abstract_eval_value(interp, e.args[i], sstate, sv) === Bottom return RTEffects(Bottom, Any, EFFECTS_THROWS) end end effects = foreigncall_effects(e) do @nospecialize x - abstract_eval_value(interp, x, vtypes, sv) + abstract_eval_value(interp, x, sstate, sv) end cconv = e.args[5] if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16})) @@ -3264,14 +3270,14 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: return RTEffects(t, Any, effects) end -function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) +function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, sstate::StatementState, sv::AbsIntState) rt = Union{} for i in 1:length(phi.values) isassigned(phi.values, i) || continue val = phi.values[i] # N.B.: Phi arguments are restricted to not have effects, so we can drop # them here safely. - thisval = abstract_eval_special_value(interp, val, vtypes, sv).rt + thisval = abstract_eval_special_value(interp, val, sstate, sv).rt rt = tmerge(typeinf_lattice(interp), rt, thisval) end return rt @@ -3293,10 +3299,6 @@ function merge_override_effects!(interp::AbstractInterpreter, effects::Effects, return effects end -function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) - @assert !isa(e, Union{Expr, PhiNode, NewvarNode}) -end - function override_effects(effects::Effects, override::EffectsOverride) return Effects(effects; consistent = override.consistent ? ALWAYS_TRUE : effects.consistent, @@ -3383,7 +3385,10 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co return RTEffects(rt, UndefVarError, generic_getglobal_effects) end -function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) +function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, saw_latestworld::Bool, sv::AbsIntState) + if saw_latestworld + return RTEffects(Any, Any, generic_getglobal_effects) + end partition = abstract_eval_binding_partition!(interp, g, sv) ret = abstract_eval_partition_load(interp, partition) if ret.rt !== Union{} && ret.exct === UndefVarError && InferenceParams(interp).assume_bindings_static @@ -3396,7 +3401,10 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv:: return ret end -function global_assignment_exct(interp::AbstractInterpreter, sv::AbsIntState, g::GlobalRef, @nospecialize(newty)) +function global_assignment_exct(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, g::GlobalRef, @nospecialize(newty)) + if saw_latestworld + return Union{ErrorException, TypeError} + end partition = abstract_eval_binding_partition!(interp, g, sv) return global_assignment_binding_exct(partition, newty) end @@ -3415,9 +3423,9 @@ function global_assignment_binding_exct(partition::Core.BindingPartition, @nospe return Union{} end -function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty)) +function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, saw_latestworld::Bool, lhs::GlobalRef, @nospecialize(newty)) effect_free = ALWAYS_FALSE - nothrow = global_assignment_exct(interp, frame, lhs, ignorelimited(newty)) === Union{} + nothrow = global_assignment_exct(interp, frame, saw_latestworld, lhs, ignorelimited(newty)) === Union{} inaccessiblememonly = ALWAYS_FALSE if !nothrow sub_curr_ssaflag!(frame, IR_FLAG_NOTHROW) @@ -3609,7 +3617,8 @@ function handle_control_backedge!(interp::AbstractInterpreter, frame::InferenceS return nothing end -function update_bbstate!(𝕃ᡒ::AbstractLattice, frame::InferenceState, bb::Int, vartable::VarTable) +function update_bbstate!(𝕃ᡒ::AbstractLattice, frame::InferenceState, bb::Int, vartable::VarTable, saw_latestworld::Bool) + frame.bb_saw_latestworld[bb] |= saw_latestworld bbtable = frame.bb_vartables[bb] if bbtable === nothing # if a basic block hasn't been analyzed yet, @@ -3686,14 +3695,14 @@ function update_exc_bestguess!(interp::AbstractInterpreter, @nospecialize(exct), end end -function propagate_to_error_handler!(currstate::VarTable, frame::InferenceState, 𝕃ᡒ::AbstractLattice) +function propagate_to_error_handler!(currstate::VarTable, currsaw_latestworld::Bool, frame::InferenceState, 𝕃ᡒ::AbstractLattice) # If this statement potentially threw, propagate the currstate to the # exception handler, BEFORE applying any state changes. curr_hand = gethandler(frame) if curr_hand !== nothing enter = frame.src.code[curr_hand.enter_idx]::EnterNode exceptbb = block_for_inst(frame.cfg, enter.catch_dest) - if update_bbstate!(𝕃ᡒ, frame, exceptbb, currstate) + if update_bbstate!(𝕃ᡒ, frame, exceptbb, currstate, currsaw_latestworld) push!(frame.ip, exceptbb) end end @@ -3711,9 +3720,10 @@ end struct CurrentState result::Future currstate::VarTable + currsaw_latestworld::Bool bbstart::Int bbend::Int - CurrentState(result::Future, currstate::VarTable, bbstart::Int, bbend::Int) = new(result, currstate, bbstart, bbend) + CurrentState(result::Future, currstate::VarTable, currsaw_latestworld::Bool, bbstart::Int, bbend::Int) = new(result, currstate, currsaw_latestworld, bbstart, bbend) CurrentState() = new() end function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextresult::CurrentState) @@ -3724,6 +3734,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr nbbs = length(bbs) 𝕃ᡒ = typeinf_lattice(interp) states = frame.bb_vartables + saw_latestworld = frame.bb_saw_latestworld currbb = frame.currbb currpc = frame.currpc @@ -3732,6 +3743,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr bbstart = nextresult.bbstart bbend = nextresult.bbend currstate = nextresult.currstate + currsaw_latestworld = nextresult.currsaw_latestworld @goto injectresult end @@ -3739,6 +3751,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr currbb = frame.currbb = _bits_findnext(W.bits, 1)::Int # next basic block end currstate = copy(states[currbb]::VarTable) + currsaw_latestworld = saw_latestworld[currbb] while currbb <= nbbs delete!(W, currbb) bbstart = first(bbs[currbb].stmts) @@ -3763,7 +3776,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr elseif isa(stmt, GotoIfNot) condx = stmt.cond condslot = ssa_def_slot(condx, frame) - condt = abstract_eval_value(interp, condx, currstate, frame) + condt = abstract_eval_value(interp, condx, StatementState(currstate, currsaw_latestworld), frame) if condt === Bottom ssavaluetypes[currpc] = Bottom empty!(frame.pclimitations) @@ -3781,7 +3794,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr add_curr_ssaflag!(frame, IR_FLAG_NOTHROW) else update_exc_bestguess!(interp, TypeError, frame) - propagate_to_error_handler!(currstate, frame, 𝕃ᡒ) + propagate_to_error_handler!(currstate, currsaw_latestworld, frame, 𝕃ᡒ) merge_effects!(interp, frame, EFFECTS_THROWS) end @@ -3831,7 +3844,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr if condslot isa SlotNumber # refine the type of this conditional object itself for this else branch stoverwrite1!(elsestate, condition_object_change(currstate, condt, condslot, #=then_or_else=#false)) end - else_changed = update_bbstate!(𝕃ᡒ, frame, falsebb, elsestate) + else_changed = update_bbstate!(𝕃ᡒ, frame, falsebb, elsestate, currsaw_latestworld) then_change = conditional_change(𝕃ᡒ, currstate, condt, #=then_or_else=#true) thenstate = currstate if then_change !== nothing @@ -3841,7 +3854,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr stoverwrite1!(thenstate, condition_object_change(currstate, condt, condslot, #=then_or_else=#true)) end else - else_changed = update_bbstate!(𝕃ᡒ, frame, falsebb, currstate) + else_changed = update_bbstate!(𝕃ᡒ, frame, falsebb, currstate, currsaw_latestworld) end if else_changed handle_control_backedge!(interp, frame, currpc, stmt.dest) @@ -3850,7 +3863,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr @goto fallthrough end elseif isa(stmt, ReturnNode) - rt = abstract_eval_value(interp, stmt.val, currstate, frame) + rt = abstract_eval_value(interp, stmt.val, StatementState(currstate, currsaw_latestworld), frame) if update_bestguess!(interp, frame, currstate, rt) update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int # no reason to revisit if that call-site doesn't affect the final result @@ -3863,7 +3876,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr ssavaluetypes[currpc] = Any add_curr_ssaflag!(frame, IR_FLAG_NOTHROW) if isdefined(stmt, :scope) - scopet = abstract_eval_value(interp, stmt.scope, currstate, frame) + scopet = abstract_eval_value(interp, stmt.scope, StatementState(currstate, currsaw_latestworld), frame) handler = gethandler(frame, currpc + 1)::TryCatchFrame @assert handler.scopet !== nothing if !βŠ‘(𝕃ᡒ, scopet, handler.scopet) @@ -3897,7 +3910,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr # the incoming values from all iterations, but `abstract_eval_phi` will only tmerge # over the first and last iterations. By tmerging in the current old_rt, we ensure that # we will not lose an intermediate value. - rt = abstract_eval_phi(interp, stmt, currstate, frame) + rt = abstract_eval_phi(interp, stmt, StatementState(currstate, currsaw_latestworld), frame) old_rt = frame.ssavaluetypes[currpc] rt = old_rt === NOT_FOUND ? rt : tmerge(typeinf_lattice(interp), old_rt, rt) else @@ -3907,7 +3920,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr stmt = stmt.args[2] end if !isa(stmt, Expr) - (; rt, exct, effects, refinements) = abstract_eval_special_value(interp, stmt, currstate, frame) + (; rt, exct, effects, refinements) = abstract_eval_special_value(interp, stmt, StatementState(currstate, currsaw_latestworld), frame) else hd = stmt.head if hd === :method @@ -3919,10 +3932,13 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr hd !== :boundscheck && # :boundscheck can be narrowed to Bool is_meta_expr(stmt))) rt = Nothing + elseif hd === :latestworld + currsaw_latestworld = true + rt = Nothing else - result = abstract_eval_statement_expr(interp, stmt, currstate, frame)::Future + result = abstract_eval_statement_expr(interp, stmt, StatementState(currstate, currsaw_latestworld), frame)::Future if !isready(result) || !isempty(frame.tasks) - return CurrentState(result, currstate, bbstart, bbend) + return CurrentState(result, currstate, currsaw_latestworld, bbstart, bbend) @label injectresult # reload local variables stmt = frame.src.code[currpc] @@ -3962,7 +3978,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr if isa(lhs, SlotNumber) changes = StateUpdate(lhs, VarState(rt, false)) elseif isa(lhs, GlobalRef) - handle_global_assignment!(interp, frame, lhs, rt) + handle_global_assignment!(interp, frame, currsaw_latestworld, lhs, rt) else merge_effects!(interp, frame, EFFECTS_UNKNOWN) end @@ -3974,7 +3990,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr # TODO: assert that these conditions match. For now, we assume the `nothrow` flag # to be correct, but allow the exct to be an over-approximation. end - propagate_to_error_handler!(currstate, frame, 𝕃ᡒ) + propagate_to_error_handler!(currstate, currsaw_latestworld, frame, 𝕃ᡒ) end if rt === Bottom ssavaluetypes[currpc] = Bottom @@ -4010,7 +4026,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr # Case 2: Directly branch to a different BB begin @label branch - if update_bbstate!(𝕃ᡒ, frame, nextbb, currstate) + if update_bbstate!(𝕃ᡒ, frame, nextbb, currstate, currsaw_latestworld) push!(W, nextbb) end end diff --git a/Compiler/src/inferencestate.jl b/Compiler/src/inferencestate.jl index 0ba37888b34d5..9eb929b725fbf 100644 --- a/Compiler/src/inferencestate.jl +++ b/Compiler/src/inferencestate.jl @@ -209,6 +209,11 @@ to enable flow-sensitive analysis. """ const VarTable = Vector{VarState} +struct StatementState + vtypes::Union{VarTable,Nothing} + saw_latestworld::Bool +end + const CACHE_MODE_NULL = 0x00 # not cached, optimization optional const CACHE_MODE_GLOBAL = 0x01 << 0 # cached globally, optimization required const CACHE_MODE_LOCAL = 0x01 << 1 # cached locally, optimization required @@ -260,6 +265,7 @@ mutable struct InferenceState ssavalue_uses::Vector{BitSet} # ssavalue sparsity and restart info # TODO: Could keep this sparsely by doing structural liveness analysis ahead of time. bb_vartables::Vector{Union{Nothing,VarTable}} # nothing if not analyzed yet + bb_saw_latestworld::Vector{Bool} ssavaluetypes::Vector{Any} edges::Vector{Any} stmt_info::Vector{CallInfo} @@ -320,6 +326,7 @@ mutable struct InferenceState nslots = length(src.slotflags) slottypes = Vector{Any}(undef, nslots) + bb_saw_latestworld = Bool[false for i = 1:length(cfg.blocks)] bb_vartables = Union{Nothing,VarTable}[ nothing for i = 1:length(cfg.blocks) ] bb_vartable1 = bb_vartables[1] = VarTable(undef, nslots) argtypes = result.argtypes @@ -367,7 +374,7 @@ mutable struct InferenceState this = new( mi, WorldWithRange(world, valid_worlds), mod, sptypes, slottypes, src, cfg, spec_info, - currbb, currpc, ip, handler_info, ssavalue_uses, bb_vartables, ssavaluetypes, edges, stmt_info, + currbb, currpc, ip, handler_info, ssavalue_uses, bb_vartables, bb_saw_latestworld, ssavaluetypes, edges, stmt_info, tasks, pclimitations, limitations, cycle_backedges, callstack, parentid, frameid, cycleid, result, unreachable, bestguess, exc_bestguess, ipo_effects, restrict_abstract_call_sites, cache_mode, insert_coverage, diff --git a/Compiler/src/ssair/irinterp.jl b/Compiler/src/ssair/irinterp.jl index 0a8239dc590db..dd5c907d3c25f 100644 --- a/Compiler/src/ssair/irinterp.jl +++ b/Compiler/src/ssair/irinterp.jl @@ -38,7 +38,7 @@ function abstract_eval_invoke_inst(interp::AbstractInterpreter, inst::Instructio mi_cache = WorldView(code_cache(interp), world) code = get(mi_cache, mi, nothing) code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false)) - argtypes = collect_argtypes(interp, stmt.args[2:end], nothing, irsv) + argtypes = collect_argtypes(interp, stmt.args[2:end], StatementState(nothing, false), irsv) argtypes === nothing && return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, false)) return concrete_eval_invoke(interp, code, argtypes, irsv) end @@ -46,11 +46,11 @@ end abstract_eval_ssavalue(s::SSAValue, sv::IRInterpretationState) = abstract_eval_ssavalue(s, sv.ir) function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int, irsv::IRInterpretationState) - return abstract_eval_phi(interp, phi, nothing, irsv) + return abstract_eval_phi(interp, phi, StatementState(nothing, false), irsv) end -function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRInterpretationState) - si = StmtInfo(true) # TODO better job here? +function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sstate::StatementState, irsv::IRInterpretationState) + si = StmtInfo(true, sstate.saw_latestworld) # TODO better job here? call = abstract_call(interp, arginfo, si, irsv)::Future Future{Any}(call, interp, irsv) do call, interp, irsv irsv.ir.stmts[irsv.curridx][:info] = call.info @@ -147,7 +147,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, if (head === :call || head === :foreigncall || head === :new || head === :splatnew || head === :static_parameter || head === :isdefined || head === :boundscheck) @assert isempty(irsv.tasks) # TODO: this whole function needs to be converted to a stackless design to be a valid AbsIntState, but this should work here for now - result = abstract_eval_statement_expr(interp, stmt, nothing, irsv) + result = abstract_eval_statement_expr(interp, stmt, StatementState(nothing, false), irsv) reverse!(irsv.tasks) while true if length(irsv.callstack) > irsv.frameid @@ -302,7 +302,7 @@ populate_def_use_map!(tpdum::TwoPhaseDefUseMap, ir::IRCode) = function is_all_const_call(@nospecialize(stmt), interp::AbstractInterpreter, irsv::IRInterpretationState) isexpr(stmt, :call) || return false @inbounds for i = 2:length(stmt.args) - argtype = abstract_eval_value(interp, stmt.args[i], nothing, irsv) + argtype = abstract_eval_value(interp, stmt.args[i], StatementState(nothing, false), irsv) is_const_argtype(argtype) || return false end return true diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index 3b524742b1609..87dad13c50a30 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -1426,7 +1426,7 @@ end # as well as compute the info for the method matches op = unwrapva(argtypes[op_argi]) v = unwrapva(argtypes[v_argi]) - callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), sv, #=max_methods=#1) + callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true, si.saw_latestworld), sv, #=max_methods=#1) TF = Core.Box(TF) RT = Core.Box(RT) return Future{CallMeta}(callinfo, interp, sv) do callinfo, interp, sv diff --git a/Compiler/src/types.jl b/Compiler/src/types.jl index 35c7880da2281..5669ec3175c9e 100644 --- a/Compiler/src/types.jl +++ b/Compiler/src/types.jl @@ -41,6 +41,7 @@ struct StmtInfo need thus not be computed. """ used::Bool + saw_latestworld::Bool end struct SpecInfo diff --git a/Compiler/src/validation.jl b/Compiler/src/validation.jl index 78db5ef5e4ed8..6700aa8d4508f 100644 --- a/Compiler/src/validation.jl +++ b/Compiler/src/validation.jl @@ -39,6 +39,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :using => 1:typemax(Int), :export => 1:typemax(Int), :public => 1:typemax(Int), + :latestworld => 0:0, ) # @enum isn't defined yet, otherwise I'd use it for this diff --git a/base/boot.jl b/base/boot.jl index 0df0cde64f8c0..f66ee69780193 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -259,13 +259,14 @@ else const UInt = UInt32 end -function iterate end function Typeof end ccall(:jl_toplevel_eval_in, Any, (Any, Any), Core, quote (f::typeof(Typeof))(x) = ($(_expr(:meta,:nospecialize,:x)); isa(x,Type) ? Type{x} : typeof(x)) end) +function iterate end + macro nospecialize(x) _expr(:meta, :nospecialize, x) end diff --git a/base/essentials.jl b/base/essentials.jl index efae59b82b5f9..5683120df8d51 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -467,10 +467,18 @@ Evaluate an expression with values interpolated into it using `eval`. If two arguments are provided, the first is the module to evaluate in. """ macro eval(ex) - return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), __module__, Expr(:quote, ex))) + return Expr(:let, Expr(:(=), :eval_local_result, + Expr(:escape, Expr(:call, GlobalRef(Core, :eval), __module__, Expr(:quote, ex)))), + Expr(:block, + Expr(:var"latestworld-if-toplevel"), + :eval_local_result)) end macro eval(mod, ex) - return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex))) + return Expr(:let, Expr(:(=), :eval_local_result, + Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex)))), + Expr(:block, + Expr(:var"latestworld-if-toplevel"), + :eval_local_result)) end # use `@eval` here to directly form `:new` expressions avoid implicit `convert`s diff --git a/base/sysimg.jl b/base/sysimg.jl index 476b9715b7e11..e57ec1c99bfe6 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -39,6 +39,13 @@ actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`i Use [`Base.include`](@ref) to evaluate a file into another module. +!!! note + Julia's syntax lowering recognizes an explicit call to a literal `include` + at top-level and inserts an implicit `@Core.latestworld` to make any include'd + definitions visible to subsequent code. Note however that this recognition + is *syntactic*. I.e. assigning `const myinclude = include` may require + and explicit `@Core.latestworld` call after `myinclude`. + !!! compat "Julia 1.5" Julia 1.5 is required for passing the `mapexpr` argument. """ diff --git a/base/tuple.jl b/base/tuple.jl index 3791d74bfc698..ee3174d783531 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -60,7 +60,7 @@ end function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} @inline - return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}()) + return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}())::NTuple{N, Any} end diff --git a/src/codegen.cpp b/src/codegen.cpp index 968dab0f00430..85d791052484c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6721,6 +6721,18 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met return std::make_pair(F, specF); } +static void emit_latestworld(jl_codectx_t &ctx) +{ + auto world_age_field = get_tls_world_age_field(ctx); + LoadInst *world = ctx.builder.CreateAlignedLoad(ctx.types().T_size, + prepare_global_in(jl_Module, jlgetworld_global), ctx.types().alignof_ptr, + /*isVolatile*/false); + world->setOrdering(AtomicOrdering::Acquire); + StoreInst *store_world = ctx.builder.CreateAlignedStore(world, world_age_field, + ctx.types().alignof_ptr, /*isVolatile*/false); + (void)store_world; +} + // `expr` is not clobbered in JL_TRY JL_GCC_IGNORE_START("-Wclobbered") static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_0based) @@ -7141,6 +7153,10 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ ctx.builder.CreateCall(prepare_call(gc_preserve_end_func), {token.V}); return jl_cgval_t((jl_value_t*)jl_nothing_type); } + else if (head == jl_latestworld_sym && !jl_is_method(ctx.linfo->def.method)) { + emit_latestworld(ctx); + return jl_cgval_t((jl_value_t*)jl_nothing_type); + } else { if (jl_is_toplevel_only_expr(expr) && !jl_is_method(ctx.linfo->def.method)) { @@ -9568,7 +9584,9 @@ static jl_llvm_functions_t } mallocVisitStmt(sync_bytes, have_dbg_update); - if (toplevel || ctx.is_opaque_closure) + // N.B.: For toplevel thunks, we expect world age restore to be handled + // by the interpreter which invokes us. + if (ctx.is_opaque_closure) ctx.builder.CreateStore(last_age, world_age_field); assert(type_is_ghost(retty) || returninfo.cc == jl_returninfo_t::SRet || retval->getType() == ctx.f->getReturnType()); @@ -9933,17 +9951,6 @@ static jl_llvm_functions_t I.setDebugLoc(topdebugloc); } } - if (toplevel && !ctx.is_opaque_closure && !in_prologue) { - // we're at toplevel; insert an atomic barrier between every instruction - // TODO: inference is invalid if this has any effect (which it often does) - LoadInst *world = new LoadInst(ctx.types().T_size, - prepare_global_in(jl_Module, jlgetworld_global), Twine(), - /*isVolatile*/false, ctx.types().alignof_ptr, /*insertBefore*/&I); - world->setOrdering(AtomicOrdering::Acquire); - StoreInst *store_world = new StoreInst(world, world_age_field, - /*isVolatile*/false, ctx.types().alignof_ptr, /*insertBefore*/&I); - (void)store_world; - } } if (&I == &prologue_end) in_prologue = false; diff --git a/src/interpreter.c b/src/interpreter.c index 252049ad2db6d..cf2ae1a0d9f44 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -463,8 +463,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, s->ip = ip; if (ip >= ns) jl_error("`body` expression must terminate in `return`. Use `block` instead."); - if (toplevel) - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); jl_value_t *stmt = jl_array_ptr_ref(stmts, ip); assert(!jl_is_phinode(stmt)); size_t next_ip = ip + 1; diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index 808af18ebfdbd..3d46940d6fcbb 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -139,7 +139,7 @@ (define (toplevel-only-expr? e) (and (pair? e) - (or (memq (car e) '(toplevel line module import using export public + (or (memq (car e) '(toplevel line module export public error incomplete)) (and (memq (car e) '(global const)) (every symbol? (cdr e)))))) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 6a9558bb06ba5..72e97da3c2daa 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1038,6 +1038,7 @@ '()))))) (call (core _typebody!) ,name (call (core svec) ,@(insert-struct-shim field-types name))) (const (globalref (thismodule) ,name) ,name) + (latestworld) (null))) ;; "inner" constructors (scope-block @@ -1087,6 +1088,7 @@ (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) (const (globalref (thismodule) ,name) ,name)) + (latestworld) (null)))))) (define (primitive-type-def-expr n name params super) @@ -1107,6 +1109,7 @@ (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) (const (globalref (thismodule) ,name) ,name)) + (latestworld) (null)))))) ;; take apart a type signature, e.g. T{X} <: S{Y} @@ -2744,6 +2747,9 @@ ((and (eq? (identifier-name f) '^) (length= e 4) (integer? (cadddr e))) (expand-forms `(call (top literal_pow) ,f ,(caddr e) (call (call (core apply_type) (top Val) ,(cadddr e)))))) + ((eq? f 'include) + (let ((r (make-ssavalue))) + `(block (= ,r ,(map expand-forms e)) (latestworld-if-toplevel) ,r))) (else (map expand-forms e)))) (map expand-forms e))) @@ -4125,7 +4131,8 @@ f(x) = yt(x) `(lambda ,(cadr lam2) (,(clear-capture-bits (car vis)) ,@(cdr vis)) - ,body))))) + ,body))) + (latestworld))) (else (let* ((exprs (lift-toplevel (convert-lambda lam2 '|#anon| #t '() #f parsed-method-stack))) (top-stmts (cdr exprs)) @@ -4133,7 +4140,8 @@ f(x) = yt(x) `(toplevel-butfirst (block ,@sp-inits (method ,(cadr e) ,(cl-convert sig fname lam namemap defined toplevel interp opaq parsed-method-stack globals locals) - ,(julia-bq-macro newlam))) + ,(julia-bq-macro newlam)) + (latestworld)) ,@top-stmts)))) ;; local case - lift to a new type at top level @@ -4272,7 +4280,8 @@ f(x) = yt(x) `(toplevel-butfirst (null) ,@sp-inits - ,@mk-method) + ,@mk-method + (latestworld)) (begin (put! defined name #t) `(toplevel-butfirst @@ -4280,7 +4289,8 @@ f(x) = yt(x) ,@typedef ,@(map (lambda (v) `(moved-local ,v)) moved-vars) ,@sp-inits - ,@mk-method)))))))) + ,@mk-method + (latestworld))))))))) ((lambda) ;; happens inside (thunk ...) and generated function bodies (for-each (lambda (vi) (vinfo:set-asgn! vi #t)) (list-tail (car (lam:vinfo e)) (length (lam:args e)))) @@ -4513,7 +4523,7 @@ f(x) = yt(x) ((struct_type) "\"struct\" expression") ((method) "method definition") ((set_binding_type!) (string "type declaration for global \"" (deparse (cadr e)) "\"")) - ((latestworld) "World age increment") + ((latestworld) "World age increment") (else (string "\"" h "\" expression")))) (if (not (null? (cadr lam))) (error (string (head-to-text (car e)) " not at top level")))) @@ -4965,7 +4975,12 @@ f(x) = yt(x) (else (emit temp))))) ;; top level expressions - ((thunk module) + ((thunk) + (check-top-level e) + (emit e) + (if tail (emit-return tail '(null))) + '(null)) + ((module) (check-top-level e) (emit e) (if tail (emit-return tail '(null))) @@ -4989,7 +5004,9 @@ f(x) = yt(x) ;; other top level expressions ((import using export public latestworld) (check-top-level e) - (emit e) + (if (not (eq? (car e) 'latestworld)) + (emit e)) + (emit `(latestworld)) (let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return)))) (if (and tail (not have-ret?)) (emit-return tail '(null)))) diff --git a/src/toplevel.c b/src/toplevel.c index cedc008af5cd0..56a5f21f43661 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -607,8 +607,7 @@ int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT ((jl_expr_t*)e)->head == jl_const_sym || ((jl_expr_t*)e)->head == jl_toplevel_sym || ((jl_expr_t*)e)->head == jl_error_sym || - ((jl_expr_t*)e)->head == jl_incomplete_sym || - ((jl_expr_t*)e)->head == jl_latestworld_sym); + ((jl_expr_t*)e)->head == jl_incomplete_sym); } int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT @@ -1002,8 +1001,15 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val jl_value_t *res = jl_nothing; int i; for (i = 0; i < jl_array_nrows(ex->args); i++) { - res = jl_toplevel_eval_flex(m, jl_array_ptr_ref(ex->args, i), fast, 0, toplevel_filename, toplevel_lineno); + root = jl_array_ptr_ref(ex->args, i); + if (jl_needs_lowering(root)) { + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + root = jl_expand_with_loc_warn(root, m, *toplevel_filename, *toplevel_lineno); + } + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + res = jl_toplevel_eval_flex(m, root, fast, 1, toplevel_filename, toplevel_lineno); } + ct->world_age = last_age; JL_GC_POP(); return res; } @@ -1112,9 +1118,12 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) jl_value_t *v = NULL; int last_lineno = jl_lineno; const char *last_filename = jl_filename; + jl_task_t *ct = jl_current_task; jl_lineno = 1; jl_filename = "none"; + size_t last_age = ct->world_age; JL_TRY { + ct->world_age = jl_atomic_load_relaxed(&jl_world_counter); v = jl_toplevel_eval(m, ex); } JL_CATCH { @@ -1124,6 +1133,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) } jl_lineno = last_lineno; jl_filename = last_filename; + ct->world_age = last_age; assert(v); return v; } @@ -1178,6 +1188,7 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, int err = 0; JL_TRY { + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); for (size_t i = 0; i < jl_expr_nargs(ast); i++) { expression = jl_exprarg(ast, i); if (jl_is_linenode(expression)) { @@ -1186,9 +1197,10 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, jl_lineno = lineno; continue; } + ct->world_age = jl_atomic_load_relaxed(&jl_world_counter); expression = jl_expand_with_loc_warn(expression, module, jl_string_data(filename), lineno); - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + ct->world_age = jl_atomic_load_relaxed(&jl_world_counter); result = jl_toplevel_eval_flex(module, expression, 1, 1, &filename_str, &lineno); } } diff --git a/stdlib/Logging/test/runtests.jl b/stdlib/Logging/test/runtests.jl index 176860fcdec63..2fedbde557078 100644 --- a/stdlib/Logging/test/runtests.jl +++ b/stdlib/Logging/test/runtests.jl @@ -285,7 +285,7 @@ end AboveMaxLevel === Logging.AboveMaxLevel end """) - @test m.run() + @test invokelatest(m.run) end @testset "custom log macro" begin diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index e3a58ec362d89..50f610ff3b3e8 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -343,9 +343,9 @@ __repl_entry_eval_expanded_with_loc(mod::Module, @nospecialize(ast), toplevel_fi function toplevel_eval_with_hooks(mod::Module, @nospecialize(ast), toplevel_file=Ref{Ptr{UInt8}}(Base.unsafe_convert(Ptr{UInt8}, :REPL)), toplevel_line=Ref{Cint}(1)) if !isexpr(ast, :toplevel) - ast = __repl_entry_lower_with_loc(mod, ast, toplevel_file, toplevel_line) + ast = invokelatest(__repl_entry_lower_with_loc, mod, ast, toplevel_file, toplevel_line) check_for_missing_packages_and_run_hooks(ast) - return __repl_entry_eval_expanded_with_loc(mod, ast, toplevel_file, toplevel_line) + return invokelatest(__repl_entry_eval_expanded_with_loc, mod, ast, toplevel_file, toplevel_line) end local value=nothing for i = 1:length(ast.args) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index df3a0cad76878..1f2c0cabbdb38 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -630,7 +630,7 @@ end isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g)) # aggressive global binding resolution within `repl_frame` -function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, +function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, bailed::Bool, sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) if isdefined_globalref(g) @@ -638,7 +638,7 @@ function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, end return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS) end - return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, + return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool, sv::CC.InferenceState) end diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 1355f74c9bfff..b259567884486 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -12,150 +12,151 @@ using REPL end end -let ex = quote - module CompletionFoo - using Random - import Test - - mutable struct Test_y - yy - end - mutable struct Test_x - xx :: Test_y - end - type_test = Test_x(Test_y(1)) - (::Test_y)() = "", "" - unicode_Ξ±Ξ²Ξ³ = Test_y(1) +let ex = + quote + module CompletionFoo + using Random + import Test + + mutable struct Test_y + yy + end + mutable struct Test_x + xx :: Test_y + end + type_test = Test_x(Test_y(1)) + (::Test_y)() = "", "" + unicode_Ξ±Ξ²Ξ³ = Test_y(1) - Base.:(+)(x::Test_x, y::Test_y) = Test_x(Test_y(x.xx.yy + y.yy)) - module CompletionFoo2 + Base.:(+)(x::Test_x, y::Test_y) = Test_x(Test_y(x.xx.yy + y.yy)) + module CompletionFoo2 - end - const bar = 1 - foo() = bar - macro foobar() - :() - end - macro barfoo(ex) - ex - end - macro error_expanding() - error("cannot expand @error_expanding") - :() - end - macro error_lowering_conditional(a) - if isa(a, Number) - return a end - throw(AssertionError("Not a Number")) - :() - end - macro error_throwing() - return quote - error("@error_throwing throws an error") + const bar = 1 + foo() = bar + macro foobar() + :() + end + macro barfoo(ex) + ex + end + macro error_expanding() + error("cannot expand @error_expanding") + :() + end + macro error_lowering_conditional(a) + if isa(a, Number) + return a + end + throw(AssertionError("Not a Number")) + :() + end + macro error_throwing() + return quote + error("@error_throwing throws an error") + end end - end - primitive type NonStruct 8 end - Base.propertynames(::NonStruct) = (:a, :b, :c) - x = reinterpret(NonStruct, 0x00) + primitive type NonStruct 8 end + Base.propertynames(::NonStruct) = (:a, :b, :c) + x = reinterpret(NonStruct, 0x00) - # Support non-Dict AbstractDicts, #19441 - mutable struct CustomDict{K, V} <: AbstractDict{K, V} - mydict::Dict{K, V} - end + # Support non-Dict AbstractDicts, #19441 + mutable struct CustomDict{K, V} <: AbstractDict{K, V} + mydict::Dict{K, V} + end - Base.keys(d::CustomDict) = collect(keys(d.mydict)) - Base.length(d::CustomDict) = length(d.mydict) + Base.keys(d::CustomDict) = collect(keys(d.mydict)) + Base.length(d::CustomDict) = length(d.mydict) - # Support AbstractDict with unknown length, #55931 - struct NoLengthDict{K,V} <: AbstractDict{K,V} - dict::Dict{K,V} - NoLengthDict{K,V}() where {K,V} = new(Dict{K,V}()) - end - Base.iterate(d::NoLengthDict, s...) = iterate(d.dict, s...) - Base.IteratorSize(::Type{<:NoLengthDict}) = Base.SizeUnknown() - Base.eltype(::Type{NoLengthDict{K,V}}) where {K,V} = Pair{K,V} - Base.setindex!(d::NoLengthDict, v, k) = d.dict[k] = v - - test(x::T, y::T) where {T<:Real} = pass - test(x::Real, y::Real) = pass - test(x::AbstractArray{T}, y) where {T<:Real} = pass - test(args...) = pass - - test1(x::Type{Float64}) = pass - - test2(x::AbstractString) = pass - test2(x::Char) = pass - test2(x::Cmd) = pass - - test3(x::AbstractArray{Int}, y::Int) = pass - test3(x::AbstractArray{Float64}, y::Float64) = pass - - test4(x::AbstractString, y::AbstractString) = pass - test4(x::AbstractString, y::Regex) = pass - - test5(x::Array{Bool,1}) = pass - test5(x::BitArray{1}) = pass - test5(x::Float64) = pass - const a=x->x - test6()=[a, a] - test7() = rand(Bool) ? 1 : 1.0 - test8() = Any[1][1] - test9(x::Char) = pass - test9(x::Char, i::Int) = pass - - test10(a, x::Int...) = pass - test10(a::Integer, b::Integer, c) = pass - test10(a, y::Bool...) = pass - test10(a, d::Integer, z::Signed...) = pass - test10(s::String...) = pass - - test11(a::Integer, b, c) = pass - test11(u, v::Integer, w) = pass - test11(x::Int, y::Int, z) = pass - test11(_, _, s::String) = pass - - test!12() = pass - - kwtest(; x=1, y=2, w...) = pass - kwtest2(a; x=1, y=2, w...) = pass - kwtest3(a::Number; length, len2, foobar, kwargs...) = pass - kwtest3(a::Real; another!kwarg, len2) = pass - kwtest3(a::Integer; namedarg, foobar, slurp...) = pass - kwtest4(a::AbstractString; _a1b, x23) = pass - kwtest4(a::String; _a1b, xΞ±Ξ²Ξ³) = pass - kwtest4(a::SubString; x23, _something) = pass - kwtest5(a::Int, b, x...; somekwarg, somekotherkwarg) = pass - kwtest5(a::Char, b; xyz) = pass - - const named = (; len2=3) - const fmsoebelkv = (; len2=3) - - array = [1, 1] - varfloat = 0.1 - - const tuple = (1, 2) - - test_y_array=[(@__MODULE__).Test_y(rand()) for i in 1:10] - test_dict = Dict("abc"=>1, "abcd"=>10, :bar=>2, :bar2=>9, Base=>3, - occursin=>4, `ls`=>5, 66=>7, 67=>8, ("q",3)=>11, - "Ξ±"=>12, :Ξ±=>13) - test_customdict = CustomDict(test_dict) - - macro teststr_str(s) end - macro tΟ΅sΟ„stρ_str(s) end - macro testcmd_cmd(s) end - macro tΟ΅sΟ„cmΞ΄_cmd(s) end - - var"complicated symbol with spaces" = 5 - - struct WeirdNames end - Base.propertynames(::WeirdNames) = (Symbol("oh no!"), Symbol("oh yes!")) - - # https://github.com/JuliaLang/julia/issues/52551#issuecomment-1858543413 - export exported_symbol - exported_symbol(::WeirdNames) = nothing + # Support AbstractDict with unknown length, #55931 + struct NoLengthDict{K,V} <: AbstractDict{K,V} + dict::Dict{K,V} + NoLengthDict{K,V}() where {K,V} = new(Dict{K,V}()) + end + Base.iterate(d::NoLengthDict, s...) = iterate(d.dict, s...) + Base.IteratorSize(::Type{<:NoLengthDict}) = Base.SizeUnknown() + Base.eltype(::Type{NoLengthDict{K,V}}) where {K,V} = Pair{K,V} + Base.setindex!(d::NoLengthDict, v, k) = d.dict[k] = v + + test(x::T, y::T) where {T<:Real} = pass + test(x::Real, y::Real) = pass + test(x::AbstractArray{T}, y) where {T<:Real} = pass + test(args...) = pass + + test1(x::Type{Float64}) = pass + + test2(x::AbstractString) = pass + test2(x::Char) = pass + test2(x::Cmd) = pass + + test3(x::AbstractArray{Int}, y::Int) = pass + test3(x::AbstractArray{Float64}, y::Float64) = pass + + test4(x::AbstractString, y::AbstractString) = pass + test4(x::AbstractString, y::Regex) = pass + + test5(x::Array{Bool,1}) = pass + test5(x::BitArray{1}) = pass + test5(x::Float64) = pass + const a=x->x + test6()=[a, a] + test7() = rand(Bool) ? 1 : 1.0 + test8() = Any[1][1] + test9(x::Char) = pass + test9(x::Char, i::Int) = pass + + test10(a, x::Int...) = pass + test10(a::Integer, b::Integer, c) = pass + test10(a, y::Bool...) = pass + test10(a, d::Integer, z::Signed...) = pass + test10(s::String...) = pass + + test11(a::Integer, b, c) = pass + test11(u, v::Integer, w) = pass + test11(x::Int, y::Int, z) = pass + test11(_, _, s::String) = pass + + test!12() = pass + + kwtest(; x=1, y=2, w...) = pass + kwtest2(a; x=1, y=2, w...) = pass + kwtest3(a::Number; length, len2, foobar, kwargs...) = pass + kwtest3(a::Real; another!kwarg, len2) = pass + kwtest3(a::Integer; namedarg, foobar, slurp...) = pass + kwtest4(a::AbstractString; _a1b, x23) = pass + kwtest4(a::String; _a1b, xΞ±Ξ²Ξ³) = pass + kwtest4(a::SubString; x23, _something) = pass + kwtest5(a::Int, b, x...; somekwarg, somekotherkwarg) = pass + kwtest5(a::Char, b; xyz) = pass + + const named = (; len2=3) + const fmsoebelkv = (; len2=3) + + array = [1, 1] + varfloat = 0.1 + + const tuple = (1, 2) + + test_y_array=[(@__MODULE__).Test_y(rand()) for i in 1:10] + test_dict = Dict("abc"=>1, "abcd"=>10, :bar=>2, :bar2=>9, Base=>3, + occursin=>4, `ls`=>5, 66=>7, 67=>8, ("q",3)=>11, + "Ξ±"=>12, :Ξ±=>13) + test_customdict = CustomDict(test_dict) + + macro teststr_str(s) end + macro tΟ΅sΟ„stρ_str(s) end + macro testcmd_cmd(s) end + macro tΟ΅sΟ„cmΞ΄_cmd(s) end + + var"complicated symbol with spaces" = 5 + + struct WeirdNames end + Base.propertynames(::WeirdNames) = (Symbol("oh no!"), Symbol("oh yes!")) + + # https://github.com/JuliaLang/julia/issues/52551#issuecomment-1858543413 + export exported_symbol + exported_symbol(::WeirdNames) = nothing end # module CompletionFoo test_repl_comp_dict = CompletionFoo.test_dict diff --git a/stdlib/Serialization/test/runtests.jl b/stdlib/Serialization/test/runtests.jl index a7d5023e1ec51..4d9b439e639d7 100644 --- a/stdlib/Serialization/test/runtests.jl +++ b/stdlib/Serialization/test/runtests.jl @@ -577,7 +577,7 @@ let io = IOBuffer() serialize(io, f) seekstart(io) f2 = deserialize(io) - @test f2(1) === 1f0 + @test invokelatest(f2, 1) === 1f0 end # using a filename; #30151 @@ -595,7 +595,7 @@ let f_data f_data = "N0pMBwAAAAA0MxMAAAAAAAAAAAEFIyM1IzYiAAAAABBYH04BBE1haW6bRCIAAAAAIgAAAABNTEy+AQIjNRUAI78jAQAAAAAAAAAfTgEETWFpbkQBAiM1AQdSRVBMWzJdvxBTH04BBE1haW6bRAMAAAAzLAAARkYiAAAAAE7BTBsVRsEWA1YkH04BBE1haW5EAQEqwCXAFgNWJB9OAQRNYWluRJ0ovyXBFgFVKMAVAAbBAQAAAAEAAAABAAAATsEVRr80EAEMTGluZUluZm9Ob2RlH04BBE1haW6bRB9OAQRNYWluRAECIzUBB1JFUExbMl2/vhW+FcEAAAAVRsGifX5MTExMTsEp" end f = deserialize(IOBuffer(base64decode(f_data))) - @test f(10,3) == 23 + @test invokelatest(f, 10,3) == 23 end # issue #33466, IdDict diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 1b9505c59e327..7c985828d47f2 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1563,6 +1563,13 @@ parent test set (with the context object appended to any failing tests.) !!! compat "Julia 1.10" Multiple `let` assignments are supported since Julia 1.10. +# Special implicit world age increment for `@testset begin` + +World age inside `@testset begin` increments implicitly after every statement. +This matches the behavior of ordinary toplevel code, but not that of ordinary +`begin/end` blocks, i.e. with respect to world age, `@testset begin` behaves +as if the body of the `begin/end` block was written at toplevel. + ## Examples ```jldoctest julia> @testset let logi = log(im) @@ -1657,6 +1664,21 @@ function testset_context(args, ex, source) return esc(ex) end +function insert_toplevel_latestworld(@nospecialize(tests)) + isa(tests, Expr) || return tests + (tests.head !== :block) && return tests + ret = Expr(:block) + for arg in tests.args + push!(ret.args, arg) + if isa(arg, LineNumberNode) || + (isa(arg, Expr) && arg.head in (:latestworld, :var"latestworld-if-toplevel")) + continue + end + push!(ret.args, Expr(:var"latestworld-if-toplevel")) + end + return ret +end + """ Generate the code for a `@testset` with a function call or `begin`/`end` argument """ @@ -1675,6 +1697,8 @@ function testset_beginend_call(args, tests, source) testsettype = :(get_testset_depth() == 0 ? DefaultTestSet : typeof(get_testset())) end + tests = insert_toplevel_latestworld(tests) + # Generate a block of code that initializes a new testset, adds # it to the task local storage, evaluates the test(s), before # finally removing the testset and giving it a chance to take diff --git a/test/arrayops.jl b/test/arrayops.jl index 49d51176dcf71..ca378c3f3036b 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2667,31 +2667,32 @@ end end @testset "sign, conj[!], ~" begin - local A, B, C, D, E - A = [-10,0,3] - B = [-10.0,0.0,3.0] - C = [1,im,0] - - @test sign.(A) == [-1,0,1] - @test sign.(B) == [-1,0,1] - @test typeof(sign.(A)) == Vector{Int} - @test typeof(sign.(B)) == Vector{Float64} - - @test conj(A) == A - @test conj!(copy(A)) == A - @test conj(B) == A - @test conj(C) == [1,-im,0] - @test typeof(conj(A)) == Vector{Int} - @test typeof(conj(B)) == Vector{Float64} - @test typeof(conj(C)) == Vector{Complex{Int}} - D = [C copy(C); copy(C) copy(C)] - @test conj(D) == conj!(copy(D)) - E = [D, copy(D)] - @test conj(E) == conj!(copy(E)) - @test (@allocations conj!(E)) == 0 - - @test .~A == [9,-1,-4] - @test typeof(.~A) == Vector{Int} + let A, B, C, D, E # Suppress :latestworld to get good inference for the allocations test + A = [-10,0,3] + B = [-10.0,0.0,3.0] + C = [1,im,0] + + @test sign.(A) == [-1,0,1] + @test sign.(B) == [-1,0,1] + @test typeof(sign.(A)) == Vector{Int} + @test typeof(sign.(B)) == Vector{Float64} + + @test conj(A) == A + @test conj!(copy(A)) == A + @test conj(B) == A + @test conj(C) == [1,-im,0] + @test typeof(conj(A)) == Vector{Int} + @test typeof(conj(B)) == Vector{Float64} + @test typeof(conj(C)) == Vector{Complex{Int}} + D = [C copy(C); copy(C) copy(C)] + @test conj(D) == conj!(copy(D)) + E = [D, copy(D)] + @test conj(E) == conj!(copy(E)) + @test (@allocations conj!(E)) == 0 + + @test .~A == [9,-1,-4] + @test typeof(.~A) == Vector{Int} + end end # @inbounds is expression-like, returning its value; #15558 diff --git a/test/core.jl b/test/core.jl index 1b36db466ce19..836532d661638 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2621,7 +2621,7 @@ end # issue #8338 let ex = Expr(:(=), :(f8338(x;y=4)), :(x*y)) eval(ex) - @test f8338(2) == 8 + @test invokelatest(f8338, 2) == 8 end # call overloading (#2403) @@ -8332,3 +8332,23 @@ let s = mktemp() do path, io end @test strip(s) == "xxx = 42" end + +# `module` has an implicit world-age increment +let foo = eval(Expr(:toplevel, :(module BarModuleInc; struct FooModuleInc; end; end), :(BarModuleInc.FooModuleInc()))) + @Core.latestworld + @test foo == BarModuleInc.FooModuleInc() +end + +let + eval(:(module BarModuleInc2; module BazModuleInc; struct FooModuleInc; end; end; const foo = BazModuleInc.FooModuleInc(); end)) + @Core.latestworld + @test BarModuleInc2.foo == BarModuleInc2.BazModuleInc.FooModuleInc() +end + +# `toplevel` has implicit world age increment between expansion and evaluation +macro define_call(sym) + Core.eval(__module__, :($sym() = 1)) + :($sym()) +end +@test eval(Expr(:toplevel, :(@define_call(f_macro_defined1)))) == 1 +@test @define_call(f_macro_defined2) == 1 diff --git a/test/deprecation_exec.jl b/test/deprecation_exec.jl index 61ffcc2a59ac6..8209b0e920a18 100644 --- a/test/deprecation_exec.jl +++ b/test/deprecation_exec.jl @@ -68,6 +68,7 @@ begin # @deprecate ex = :(module M22845; import ..DeprecationTests: bar; bar(x::Number) = x + 3; end) @test_warn "importing deprecated binding" eval(ex) + @Core.latestworld @test @test_nowarn(DeprecationTests.bar(4)) == 7 @test @test_warn "`f1` is deprecated, use `f` instead." f1() diff --git a/test/error.jl b/test/error.jl index 8657c70720779..f76a7809b08a9 100644 --- a/test/error.jl +++ b/test/error.jl @@ -93,7 +93,7 @@ end @testset "MethodError for methods without line numbers" begin try eval(Expr(:function, :(f44319()), 0)) - f44319(1) + @invokelatest f44319() catch e s = sprint(showerror, e) @test s == """MethodError: no method matching f44319(::Int$(Sys.WORD_SIZE)) diff --git a/test/math.jl b/test/math.jl index 5a9f3248e59f4..d794facb02d25 100644 --- a/test/math.jl +++ b/test/math.jl @@ -23,44 +23,46 @@ has_fma = Dict( ) @testset "clamp" begin - @test clamp(0, 1, 3) == 1 - @test clamp(1, 1, 3) == 1 - @test clamp(2, 1, 3) == 2 - @test clamp(3, 1, 3) == 3 - @test clamp(4, 1, 3) == 3 - - @test clamp(0.0, 1, 3) == 1.0 - @test clamp(1.0, 1, 3) == 1.0 - @test clamp(2.0, 1, 3) == 2.0 - @test clamp(3.0, 1, 3) == 3.0 - @test clamp(4.0, 1, 3) == 3.0 - - @test clamp.([0, 1, 2, 3, 4], 1.0, 3.0) == [1.0, 1.0, 2.0, 3.0, 3.0] - @test clamp.([0 1; 2 3], 1.0, 3.0) == [1.0 1.0; 2.0 3.0] - - @test clamp(-200, Int8) === typemin(Int8) - @test clamp(100, Int8) === Int8(100) - @test clamp(200, Int8) === typemax(Int8) - - begin - x = [0.0, 1.0, 2.0, 3.0, 4.0] - clamp!(x, 1, 3) - @test x == [1.0, 1.0, 2.0, 3.0, 3.0] - end + let + @test clamp(0, 1, 3) == 1 + @test clamp(1, 1, 3) == 1 + @test clamp(2, 1, 3) == 2 + @test clamp(3, 1, 3) == 3 + @test clamp(4, 1, 3) == 3 + + @test clamp(0.0, 1, 3) == 1.0 + @test clamp(1.0, 1, 3) == 1.0 + @test clamp(2.0, 1, 3) == 2.0 + @test clamp(3.0, 1, 3) == 3.0 + @test clamp(4.0, 1, 3) == 3.0 + + @test clamp.([0, 1, 2, 3, 4], 1.0, 3.0) == [1.0, 1.0, 2.0, 3.0, 3.0] + @test clamp.([0 1; 2 3], 1.0, 3.0) == [1.0 1.0; 2.0 3.0] + + @test clamp(-200, Int8) === typemin(Int8) + @test clamp(100, Int8) === Int8(100) + @test clamp(200, Int8) === typemax(Int8) + + begin + x = [0.0, 1.0, 2.0, 3.0, 4.0] + clamp!(x, 1, 3) + @test x == [1.0, 1.0, 2.0, 3.0, 3.0] + end - @test clamp(typemax(UInt64), Int64) === typemax(Int64) - @test clamp(typemin(Int), UInt64) === typemin(UInt64) - @test clamp(Int16(-1), UInt16) === UInt16(0) - @test clamp(-1, 2, UInt(0)) === UInt(2) - @test clamp(typemax(UInt16), Int16) === Int16(32767) + @test clamp(typemax(UInt64), Int64) === typemax(Int64) + @test clamp(typemin(Int), UInt64) === typemin(UInt64) + @test clamp(Int16(-1), UInt16) === UInt16(0) + @test clamp(-1, 2, UInt(0)) === UInt(2) + @test clamp(typemax(UInt16), Int16) === Int16(32767) - # clamp should not allocate a BigInt for typemax(Int16) - x = big(2) ^ 100 - @test (@allocated clamp(x, Int16)) == 0 + # clamp should not allocate a BigInt for typemax(Int16) + x = big(2) ^ 100 + @test (@allocated clamp(x, Int16)) == 0 - x = clamp(2.0, BigInt) - @test x isa BigInt - @test x == big(2) + x = clamp(2.0, BigInt) + @test x isa BigInt + @test x == big(2) + end end @testset "constants" begin @@ -1608,8 +1610,10 @@ function f44336() @inline hypot(as...) end @testset "Issue #44336" begin - f44336() - @test (@allocated f44336()) == 0 + let + f44336() + @test (@allocated f44336()) == 0 + end end @testset "constant-foldability of core math functions" begin diff --git a/test/ranges.jl b/test/ranges.jl index d79851d7056e0..89134be897ddd 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -2058,8 +2058,10 @@ end end @testset "allocation of TwicePrecision call" begin - @test @allocated(0:286.493442:360) == 0 - @test @allocated(0:286:360) == 0 + let + @test @allocated(0:286.493442:360) == 0 + @test @allocated(0:286:360) == 0 + end end @testset "range with start and stop" begin diff --git a/test/sorting.jl b/test/sorting.jl index 93e0cdd7de5ba..f12486b9c9b40 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -968,9 +968,10 @@ end end @testset "ScratchQuickSort allocations on non-concrete eltype" begin - v = Vector{Union{Nothing, Bool}}(rand(Bool, 10000)) - @test 10 > @allocations sort(v) - @test 10 > @allocations sort(v; alg=Base.Sort.ScratchQuickSort()) + let v = Vector{Union{Nothing, Bool}}(rand(Bool, 10000)) + @test 10 > @allocations sort(v) + @test 10 > @allocations sort(v; alg=Base.Sort.ScratchQuickSort()) + end # it would be nice if these numbers were lower (1 or 2), but these # test that we don't have O(n) allocations due to type instability end diff --git a/test/syntax.jl b/test/syntax.jl index 1f9d1d592931b..d9d311ac6615d 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -553,7 +553,14 @@ for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :c end # meta nodes for optional positional arguments -let src = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-2].args[3] +let code = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code + local src + for i = length(code):-1:1 + if Meta.isexpr(code[i], :method) + src = code[i].args[3] + break + end + end @test Core.Compiler.is_declared_inline(src) end @@ -578,6 +585,7 @@ let thismodule = @__MODULE__, @test isa(ex, Expr) @test !isdefined(M16096, :foo16096) local_foo16096 = Core.eval(@__MODULE__, ex) + Core.@latestworld @test local_foo16096(2.0) == 1 @test !@isdefined foo16096 @test !@isdefined it @@ -3102,6 +3110,7 @@ end ex = Expr(:block) ex.args = fill!(Vector{Any}(undef, 700000), 1) f = eval(Expr(:function, :(), ex)) + @Core.latestworld @test f() == 1 ex = Expr(:vcat) ex.args = fill!(Vector{Any}(undef, 600000), 1) @@ -4010,3 +4019,17 @@ end @test f45494() === (0,) @test_throws "\"esc(...)\" used outside of macro expansion" eval(esc(:(const x=1))) + +# Inner function declaration world age +function create_inner_f_no_methods() + function inner_f end +end +@test isa(create_inner_f_no_methods(), Function) +@test length(methods(create_inner_f_no_methods())) == 0 + +function create_inner_f_one_method() + inner_f() = 1 +end +@test isa(create_inner_f_no_methods(), Function) +@test length(methods(create_inner_f_no_methods())) == 0 +@test Base.invoke_in_world(first(methods(create_inner_f_one_method)).primary_world, create_inner_f_one_method()) == 1