diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index d1e28ce220b13..9bdd5b50e512e 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -2421,11 +2421,15 @@ function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, s end function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) - if length(argtypes) == 3 - return abstract_eval_getglobal(interp, sv, saw_latestworld, argtypes[2], argtypes[3]) - elseif length(argtypes) == 4 - return abstract_eval_getglobal(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) - elseif !isvarargtype(argtypes[end]) || length(argtypes) > 5 + if !isvarargtype(argtypes[end]) + if length(argtypes) == 3 + return abstract_eval_getglobal(interp, sv, saw_latestworld, argtypes[2], argtypes[3]) + elseif length(argtypes) == 4 + return abstract_eval_getglobal(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) + else + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + elseif length(argtypes) > 5 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else return CallMeta(Any, generic_getglobal_exct, generic_getglobal_effects, NoCallInfo()) @@ -2466,12 +2470,17 @@ end end function abstract_eval_get_binding_type(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) - if length(argtypes) == 3 - return abstract_eval_get_binding_type(interp, sv, argtypes[2], argtypes[3]) - elseif !isvarargtype(argtypes[end]) || length(argtypes) > 4 + if !isvarargtype(argtypes[end]) + if length(argtypes) == 3 + return abstract_eval_get_binding_type(interp, sv, argtypes[2], argtypes[3]) + else + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + elseif length(argtypes) > 4 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + else + return CallMeta(Type, Union{TypeError, ArgumentError}, EFFECTS_THROWS, NoCallInfo()) end - return CallMeta(Type, Union{TypeError, ArgumentError}, EFFECTS_THROWS, NoCallInfo()) end const setglobal!_effects = Effects(EFFECTS_TOTAL; effect_free=ALWAYS_FALSE, nothrow=false, inaccessiblememonly=ALWAYS_FALSE) @@ -2504,11 +2513,15 @@ end const generic_setglobal!_exct = Union{ArgumentError, TypeError, ErrorException, ConcurrencyViolationError} function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) - if length(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, saw_latestworld, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) - elseif !isvarargtype(argtypes[end]) || length(argtypes) > 6 + if !isvarargtype(argtypes[end]) + if length(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, saw_latestworld, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) + else + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + elseif length(argtypes) > 6 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else return CallMeta(Any, generic_setglobal!_exct, setglobal!_effects, NoCallInfo()) @@ -2532,11 +2545,15 @@ function abstract_eval_swapglobal!(interp::AbstractInterpreter, sv::AbsIntState, end function abstract_eval_swapglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) - if length(argtypes) == 4 - return abstract_eval_swapglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) - elseif length(argtypes) == 5 - return abstract_eval_swapglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) - elseif !isvarargtype(argtypes[end]) || length(argtypes) > 6 + if !isvarargtype(argtypes[end]) + if length(argtypes) == 4 + return abstract_eval_swapglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4]) + elseif length(argtypes) == 5 + return abstract_eval_swapglobal!(interp, sv, saw_latestworld, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) + else + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + elseif length(argtypes) > 6 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else return CallMeta(Any, Union{generic_getglobal_exct,generic_setglobal!_exct}, setglobal!_effects, NoCallInfo()) @@ -2544,18 +2561,22 @@ function abstract_eval_swapglobal!(interp::AbstractInterpreter, sv::AbsIntState, end 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, 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) - end - if length(argtypes) == 6 - goe = global_order_exct(argtypes[6], #=loading=#true, #=storing=#false) - cm = merge_exct(cm, goe) - end - return CallMeta(Bool, cm.exct, cm.effects, cm.info) - elseif !isvarargtype(argtypes[end]) || length(argtypes) > 6 + if !isvarargtype(argtypes[end]) + if length(argtypes) in (4, 5, 6) + 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) + end + if length(argtypes) == 6 + goe = global_order_exct(argtypes[6], #=loading=#true, #=storing=#false) + cm = merge_exct(cm, goe) + end + return CallMeta(Bool, cm.exct, cm.effects, cm.info) + else + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + elseif length(argtypes) > 7 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else return CallMeta(Bool, generic_setglobal!_exct, setglobal!_effects, NoCallInfo()) @@ -2563,44 +2584,48 @@ function abstract_eval_setglobalonce!(interp::AbstractInterpreter, sv::AbsIntSta end 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] - T = nothing - if isa(M, Const) && isa(s, Const) - M, s = M.val, s.val - M isa Module || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) - s isa Symbol || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) - gr = GlobalRef(M, s) - v′ = RefValue{Any}(v) - (valid_worlds, (rte, T)) = scan_leaf_partitions(interp, gr, sv.world) do interp::AbstractInterpreter, binding::Core.Binding, partition::Core.BindingPartition - partition_T = nothing - partition_rte = abstract_eval_partition_load(interp, binding, partition) - if binding_kind(partition) == PARTITION_KIND_GLOBAL - partition_T = partition_restriction(partition) + if !isvarargtype(argtypes[end]) + if length(argtypes) in (5, 6, 7) + (M, s, x, v) = argtypes[2], argtypes[3], argtypes[4], argtypes[5] + T = nothing + if isa(M, Const) && isa(s, Const) + M, s = M.val, s.val + M isa Module || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + s isa Symbol || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + gr = GlobalRef(M, s) + v′ = RefValue{Any}(v) + (valid_worlds, (rte, T)) = scan_leaf_partitions(interp, gr, sv.world) do interp::AbstractInterpreter, binding::Core.Binding, partition::Core.BindingPartition + partition_T = nothing + partition_rte = abstract_eval_partition_load(interp, binding, partition) + if binding_kind(partition) == PARTITION_KIND_GLOBAL + partition_T = partition_restriction(partition) + end + partition_exct = Union{partition_rte.exct, global_assignment_binding_rt_exct(interp, partition, v′[])[2]} + partition_rte = RTEffects(partition_rte.rt, partition_exct, partition_rte.effects) + Pair{RTEffects, Any}(partition_rte, partition_T) end - partition_exct = Union{partition_rte.exct, global_assignment_binding_rt_exct(interp, partition, v′[])[2]} - partition_rte = RTEffects(partition_rte.rt, partition_exct, partition_rte.effects) - Pair{RTEffects, Any}(partition_rte, partition_T) + update_valid_age!(sv, valid_worlds) + effects = merge_effects(rte.effects, Effects(setglobal!_effects, nothrow=rte.exct===Bottom)) + sg = CallMeta(Any, rte.exct, effects, GlobalAccessInfo(convert(Core.Binding, gr))) + else + 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) + sg = merge_exct(sg, goe) end - update_valid_age!(sv, valid_worlds) - effects = merge_effects(rte.effects, Effects(setglobal!_effects, nothrow=rte.exct===Bottom)) - sg = CallMeta(Any, rte.exct, effects, GlobalAccessInfo(convert(Core.Binding, gr))) + if length(argtypes) == 7 + goe = global_order_exct(argtypes[7], #=loading=#true, #=storing=#false) + sg = merge_exct(sg, goe) + end + rt = T === nothing ? + ccall(:jl_apply_cmpswap_type, Any, (Any,), S) where S : + ccall(:jl_apply_cmpswap_type, Any, (Any,), T) + return CallMeta(rt, sg.exct, sg.effects, sg.info) else - 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) - sg = merge_exct(sg, goe) - end - if length(argtypes) == 7 - goe = global_order_exct(argtypes[7], #=loading=#true, #=storing=#false) - sg = merge_exct(sg, goe) + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) end - rt = T === nothing ? - ccall(:jl_apply_cmpswap_type, Any, (Any,), S) where S : - ccall(:jl_apply_cmpswap_type, Any, (Any,), T) - return CallMeta(rt, sg.exct, sg.effects, sg.info) - elseif !isvarargtype(argtypes[end]) || length(argtypes) > 8 + elseif length(argtypes) > 8 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) else return CallMeta(Any, Union{generic_getglobal_exct,generic_setglobal!_exct}, setglobal!_effects, NoCallInfo()) @@ -2655,11 +2680,8 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3], Const(true), length(argtypes) == 4 ? argtypes[4] : Const(:unordered), si.saw_latestworld, sv)) - elseif f === Core.isdefinedglobal && 3 <= length(argtypes) <= 5 - return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3], - length(argtypes) >= 4 ? argtypes[4] : Const(true), - length(argtypes) >= 5 ? argtypes[5] : Const(:unordered), - si.saw_latestworld, sv)) + elseif f === Core.isdefinedglobal + return Future(abstract_eval_isdefinedglobal(interp, sv, si.saw_latestworld, argtypes)) elseif f === Core.get_binding_type return Future(abstract_eval_get_binding_type(interp, sv, argtypes)) end @@ -3299,6 +3321,23 @@ function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, @nospecializ return CallMeta(Bool, Union{exct, TypeError, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo()) end +function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any}) + if !isvarargtype(argtypes[end]) + if 3 <= length(argtypes) <= 5 + return abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3], + length(argtypes) >= 4 ? argtypes[4] : Const(true), + length(argtypes) >= 5 ? argtypes[5] : Const(:unordered), + saw_latestworld, sv) + else + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + elseif length(argtypes) > 6 + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + else + return CallMeta(Bool, Union{ConcurrencyViolationError, TypeError, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo()) + end +end + 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) @@ -3452,6 +3491,20 @@ function merge_override_effects!(interp::AbstractInterpreter, effects::Effects, # It is possible for arguments (GlobalRef/:static_parameter) to throw, # but these will be recomputed during SSA construction later. override = decode_statement_effects_override(sv) + if override.consistent + m = sv.linfo.def + if isa(m, Method) + # N.B.: We'd like deleted_world here, but we can't add an appropriate edge at this point. + # However, in order to reach here in the first place, ordinary method lookup would have + # had to add an edge and appropriate invalidation trigger. + valid_worlds = WorldRange(m.primary_world, typemax(Int)) + if sv.world.this in valid_worlds + update_valid_age!(sv, valid_worlds) + else + override = EffectsOverride(override, consistent=false) + end + end + end effects = override_effects(effects, override) set_curr_ssaflag!(sv, flags_for_effects(effects), IR_FLAGS_EFFECTS) merge_effects!(interp, sv, effects) diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index 2db1a6983e51d..e9fe671b2f781 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -449,6 +449,10 @@ end return Const(true) end end + # datatype_fieldcount is what `fieldcount` uses internally + # and returns nothing (!==0) for non-definite field counts. + elseif datatype_fieldcount(a1) === 0 + return Const(false) end elseif isa(a1, Union) # Results can only be `Const` or `Bool` diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index 59e0fbd8262e5..8acec133d7912 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -356,11 +356,17 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState, cycleid::Int) return typ end -function adjust_effects(ipo_effects::Effects, def::Method) +function adjust_effects(ipo_effects::Effects, def::Method, world::UInt) # override the analyzed effects using manually annotated effect settings override = decode_effects_override(def.purity) + valid_worlds = WorldRange(0, typemax(UInt)) if is_effect_overridden(override, :consistent) - ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) + # See note on `typemax(Int)` instead of `deleted_world` in adjust_effects! + override_valid_worlds = WorldRange(def.primary_world, typemax(Int)) + if world in override_valid_worlds + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) + valid_worlds = override_valid_worlds + end end if is_effect_overridden(override, :effect_free) ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE) @@ -388,7 +394,7 @@ function adjust_effects(ipo_effects::Effects, def::Method) if is_effect_overridden(override, :nortcall) ipo_effects = Effects(ipo_effects; nortcall=true) end - return ipo_effects + return (ipo_effects, valid_worlds) end function adjust_effects(sv::InferenceState) @@ -442,7 +448,8 @@ function adjust_effects(sv::InferenceState) # override the analyzed effects using manually annotated effect settings def = sv.linfo.def if isa(def, Method) - ipo_effects = adjust_effects(ipo_effects, def) + (ipo_effects, valid_worlds) = adjust_effects(ipo_effects, def, sv.world.this) + update_valid_age!(sv, valid_worlds) end return ipo_effects @@ -480,9 +487,9 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter, cycleid:: end end result = me.result - result.valid_worlds = me.world.valid_worlds result.result = bestguess ipo_effects = result.ipo_effects = me.ipo_effects = adjust_effects(me) + result.valid_worlds = me.world.valid_worlds result.exc_result = me.exc_bestguess = refine_exception_type(me.exc_bestguess, ipo_effects) me.src.rettype = widenconst(ignorelimited(bestguess)) me.src.ssaflags = me.ssaflags @@ -491,18 +498,17 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter, cycleid:: istoplevel = !(me.linfo.def isa Method) istoplevel || compute_edges!(me) # don't add backedges to toplevel method instance - if limited_ret - # a parent may be cached still, but not this intermediate work: - # we can throw everything else away now + if limited_ret || limited_src + # A parent may be cached still, but not this intermediate work: + # we can throw everything else away now. Caching anything can confuse later + # heuristics to consider it worth trying to pursue compiling this further and + # finding infinite work as a result. Avoiding caching helps to ensure there is only + # a finite amount of work that can be discovered later (although potentially still a + # large multiplier on it). result.src = nothing result.tombstone = true me.cache_mode = CACHE_MODE_NULL set_inlineable!(me.src, false) - elseif limited_src - # a type result will be cached still, but not this intermediate work: - # we can throw everything else away now - result.src = nothing - set_inlineable!(me.src, false) else # annotate fulltree with type information, # either because we are the outermost code, or we might use this later @@ -943,8 +949,13 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize update_valid_age!(caller, frame.world.valid_worlds) local isinferred = is_inferred(frame) local edge = isinferred ? edge_ci : nothing - local effects = isinferred ? frame.result.ipo_effects : # effects are adjusted already within `finish` for ipo_effects - adjust_effects(effects_for_cycle(frame.ipo_effects), method) + local effects, valid_worlds + if isinferred + effects = frame.result.ipo_effects # effects are adjusted already within `finish` for ipo_effects + else + (effects, valid_worlds) = adjust_effects(effects_for_cycle(frame.ipo_effects), method, frame.world.this) + update_valid_age!(caller, valid_worlds) + end local bestguess = frame.bestguess local exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) # propagate newly inferred source to the inliner, allowing efficient inlining w/o deserialization: @@ -967,7 +978,8 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(caller, frame.world.valid_worlds) - effects = adjust_effects(effects_for_cycle(frame.ipo_effects), method) + (effects, valid_worlds) = adjust_effects(effects_for_cycle(frame.ipo_effects), method, frame.world.this) + update_valid_age!(caller, valid_worlds) bestguess = frame.bestguess exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) return Future(MethodCallResult(interp, caller, method, bestguess, exc_bestguess, effects, nothing, edgecycle, edgelimited)) @@ -1243,15 +1255,92 @@ function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance) return ci.rettype end +# Resolve a call, as described by `argtype` to a single matching +# Method and return a compilable MethodInstance for the call, if +# it will be runtime-dispatched to exactly that MethodInstance +function compileable_specialization_for_call(interp::AbstractInterpreter, @nospecialize(argtype)) + mt = ccall(:jl_method_table_for, Any, (Any,), argtype) + if mt === nothing + # this would require scanning all method tables, so give up instead + return nothing + end + + matches = findall(argtype, method_table(interp); limit = 1) + matches === nothing && return nothing + length(matches.matches) == 0 && return nothing + match = only(matches.matches) + + compileable_atype = get_compileable_sig(match.method, match.spec_types, match.sparams) + compileable_atype === nothing && return nothing + if match.spec_types !== compileable_atype + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), compileable_atype, match.method.sig)::SimpleVector + sparams = sp_[2]::SimpleVector + mi = specialize_method(match.method, compileable_atype, sparams) + else + mi = specialize_method(match.method, compileable_atype, match.sparams) + end + + return mi +end + +const QueueItems = Union{CodeInstance,MethodInstance,SimpleVector} + +struct CompilationQueue + tocompile::Vector{QueueItems} + inspected::IdSet{QueueItems} + interp::Union{AbstractInterpreter,Nothing} + + CompilationQueue(; + interp::Union{AbstractInterpreter,Nothing} + ) = new(QueueItems[], IdSet{QueueItems}(), interp) + + CompilationQueue(queue::CompilationQueue; + interp::Union{AbstractInterpreter,Nothing} + ) = new(empty!(queue.tocompile), empty!(queue.inspected), interp) +end + +Base.push!(queue::CompilationQueue, item) = push!(queue.tocompile, item) +Base.append!(queue::CompilationQueue, items) = append!(queue.tocompile, items) +Base.pop!(queue::CompilationQueue) = pop!(queue.tocompile) +Base.empty!(queue::CompilationQueue) = (empty!(queue.tocompile); empty!(queue.inspected)) +markinspected!(queue::CompilationQueue, item) = push!(queue.inspected, item) +isinspected(queue::CompilationQueue, item) = item in queue.inspected +Base.isempty(queue::CompilationQueue) = isempty(queue.tocompile) + # collect a list of all code that is needed along with CodeInstance to codegen it fully -function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo) +function collectinvokes!(workqueue::CompilationQueue, ci::CodeInfo, sptypes::Vector{VarState}; + invokelatest_queue::Union{CompilationQueue,Nothing} = nothing) src = ci.code for i = 1:length(src) stmt = src[i] isexpr(stmt, :(=)) && (stmt = stmt.args[2]) if isexpr(stmt, :invoke) || isexpr(stmt, :invoke_modify) edge = stmt.args[1] - edge isa CodeInstance && isdefined(edge, :inferred) && push!(wq, edge) + edge isa CodeInstance && isdefined(edge, :inferred) && push!(workqueue, edge) + end + + invokelatest_queue === nothing && continue + if isexpr(stmt, :call) + farg = stmt.args[1] + !applicable(argextype, farg, ci, sptypes) && continue # TODO: Why is this failing during bootstrap + ftyp = widenconst(argextype(farg, ci, sptypes)) + + if ftyp === typeof(Core.finalizer) && length(stmt.args) == 3 + finalizer = argextype(stmt.args[2], ci, sptypes) + obj = argextype(stmt.args[3], ci, sptypes) + atype = argtypes_to_type(Any[finalizer, obj]) + else + # No dynamic dispatch to resolve / enqueue + continue + end + + let workqueue = invokelatest_queue + # make a best-effort attempt to enqueue the relevant code for the finalizer + mi = compileable_specialization_for_call(workqueue.interp, atype) + mi === nothing && continue + + push!(workqueue, mi) + end end # TODO: handle other StmtInfo like @cfunction and OpaqueClosure? end @@ -1262,14 +1351,13 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn ci isa CodeInstance && !ci_has_invoke(ci) || return ci codegen = codegen_cache(interp) codegen === nothing && return ci - inspected = IdSet{CodeInstance}() - tocompile = Vector{CodeInstance}() - push!(tocompile, ci) - while !isempty(tocompile) + workqueue = CompilationQueue(; interp) + push!(workqueue, ci) + while !isempty(workqueue) # ci_has_real_invoke(ci) && return ci # optimization: cease looping if ci happens to get compiled (not just jl_fptr_wait_for_compiled, but fully jl_is_compiled_codeinst) - callee = pop!(tocompile) + callee = pop!(workqueue) ci_has_invoke(callee) && continue - callee in inspected && continue + isinspected(workqueue, callee) && continue src = get(codegen, callee, nothing) if !isa(src, CodeInfo) src = @atomic :monotonic callee.inferred @@ -1277,25 +1365,26 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn src = _uncompressed_ir(callee, src) end if !isa(src, CodeInfo) - newcallee = typeinf_ext(interp, callee.def, source_mode) # always SOURCE_MODE_ABI + newcallee = typeinf_ext(workqueue.interp, callee.def, source_mode) # always SOURCE_MODE_ABI if newcallee isa CodeInstance callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee - push!(tocompile, newcallee) + push!(workqueue, newcallee) end if newcallee !== callee - push!(inspected, callee) + markinspected!(workqueue, callee) end continue end end - push!(inspected, callee) - collectinvokes!(tocompile, src) + markinspected!(workqueue, callee) mi = get_ci_mi(callee) + sptypes = sptypes_from_meth_instance(mi) + collectinvokes!(workqueue, src, sptypes) if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee)) - cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance + 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_codeinst_to_jit - code_cache(interp)[mi] = callee + 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) @@ -1319,58 +1408,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt return typeinf_ext_toplevel(interp, mi, source_mode) end -# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches -# The trim_mode can be any of: -const TRIM_NO = 0 -const TRIM_SAFE = 1 -const TRIM_UNSAFE = 2 -const TRIM_UNSAFE_WARN = 3 -function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int) - inspected = IdSet{CodeInstance}() - tocompile = Vector{CodeInstance}() - codeinfos = [] - # first compute the ABIs of everything - latest = true # whether this_world == world_counter() - for this_world in reverse(sort!(worlds)) - interp = NativeInterpreter( - this_world; - inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO) - ) - for i = 1:length(methods) - # each item in this list is either a MethodInstance indicating something - # to compile, or an svec(rettype, sig) describing a C-callable alias to create. - item = methods[i] - if item isa MethodInstance - # if this method is generally visible to the current compilation world, - # and this is either the primary world, or not applicable in the primary world - # then we want to compile and emit this - if item.def.primary_world <= this_world <= item.def.deleted_world - ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE) - ci isa CodeInstance && push!(tocompile, ci) - end - elseif item isa SimpleVector && latest - (rt::Type, sig::Type) = item - # make a best-effort attempt to enqueue the relevant code for the ccallable - ptr = ccall(:jl_get_specialization1, - #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint), - sig, this_world, #= mt_cache =# 0) - if ptr !== C_NULL - mi = unsafe_pointer_to_objref(ptr)::MethodInstance - ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) - ci isa CodeInstance && push!(tocompile, ci) - end - # additionally enqueue the ccallable entrypoint / adapter, which implicitly - # invokes the above ci - push!(codeinfos, rt) - push!(codeinfos, sig) +function compile!(codeinfos::Vector{Any}, workqueue::CompilationQueue; + invokelatest_queue::Union{CompilationQueue,Nothing} = nothing, +) + interp = workqueue.interp + world = get_inference_world(interp) + while !isempty(workqueue) + item = pop!(workqueue) + # each item in this list is either a MethodInstance indicating something + # to compile, or an svec(rettype, sig) describing a C-callable alias to create. + if item isa MethodInstance + isinspected(workqueue, item) && continue + # if this method is generally visible to the current compilation world, + # and this is either the primary world, or not applicable in the primary world + # then we want to compile and emit this + if item.def.primary_world <= world <= item.def.deleted_world + ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE) + ci isa CodeInstance && push!(workqueue, ci) end - end - while !isempty(tocompile) - callee = pop!(tocompile) - callee in inspected && continue - # now make sure everything has source code, if desired + markinspected!(workqueue, item) + elseif item isa SimpleVector + invokelatest_queue === nothing && continue + (rt::Type, sig::Type) = item + # make a best-effort attempt to enqueue the relevant code for the ccallable + ptr = ccall(:jl_get_specialization1, + #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint), + sig, world, #= mt_cache =# 0) + if ptr !== C_NULL + mi = unsafe_pointer_to_objref(ptr)::MethodInstance + ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) + ci isa CodeInstance && push!(invokelatest_queue, ci) + end + # additionally enqueue the ccallable entrypoint / adapter, which implicitly + # invokes the above ci + push!(codeinfos, item) + elseif item isa CodeInstance + callee = item + isinspected(workqueue, callee) && continue mi = get_ci_mi(callee) - def = mi.def + # now make sure everything has source code, if desired if use_const_api(callee) src = codeinfo_for_const(interp, mi, callee.rettype_const) else @@ -1379,20 +1455,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m newcallee = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) if newcallee isa CodeInstance @assert use_const_api(newcallee) || haskey(interp.codegen, newcallee) - push!(tocompile, newcallee) + push!(workqueue, newcallee) end if newcallee !== callee - push!(inspected, callee) + markinspected!(workqueue, callee) end continue end end - push!(inspected, callee) + markinspected!(workqueue, callee) if src isa CodeInfo - collectinvokes!(tocompile, src) + sptypes = sptypes_from_meth_instance(mi) + collectinvokes!(workqueue, src, sptypes; invokelatest_queue) # try to reuse an existing CodeInstance from before to avoid making duplicates in the cache if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee)) - cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance + cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, world)::CodeInstance if cached === callee code_cache(interp)[mi] = callee else @@ -1403,9 +1480,43 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m push!(codeinfos, callee) push!(codeinfos, src) end - end - latest = false + else @assert false "unexpected item in queue" end end + return codeinfos +end + +# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches +# The trim_mode can be any of: +const TRIM_NO = 0 +const TRIM_SAFE = 1 +const TRIM_UNSAFE = 2 +const TRIM_UNSAFE_WARN = 3 +function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int) + inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO) + + # Create an "invokelatest" queue to enable eager compilation of speculative + # invokelatest calls such as from `Core.finalizer` and `ccallable` + invokelatest_queue = CompilationQueue(; + interp = NativeInterpreter(get_world_counter(); inf_params) + ) + + codeinfos = [] + workqueue = CompilationQueue(; interp = nothing) + for this_world in reverse!(sort!(worlds)) + workqueue = CompilationQueue(workqueue; + interp = NativeInterpreter(this_world; inf_params) + ) + + append!(workqueue, methods) + compile!(codeinfos, workqueue; invokelatest_queue) + end + + if invokelatest_queue !== nothing + # This queue is intentionally aliased, to handle e.g. a `finalizer` calling `Core.finalizer` + # (it will enqueue into itself and immediately drain) + compile!(codeinfos, invokelatest_queue; invokelatest_queue) + end + if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE verify_typeinf_trim(codeinfos, trim_mode == TRIM_UNSAFE_WARN) end diff --git a/Compiler/src/validation.jl b/Compiler/src/validation.jl index 4f9362e97b30d..d5faf51a89356 100644 --- a/Compiler/src/validation.jl +++ b/Compiler/src/validation.jl @@ -225,7 +225,7 @@ function validate_code!(errors::Vector{InvalidCodeError}, mi::Core.MethodInstanc mnargs = 0 else m = mi.def::Method - mnargs = m.nargs + mnargs = Int(m.nargs) n_sig_params = length((unwrap_unionall(m.sig)::DataType).parameters) if m.is_for_opaque_closure m.sig === Tuple || push!(errors, InvalidCodeError(INVALID_SIGNATURE_OPAQUE_CLOSURE, (m.sig, m.isva))) @@ -234,6 +234,7 @@ function validate_code!(errors::Vector{InvalidCodeError}, mi::Core.MethodInstanc end end if isa(c, CodeInfo) + mnargs = Int(c.nargs) mnargs > length(c.slotnames) && push!(errors, InvalidCodeError(SLOTNAMES_NARGS_MISMATCH)) validate_code!(errors, c, is_top_level) end diff --git a/Compiler/src/verifytrim.jl b/Compiler/src/verifytrim.jl index 2365d885efd79..09a189b2ff223 100644 --- a/Compiler/src/verifytrim.jl +++ b/Compiler/src/verifytrim.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -import ..Compiler: verify_typeinf_trim +import ..Compiler: verify_typeinf_trim, NativeInterpreter, argtypes_to_type, compileable_specialization_for_call using ..Compiler: # operators @@ -15,9 +15,9 @@ using ..Compiler: hasintersect, haskey, in, isdispatchelem, isempty, isexpr, iterate, length, map!, max, pop!, popfirst!, push!, pushfirst!, reinterpret, reverse!, reverse, setindex!, setproperty!, similar, singleton_type, sptypes_from_meth_instance, - unsafe_pointer_to_objref, widenconst, + unsafe_pointer_to_objref, widenconst, isconcretetype, # misc - @nospecialize, C_NULL + @nospecialize, @assert, C_NULL using ..IRShow: LineInfoNode, print, show, println, append_scopes!, IOContext, IO, normalize_method_name using ..Base: Base, sourceinfo_slotnames using ..Base.StackTraces: StackFrame @@ -166,7 +166,7 @@ function may_dispatch(@nospecialize ftyp) end end -function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspected::IdSet{CodeInstance}, caches::IdDict{MethodInstance,CodeInstance}, parents::ParentMap, errors::ErrorList) +function verify_codeinstance!(interp::NativeInterpreter, codeinst::CodeInstance, codeinfo::CodeInfo, inspected::IdSet{CodeInstance}, caches::IdDict{MethodInstance,CodeInstance}, parents::ParentMap, errors::ErrorList) mi = get_ci_mi(codeinst) sptypes = sptypes_from_meth_instance(mi) src = codeinfo.code @@ -199,7 +199,9 @@ function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspec if !may_dispatch(ftyp) continue end - if Core._apply_iterate isa ftyp + if !isconcretetype(ftyp) + error = "unresolved call to (unknown) builtin" + elseif Core._apply_iterate isa ftyp if length(stmt.args) >= 3 # args[1] is _apply_iterate object # args[2] is invoke object @@ -219,13 +221,35 @@ function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspec end elseif Core.finalizer isa ftyp if length(stmt.args) == 3 - # TODO: check that calling `args[1](args[2])` is defined before warning + finalizer = argextype(stmt.args[2], codeinfo, sptypes) + obj = argextype(stmt.args[3], codeinfo, sptypes) + atype = argtypes_to_type(Any[finalizer, obj]) + + mi = compileable_specialization_for_call(interp, atype) + if mi !== nothing + ci = get(caches, mi, nothing) + ci isa CodeInstance && continue + end + error = "unresolved finalizer registered" - warn = true end - else - error = "unresolved call to builtin" - end + elseif Core._apply isa ftyp + error = "trim verification not yet implemented for builtin `Core._apply`" + elseif Core._call_in_world_total isa ftyp + error = "trim verification not yet implemented for builtin `Core._call_in_world_total`" + elseif Core.invoke isa ftyp + error = "trim verification not yet implemented for builtin `Core.invoke`" + elseif Core.invoke_in_world isa ftyp + error = "trim verification not yet implemented for builtin `Core.invoke_in_world`" + elseif Core.invokelatest isa ftyp + error = "trim verification not yet implemented for builtin `Core.invokelatest`" + elseif Core.modifyfield! isa ftyp + error = "trim verification not yet implemented for builtin `Core.modifyfield!`" + elseif Core.modifyglobal! isa ftyp + error = "trim verification not yet implemented for builtin `Core.modifyglobal!`" + elseif Core.memoryrefmodify! isa ftyp + error = "trim verification not yet implemented for builtin `Core.memoryrefmodify!`" + else @assert false "unexpected builtin" end end extyp = argextype(SSAValue(i), codeinfo, sptypes) if extyp === Union{} @@ -257,11 +281,12 @@ end function get_verify_typeinf_trim(codeinfos::Vector{Any}) this_world = get_world_counter() + interp = NativeInterpreter(this_world) inspected = IdSet{CodeInstance}() caches = IdDict{MethodInstance,CodeInstance}() errors = ErrorList() parents = ParentMap() - for i = 1:2:length(codeinfos) + for i = 1:length(codeinfos) item = codeinfos[i] if item isa CodeInstance push!(inspected, item) @@ -273,14 +298,14 @@ function get_verify_typeinf_trim(codeinfos::Vector{Any}) end end end - for i = 1:2:length(codeinfos) + for i = 1:length(codeinfos) item = codeinfos[i] if item isa CodeInstance src = codeinfos[i + 1]::CodeInfo - verify_codeinstance!(item, src, inspected, caches, parents, errors) - else - rt = item::Type - sig = codeinfos[i + 1]::Type + verify_codeinstance!(interp, item, src, inspected, caches, parents, errors) + elseif item isa SimpleVector + rt = item[1]::Type + sig = item[2]::Type ptr = ccall(:jl_get_specialization1, #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint), sig, this_world, #= mt_cache =# 0) diff --git a/Compiler/test/contextual.jl b/Compiler/test/contextual.jl index a9c63ab34c0c0..941ce172d41e2 100644 --- a/Compiler/test/contextual.jl +++ b/Compiler/test/contextual.jl @@ -1,19 +1,23 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +module contextual + # N.B.: This file is also run from interpreter.jl, so needs to be standalone-executable using Test -include("setup_Compiler.jl") # Cassette # ======== +# TODO Use CassetteBase.jl instead of this mini-cassette? + module MiniCassette # A minimal demonstration of the cassette mechanism. Doesn't support all the # fancy features, but sufficient to exercise this code path in the compiler. + using Core: SimpleVector using Core.IR - using ..Compiler - using ..Compiler: retrieve_code_info, quoted, anymap + using Base: Compiler as CC + using .CC: retrieve_code_info, quoted, anymap using Base.Meta: isexpr export Ctx, overdub @@ -21,7 +25,7 @@ module MiniCassette struct Ctx; end # A no-op cassette-like transform - function transform_expr(expr, map_slot_number, map_ssa_value, sparams::Core.SimpleVector) + function transform_expr(expr, map_slot_number, map_ssa_value, sparams::SimpleVector) @nospecialize expr transform(@nospecialize expr) = transform_expr(expr, map_slot_number, map_ssa_value, sparams) if isexpr(expr, :call) @@ -45,11 +49,11 @@ module MiniCassette end end - function transform!(mi::MethodInstance, ci::CodeInfo, nargs::Int, sparams::Core.SimpleVector) + function transform!(mi::MethodInstance, ci::CodeInfo, nargs::Int, sparams::SimpleVector) code = ci.code - di = Compiler.DebugInfoStream(mi, ci.debuginfo, length(code)) - ci.slotnames = Symbol[Symbol("#self#"), :ctx, :f, :args, ci.slotnames[nargs+1:end]...] - ci.slotflags = UInt8[(0x00 for i = 1:4)..., ci.slotflags[nargs+1:end]...] + di = CC.DebugInfoStream(mi, ci.debuginfo, length(code)) + ci.slotnames = Symbol[Symbol("#self#"), :ctx, :f, :args, ci.slotnames[nargs+2:end]...] + ci.slotflags = UInt8[(0x00 for i = 1:4)..., ci.slotflags[nargs+2:end]...] # Insert one SSAValue for every argument statement prepend!(code, Any[Expr(:call, getfield, SlotNumber(4), i) for i = 1:nargs]) prepend!(di.codelocs, fill(Int32(0), 3nargs)) @@ -76,21 +80,26 @@ module MiniCassette function overdub_generator(world::UInt, source, self, ctx, f, args) @nospecialize + argnames = Core.svec(:overdub, :ctx, :f, :args) + spnames = Core.svec() + if !Base.issingletontype(f) # (c, f, args..) -> f(args...) - ex = :(return f(args...)) - return Core.GeneratedFunctionStub(identity, Core.svec(:overdub, :ctx, :f, :args), Core.svec())(world, source, ex) + return generate_lambda_ex(world, source, argnames, spnames, :(return f(args...))) end tt = Tuple{f, args...} match = Base._which(tt; world) mi = Base.specialize_method(match) # Unsupported in this mini-cassette - @assert !mi.def.isva + !mi.def.isva || + return generate_lambda_ex(world, source, argnames, spnames, :(error("Unsupported vararg method"))) src = retrieve_code_info(mi, world) - @assert isa(src, CodeInfo) + isa(src, CodeInfo) || + return generate_lambda_ex(world, source, argnames, spnames, :(error("Unexpected code transformation"))) src = copy(src) - @assert src.edges === Core.svec() + src.edges === Core.svec() || + return generate_lambda_ex(world, source, argnames, spnames, :(error("Unexpected code transformation"))) src.edges = Any[mi] transform!(mi, src, length(args), match.sparams) # TODO: this is mandatory: code_info.min_world = max(code_info.min_world, min_world[]) @@ -98,9 +107,21 @@ module MiniCassette # Match the generator, since that's what our transform! does src.nargs = 4 src.isva = true + errors = CC.validate_code(mi, src) + if !isempty(errors) + foreach(Core.println, errors) + return generate_lambda_ex(world, source, argnames, spnames, :(error("Found errors in generated code"))) + end return src end + function generate_lambda_ex(world::UInt, source::Method, + argnames::SimpleVector, spnames::SimpleVector, + body::Expr) + stub = Core.GeneratedFunctionStub(identity, argnames, spnames) + return stub(world, source, body) + end + @inline overdub(::Ctx, f::Union{Core.Builtin, Core.IntrinsicFunction}, args...) = f(args...) @eval function overdub(ctx::Ctx, f, args...) @@ -124,3 +145,8 @@ f() = 2 foo(i) = i+bar(Val(1)) @test @inferred(overdub(Ctx(), foo, 1)) == 43 + +morethan4args(a, b, c, d, e) = (((a + b) + c) + d) + e +@test overdub(Ctx(), morethan4args, 1, 2, 3, 4, 5) == 15 + +end # module contextual diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index 57b2b50053579..e8a10d537c1ad 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -1207,6 +1207,7 @@ let isdefined_tfunc(@nospecialize xs...) = @test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:x)) === Const(true) @test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:y)) === Const(false) @test isdefined_tfunc(Union{UnionIsdefinedA,Nothing}, Const(:x)) === Bool + @test isdefined_tfunc(Nothing, Any) === Const(false) end # https://github.com/aviatesk/JET.jl/issues/379 @@ -6213,3 +6214,58 @@ global invalid_setglobal!_exct_modeling::Int @test Base.infer_exception_type((Float64,)) do x setglobal!(@__MODULE__, :invalid_setglobal!_exct_modeling, x) end == ErrorException + +# Issue #58257 - Hang in inference during BindingPartition resolution +module A58257 + module B58257 + using ..A58257 + # World age here is N + end + using .B58257 + # World age here is N+1 + @eval f() = $(GlobalRef(B58257, :get!)) +end + +## The sequence of events is critical here. +A58257.get! # Creates binding partition in A, N+1:∞ +A58257.B58257.get! # Creates binding partition in A.B, N+1:∞ +Base.invoke_in_world(UInt(38678), getglobal, A58257, :get!) # Expands binding partition in A through 4 +let infos = typeinf_ext_toplevel(Any[Core.svec(Ptr{Cvoid}, Tuple{typeof(make_cfunction)})], [Base.get_world_counter()], TRIM_UNSAFE) errors, parents = get_verify_typeinf_trim(infos) @test_broken isempty(errors) # missing cfunction - #resize!(infos, 4) - #errors, = get_verify_typeinf_trim(infos) desc = only(errors) @test desc.first desc = desc.second @test desc isa CallMissing - @test occursin("finalizer", desc.desc) + @test occursin("cfunction", desc.desc) repr = sprint(verify_print_error, desc, parents) @test occursin( - r"""^unresolved finalizer registered from statement \(Core.finalizer\)\(Base.final_shred!, %new\(\)::Base.SecretBuffer\)::Nothing + r"""^unresolved cfunction from statement \$\(Expr\(:cfunction, Ptr{Nothing}, :\(\$\(QuoteNode\(\+\)\)\), Float64, :\(svec\(Int64, Int64\)::Core.SimpleVector\), :\(:ccall\)\)\)::Ptr{Nothing} Stacktrace: - \[1\] finalizer\(f::typeof\(Base.final_shred!\), o::Base.SecretBuffer\) - @ Base gcutils.jl:(\d+) \[inlined\] - \[2\] Base.SecretBuffer\(; sizehint::Int\d\d\) - @ Base secretbuffer.jl:(\d+) \[inlined\] - \[3\] Base.SecretBuffer\(\) - @ Base secretbuffer.jl:(\d+) + \[1\] make_cfunction\(\)""", repr) - $""", repr) + resize!(infos, 1) + @test infos[1] isa Core.SimpleVector && infos[1][1] isa Type && infos[1][2] isa Type + errors, parents = get_verify_typeinf_trim(infos) + desc = only(errors) + @test !desc.first + desc = desc.second + @test desc isa CCallableMissing + @test desc.rt == Ptr{Cvoid} + @test desc.sig == Tuple{typeof(make_cfunction)} + @test occursin("unresolved ccallable", desc.desc) + repr = sprint(verify_print_error, desc, parents) + @test repr == "unresolved ccallable for Tuple{$(typeof(make_cfunction))} => Ptr{Nothing}\n\n" +end + +let infos = typeinf_ext_toplevel(Any[Core.svec(Base.SecretBuffer, Tuple{Type{Base.SecretBuffer}})], [Base.get_world_counter()], TRIM_UNSAFE) + @test length(infos) > 4 + errors, parents = get_verify_typeinf_trim(infos) + @test isempty(errors) - resize!(infos, 2) - @test infos[1] isa Type && infos[2] isa Type + resize!(infos, 1) + @test infos[1] isa Core.SimpleVector && infos[1][1] isa Type && infos[1][2] isa Type errors, parents = get_verify_typeinf_trim(infos) desc = only(errors) @test !desc.first diff --git a/HISTORY.md b/HISTORY.md index c3ca212453d07..85fafc548f6a4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,20 +11,6 @@ New language features * The new macro `Base.Cartesian.@ncallkw` is analogous to `Base.Cartesian.@ncall`, but allows to add keyword arguments to the function call ([#51501]). * Support for Unicode 15.1 ([#51799]). -* Three new types around the idea of text with "annotations" (`Pair{Symbol, Any}` - entries, e.g. `:lang => "en"` or `:face => :magenta`). These annotations - are preserved across operations (e.g. string concatenation with `*`) when - possible. - * `AnnotatedString` is a new `AbstractString` type. It wraps an underlying - string and allows for annotations to be attached to regions of the string. - This type is used extensively in the new `StyledStrings` standard library to - hold styling information. - * `AnnotatedChar` is a new `AbstractChar` type. It wraps another char and - holds a list of annotations that apply to it. - * `AnnotatedIOBuffer` is a new `IO` type that mimics an `IOBuffer`, but has - specialised `read`/`write` methods for annotated content. This can be - thought of both as a "string builder" of sorts and also as glue between - annotated and unannotated content. * `Manifest.toml` files can now be renamed in the format `Manifest-v{major}.{minor}.toml` to be preferentially picked up by the given julia version. i.e. in the same folder, a `Manifest-v1.11.toml` would be used by v1.11 and `Manifest.toml` by every other julia @@ -126,7 +112,20 @@ Standard library changes #### StyledStrings -* A new standard library for handling styling in a more comprehensive and structured way ([#49586]). +* A new experimental standard library for handling styling in a more comprehensive and structured way ([#49586]). +* Three new types around the idea of text with "annotations" (`Pair{Symbol, Any}` + entries, e.g. `:lang => "en"` or `:face => :magenta`). These annotations + are preserved across operations (e.g. string concatenation with `*`) when + possible. + * `AnnotatedString` is a new `AbstractString` type. It wraps an underlying + string and allows for annotations to be attached to regions of the string. + This type is used extensively to hold styling information. + * `AnnotatedChar` is a new `AbstractChar` type. It wraps another char and + holds a list of annotations that apply to it. + * `AnnotatedIOBuffer` is a new `IO` type that mimics an `IOBuffer`, but has + specialised `read`/`write` methods for annotated content. This can be + thought of both as a "string builder" of sorts and also as glue between + annotated and unannotated content. * The new `Faces` struct serves as a container for text styling information (think typeface, as well as color and decoration), and comes with a framework to provide a convenient, extensible (via `addface!`), and customisable (with a diff --git a/NEWS.md b/NEWS.md index 655c670c1c947..6a2c3b8f28a21 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ New language features * New option `--trim` creates smaller binaries by removing code that was not proven to be reachable from entry points. Entry points can be marked using `Base.Experimental.entrypoint` ([#55047]). +* Redefinition of constants is now well defined and follows world age semantics. Additional redefinitions (e.g. of structs) are now allowed. See [the new manual chapter on world age](https://docs.julialang.org/en/v1.13-dev/manual/worldage/). * A new keyword argument `usings::Bool` has been added to `names`, returning all names visible via `using` ([#54609]). * The `@atomic` macro family now supports reference assignment syntax, e.g. `@atomic :monotonic v[3] += 4`, diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index 81660d17322e5..702a00c8b8b07 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -185,8 +185,12 @@ end """ time_ns() -> UInt64 -Get the time in nanoseconds relative to some arbitrary time in the past. The primary use is for measuring the elapsed time -between two moments in time. +Get the time in nanoseconds relative to some machine-specific arbitrary time in the past. +The primary use is for measuring elapsed times during program execution. The return value is guaranteed to +be monotonic (mod 2⁶⁴) while the system is running, and is unaffected by clock drift or changes to local calendar time, +but it may change arbitrarily across system reboots or suspensions. + +(Although the returned time is always in nanoseconds, the timing resolution is platform-dependent.) """ time_ns() = ccall(:jl_hrtime, UInt64, ()) @@ -258,7 +262,7 @@ support libraries, etc. In these cases it can be useful to prevent unwanted method invalidation and recompilation latency, and to prevent the user from breaking supporting infrastructure by mistake. -The current world age can be queried using [`Base.get_world_counter()`](@ref) +The global world age can be queried using [`Base.get_world_counter()`](@ref) and stored for later use within the lifetime of the current Julia session, or when serializing and reloading the system image. diff --git a/base/accumulate.jl b/base/accumulate.jl index 2748a4da481fa..c155ecfb4f75f 100644 --- a/base/accumulate.jl +++ b/base/accumulate.jl @@ -5,12 +5,14 @@ # it does double the number of operations compared to accumulate, # though for cheap operations like + this does not have much impact (20%) function _accumulate_pairwise!(op::Op, c::AbstractVector{T}, v::AbstractVector, s, i1, n)::T where {T,Op} - @inbounds if n < 128 - s_ = v[i1] - c[i1] = op(s, s_) + if n < 128 + @inbounds s_ = v[i1] + ci1 = op(s, s_) + @inbounds c[i1] = ci1 for i = i1+1:i1+n-1 - s_ = op(s_, v[i]) - c[i] = op(s, s_) + s_ = op(s_, @inbounds(v[i])) + ci = op(s, s_) + @inbounds c[i] = ci end else n2 = n >> 1 @@ -26,7 +28,8 @@ function accumulate_pairwise!(op::Op, result::AbstractVector, v::AbstractVector) n = length(li) n == 0 && return result i1 = first(li) - @inbounds result[i1] = v1 = reduce_first(op,v[i1]) + v1 = reduce_first(op, @inbounds(v[i1])) + @inbounds result[i1] = v1 n == 1 && return result _accumulate_pairwise!(op, result, v, v1, i1+1, n-1) return result @@ -378,16 +381,16 @@ function _accumulate!(op, B, A, dims::Integer, init::Union{Nothing, Some}) # We can accumulate to a temporary variable, which allows # register usage and will be slightly faster ind1 = inds_t[1] - @inbounds for I in CartesianIndices(tail(inds_t)) + for I in CartesianIndices(tail(inds_t)) if init === nothing - tmp = reduce_first(op, A[first(ind1), I]) + tmp = reduce_first(op, @inbounds(A[first(ind1), I])) else - tmp = op(something(init), A[first(ind1), I]) + tmp = op(something(init), @inbounds(A[first(ind1), I])) end - B[first(ind1), I] = tmp + @inbounds B[first(ind1), I] = tmp for i_1 = first(ind1)+1:last(ind1) - tmp = op(tmp, A[i_1, I]) - B[i_1, I] = tmp + tmp = op(tmp, @inbounds(A[i_1, I])) + @inbounds B[i_1, I] = tmp end end else @@ -401,12 +404,15 @@ end @noinline function _accumulaten!(op, B, A, R1, ind, R2, init::Nothing) # Copy the initial element in each 1d vector along dimension `dim` ii = first(ind) - @inbounds for J in R2, I in R1 - B[I, ii, J] = reduce_first(op, A[I, ii, J]) + for J in R2, I in R1 + tmp = reduce_first(op, @inbounds(A[I, ii, J])) + @inbounds B[I, ii, J] = tmp end # Accumulate - @inbounds for J in R2, i in first(ind)+1:last(ind), I in R1 - B[I, i, J] = op(B[I, i-1, J], A[I, i, J]) + for J in R2, i in first(ind)+1:last(ind), I in R1 + @inbounds Bv, Av = B[I, i-1, J], A[I, i, J] + tmp = op(Bv, Av) + @inbounds B[I, i, J] = tmp end B end @@ -414,12 +420,15 @@ end @noinline function _accumulaten!(op, B, A, R1, ind, R2, init::Some) # Copy the initial element in each 1d vector along dimension `dim` ii = first(ind) - @inbounds for J in R2, I in R1 - B[I, ii, J] = op(something(init), A[I, ii, J]) + for J in R2, I in R1 + tmp = op(something(init), @inbounds(A[I, ii, J])) + @inbounds B[I, ii, J] = tmp end # Accumulate - @inbounds for J in R2, i in first(ind)+1:last(ind), I in R1 - B[I, i, J] = op(B[I, i-1, J], A[I, i, J]) + for J in R2, i in first(ind)+1:last(ind), I in R1 + @inbounds Bv, Av = B[I, i-1, J], A[I, i, J] + tmp = op(Bv, Av) + @inbounds B[I, i, J] = tmp end B end @@ -433,10 +442,10 @@ function _accumulate1!(op, B, v1, A::AbstractVector, dim::Integer) cur_val = v1 B[i1] = cur_val next = iterate(inds, state) - @inbounds while next !== nothing + while next !== nothing (i, state) = next - cur_val = op(cur_val, A[i]) - B[i] = cur_val + cur_val = op(cur_val, @inbounds(A[i])) + @inbounds B[i] = cur_val next = iterate(inds, state) end return B diff --git a/base/c.jl b/base/c.jl index 77ef631e295a3..78c48f267ca71 100644 --- a/base/c.jl +++ b/base/c.jl @@ -203,11 +203,11 @@ function exit_on_sigint(on::Bool) ccall(:jl_exit_on_sigint, Cvoid, (Cint,), on) end -function _ccallable(rt::Type, sigt::Type) - ccall(:jl_extern_c, Cvoid, (Any, Any), rt, sigt) +function _ccallable(name::Union{Nothing, String}, rt::Type, sigt::Type) + ccall(:jl_extern_c, Cvoid, (Any, Any, Any), name, rt, sigt) end -function expand_ccallable(rt, def) +function expand_ccallable(name, rt, def) if isa(def,Expr) && (def.head === :(=) || def.head === :function) sig = def.args[1] if sig.head === :(::) @@ -235,7 +235,7 @@ function expand_ccallable(rt, def) end return quote @__doc__ $(esc(def)) - _ccallable($(esc(rt)), $(Expr(:curly, :Tuple, esc(f), map(esc, at)...))) + _ccallable($name, $(esc(rt)), $(Expr(:curly, :Tuple, esc(f), map(esc, at)...))) end end end @@ -243,16 +243,22 @@ function expand_ccallable(rt, def) end """ - @ccallable(def) + @ccallable ["name"] function f(...)::RetType ... end Make the annotated function be callable from C using its name. This can, for example, -be used to expose functionality as a C-API when creating a custom Julia sysimage. +be used to expose functionality as a C API when creating a custom Julia sysimage. + +If the first argument is a string, it is used as the external name of the function. """ macro ccallable(def) - expand_ccallable(nothing, def) + expand_ccallable(nothing, nothing, def) end macro ccallable(rt, def) - expand_ccallable(rt, def) + if rt isa String + expand_ccallable(rt, nothing, def) + else + expand_ccallable(nothing, rt, def) + end end # @ccall implementation diff --git a/base/channels.jl b/base/channels.jl index ef508bd40e3ed..0bb73e9acba87 100644 --- a/base/channels.jl +++ b/base/channels.jl @@ -61,7 +61,7 @@ Channel(sz=0) = Channel{Any}(sz) """ Channel{T=Any}(func::Function, size=0; taskref=nothing, spawn=false, threadpool=nothing) -Create a new task from `func`, bind it to a new channel of type +Create a new task from `func`, [`bind`](@ref) it to a new channel of type `T` and size `size`, and schedule the task, all in a single call. The channel is automatically closed when the task terminates. diff --git a/base/client.jl b/base/client.jl index 9b7d80f51c219..70d564a54615a 100644 --- a/base/client.jl +++ b/base/client.jl @@ -439,11 +439,12 @@ function run_fallback_repl(interactive::Bool) eval_user_input(stderr, ex, true) end else - while !eof(input) + while true if interactive print("julia> ") flush(stdout) end + eof(input) && break try line = "" ex = nothing diff --git a/base/errorshow.jl b/base/errorshow.jl index 16634efe97d8a..809ba29d96501 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1159,6 +1159,8 @@ function UndefVarError_hint(io::IO, ex::UndefVarError) print(io, "\nSuggestion: check for spelling errors or missing imports.") elseif Base.is_some_explicit_imported(kind) print(io, "\nSuggestion: this global was defined as `$(Base.partition_restriction(bpart).globalref)` but not assigned a value.") + elseif kind === Base.PARTITION_KIND_BACKDATED_CONST + print(io, "\nSuggestion: define the const at top-level before running function that uses it (stricter Julia v1.12+ rule).") end elseif scope === :static_parameter print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.") diff --git a/base/expr.jl b/base/expr.jl index ba66198822eca..ab42d5bb1933b 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -532,16 +532,20 @@ The `:consistent` setting asserts that for egal (`===`) inputs: contents) are not egal. !!! note - The `:consistent`-cy assertion is made world-age wise. More formally, write - ``fᵢ`` for the evaluation of ``f`` in world-age ``i``, then this setting requires: + The `:consistent`-cy assertion is made with respect to a particular world range `R`. + More formally, write ``fᵢ`` for the evaluation of ``f`` in world-age ``i``, then this setting requires: ```math - ∀ i, x, y: x ≡ y → fᵢ(x) ≡ fᵢ(y) + ∀ i ∈ R, j ∈ R, x, y: x ≡ y → fᵢ(x) ≡ fⱼ(y) ``` - However, for two world ages ``i``, ``j`` s.t. ``i ≠ j``, we may have ``fᵢ(x) ≢ fⱼ(y)``. + + For `@assume_effects`, the range `R` is `m.primary_world:m.deleted_world` of + the annotated or containing method. + + For ordinary code instances, `R` is `ci.min_world:ci.max_world`. A further implication is that `:consistent` functions may not make their return value dependent on the state of the heap or any other global state - that is not constant for a given world age. + that is not constant over the given world age range. !!! note The `:consistent`-cy includes all legal rewrites performed by the optimizer. diff --git a/base/filesystem.jl b/base/filesystem.jl index 87b3552c80a2f..2934fc15e392f 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -139,7 +139,7 @@ export File, import .Base: IOError, _UVError, _sizeof_uv_fs, check_open, close, closewrite, eof, eventloop, fd, isopen, bytesavailable, position, read, read!, readbytes!, readavailable, seek, seekend, show, - skip, stat, unsafe_read, unsafe_write, write, transcode, uv_error, + skip, stat, unsafe_read, unsafe_write, write, transcode, uv_error, _uv_error, setup_stdio, rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror, filesize, isexecutable, isreadable, iswritable, MutableDenseArrayType, truncate diff --git a/base/idset.jl b/base/idset.jl index c46d49968ff73..95c9bf784f557 100644 --- a/base/idset.jl +++ b/base/idset.jl @@ -92,8 +92,17 @@ function sizehint!(s::IdSet, newsz) nothing end +function _zero!(a::Memory{<:BitInteger}) + t = @_gc_preserve_begin a + p = unsafe_convert(Ptr{Cvoid}, a) + T = eltype(a) + memset(p, 0x0, (sizeof(T) * length(a)) % UInt) + @_gc_preserve_end t + return a +end + function empty!(s::IdSet) - fill!(s.idxs, 0x00) + _zero!(s.idxs) list = s.list for i = 1:s.max _unsetindex!(list, i) diff --git a/base/libuv.jl b/base/libuv.jl index 306854e9f4436..35b1a9097293e 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -103,7 +103,8 @@ struverror(err::Int32) = unsafe_string(ccall(:uv_strerror, Cstring, (Int32,), er uverrorname(err::Int32) = unsafe_string(ccall(:uv_err_name, Cstring, (Int32,), err)) uv_error(prefix::Symbol, c::Integer) = uv_error(string(prefix), c) -uv_error(prefix::AbstractString, c::Integer) = c < 0 ? throw(_UVError(prefix, c)) : nothing +uv_error(prefix::AbstractString, c::Integer) = c < 0 ? _uv_error(prefix, c) : nothing +_uv_error(prefix::AbstractString, c::Integer) = throw(_UVError(prefix, c)) ## event loop ## diff --git a/base/lock.jl b/base/lock.jl index 40bc9e08bd9b0..b6d633d7907a2 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -705,7 +705,9 @@ mutable struct OncePerProcess{T, F} <: Function return once end end +OncePerProcess{T}(initializer::Type{U}) where {T, U} = OncePerProcess{T, Type{U}}(initializer) OncePerProcess{T}(initializer::F) where {T, F} = OncePerProcess{T, F}(initializer) +OncePerProcess(initializer::Type{U}) where U = OncePerProcess{Base.promote_op(initializer), Type{U}}(initializer) OncePerProcess(initializer) = OncePerProcess{Base.promote_op(initializer), typeof(initializer)}(initializer) @inline function (once::OncePerProcess{T,F})() where {T,F} state = (@atomic :acquire once.state) @@ -812,7 +814,9 @@ mutable struct OncePerThread{T, F} <: Function return once end end +OncePerThread{T}(initializer::Type{U}) where {T, U} = OncePerThread{T,Type{U}}(initializer) OncePerThread{T}(initializer::F) where {T, F} = OncePerThread{T,F}(initializer) +OncePerThread(initializer::Type{U}) where U = OncePerThread{Base.promote_op(initializer), Type{U}}(initializer) OncePerThread(initializer) = OncePerThread{Base.promote_op(initializer), typeof(initializer)}(initializer) @inline (once::OncePerThread{T,F})() where {T,F} = once[Threads.threadid()] @inline function getindex(once::OncePerThread{T,F}, tid::Integer) where {T,F} @@ -931,8 +935,10 @@ false mutable struct OncePerTask{T, F} <: Function const initializer::F + OncePerTask{T}(initializer::Type{U}) where {T, U} = new{T,Type{U}}(initializer) OncePerTask{T}(initializer::F) where {T, F} = new{T,F}(initializer) OncePerTask{T,F}(initializer::F) where {T, F} = new{T,F}(initializer) + OncePerTask(initializer::Type{U}) where U = new{Base.promote_op(initializer), Type{U}}(initializer) OncePerTask(initializer) = new{Base.promote_op(initializer), typeof(initializer)}(initializer) end @inline function (once::OncePerTask{T,F})() where {T,F} diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 510d27ecb3701..b6190b3e9044e 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -1115,7 +1115,7 @@ function datatype_fieldcount(t::DataType) return length(names) end if types isa DataType && types <: Tuple - return fieldcount(types) + return datatype_fieldcount(types) end return nothing elseif isabstracttype(t) @@ -1570,12 +1570,18 @@ end is_nospecialized(method::Method) = method.nospecialize ≠ 0 is_nospecializeinfer(method::Method) = method.nospecializeinfer && is_nospecialized(method) + +""" +Return MethodInstance corresponding to `atype` and `sparams`. + +No widening / narrowing / compileable-normalization of `atype` is performed. +""" function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false) @inline if isa(atype, UnionAll) atype, sparams = normalize_typevars(method, atype, sparams) end - if is_nospecializeinfer(method) + if is_nospecializeinfer(method) # TODO: this shouldn't be here atype = get_nospecializeinfer_sig(method, atype, sparams) end if preexisting diff --git a/base/stat.jl b/base/stat.jl index fc2ac9a04b0bf..4f248ec47e6da 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -183,7 +183,8 @@ show(io::IO, ::MIME"text/plain", st::StatStruct) = show_statstruct(io, st, false # stat & lstat functions -checkstat(s::StatStruct) = Int(s.ioerrno) in (0, Base.UV_ENOENT, Base.UV_ENOTDIR, Base.UV_EINVAL) ? s : uv_error(string("stat(", repr(s.desc), ")"), s.ioerrno) +checkstat(s::StatStruct) = Int(s.ioerrno) in (0, Base.UV_ENOENT, Base.UV_ENOTDIR, Base.UV_EINVAL) ? s : + _uv_error(string("stat(", repr(s.desc), ")"), s.ioerrno) macro stat_call(sym, arg1type, arg) return quote diff --git a/base/staticdata.jl b/base/staticdata.jl index 741937369b73b..cb0888e9d56bb 100644 --- a/base/staticdata.jl +++ b/base/staticdata.jl @@ -174,7 +174,7 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi edge = get_ci_mi(edge) end if edge isa MethodInstance - sig = typeintersect((edge.def::Method).sig, edge.specTypes) # TODO?? + sig = edge.specTypes min_valid2, max_valid2, matches = verify_call(sig, callees, j, 1, world) j += 1 elseif edge isa Int @@ -346,6 +346,7 @@ function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UIn matched = nothing if invokesig === expected.sig # the invoke match is `expected` for `expected->sig`, unless `expected` is invalid + # TODO: this is broken since PR #53415 minworld = expected.primary_world maxworld = expected.deleted_world @assert minworld ≤ world diff --git a/base/timing.jl b/base/timing.jl index 2dabe964f08c6..9e3a4cf128413 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -472,6 +472,35 @@ function gc_bytes() b[] end +function allocated(f, args::Vararg{Any,N}) where {N} + b0 = Ref{Int64}(0) + b1 = Ref{Int64}(0) + Base.gc_bytes(b0) + f(args...) + Base.gc_bytes(b1) + return b1[] - b0[] +end +only(methods(allocated)).called = 0xff + +function allocations(f, args::Vararg{Any,N}) where {N} + stats = Base.gc_num() + f(args...) + diff = Base.GC_Diff(Base.gc_num(), stats) + return Base.gc_alloc_count(diff) +end +only(methods(allocations)).called = 0xff + +function is_simply_call(@nospecialize ex) + Meta.isexpr(ex, :call) || return false + for a in ex.args + a isa QuoteNode && continue + a isa Symbol && continue + Base.is_self_quoting(a) && continue + return false + end + return true +end + """ @allocated @@ -487,15 +516,11 @@ julia> @allocated rand(10^6) ``` """ macro allocated(ex) - quote - Experimental.@force_compile - local b0 = Ref{Int64}(0) - local b1 = Ref{Int64}(0) - gc_bytes(b0) - $(esc(ex)) - gc_bytes(b1) - b1[] - b0[] + if !is_simply_call(ex) + ex = :((() -> $ex)()) end + pushfirst!(ex.args, GlobalRef(Base, :allocated)) + return esc(ex) end """ @@ -516,15 +541,14 @@ julia> @allocations rand(10^6) This macro was added in Julia 1.9. """ macro allocations(ex) - quote - Experimental.@force_compile - local stats = Base.gc_num() - $(esc(ex)) - local diff = Base.GC_Diff(Base.gc_num(), stats) - Base.gc_alloc_count(diff) + if !is_simply_call(ex) + ex = :((() -> $ex)()) end + pushfirst!(ex.args, GlobalRef(Base, :allocations)) + return esc(ex) end + """ @lock_conflicts diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index ca95b4fa2fcdb..13ad25e620b02 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # Prevent this from putting anything into the Main namespace -@eval Core.Module() begin +@eval Base module __precompile_script if Threads.maxthreadid() != 1 @warn "Running this file with multiple Julia threads may lead to a build error" Threads.maxthreadid() diff --git a/contrib/juliac.jl b/contrib/juliac.jl index 7087462afc7a1..b110f1d233690 100644 --- a/contrib/juliac.jl +++ b/contrib/juliac.jl @@ -88,8 +88,6 @@ allflags = Base.shell_split(allflags) rpath = get_rpath(; relative = relative_rpath) rpath = Base.shell_split(rpath) tmpdir = mktempdir(cleanup=false) -initsrc_path = joinpath(tmpdir, "init.c") -init_path = joinpath(tmpdir, "init.a") img_path = joinpath(tmpdir, "img.a") bc_path = joinpath(tmpdir, "img-bc.a") @@ -122,19 +120,6 @@ function compile_products(enable_trim::Bool) exit(1) end - # Compile the initialization code - open(initsrc_path, "w") do io - print(io, """ - #include - __attribute__((constructor)) void static_init(void) { - if (jl_is_initialized()) - return; - julia_init(JL_IMAGE_IN_MEMORY); - jl_exception_clear(); - } - """) - end - run(`cc $(cflags) -g -c -o $init_path $initsrc_path`) end function link_products() @@ -150,11 +135,11 @@ function link_products() julia_libs = Base.shell_split(Base.isdebugbuild() ? "-ljulia-debug -ljulia-internal-debug" : "-ljulia -ljulia-internal") try if output_type == "--output-lib" - cmd2 = `cc $(allflags) $(rpath) -o $outname -shared -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $init_path $(julia_libs)` + cmd2 = `cc $(allflags) $(rpath) -o $outname -shared -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $(julia_libs)` elseif output_type == "--output-sysimage" cmd2 = `cc $(allflags) $(rpath) -o $outname -shared -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $(julia_libs)` else - cmd2 = `cc $(allflags) $(rpath) -o $outname -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $init_path $(julia_libs)` + cmd2 = `cc $(allflags) $(rpath) -o $outname -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $(julia_libs)` end verbose && println("Running: $cmd2") run(cmd2) diff --git a/deps/checksums/cacert-2024-12-31.pem/md5 b/deps/checksums/cacert-2024-12-31.pem/md5 deleted file mode 100644 index b01bf68ddc247..0000000000000 --- a/deps/checksums/cacert-2024-12-31.pem/md5 +++ /dev/null @@ -1 +0,0 @@ -d9178b626f8b87f51b47987418d012bf diff --git a/deps/checksums/cacert-2024-12-31.pem/sha512 b/deps/checksums/cacert-2024-12-31.pem/sha512 deleted file mode 100644 index c12b8215a7855..0000000000000 --- a/deps/checksums/cacert-2024-12-31.pem/sha512 +++ /dev/null @@ -1 +0,0 @@ -bf578937d7826106bae1ebe74a70bfbc439387445a1f41ef57430de9d9aea6fcfa1884381bf0ef14632f6b89e9543642c9b774fcca93837efffdc557c4958dbd diff --git a/deps/checksums/cacert-2025-02-25.pem/md5 b/deps/checksums/cacert-2025-02-25.pem/md5 new file mode 100644 index 0000000000000..3dced8d2bee6b --- /dev/null +++ b/deps/checksums/cacert-2025-02-25.pem/md5 @@ -0,0 +1 @@ +1a7de82bb9f0fcc779ca18a7a9310898 diff --git a/deps/checksums/cacert-2025-02-25.pem/sha512 b/deps/checksums/cacert-2025-02-25.pem/sha512 new file mode 100644 index 0000000000000..bb59a65af401e --- /dev/null +++ b/deps/checksums/cacert-2025-02-25.pem/sha512 @@ -0,0 +1 @@ +e5fe41820460e6b65e8cd463d1a5f01b7103e1ef66cb75fedc15ebcba3ba6600d77e5e7c2ab94cbb1f11c63b688026a04422bbe2d7a861f7a988f67522ffae3c diff --git a/deps/checksums/openssl b/deps/checksums/openssl index 134ad867cbd3f..3b41bfa69231d 100644 --- a/deps/checksums/openssl +++ b/deps/checksums/openssl @@ -1,38 +1,38 @@ -OpenSSL.v3.0.16+0.aarch64-apple-darwin.tar.gz/md5/72c29fd0048b0fee44410cfb82ed6235 -OpenSSL.v3.0.16+0.aarch64-apple-darwin.tar.gz/sha512/a1d23c15c16d577e7f350004c3e3e8c9f14375ca31256f4e42dcd828b610eeea269eecac58e1c3449c99dcc80b7a8885bbbd39beb5d5ff85167d953c26c10d00 -OpenSSL.v3.0.16+0.aarch64-linux-gnu.tar.gz/md5/29bf1097dbe8ac0c42ffb0c1ff9234cf -OpenSSL.v3.0.16+0.aarch64-linux-gnu.tar.gz/sha512/b388a13fbfd416feb95bac4f2f4f47605ac8ad971551ec218a8822618cd6e127ad538782524fed5c5f75ab3caf5b86107598967993ae03516706efa6a3af1010 -OpenSSL.v3.0.16+0.aarch64-linux-musl.tar.gz/md5/971ba30a9e0be025433afe3b0aae6260 -OpenSSL.v3.0.16+0.aarch64-linux-musl.tar.gz/sha512/43b6bea8d3e0ab783ed2ec1140fb9054ef0cdd0ddd34e5e95fb36c7b1b72d7e988b2bb17c878c57b0721c44b7783b2db9d4fc614a63bb557b1c32088dd01d506 -OpenSSL.v3.0.16+0.aarch64-unknown-freebsd.tar.gz/md5/3a3d963e16c7efbacdaea9754db640e3 -OpenSSL.v3.0.16+0.aarch64-unknown-freebsd.tar.gz/sha512/7c3d0ed3a7f37e879e3b8f4c5c67cf2766b5e421fab273806a0412ba12ab4de421bce094713aeb3f6c3915260cb8d7fcb35e214344131e9a5b0081cb7bf0d5dc -OpenSSL.v3.0.16+0.armv6l-linux-gnueabihf.tar.gz/md5/c94d4882e57cb9d3688127f5f82331a5 -OpenSSL.v3.0.16+0.armv6l-linux-gnueabihf.tar.gz/sha512/b8133e873a960b125d0ab8ddd5b4071a6ab269e2b2f5b36e0d723e759875d21f734ac44f4baa4122bc6b94e23f0d82d401f16c88d9c70dac4031f07dcd20597a -OpenSSL.v3.0.16+0.armv6l-linux-musleabihf.tar.gz/md5/149aacf601e86ed15cd4305d035fb7b2 -OpenSSL.v3.0.16+0.armv6l-linux-musleabihf.tar.gz/sha512/dad7bade8fdf62c642ba1a8553f8a02218dbddd15040388b0a0eaac5391fb3c606860378c5dc40b16978c2f8f3837a1698788788d83006a4b312b1c6e73b2a53 -OpenSSL.v3.0.16+0.armv7l-linux-gnueabihf.tar.gz/md5/a1d45f34e463df42c2ea77d9084a4360 -OpenSSL.v3.0.16+0.armv7l-linux-gnueabihf.tar.gz/sha512/5f5ba285564b1ff1c5db3a2fe4d2051b9b17a77e6c6da37bc739f64051d64a5bff3968d5326760c102f9ddbc3509bf3eeb3ae267acfecbd0a55ff86ec90b5cb0 -OpenSSL.v3.0.16+0.armv7l-linux-musleabihf.tar.gz/md5/81ed6b1188a7ccda1768b67fdfc6e094 -OpenSSL.v3.0.16+0.armv7l-linux-musleabihf.tar.gz/sha512/2424620b462b5596e317e77ebc294377811ec1210c65baf4cd072b62f8d303aa77b2b4f799611ca91202dc371ea4575c2c13cb222e6edd809a036b639c1595c0 -OpenSSL.v3.0.16+0.i686-linux-gnu.tar.gz/md5/714bab6e53849d0bf6d9922be51871e2 -OpenSSL.v3.0.16+0.i686-linux-gnu.tar.gz/sha512/141102d20810986b75ba3b2b4446e4a260f4ae38583b7f8dd8a59b8e0ec8b2bac03ee83127b8f4cdb67bd8fd5ebbb102cb581ed182735283109ff85d049cc55b -OpenSSL.v3.0.16+0.i686-linux-musl.tar.gz/md5/e4f5f918c011d87626a0f830243324f9 -OpenSSL.v3.0.16+0.i686-linux-musl.tar.gz/sha512/128e6c29f0537818a68cbbd262a3735ee99ccca2377874c22978782abd43c3d0f9bd49d68c05d09f37293df52c238aa33d87244276eef080959aefe42fe8c92f -OpenSSL.v3.0.16+0.i686-w64-mingw32.tar.gz/md5/a68b80b7725887ef33ea36e7d19f7cd1 -OpenSSL.v3.0.16+0.i686-w64-mingw32.tar.gz/sha512/91aeb66c49f73eaa00114a61fc2b644e278bc39948f408806c66f61529ad98d3bc1f885152e1cc275a8374dde2d5ace3695e6f70eec718bacb0269560bce83b0 -OpenSSL.v3.0.16+0.powerpc64le-linux-gnu.tar.gz/md5/ec36f9b42c64ab4b1ce862229bc06924 -OpenSSL.v3.0.16+0.powerpc64le-linux-gnu.tar.gz/sha512/71470814b096ca9127fc2055b4ecc6e6acb30f03fe16754010c5c860f8d82cb37c2e723dca3f92f2aa2e9604fd1f4d141eca333d2c7524274e33d98da34326df -OpenSSL.v3.0.16+0.riscv64-linux-gnu.tar.gz/md5/5aecf142b6849b8c2cca957830d49f4e -OpenSSL.v3.0.16+0.riscv64-linux-gnu.tar.gz/sha512/d25879a4d6b8cc76c8fc4ed49b38d48938c80ba163e7ffe276bb86fdc4bb76cd596f150564bfa3bd88fa90451916a086ca04d31ba884bd978f40f57f0e4332f7 -OpenSSL.v3.0.16+0.x86_64-apple-darwin.tar.gz/md5/41c847ee490a5935fac8c4b663d8e325 -OpenSSL.v3.0.16+0.x86_64-apple-darwin.tar.gz/sha512/04979622fae5b24b0f0d79b0853ea311e872a7ca465c6e6700e713a34fa90a182ad78db8babacf322fb288cb62caed1a73f8d47e55fd2b074238191649e0141f -OpenSSL.v3.0.16+0.x86_64-linux-gnu.tar.gz/md5/b3c143deff5a311740ccc592a1a433e9 -OpenSSL.v3.0.16+0.x86_64-linux-gnu.tar.gz/sha512/31819e2e78dbd7aeb95b84664eac9ee29b0ec4e1b0bc9e06a4ea8f7cb65c21929414d9e4e3fa6ce15944bfd7fc68d699d4016cbe5ace16e98394a60fe369541f -OpenSSL.v3.0.16+0.x86_64-linux-musl.tar.gz/md5/bed866803232e6ada4e22eadfd2b98d1 -OpenSSL.v3.0.16+0.x86_64-linux-musl.tar.gz/sha512/ee6f26d150c81e30c93f08de5428d2f92e02e12565e6dcef09bbd1029ff5477bb2176b6cc7616a7cc898dfec8c5d8263108c3558460397c71ca90af8b230e0c1 -OpenSSL.v3.0.16+0.x86_64-unknown-freebsd.tar.gz/md5/ee6f11020b0ce6eac8016877d1635a04 -OpenSSL.v3.0.16+0.x86_64-unknown-freebsd.tar.gz/sha512/dd154fe1e8c537d42919c0889036ac79e181b765c87130edfeffd2ef8414da902995469167812664e6f77f3c89379c799d4311336f4233b05796b6823838e01c -OpenSSL.v3.0.16+0.x86_64-w64-mingw32.tar.gz/md5/d08f7d27c775eec9c7e4709d0898d60e -OpenSSL.v3.0.16+0.x86_64-w64-mingw32.tar.gz/sha512/4806c67ff8f629ee6b3a7c358146675310f0812772773aad7e30d0ccee0cc085741c06d252ed16bf8f874fe794d1b8291e0cbcd65c8eacb9e87c0a5f65742c5f -openssl-3.0.16.tar.gz/md5/7b6a9cded21b9fa51877444f5defebd4 -openssl-3.0.16.tar.gz/sha512/5eea2b0c60d870549fc2b8755f1220a57f870d95fbc8d5cc5abb9589f212d10945f355c3e88ff48540a7ee1c4db774b936023ca33d7c799ea82d91eef9c1c16d +OpenSSL.v3.5.0+0.aarch64-apple-darwin.tar.gz/md5/b8dc9909528f769bd9ac56cf2681f387 +OpenSSL.v3.5.0+0.aarch64-apple-darwin.tar.gz/sha512/0d9ea24d8f856c31c8b88afa1de317d13aff1f1f60b309e06e77eea91d195526ec91ed2d077f0dbb75370c17b8875c24d3066e6872bbef04312616e99d0aff3d +OpenSSL.v3.5.0+0.aarch64-linux-gnu.tar.gz/md5/7cf5baeacf4d882b547c229758a9fa9b +OpenSSL.v3.5.0+0.aarch64-linux-gnu.tar.gz/sha512/726ceee82379e667a65abe27c482d3b57e611c630d82b1314f6d385f0f2e8256835ef707c2e015f9204d563d7ee469bed2dee88d245af81dcde2af3b8331b19c +OpenSSL.v3.5.0+0.aarch64-linux-musl.tar.gz/md5/4601e56eaed365548203752a19f4f8e8 +OpenSSL.v3.5.0+0.aarch64-linux-musl.tar.gz/sha512/da349081850d47b9393665c4365787c26f61471362475c2acd3c8205063d09a785f7b6c836ba6793e880440115b19e85821b4d1938e57dafea0cabb45048a70b +OpenSSL.v3.5.0+0.aarch64-unknown-freebsd.tar.gz/md5/6a9e78436727e67af2f537170e18445e +OpenSSL.v3.5.0+0.aarch64-unknown-freebsd.tar.gz/sha512/4dc2f7a39f17255871773d10ed1b74de5c908af0f7a4bd3f94fd71bc12480fd4cdee0bd859a154328218935f004eee20359dacc353e366c47ed890229a579fc4 +OpenSSL.v3.5.0+0.armv6l-linux-gnueabihf.tar.gz/md5/5c751092c27910a48cab31f87700fe19 +OpenSSL.v3.5.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/b44e2356f719549dd831745963b8c74346be173d176ca15ab2ee6f4a1ec7e105086d89115cb76831a3251eb67bf7c5ff5cba3a03fd4614a3501af235a8e03beb +OpenSSL.v3.5.0+0.armv6l-linux-musleabihf.tar.gz/md5/fc05f9645ff000b21e46951f16833fb0 +OpenSSL.v3.5.0+0.armv6l-linux-musleabihf.tar.gz/sha512/8c960294fe542ab9d9ae7dc283c0c30621f348ff8011a9a47f38c1460234b3b128011426c3e5d0cb6c9b02fbee261b7b264d0b0c55bdf3be2a2cd5bdd210d71d +OpenSSL.v3.5.0+0.armv7l-linux-gnueabihf.tar.gz/md5/8928d47a0f549d15240eb934caddf599 +OpenSSL.v3.5.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/4b5dbfb3a4ea4ebe6510cbe63da2de0bb3762a0fc98946acbb059e9a92791ac65a3519577250dcb571fa07f29be182f165a5d4fa05fc96b60270441adab30e74 +OpenSSL.v3.5.0+0.armv7l-linux-musleabihf.tar.gz/md5/1e4c11043d05bea0fcdbf92525152c51 +OpenSSL.v3.5.0+0.armv7l-linux-musleabihf.tar.gz/sha512/e9514cd0c3a8c3659ff87d505490ca3011a65800222b21e4f932bc2a80fb38bb11de1d13925c3a6313f6bea1c2baf35b38b3db18193ac11ec42eb434edee3418 +OpenSSL.v3.5.0+0.i686-linux-gnu.tar.gz/md5/ee699f302edd1f7677baa565ae631c74 +OpenSSL.v3.5.0+0.i686-linux-gnu.tar.gz/sha512/16dd396b192b4ca23d1fad54d130a92ef43a36259482cd3b276001d084711ef8674dcd167c9f832f5989d6197889af69d2ae6bcef3e6b9f538066bf347c89584 +OpenSSL.v3.5.0+0.i686-linux-musl.tar.gz/md5/e6ffea118acb68d39ccb13df51e15125 +OpenSSL.v3.5.0+0.i686-linux-musl.tar.gz/sha512/44335dcaf144d388bd47dd80db08862f4cff1d5a90861f34001127df74d0d16babedbe0ffd02ab398bddd17ecda605f433a940b3cc5159947cb54810a564b0df +OpenSSL.v3.5.0+0.i686-w64-mingw32.tar.gz/md5/8ef4284ac47a6b45f8c5b01d203ae668 +OpenSSL.v3.5.0+0.i686-w64-mingw32.tar.gz/sha512/d7ea8c94d54a139631f2710cb2c47c0387b694e60dc7afddbca3c6705e17d25ec8958a84b4424edd1ea27d6d1c78457fbacd92f7634345f4ccc1a81cf242c28f +OpenSSL.v3.5.0+0.powerpc64le-linux-gnu.tar.gz/md5/21674471f2a3352ede9aef3454381edd +OpenSSL.v3.5.0+0.powerpc64le-linux-gnu.tar.gz/sha512/5615d438db7c3e97dc422b260b3152cd89a2412b7b9b5d7cea36b0ce471fbd3f1a2e8a9d77f399e257f5c38b8b5dfc256acfbdbe2645ba47b89c177dadd066e9 +OpenSSL.v3.5.0+0.riscv64-linux-gnu.tar.gz/md5/7761384fd5991eb56286f24c9a0fbdba +OpenSSL.v3.5.0+0.riscv64-linux-gnu.tar.gz/sha512/e63d5f7ddc368f4cdb03c299361faef7274930c622404907c3560eb04e6110f851b9a201b402bb6e52fdafe64988f909c209f659f84ba77957eb45a933c8baf1 +OpenSSL.v3.5.0+0.x86_64-apple-darwin.tar.gz/md5/a970728a9aa6f25d56db7e43e7b0cae2 +OpenSSL.v3.5.0+0.x86_64-apple-darwin.tar.gz/sha512/8ab5b2dd90914e193d1f7689c8560228d03cb6ee79fd43a48ae9339b61274fea0557a2bf3a7ae4ce4d4b51630aede55d6d6e860f263e1ffc0bfd6141367a9514 +OpenSSL.v3.5.0+0.x86_64-linux-gnu.tar.gz/md5/4530c0e1791b0eaec99b68f2967a3c2f +OpenSSL.v3.5.0+0.x86_64-linux-gnu.tar.gz/sha512/ba952738be38f52ebc23f48c52c12c1bec9c8b81416264612da21ca21f23604c8e59bf49f73d4b80256ea17b6b662179620deadb8660be98d8ad5ed57e346394 +OpenSSL.v3.5.0+0.x86_64-linux-musl.tar.gz/md5/eb49cefbb938d80198dbab90e1ad9108 +OpenSSL.v3.5.0+0.x86_64-linux-musl.tar.gz/sha512/f038e9bd950e4472cdd82b0c39aebbfd60e75cdf24fd8408d39e4db0793813c9d30471d1ca8d112b0bb4049f18f8fb36b4c3069dfce61032dc73cb6568852b77 +OpenSSL.v3.5.0+0.x86_64-unknown-freebsd.tar.gz/md5/25023844dae8c7d326620b1f9e730a07 +OpenSSL.v3.5.0+0.x86_64-unknown-freebsd.tar.gz/sha512/e38f1f7c452903a09b3f0127e377d5e46e538903f9a58076e53dfc53883b2423463d3fdcf13dc961516965b6dbc2d289bfbfa1027f8c3110a61bdee060bccf73 +OpenSSL.v3.5.0+0.x86_64-w64-mingw32.tar.gz/md5/a73f5220598dfc5e71e1eee6b26f7a27 +OpenSSL.v3.5.0+0.x86_64-w64-mingw32.tar.gz/sha512/c028527230b6e9e675b7e22a21997e5d032e1099dd1f3437c6e764b7967fd0196d4cb46d66b36f2f6ddeb8200f445aa8d6a7a61f7be61288ee5e0e510b5800f8 +openssl-3.5.0.tar.gz/md5/51da7d2bdf7f4f508cb024f562eb9b03 +openssl-3.5.0.tar.gz/sha512/39cc80e2843a2ee30f3f5de25cd9d0f759ad8de71b0b39f5a679afaaa74f4eb58d285ae50e29e4a27b139b49343ac91d1f05478f96fb0c6b150f16d7b634676f diff --git a/deps/libgit2.version b/deps/libgit2.version index 6bfb6106e67d2..3f1f7a66fe972 100644 --- a/deps/libgit2.version +++ b/deps/libgit2.version @@ -11,4 +11,4 @@ LIBGIT2_SHA1=338e6fb681369ff0537719095e22ce9dc602dbf0 # The versions of cacert.pem are identified by the date (YYYY-MM-DD) of their changes. # See https://curl.haxx.se/docs/caextract.html for more details. # Keep in sync with `stdlib/MozillaCACerts_jll/Project.toml`. -MOZILLA_CACERT_VERSION := 2024-12-31 +MOZILLA_CACERT_VERSION := 2025-02-25 diff --git a/deps/openssl.version b/deps/openssl.version index 48831039a313e..49c463aad1565 100644 --- a/deps/openssl.version +++ b/deps/openssl.version @@ -3,4 +3,4 @@ OPENSSL_JLL_NAME := OpenSSL ## source build -OPENSSL_VER := 3.0.16 +OPENSSL_VER := 3.5.0 diff --git a/doc/make.jl b/doc/make.jl index 43d51e9936b58..cb091c4bcc247 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -173,6 +173,7 @@ Manual = [ "manual/noteworthy-differences.md", "manual/unicode-input.md", "manual/command-line-interface.md", + "manual/worldage.md", ] BaseDocs = [ diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index f578e10764101..f14fc1bc3ccda 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -54,7 +54,7 @@ linking against `libjulia`. The first thing that must be done before calling any other Julia C function is to initialize Julia. This is done by calling `jl_init`, which tries to automatically determine Julia's install location. If you need to specify a custom location, or specify which system -image to load, use `jl_init_with_image` instead. +image to load, use `jl_init_with_image_file` or `jl_init_with_image_handle` instead. The second statement in the test program evaluates a Julia statement using a call to `jl_eval_string`. diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 45d22e08aaffe..6014b49c346d2 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -578,73 +578,8 @@ However, future calls to `tryeval` will continue to see the definition of `newfu You may want to try this for yourself to see how it works. -The implementation of this behavior is a "world age counter". -This monotonically increasing value tracks each method definition operation. -This allows describing "the set of method definitions visible to a given runtime environment" -as a single number, or "world age". -It also allows comparing the methods available in two worlds just by comparing their ordinal value. -In the example above, we see that the "current world" (in which the method `newfun` exists), -is one greater than the task-local "runtime world" that was fixed when the execution of `tryeval` started. - -Sometimes it is necessary to get around this (for example, if you are implementing the above REPL). -Fortunately, there is an easy solution: call the function using [`Base.invokelatest`](@ref) or -the macro version [`Base.@invokelatest`](@ref): - -```jldoctest -julia> function tryeval2() - @eval newfun2() = 2 - @invokelatest newfun2() - end -tryeval2 (generic function with 1 method) - -julia> tryeval2() -2 -``` - -Finally, let's take a look at some more complex examples where this rule comes into play. -Define a function `f(x)`, which initially has one method: - -```jldoctest redefinemethod -julia> f(x) = "original definition" -f (generic function with 1 method) -``` - -Start some other operations that use `f(x)`: - -```jldoctest redefinemethod -julia> g(x) = f(x) -g (generic function with 1 method) - -julia> t = @async f(wait()); yield(); -``` - -Now we add some new methods to `f(x)`: - -```jldoctest redefinemethod -julia> f(x::Int) = "definition for Int" -f (generic function with 2 methods) - -julia> f(x::Type{Int}) = "definition for Type{Int}" -f (generic function with 3 methods) -``` - -Compare how these results differ: - -```jldoctest redefinemethod -julia> f(1) -"definition for Int" - -julia> g(1) -"definition for Int" - -julia> fetch(schedule(t, 1)) -"original definition" - -julia> t = @async f(wait()); yield(); - -julia> fetch(schedule(t, 1)) -"definition for Int" -``` +The implementation of this behavior is a "world age counter", which is further described in the [Worldage](@ref man-worldage) +manual chapter. ## Design Patterns with Parametric Methods diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index a6c66bbd14f77..23974ae7ecce1 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -316,6 +316,68 @@ Here, Julia cannot decide which `f` you are referring to, so you have to make a 3. When the names in question *do* share a meaning, it is common for one module to import it from another, or have a lightweight “base” package with the sole function of defining an interface like this, which can be used by other packages. It is conventional to have such package names end in `...Base` (which has nothing to do with Julia's `Base` module). +### Precedence order of definitions + +There are in general four kinds of binding definitions: + 1. Those provided via implicit import through `using M` + 2. Those provided via explicit import (e.g. `using M: x`, `import M: x`) + 3. Those declared implicitly as global (via `global x` without type specification) + 4. Those declared explicitly using definition syntax (`const`, `global x::T`, `struct`, etc.) + +Syntactically, we divide these into three precedence levels (from weakest to strongest) + 1. Implicit imports + 2. Implicit declarations + 3. Explicit declarations and imports + +In general, we permit replacement of weaker bindings by stronger ones: + +```julia-repl +julia> module M1; const x = 1; export x; end +Main.M1 + +julia> using .M1 + +julia> x # Implicit import from M1 +1 + +julia> begin; f() = (global x; x = 1) end + +julia> x # Implicit declaration +ERROR: UndefVarError: `x` not defined in `Main` +Suggestion: add an appropriate import or assignment. This global was declared but not assigned. + +julia> const x = 2 # Explicit declaration +2 +``` + +However, within the explicit precedence level, replacement is syntactically disallowed: +```julia-repl +julia> module M1; const x = 1; export x; end +Main.M1 + +julia> import .M1: x + +julia> const x = 2 +ERROR: cannot declare Main.x constant; it was already declared as an import +Stacktrace: + [1] top-level scope + @ REPL[3]:1 +``` + +or ignored: + +```julia-repl +julia> const y = 2 +2 + +julia> import .M1: x as y +WARNING: import of M1.x into Main conflicts with an existing identifier; ignored. +``` + +The resolution of an implicit binding depends on the set of all `using`'d modules visible +in the current world age. See [the manual chapter on world age](@ref man-worldage) for more +details. + ### Default top-level definitions and bare modules Modules automatically contain `using Core`, `using Base`, and definitions of the [`eval`](@ref) diff --git a/doc/src/manual/worldage.md b/doc/src/manual/worldage.md new file mode 100644 index 0000000000000..26853b84b3031 --- /dev/null +++ b/doc/src/manual/worldage.md @@ -0,0 +1,295 @@ +# The World Age mechanism + +!!! note + World age is an advanced concept. For the vast majority of Julia users, the world age + mechanism operates invisibly in the background. This documentation is intended for the + few users who may encounter world-age related issues or error messages. + +!!! compat "Julia 1.12" + Prior to Julia 1.12, the world age mechanism did not apply to changes to the global binding table. + The documentation in this chapter is specific to Julia 1.12+. + +!!! warning + This manual chapter uses internal functions to introspect world age and runtime data structures + as an explanatory aid. In general, unless otherwise noted the world age mechanism is not a stable + interface and should be interacted with in packages through stable APIs (e.g. `invokelatest`) only. + In particular, do not assume that world ages are always integers or that they have a linear order. + +## World age in general + +The "world age counter" is a monotonically increasing counter that is incremented for every +change to the global method table or the global binding table (e.g. through method definition, +type definition, `import`/`using` declaration, creation of (typed) globals or definition of constants). + +The current value of the global world age counter can be retrieved using the (internal) function [`Base.get_world_counter`](@ref). + +```julia-repl +julia> Base.get_world_counter() +0x0000000000009632 + +julia> const x = 1 + +julia> Base.get_world_counter() +0x0000000000009633 +``` + +In addition, each [`Task`](@ref) stores a local world age that determines which modifications to +the global binding and method tables are currently visible to the running task. The world age of +the running task will never exceed the global world age counter, but may run arbitrarily behind it. +In general the term "current world age" refers to the local world age of the currently running task. +The current world age may be retrieved using the (internal) function [`Base.tls_world_age`](@ref) + +```julia-repl +julia> function f end +f (generic function with 0 methods) + +julia> begin + @show (Int(Base.get_world_counter()), Int(Base.tls_world_age())) + Core.eval(@__MODULE__, :(f() = 1)) + @show (Int(Base.get_world_counter()), Int(Base.tls_world_age())) + f() + end +(Int(Base.get_world_counter()), Int(Base.tls_world_age())) = (38452, 38452) +(Int(Base.get_world_counter()), Int(Base.tls_world_age())) = (38453, 38452) +ERROR: MethodError: no method matching f() +The applicable method may be too new: running in current world age 38452, while global world is 38453. + +Closest candidates are: + f() (method too new to be called from this world context.) + @ Main REPL[2]:3 + +Stacktrace: + [1] top-level scope + @ REPL[2]:5 + +julia> (f(), Int(Base.tls_world_age())) +(1, 38453) +``` + +Here the definition of the method `f` raised the global world counter, but the current world +age did not change. As a result, the definition of `f` was not visible in the currently +executing task and a [`MethodError`](@ref) resulted. + +!!! note + The method error printing provided additional information that `f()` is available in a newer world age. + This information is added by the error display, not the task that threw the `MethodError`. + The thrown `MethodError` is identical whether or not a matching definition of `f()` exists + in a newer world age. + +However, note that the definition of `f()` was subsequently available at the next REPL prompt, because +the current task's world age had been raised. In general, certain syntactic constructs (in particular most definitions) +will raise the current task's world age to the latest global world age, thus making all changes +(both from the current task and any concurrently executing other tasks) visible. The following statements +raise the current world age: + +1. An explicit invocation of `Core.@latestworld` +2. The start of every top-level statement +3. The start of every REPL prompt +4. Any type or struct definition +5. Any method definition +6. Any constant declaration +7. Any global variable declaration (but not a global variable assignment) +8. Any `using`, `import`, `export` or `public` statement +9. Certain other macros like [`@eval`](@ref) (depends on the macro implementation) + +Note, however, that the current task's world age may only ever be permanently incremented at +top level. As a general rule, using any of the above statements in non-top-level scope is a syntax error: + +```julia-repl +julia> f() = Core.@latestworld +ERROR: syntax: World age increment not at top level +Stacktrace: + [1] top-level scope + @ REPL[5]:1 +``` + +When it isn't (for example for `@eval`), the world age side effect is ignored. + +As a result of these rules, Julia may assume that the world age does not change +within the execution of an ordinary function. + +```julia +function my_function() + before = Base.tls_world_age() + # Any arbitrary code + after = Base.tls_world_age() + @assert before === after # always true +end +``` + +This is the key invariant that allows Julia to optimize based on the current state +of its global data structures, while still having the well-defined ability to change +these data structures. + +## Temporarily raising the world age using `invokelatest` + +As described above, it is not possible to permanently raise the world age for the remainder of +a `Task`'s execution unless the task is executing top-level statements. However, it is possible to +temporarily change the world age in a scoped manner using `invokelatest`: + +```jldoctest +julia> function f end +f (generic function with 0 methods) + +julia> begin + Core.eval(@__MODULE__, :(f() = 1)) + invokelatest(f) + end +1 +``` + +`invokelatest` will temporarily raise the current task's world age to the latest global world age (at +entry to `invokelatest`) and execute the provided function. Note that the world age will return +to its prior value upon exit from `invokelatest`. + +## World age and const struct redefinitions + +The semantics described above for method redefinition also apply to redefinition of constants: + +```jldoctest +julia> const x = 1 +1 + +julia> get_const() = x +get_const (generic function with 1 method) + +julia> begin + @show get_const() + Core.eval(@__MODULE__, :(const x = 2)) + @show get_const() + Core.@latestworld + @show get_const() + end +get_const() = 1 +get_const() = 1 +get_const() = 2 +2 +``` + +However, for the avoidance of doubt, they do not apply to ordinary assignment to global variables, which becomes visible immediately: +```jldoctest +julia> global y = 1 +1 + +julia> get_global() = y +get_global (generic function with 1 method) + +julia> begin + @show get_global() + Core.eval(@__MODULE__, :(y = 2)) + @show get_global() + end +get_global() = 1 +get_global() = 2 +2 +``` + +One particular special case of constant reassignment is the redefinition of struct types: + +```jldoctest; filter = r"\@world\(MyStruct, \d+\:\d+\)" +julia> struct MyStruct + x::Int + end + +julia> const one_field = MyStruct(1) +MyStruct(1) + +julia> struct MyStruct + x::Int + y::Float64 + end + +julia> const two_field = MyStruct(1, 2.0) +MyStruct(1, 2.0) + +julia> one_field +@world(MyStruct, 38452:38455)(1) + +julia> two_field +MyStruct(1, 2.0) +``` + +Internally the two definitions of `MyStruct` are entirely separate types. However, +after the new `MyStruct` type is defined, there is no longer any default binding +for the original definition of `MyStruct`. To nevertheless facilitate access to +these types, the special [`@world`](@ref) macro may be used to access the meaning +of a name in a previous world. However, this facility is intended for introspection +only and in particular note that world age numbers are not stable across precompilation +and should in general be treated opaquely. + +### Binding partition introspection + +In certain cases, it can be helpful to introspect the system's understanding of what +a binding means in any particular world age. The default display printing of `Core.Binding` +provides a helpful summary (e.g. on the `MyStruct` example from above): + +```julia-repl +julia> convert(Core.Binding, GlobalRef(@__MODULE__, :MyStruct)) +Binding Main.MyStruct + 38456:∞ - constant binding to MyStruct + 38452:38455 - constant binding to @world(MyStruct, 38452:38455) + 38451:38451 - backdated constant binding to @world(MyStruct, 38452:38455) + 0:38450 - backdated constant binding to @world(MyStruct, 38452:38455) +``` + +## World age and `using`/`import` + +Bindings provided via `using` and `import` also operate via the world age mechanism. +Binding resolution is a stateless function of the `import` and `using` definitions +visible in the current world age. For example: + +```julia-repl +julia> module M1; const x = 1; export x; end + +julia> module M2; const x = 2; export x; end + +julia> using .M1 + +julia> x +1 + +julia> using .M2 + +julia> x +ERROR: UndefVarError: `x` not defined in `Main` +Hint: It looks like two or more modules export different bindings with this name, resulting in ambiguity. Try explicitly importing it from a particular module, or qualifying the name with the module it should come from. + +julia> convert(Core.Binding, GlobalRef(@__MODULE__, :x)) +Binding Main.x + 38458:∞ - ambiguous binding - guard entry + 38457:38457 - implicit `using` resolved to constant 1 +``` + +## World age capture + +Certain language features capture the current task's world age. Perhaps the most common of +these is creation of new tasks. Newly created tasks will inherit the creating task's local +world age at creation time and will retain said world age (unless explicitly raised) even +if the originating tasks raises its world age: + +```julia-repl +julia> const x = 1 + +julia> t = @task (wait(); println("Running now"); x); + +julia> const x = 2 + +julia> schedule(t); +Running now + +julia> x +2 + +julia> fetch(t) +1 +``` + +In addition to tasks, opaque closures also capture their world age at creation. See [`Base.Experimental.@opaque`](@ref). + +```@docs +Base.@world +Base.get_world_counter +Base.tls_world_age +Base.invoke_in_world +Base.Experimental.@opaque +``` diff --git a/src/Makefile b/src/Makefile index 6d4a842c7a711..e859acc765354 100644 --- a/src/Makefile +++ b/src/Makefile @@ -56,7 +56,7 @@ SRCS := \ jltypes gf typemap smallintset ast builtins module interpreter symbol \ dlload sys init task array genericmemory staticdata toplevel jl_uv datatype \ simplevector runtime_intrinsics precompile jloptions mtarraylist \ - threading scheduler stackwalk \ + threading scheduler stackwalk null_sysimage \ method jlapi signal-handling safepoint timing subtype rtutils \ crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage runtime_ccall engine \ $(GC_SRCS) @@ -75,7 +75,7 @@ GC_CODEGEN_SRCS += llvm-late-gc-lowering-stock endif CODEGEN_SRCS := codegen jitlayers aotcompile debuginfo disasm llvm-simdloop \ llvm-pass-helpers llvm-ptls \ - llvm-lower-handlers llvm-propagate-addrspaces \ + llvm-lower-handlers llvm-propagate-addrspaces null_sysimage \ llvm-multiversioning llvm-alloc-opt llvm-alloc-helpers cgmemmgr llvm-remove-addrspaces \ llvm-remove-ni llvm-julia-licm llvm-demote-float16 llvm-cpufeatures pipeline llvm_api \ $(GC_CODEGEN_SRCS) @@ -184,7 +184,7 @@ endif CLANG_LDFLAGS := $(LLVM_LDFLAGS) ifeq ($(OS), Darwin) CLANG_LDFLAGS += -Wl,-undefined,dynamic_lookup -OSLIBS += -Wl,-U,__dyld_atfork_parent -Wl,-U,__dyld_atfork_prepare -Wl,-U,__dyld_dlopen_atfork_parent -Wl,-U,__dyld_dlopen_atfork_prepare -Wl,-U,_jl_image_pointers -Wl,-U,_jl_system_image_data -Wl,-U,_jl_system_image_size +OSLIBS += -Wl,-U,__dyld_atfork_parent -Wl,-U,__dyld_atfork_prepare -Wl,-U,__dyld_dlopen_atfork_parent -Wl,-U,__dyld_dlopen_atfork_prepare LIBJULIA_PATH_REL := @rpath/libjulia else LIBJULIA_PATH_REL := libjulia diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 275a8004be2b9..e3c52e4796788 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -798,9 +798,12 @@ void *jl_emit_native_impl(jl_array_t *codeinfos, LLVMOrcThreadSafeModuleRef llvm compiled_functions[codeinst] = {std::move(result_m), std::move(decls)}; } else { - jl_value_t *sig = jl_array_ptr_ref(codeinfos, ++i); - assert(jl_is_type(item) && jl_is_type(sig)); - jl_generate_ccallable(clone.getModuleUnlocked(), nullptr, item, sig, params); + assert(jl_is_simplevector(item)); + jl_value_t *rt = jl_svecref(item, 0); + jl_value_t *sig = jl_svecref(item, 1); + jl_value_t *nameval = jl_svec_len(item) == 2 ? jl_nothing : jl_svecref(item, 2); + assert(jl_is_type(rt) && jl_is_type(sig)); + jl_generate_ccallable(clone.getModuleUnlocked(), nameval, rt, sig, params); } } // finally, make sure all referenced methods get fixed up, particularly if the user declined to compile them @@ -968,10 +971,18 @@ static GlobalVariable *emit_shard_table(Module &M, Type *T_size, Type *T_psize, return tables_gv; } +static Function *emit_pgcstack_default_func(Module &M, Type *T_ptr) { + auto FT = FunctionType::get(T_ptr, false); + auto F = Function::Create(FT, GlobalValue::InternalLinkage, "pgcstack_default_func", &M); + llvm::IRBuilder<> builder(BasicBlock::Create(M.getContext(), "top", F)); + builder.CreateRet(Constant::getNullValue(T_ptr)); + return F; +} + // See src/processor.h for documentation about this table. Corresponds to jl_image_ptls_t. -static GlobalVariable *emit_ptls_table(Module &M, Type *T_size, Type *T_psize) { +static GlobalVariable *emit_ptls_table(Module &M, Type *T_size, Type *T_ptr) { std::array ptls_table{ - new GlobalVariable(M, T_size, false, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), "jl_pgcstack_func_slot"), + new GlobalVariable(M, T_ptr, false, GlobalValue::ExternalLinkage, emit_pgcstack_default_func(M, T_ptr), "jl_pgcstack_func_slot"), new GlobalVariable(M, T_size, false, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), "jl_pgcstack_key_slot"), new GlobalVariable(M, T_size, false, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), "jl_tls_offset"), }; @@ -979,7 +990,7 @@ static GlobalVariable *emit_ptls_table(Module &M, Type *T_size, Type *T_psize) { cast(gv)->setVisibility(GlobalValue::HiddenVisibility); cast(gv)->setDSOLocal(true); } - auto ptls_table_arr = ConstantArray::get(ArrayType::get(T_psize, ptls_table.size()), ptls_table); + auto ptls_table_arr = ConstantArray::get(ArrayType::get(T_ptr, ptls_table.size()), ptls_table); auto ptls_table_gv = new GlobalVariable(M, ptls_table_arr->getType(), false, GlobalValue::ExternalLinkage, ptls_table_arr, "jl_ptls_table"); ptls_table_gv->setVisibility(GlobalValue::HiddenVisibility); @@ -2178,6 +2189,7 @@ void jl_dump_native_impl(void *native_code, Type *T_size = DL.getIntPtrType(Context); Type *T_psize = T_size->getPointerTo(); + Type *T_ptr = PointerType::get(Context, 0); auto FT = FunctionType::get(Type::getInt8Ty(Context)->getPointerTo()->getPointerTo(), {}, false); auto F = Function::Create(FT, Function::ExternalLinkage, "get_jl_RTLD_DEFAULT_handle_addr", metadataM); @@ -2201,7 +2213,7 @@ void jl_dump_native_impl(void *native_code, builder.CreateRet(ConstantInt::get(T_int32, 1)); } if (imaging_mode) { - auto specs = jl_get_llvm_clone_targets(); + auto specs = jl_get_llvm_clone_targets(jl_options.cpu_target); const uint32_t base_flags = has_veccall ? JL_TARGET_VEC_CALL : 0; SmallVector data; auto push_i32 = [&] (uint32_t v) { @@ -2220,7 +2232,7 @@ void jl_dump_native_impl(void *native_code, GlobalVariable::InternalLinkage, value, "jl_dispatch_target_ids"); auto shards = emit_shard_table(metadataM, T_size, T_psize, threads); - auto ptls = emit_ptls_table(metadataM, T_size, T_psize); + auto ptls = emit_ptls_table(metadataM, T_size, T_ptr); auto header = emit_image_header(metadataM, threads, nfvars, ngvars); auto AT = ArrayType::get(T_size, sizeof(jl_small_typeof) / sizeof(void*)); auto jl_small_typeof_copy = new GlobalVariable(metadataM, AT, false, diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index 707811089489b..04f38fb9091be 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -70,7 +70,7 @@ JL_DLLEXPORT uint32_t jl_get_LLVM_VERSION_fallback(void) return 0; } -JL_DLLEXPORT int jl_compile_extern_c_fallback(LLVMOrcThreadSafeModuleRef llvmmod, void *params, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) +JL_DLLEXPORT int jl_compile_extern_c_fallback(LLVMOrcThreadSafeModuleRef llvmmod, void *params, void *sysimg, jl_value_t *name, jl_value_t *declrt, jl_value_t *sigt) { // Assume we were able to register the ccallable with the JIT. The // fact that we didn't is not observable since we cannot compile diff --git a/src/codegen.cpp b/src/codegen.cpp index 8c9f61d511a06..24d30e0ba8eb3 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -7696,14 +7696,14 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con // do codegen to create a C-callable alias/wrapper, or if sysimg_handle is set, // restore one from a loaded system image. -const char *jl_generate_ccallable(Module *llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms) +const char *jl_generate_ccallable(Module *llvmmod, jl_value_t *nameval, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms) { ++GeneratedCCallables; jl_datatype_t *ft = (jl_datatype_t*)jl_tparam0(sigt); assert(jl_is_datatype(ft)); jl_value_t *ff = ft->instance; assert(ff); - const char *name = jl_symbol_name(ft->name->mt->name); + const char *name = !jl_is_string(nameval) ? jl_symbol_name(ft->name->mt->name) : jl_string_data(nameval); jl_value_t *crt = declrt; if (jl_is_abstract_ref_type(declrt)) { declrt = jl_tparam0(declrt); @@ -7725,25 +7725,12 @@ const char *jl_generate_ccallable(Module *llvmmod, void *sysimg_handle, jl_value function_sig_t sig("cfunction", lcrt, crt, toboxed, false, argtypes, NULL, false, CallingConv::C, false, ¶ms); if (sig.err_msg.empty()) { - if (sysimg_handle) { - // restore a ccallable from the system image - void *addr; - int found = jl_dlsym(sysimg_handle, name, &addr, 0); - if (found) - add_named_global(name, addr); - else { - err = jl_get_exceptionf(jl_errorexception_type, "%s not found in sysimg", name); - jl_throw(err); - } - } - else { - //Safe b/c params holds context lock - Function *cw = gen_cfun_wrapper(llvmmod, params, sig, ff, name, declrt, sigt, NULL, NULL, NULL); - auto alias = GlobalAlias::create(cw->getValueType(), cw->getType()->getAddressSpace(), - GlobalValue::ExternalLinkage, name, cw, llvmmod); - if (params.TargetTriple.isOSBinFormatCOFF()) { - alias->setDLLStorageClass(GlobalValue::DLLStorageClassTypes::DLLExportStorageClass); - } + //Safe b/c params holds context lock + Function *cw = gen_cfun_wrapper(llvmmod, params, sig, ff, name, declrt, sigt, NULL, NULL, NULL); + auto alias = GlobalAlias::create(cw->getValueType(), cw->getType()->getAddressSpace(), + GlobalValue::ExternalLinkage, name, cw, llvmmod); + if (params.TargetTriple.isOSBinFormatCOFF()) { + alias->setDLLStorageClass(GlobalValue::DLLStorageClassTypes::DLLExportStorageClass); } JL_GC_POP(); return name; diff --git a/src/dlload.c b/src/dlload.c index 7a25903d471aa..2c7ee08229394 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -240,21 +240,32 @@ JL_DLLEXPORT int jl_dlclose(void *handle) JL_NOTSAFEPOINT #endif } -void *jl_find_dynamic_library_by_addr(void *symbol) { +void *jl_find_dynamic_library_by_addr(void *symbol, int throw_err) { void *handle; #ifdef _OS_WINDOWS_ if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)symbol, (HMODULE*)&handle)) { - jl_error("could not load base module"); + if (throw_err) + jl_error("could not load base module"); + return NULL; } #else Dl_info info; if (!dladdr(symbol, &info) || !info.dli_fname) { - jl_error("could not load base module"); + if (throw_err) + jl_error("could not load base module"); + return NULL; } handle = dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_LOCAL); - dlclose(handle); // Undo ref count increment from `dlopen` +#if !defined(__APPLE__) + if (handle == RTLD_DEFAULT && (RTLD_DEFAULT != NULL || dlerror() == NULL)) { + // We loaded the executable but got RTLD_DEFAULT back, ask for a real handle instead + handle = dlopen("", RTLD_NOW | RTLD_NOLOAD | RTLD_LOCAL); + } +#endif + if (handle != NULL) + dlclose(handle); // Undo ref count increment from `dlopen` #endif return handle; } @@ -277,7 +288,7 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, // modname == NULL is a sentinel value requesting the handle of libjulia-internal if (modname == NULL) - return jl_find_dynamic_library_by_addr(&jl_load_dynamic_library); + return jl_find_dynamic_library_by_addr(&jl_load_dynamic_library, throw_err); abspath = jl_isabspath(modname); is_atpath = 0; diff --git a/src/gf.c b/src/gf.c index 8c8882ea8cf5e..f70fc08c8ec47 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3229,6 +3229,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *matc } // compile-time method lookup +// intersect types with the MT, and return a single compileable specialization that covers the intersection. jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world, int mt_cache) { if (jl_has_free_typevars((jl_value_t*)types)) @@ -4644,7 +4645,7 @@ JL_DLLEXPORT void jl_typeinf_timing_end(uint64_t start, int is_recompile) } // declare a C-callable entry point; called during code loading from the toplevel -JL_DLLEXPORT void jl_extern_c(jl_value_t *declrt, jl_tupletype_t *sigt) +JL_DLLEXPORT void jl_extern_c(jl_value_t *name, jl_value_t *declrt, jl_tupletype_t *sigt) { // validate arguments. try to do as many checks as possible here to avoid // throwing errors later during codegen. @@ -4675,7 +4676,10 @@ JL_DLLEXPORT void jl_extern_c(jl_value_t *declrt, jl_tupletype_t *sigt) if (!jl_is_method(meth)) jl_error("@ccallable: could not find requested method"); JL_GC_PUSH1(&meth); - meth->ccallable = jl_svec2(declrt, (jl_value_t*)sigt); + if (name == jl_nothing) + meth->ccallable = jl_svec2(declrt, (jl_value_t*)sigt); + else + meth->ccallable = jl_svec3(declrt, (jl_value_t*)sigt, name); jl_gc_wb(meth, meth->ccallable); JL_GC_POP(); } diff --git a/src/init.c b/src/init.c index 3ac4f8f7d770b..83cff02872b96 100644 --- a/src/init.c +++ b/src/init.c @@ -534,169 +534,6 @@ int jl_isabspath(const char *in) JL_NOTSAFEPOINT return 0; // relative path } -static char *absrealpath(const char *in, int nprefix) -{ // compute an absolute realpath location, so that chdir doesn't change the file reference - // ignores (copies directly over) nprefix characters at the start of abspath -#ifndef _OS_WINDOWS_ - char *out = realpath(in + nprefix, NULL); - if (out) { - if (nprefix > 0) { - size_t sz = strlen(out) + 1; - char *cpy = (char*)malloc_s(sz + nprefix); - memcpy(cpy, in, nprefix); - memcpy(cpy + nprefix, out, sz); - free(out); - out = cpy; - } - } - else { - size_t sz = strlen(in + nprefix) + 1; - if (in[nprefix] == PATHSEPSTRING[0]) { - out = (char*)malloc_s(sz + nprefix); - memcpy(out, in, sz + nprefix); - } - else { - size_t path_size = JL_PATH_MAX; - char *path = (char*)malloc_s(JL_PATH_MAX); - if (uv_cwd(path, &path_size)) { - jl_error("fatal error: unexpected error while retrieving current working directory"); - } - out = (char*)malloc_s(path_size + 1 + sz + nprefix); - memcpy(out, in, nprefix); - memcpy(out + nprefix, path, path_size); - out[nprefix + path_size] = PATHSEPSTRING[0]; - memcpy(out + nprefix + path_size + 1, in + nprefix, sz); - free(path); - } - } -#else - // GetFullPathName intentionally errors if given an empty string so manually insert `.` to invoke cwd - char *in2 = (char*)malloc_s(JL_PATH_MAX); - if (strlen(in) - nprefix == 0) { - memcpy(in2, in, nprefix); - in2[nprefix] = '.'; - in2[nprefix+1] = '\0'; - in = in2; - } - DWORD n = GetFullPathName(in + nprefix, 0, NULL, NULL); - if (n <= 0) { - jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); - } - char *out = (char*)malloc_s(n + nprefix); - DWORD m = GetFullPathName(in + nprefix, n, out + nprefix, NULL); - if (n != m + 1) { - jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); - } - memcpy(out, in, nprefix); - free(in2); -#endif - return out; -} - -// create an absolute-path copy of the input path format string -// formed as `joinpath(replace(pwd(), "%" => "%%"), in)` -// unless `in` starts with `%` -static const char *absformat(const char *in) -{ - if (in[0] == '%' || jl_isabspath(in)) - return in; - // get an escaped copy of cwd - size_t path_size = JL_PATH_MAX; - char path[JL_PATH_MAX]; - if (uv_cwd(path, &path_size)) { - jl_error("fatal error: unexpected error while retrieving current working directory"); - } - size_t sz = strlen(in) + 1; - size_t i, fmt_size = 0; - for (i = 0; i < path_size; i++) - fmt_size += (path[i] == '%' ? 2 : 1); - char *out = (char*)malloc_s(fmt_size + 1 + sz); - fmt_size = 0; - for (i = 0; i < path_size; i++) { // copy-replace pwd portion - char c = path[i]; - out[fmt_size++] = c; - if (c == '%') - out[fmt_size++] = '%'; - } - out[fmt_size++] = PATHSEPSTRING[0]; // path sep - memcpy(out + fmt_size, in, sz); // copy over format, including nul - return out; -} - -static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) -{ - // this function resolves the paths in jl_options to absolute file locations as needed - // and it replaces the pointers to `julia_bindir`, `julia_bin`, `image_file`, and output file paths - // it may fail, print an error, and exit(1) if any of these paths are longer than JL_PATH_MAX - // - // note: if you care about lost memory, you should call the appropriate `free()` function - // on the original pointer for each `char*` you've inserted into `jl_options`, after - // calling `julia_init()` - char *free_path = (char*)malloc_s(JL_PATH_MAX); - size_t path_size = JL_PATH_MAX; - if (uv_exepath(free_path, &path_size)) { - jl_error("fatal error: unexpected error while retrieving exepath"); - } - if (path_size >= JL_PATH_MAX) { - jl_error("fatal error: jl_options.julia_bin path too long"); - } - jl_options.julia_bin = (char*)malloc_s(path_size + 1); - memcpy((char*)jl_options.julia_bin, free_path, path_size); - ((char*)jl_options.julia_bin)[path_size] = '\0'; - if (!jl_options.julia_bindir) { - jl_options.julia_bindir = getenv("JULIA_BINDIR"); - if (!jl_options.julia_bindir) { - jl_options.julia_bindir = dirname(free_path); - } - } - if (jl_options.julia_bindir) - jl_options.julia_bindir = absrealpath(jl_options.julia_bindir, 0); - free(free_path); - free_path = NULL; - if (jl_options.image_file) { - if (rel == JL_IMAGE_JULIA_HOME && !jl_isabspath(jl_options.image_file)) { - // build time path, relative to JULIA_BINDIR - free_path = (char*)malloc_s(JL_PATH_MAX); - int n = snprintf(free_path, JL_PATH_MAX, "%s" PATHSEPSTRING "%s", - jl_options.julia_bindir, jl_options.image_file); - if (n >= JL_PATH_MAX || n < 0) { - jl_error("fatal error: jl_options.image_file path too long"); - } - jl_options.image_file = free_path; - } - if (jl_options.image_file) - jl_options.image_file = absrealpath(jl_options.image_file, 0); - if (free_path) { - free(free_path); - free_path = NULL; - } - } - if (jl_options.outputo) - jl_options.outputo = absrealpath(jl_options.outputo, 0); - if (jl_options.outputji) - jl_options.outputji = absrealpath(jl_options.outputji, 0); - if (jl_options.outputbc) - jl_options.outputbc = absrealpath(jl_options.outputbc, 0); - if (jl_options.outputasm) - jl_options.outputasm = absrealpath(jl_options.outputasm, 0); - if (jl_options.machine_file) - jl_options.machine_file = absrealpath(jl_options.machine_file, 0); - if (jl_options.output_code_coverage) - jl_options.output_code_coverage = absformat(jl_options.output_code_coverage); - if (jl_options.tracked_path) - jl_options.tracked_path = absrealpath(jl_options.tracked_path, 0); - - const char **cmdp = jl_options.cmds; - if (cmdp) { - for (; *cmdp; cmdp++) { - const char *cmd = *cmdp; - if (cmd[0] == 'L') { - *cmdp = absrealpath(cmd, 1); - } - } - } -} - JL_DLLEXPORT int jl_is_file_tracked(jl_sym_t *path) { const char* path_ = jl_symbol_name(path); @@ -722,8 +559,85 @@ static void restore_fp_env(void) jl_error("Failed to configure floating point environment"); } } +static NOINLINE void _finish_jl_init_(jl_image_buf_t sysimage, jl_ptls_t ptls, jl_task_t *ct) +{ + JL_TIMING(JULIA_INIT, JULIA_INIT); + + if (sysimage.kind == JL_IMAGE_KIND_SO) + jl_gc_notify_image_load(sysimage.data, sysimage.size); + + if (jl_options.cpu_target == NULL) + jl_options.cpu_target = "native"; + + // Parse image, perform relocations, and init JIT targets, etc. + jl_image_t parsed_image = jl_init_processor_sysimg(sysimage, jl_options.cpu_target); + + jl_init_codegen(); + jl_init_common_symbols(); + + if (sysimage.kind != JL_IMAGE_KIND_NONE) { + // Load the .ji or .so sysimage + jl_restore_system_image(&parsed_image, sysimage); + } else { + // No sysimage provided, init a minimal environment + jl_init_types(); + jl_global_roots_list = (jl_genericmemory_t*)jl_an_empty_memory_any; + jl_global_roots_keyset = (jl_genericmemory_t*)jl_an_empty_memory_any; + } + + jl_init_flisp(); + jl_init_serializer(); + + if (sysimage.kind == JL_IMAGE_KIND_NONE) { + jl_top_module = jl_core_module; + jl_init_intrinsic_functions(); + jl_init_primitives(); + jl_init_main_module(); + jl_load(jl_core_module, "boot.jl"); + jl_current_task->world_age = jl_atomic_load_acquire(&jl_world_counter); + post_boot_hooks(); + } + + if (jl_base_module == NULL) { + // nthreads > 1 requires code in Base + jl_atomic_store_relaxed(&jl_n_threads, 1); + jl_n_markthreads = 0; + jl_n_sweepthreads = 0; + jl_n_gcthreads = 0; + jl_n_threads_per_pool[JL_THREADPOOL_ID_INTERACTIVE] = 0; + jl_n_threads_per_pool[JL_THREADPOOL_ID_DEFAULT] = 1; + } else { + jl_current_task->world_age = jl_atomic_load_acquire(&jl_world_counter); + post_image_load_hooks(); + } + jl_start_threads(); + jl_start_gc_threads(); + uv_barrier_wait(&thread_init_done); + + jl_gc_enable(1); + + if ((sysimage.kind != JL_IMAGE_KIND_NONE) && + (!jl_generating_output() || jl_options.incremental) && jl_module_init_order) { + jl_array_t *init_order = jl_module_init_order; + JL_GC_PUSH1(&init_order); + jl_module_init_order = NULL; + int i, l = jl_array_nrows(init_order); + for (i = 0; i < l; i++) { + jl_value_t *mod = jl_array_ptr_ref(init_order, i); + jl_module_run_initializer((jl_module_t*)mod); + } + JL_GC_POP(); + } + + if (jl_options.trim) { + jl_entrypoint_mis = (arraylist_t *)malloc_s(sizeof(arraylist_t)); + arraylist_new(jl_entrypoint_mis, 0); + } + + if (jl_options.handle_signals == JL_OPTIONS_HANDLE_SIGNALS_ON) + jl_install_sigint_handler(); +} -static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_task_t *ct); JL_DLLEXPORT int jl_default_debug_info_kind; JL_DLLEXPORT jl_cgparams_t jl_default_cgparams = { @@ -751,12 +665,12 @@ static void init_global_mutexes(void) { JL_MUTEX_INIT(&profile_show_peek_cond_lock, "profile_show_peek_cond_lock"); } -JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) +JL_DLLEXPORT void jl_init_(jl_image_buf_t sysimage) { // initialize many things, in no particular order // but generally running from simple platform things to optional // configuration features - jl_init_timing(); + // Make sure we finalize the tls callback before starting any threads. (void)jl_get_pgcstack(); @@ -808,8 +722,8 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) void *stack_lo, *stack_hi; jl_init_stack_limits(1, &stack_lo, &stack_hi); - jl_libjulia_internal_handle = jl_find_dynamic_library_by_addr(&jl_load_dynamic_library); - jl_libjulia_handle = jl_find_dynamic_library_by_addr(&jl_any_type); + jl_libjulia_internal_handle = jl_find_dynamic_library_by_addr(&jl_load_dynamic_library, /* throw_err */ 1); + jl_libjulia_handle = jl_find_dynamic_library_by_addr(&jl_any_type, /* throw_err */ 1); #ifdef _OS_WINDOWS_ jl_exe_handle = GetModuleHandleA(NULL); jl_RTLD_DEFAULT_handle = jl_libjulia_internal_handle; @@ -861,83 +775,7 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) jl_task_t *ct = jl_init_root_task(ptls, stack_lo, stack_hi); #pragma GCC diagnostic pop JL_GC_PROMISE_ROOTED(ct); - _finish_julia_init(rel, ptls, ct); -} - -static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_task_t *ct) -{ - JL_TIMING(JULIA_INIT, JULIA_INIT); - jl_resolve_sysimg_location(rel); - // loads sysimg if available, and conditionally sets jl_options.cpu_target - if (rel == JL_IMAGE_IN_MEMORY) { - jl_set_sysimg_so(jl_exe_handle); - jl_options.image_file = jl_options.julia_bin; - } - else if (jl_options.image_file) - jl_preload_sysimg_so(jl_options.image_file); - if (jl_options.cpu_target == NULL) - jl_options.cpu_target = "native"; - jl_init_codegen(); - - jl_init_common_symbols(); - if (jl_options.image_file) { - jl_restore_system_image(jl_options.image_file); - } else { - jl_init_types(); - jl_global_roots_list = (jl_genericmemory_t*)jl_an_empty_memory_any; - jl_global_roots_keyset = (jl_genericmemory_t*)jl_an_empty_memory_any; - } - - jl_init_flisp(); - jl_init_serializer(); - - if (!jl_options.image_file) { - jl_top_module = jl_core_module; - jl_init_intrinsic_functions(); - jl_init_primitives(); - jl_init_main_module(); - jl_load(jl_core_module, "boot.jl"); - jl_current_task->world_age = jl_atomic_load_acquire(&jl_world_counter); - post_boot_hooks(); - } - - if (jl_base_module == NULL) { - // nthreads > 1 requires code in Base - jl_atomic_store_relaxed(&jl_n_threads, 1); - jl_n_markthreads = 0; - jl_n_sweepthreads = 0; - jl_n_gcthreads = 0; - jl_n_threads_per_pool[JL_THREADPOOL_ID_INTERACTIVE] = 0; - jl_n_threads_per_pool[JL_THREADPOOL_ID_DEFAULT] = 1; - } else { - jl_current_task->world_age = jl_atomic_load_acquire(&jl_world_counter); - post_image_load_hooks(); - } - jl_start_threads(); - jl_start_gc_threads(); - uv_barrier_wait(&thread_init_done); - - jl_gc_enable(1); - - if (jl_options.image_file && (!jl_generating_output() || jl_options.incremental) && jl_module_init_order) { - jl_array_t *init_order = jl_module_init_order; - JL_GC_PUSH1(&init_order); - jl_module_init_order = NULL; - int i, l = jl_array_nrows(init_order); - for (i = 0; i < l; i++) { - jl_value_t *mod = jl_array_ptr_ref(init_order, i); - jl_module_run_initializer((jl_module_t*)mod); - } - JL_GC_POP(); - } - - if (jl_options.trim) { - jl_entrypoint_mis = (arraylist_t *)malloc_s(sizeof(arraylist_t)); - arraylist_new(jl_entrypoint_mis, 0); - } - - if (jl_options.handle_signals == JL_OPTIONS_HANDLE_SIGNALS_ON) - jl_install_sigint_handler(); + _finish_jl_init_(sysimage, ptls, ct); } #ifdef __cplusplus diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 9b32464b3e11d..4537d069e4a44 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -1281,7 +1281,7 @@ namespace { #endif #endif uint32_t target_flags = 0; - auto target = jl_get_llvm_target(jl_generating_output(), target_flags); + auto target = jl_get_llvm_target(jl_options.cpu_target, jl_generating_output(), target_flags); auto &TheCPU = target.first; SmallVector targetFeatures(target.second.begin(), target.second.end()); std::string errorstr; @@ -2022,6 +2022,13 @@ void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) TSM = (*JITPointers)(std::move(TSM)); auto Lock = TSM.getContext().getLock(); Module &M = *TSM.getModuleUnlocked(); + + for (auto &f : M) { + if (!f.isDeclaration()){ + jl_timing_puts(JL_TIMING_DEFAULT_BLOCK, f.getName().str().c_str()); + } + } + // Treat this as if one of the passes might contain a safepoint // even though that shouldn't be the case and might be unwise Expected> Obj = CompileLayer.getCompiler()(M); diff --git a/src/jitlayers.h b/src/jitlayers.h index 50e82b7849b80..b411febd792b8 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -284,7 +284,7 @@ struct jl_codegen_params_t { ~jl_codegen_params_t() JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE = default; }; -const char *jl_generate_ccallable(Module *llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms); +const char *jl_generate_ccallable(Module *llvmmod, jl_value_t *nameval, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms); jl_llvm_functions_t jl_emit_code( orc::ThreadSafeModule &M, diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 6d4077a60111b..60a5256af2b58 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -39,6 +39,7 @@ XX(jl_atomic_store_bits) \ XX(jl_atomic_storeonce_bits) \ XX(jl_atomic_swap_bits) \ + XX(jl_autoinit_and_adopt_thread) \ XX(jl_backtrace_from_here) \ XX(jl_base_relative_to) \ XX(jl_bitcast) \ @@ -239,9 +240,11 @@ XX(jl_hrtime) \ XX(jl_idtable_rehash) \ XX(jl_init) \ + XX(jl_init_) \ XX(jl_init_options) \ XX(jl_init_restored_module) \ - XX(jl_init_with_image) \ + XX(jl_init_with_image_file) \ + XX(jl_init_with_image_handle) \ XX(jl_install_sigint_handler) \ XX(jl_instantiate_type_in_env) \ XX(jl_instantiate_unionall) \ @@ -358,7 +361,7 @@ XX(jl_pointerset) \ XX(jl_pop_handler) \ XX(jl_pop_handler_noexcept) \ - XX(jl_preload_sysimg_so) \ + XX(jl_preload_sysimg) \ XX(jl_prepend_cwd) \ XX(jl_printf) \ XX(jl_print_backtrace) \ @@ -388,7 +391,6 @@ XX(jl_restore_incremental) \ XX(jl_restore_package_image_from_file) \ XX(jl_restore_system_image) \ - XX(jl_restore_system_image_data) \ XX(jl_rethrow) \ XX(jl_rethrow_other) \ XX(jl_running_on_valgrind) \ diff --git a/src/jlapi.c b/src/jlapi.c index 53585555b8a23..47d5b84fa5606 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -26,11 +26,13 @@ extern "C" { #include #endif +static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel, const char* julia_bindir); + /** * @brief Check if Julia is already initialized. * - * Determine if Julia has been previously initialized - * via `jl_init` or `jl_init_with_image`. + * Determine if Julia has been previously initialized via `jl_init` or + * `jl_init_with_image_file` or `jl_init_with_image_handle`. * * @return Returns 1 if Julia is initialized, 0 otherwise. */ @@ -68,6 +70,20 @@ JL_DLLEXPORT void jl_set_ARGS(int argc, char **argv) } } +JL_DLLEXPORT void jl_init_with_image_handle(void *handle) { + if (jl_is_initialized()) + return; + + const char *image_path = jl_pathname_for_handle(handle); + jl_options.image_file = image_path; + + jl_resolve_sysimg_location(JL_IMAGE_JULIA_HOME, NULL); + jl_image_buf_t sysimage = jl_set_sysimg_so(handle); + + jl_init_(sysimage); + + jl_exception_clear(); +} /** * @brief Initialize Julia with a specified system image file. * @@ -82,18 +98,21 @@ JL_DLLEXPORT void jl_set_ARGS(int argc, char **argv) * @param image_path The path of a system image file (*.so). Interpreted as relative to julia_bindir * or the default Julia home directory if not an absolute path. */ -JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, - const char *image_path) +JL_DLLEXPORT void jl_init_with_image_file(const char *julia_bindir, + const char *image_path) { if (jl_is_initialized()) return; - libsupport_init(); - jl_options.julia_bindir = julia_bindir; if (image_path != NULL) jl_options.image_file = image_path; else jl_options.image_file = jl_get_default_sysimg_path(); - julia_init(JL_IMAGE_JULIA_HOME); + + jl_resolve_sysimg_location(JL_IMAGE_JULIA_HOME, julia_bindir); + jl_image_buf_t sysimage = jl_preload_sysimg(jl_options.image_file); + + jl_init_(sysimage); + jl_exception_clear(); } @@ -105,18 +124,7 @@ JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, */ JL_DLLEXPORT void jl_init(void) { - char *libbindir = NULL; -#ifdef _OS_WINDOWS_ - libbindir = strdup(jl_get_libdir()); -#else - (void)asprintf(&libbindir, "%s" PATHSEPSTRING ".." PATHSEPSTRING "%s", jl_get_libdir(), "bin"); -#endif - if (!libbindir) { - printf("jl_init unable to find libjulia!\n"); - abort(); - } - jl_init_with_image(libbindir, jl_get_default_sysimg_path()); - free(libbindir); + jl_init_with_image_file(NULL, jl_get_default_sysimg_path()); } static void _jl_exception_clear(jl_task_t *ct) JL_NOTSAFEPOINT @@ -1102,7 +1110,14 @@ JL_DLLEXPORT int jl_repl_entrypoint(int argc, char *argv[]) jl_error("Failed to self-execute"); } - julia_init(jl_options.image_file_specified ? JL_IMAGE_CWD : JL_IMAGE_JULIA_HOME); + JL_IMAGE_SEARCH rel = jl_options.image_file_specified ? JL_IMAGE_CWD : JL_IMAGE_JULIA_HOME; + jl_resolve_sysimg_location(rel, NULL); + jl_image_buf_t sysimage = { JL_IMAGE_KIND_NONE }; + if (jl_options.image_file) + sysimage = jl_preload_sysimg(jl_options.image_file); + + jl_init_(sysimage); + if (lisp_prompt) { jl_current_task->world_age = jl_get_world_counter(); jl_lisp_prompt(); @@ -1113,6 +1128,180 @@ JL_DLLEXPORT int jl_repl_entrypoint(int argc, char *argv[]) return ret; } +// create an absolute-path copy of the input path format string +// formed as `joinpath(replace(pwd(), "%" => "%%"), in)` +// unless `in` starts with `%` +static const char *absformat(const char *in) +{ + if (in[0] == '%' || jl_isabspath(in)) + return in; + // get an escaped copy of cwd + size_t path_size = JL_PATH_MAX; + char path[JL_PATH_MAX]; + if (uv_cwd(path, &path_size)) { + jl_error("fatal error: unexpected error while retrieving current working directory"); + } + size_t sz = strlen(in) + 1; + size_t i, fmt_size = 0; + for (i = 0; i < path_size; i++) + fmt_size += (path[i] == '%' ? 2 : 1); + char *out = (char*)malloc_s(fmt_size + 1 + sz); + fmt_size = 0; + for (i = 0; i < path_size; i++) { // copy-replace pwd portion + char c = path[i]; + out[fmt_size++] = c; + if (c == '%') + out[fmt_size++] = '%'; + } + out[fmt_size++] = PATHSEPSTRING[0]; // path sep + memcpy(out + fmt_size, in, sz); // copy over format, including nul + return out; +} + +static char *absrealpath(const char *in, int nprefix) +{ // compute an absolute realpath location, so that chdir doesn't change the file reference + // ignores (copies directly over) nprefix characters at the start of abspath +#ifndef _OS_WINDOWS_ + char *out = realpath(in + nprefix, NULL); + if (out) { + if (nprefix > 0) { + size_t sz = strlen(out) + 1; + char *cpy = (char*)malloc_s(sz + nprefix); + memcpy(cpy, in, nprefix); + memcpy(cpy + nprefix, out, sz); + free(out); + out = cpy; + } + } + else { + size_t sz = strlen(in + nprefix) + 1; + if (in[nprefix] == PATHSEPSTRING[0]) { + out = (char*)malloc_s(sz + nprefix); + memcpy(out, in, sz + nprefix); + } + else { + size_t path_size = JL_PATH_MAX; + char *path = (char*)malloc_s(JL_PATH_MAX); + if (uv_cwd(path, &path_size)) { + jl_error("fatal error: unexpected error while retrieving current working directory"); + } + out = (char*)malloc_s(path_size + 1 + sz + nprefix); + memcpy(out, in, nprefix); + memcpy(out + nprefix, path, path_size); + out[nprefix + path_size] = PATHSEPSTRING[0]; + memcpy(out + nprefix + path_size + 1, in + nprefix, sz); + free(path); + } + } +#else + // GetFullPathName intentionally errors if given an empty string so manually insert `.` to invoke cwd + char *in2 = (char*)malloc_s(JL_PATH_MAX); + if (strlen(in) - nprefix == 0) { + memcpy(in2, in, nprefix); + in2[nprefix] = '.'; + in2[nprefix+1] = '\0'; + in = in2; + } + DWORD n = GetFullPathName(in + nprefix, 0, NULL, NULL); + if (n <= 0) { + jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); + } + char *out = (char*)malloc_s(n + nprefix); + DWORD m = GetFullPathName(in + nprefix, n, out + nprefix, NULL); + if (n != m + 1) { + jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); + } + memcpy(out, in, nprefix); + free(in2); +#endif + return out; +} + +static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel, const char* julia_bindir) +{ + libsupport_init(); + jl_init_timing(); + + // this function resolves the paths in jl_options to absolute file locations as needed + // and it replaces the pointers to `julia_bindir`, `julia_bin`, `image_file`, and output file paths + // it may fail, print an error, and exit(1) if any of these paths are longer than JL_PATH_MAX + // + // note: if you care about lost memory, you should call the appropriate `free()` function + // on the original pointer for each `char*` you've inserted into `jl_options`, after + // calling `jl_init_()` + char *free_path = (char*)malloc_s(JL_PATH_MAX); + size_t path_size = JL_PATH_MAX; + if (uv_exepath(free_path, &path_size)) { + jl_error("fatal error: unexpected error while retrieving exepath"); + } + if (path_size >= JL_PATH_MAX) { + jl_error("fatal error: jl_options.julia_bin path too long"); + } + jl_options.julia_bin = (char*)malloc_s(path_size + 1); + memcpy((char*)jl_options.julia_bin, free_path, path_size); + ((char*)jl_options.julia_bin)[path_size] = '\0'; + if (julia_bindir == NULL) { + jl_options.julia_bindir = getenv("JULIA_BINDIR"); + if (!jl_options.julia_bindir) { +#ifdef _OS_WINDOWS_ + jl_options.julia_bindir = strdup(jl_get_libdir()); +#else + int written = asprintf((char**)&jl_options.julia_bindir, "%s" PATHSEPSTRING ".." PATHSEPSTRING "%s", jl_get_libdir(), "bin"); + if (written < 0) + abort(); // unexpected: memory allocation failed +#endif + } + } else { + jl_options.julia_bindir = julia_bindir; + } + if (jl_options.julia_bindir) + jl_options.julia_bindir = absrealpath(jl_options.julia_bindir, 0); + free(free_path); + free_path = NULL; + if (jl_options.image_file) { + if (rel == JL_IMAGE_JULIA_HOME && !jl_isabspath(jl_options.image_file)) { + // build time path, relative to JULIA_BINDIR + free_path = (char*)malloc_s(JL_PATH_MAX); + int n = snprintf(free_path, JL_PATH_MAX, "%s" PATHSEPSTRING "%s", + jl_options.julia_bindir, jl_options.image_file); + if (n >= JL_PATH_MAX || n < 0) { + jl_error("fatal error: jl_options.image_file path too long"); + } + jl_options.image_file = free_path; + } + if (jl_options.image_file) + jl_options.image_file = absrealpath(jl_options.image_file, 0); + if (free_path) { + free(free_path); + free_path = NULL; + } + } + if (jl_options.outputo) + jl_options.outputo = absrealpath(jl_options.outputo, 0); + if (jl_options.outputji) + jl_options.outputji = absrealpath(jl_options.outputji, 0); + if (jl_options.outputbc) + jl_options.outputbc = absrealpath(jl_options.outputbc, 0); + if (jl_options.outputasm) + jl_options.outputasm = absrealpath(jl_options.outputasm, 0); + if (jl_options.machine_file) + jl_options.machine_file = absrealpath(jl_options.machine_file, 0); + if (jl_options.output_code_coverage) + jl_options.output_code_coverage = absformat(jl_options.output_code_coverage); + if (jl_options.tracked_path) + jl_options.tracked_path = absrealpath(jl_options.tracked_path, 0); + + const char **cmdp = jl_options.cmds; + if (cmdp) { + for (; *cmdp; cmdp++) { + const char *cmd = *cmdp; + if (cmd[0] == 'L') { + *cmdp = absrealpath(cmd, 1); + } + } + } +} + #ifdef __cplusplus } #endif diff --git a/src/jltypes.c b/src/jltypes.c index 1fe6aeb0fffb9..0a14832b8ae6b 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3267,9 +3267,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(5, jl_any_type, jl_ulong_type, jl_ulong_type, jl_any_type/*jl_binding_partition_type*/, jl_ulong_type), jl_emptysvec, 0, 1, 0); - const static uint32_t binding_partition_atomicfields[] = { 0b01111 }; // Set fields 1, 2, 3, 4 as atomic + const static uint32_t binding_partition_atomicfields[] = { 0b01110 }; // Set fields 2, 3, 4 as atomic jl_binding_partition_type->name->atomicfields = binding_partition_atomicfields; - const static uint32_t binding_partition_constfields[] = { 0x100001 }; // Set fields 1, 5 as constant + const static uint32_t binding_partition_constfields[] = { 0b10001 }; // Set fields 1, 5 as constant jl_binding_partition_type->name->constfields = binding_partition_constfields; jl_binding_type = diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index b5d90a24ea13f..3c003b04e4ce4 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2583,11 +2583,13 @@ (typ-svec (caddr sig-svec)) (tvars (cddr (cadddr sig-svec))) (argtypes (cdddr typ-svec)) - (functionloc (cadr (caddddr sig-svec)))) - (let* ((argtype (foldl (lambda (var ex) `(call (core UnionAll) ,var ,ex)) - (expand-forms `(curly (core Tuple) ,@argtypes)) - (reverse tvars)))) - `(_opaque_closure ,(or argt argtype) ,rt_lb ,rt_ub ,isva ,(length argtypes) ,allow-partial ,functionloc ,lam)))) + (functionloc (cadr (caddddr sig-svec))) + (argtype (foldl (lambda (var ex) `(call (core UnionAll) ,var ,ex)) + (expand-forms `(curly (core Tuple) ,@argtypes)) + (reverse tvars))) + (argtype (or argt argtype)) + (argtype (if (null? stmts) argtype `(block ,@stmts ,argtype)))) + `(_opaque_closure ,argtype ,rt_lb ,rt_ub ,isva ,(length argtypes) ,allow-partial ,functionloc ,lam))) 'block (lambda (e) @@ -5175,6 +5177,14 @@ f(x) = yt(x) (define (set-lineno! lineinfo num) (set-car! (cddr lineinfo) num)) +;; note that the 'list and 'block atoms make all lists 1-indexed. +;; returns a 5-element vector containing: +;; code: `(block ,@(n expressions)) +;; locs: list of line-table index, where code[i] has lineinfo line-table[locs[i]] +;; line-table: list of `(lineinfo file.jl 123 0)' +;; ssavalue-table: table of (ssa-num . code-index) +;; where ssavalue references in `code` need this remapping +;; label-table: table of (label . code-index) (define (compact-ir body file line) (let ((code '(block)) (locs '(list)) @@ -5281,7 +5291,7 @@ f(x) = yt(x) e) ((ssavalue? e) (let ((idx (get ssavalue-table (cadr e) #f))) - (if (not idx) (begin (prn e) (prn lam) (error "ssavalue with no def"))) + (if (not idx) (error "internal bug: ssavalue with no def")) `(ssavalue ,idx))) ((eq? (car e) 'goto) `(goto ,(get label-table (cadr e)))) diff --git a/src/julia.h b/src/julia.h index 6310ee38a2c60..a3fcc516997df 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1943,6 +1943,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void); JL_DLLEXPORT jl_svec_t *jl_svec(size_t n, ...) JL_MAYBE_UNROOTED; JL_DLLEXPORT jl_svec_t *jl_svec1(void *a); JL_DLLEXPORT jl_svec_t *jl_svec2(void *a, void *b); +JL_DLLEXPORT jl_svec_t *jl_svec3(void *a, void *b, void *c); JL_DLLEXPORT jl_svec_t *jl_alloc_svec(size_t n); JL_DLLEXPORT jl_svec_t *jl_alloc_svec_uninit(size_t n); JL_DLLEXPORT jl_svec_t *jl_svec_copy(jl_svec_t *a); @@ -2219,11 +2220,30 @@ typedef enum { JL_IMAGE_IN_MEMORY = 2 } JL_IMAGE_SEARCH; +typedef enum { + JL_IMAGE_KIND_NONE = 0, + JL_IMAGE_KIND_JI, + JL_IMAGE_KIND_SO, +} jl_image_kind_t; + +// A loaded, but unparsed .ji or .so image file +typedef struct { + jl_image_kind_t kind; + void *handle; + const void *pointers; // jl_image_pointers_t * + const char *data; + size_t size; + uint64_t base; +} jl_image_buf_t; + +struct _jl_image_t; +typedef struct _jl_image_t jl_image_t; + JL_DLLIMPORT const char *jl_get_libdir(void); -JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel); JL_DLLEXPORT void jl_init(void); -JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, - const char *image_path); +JL_DLLEXPORT void jl_init_with_image_file(const char *julia_bindir, + const char *image_path); +JL_DLLEXPORT void jl_init_with_image_handle(void *handle); JL_DLLEXPORT const char *jl_get_default_sysimg_path(void); JL_DLLEXPORT int jl_is_initialized(void); JL_DLLEXPORT void jl_atexit_hook(int status); @@ -2235,11 +2255,10 @@ JL_DLLEXPORT const char *jl_pathname_for_handle(void *handle); JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void); JL_DLLEXPORT int jl_deserialize_verify_header(ios_t *s); -JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname); -JL_DLLEXPORT void jl_set_sysimg_so(void *handle); +JL_DLLEXPORT jl_image_buf_t jl_preload_sysimg(const char *fname); +JL_DLLEXPORT jl_image_buf_t jl_set_sysimg_so(void *handle); JL_DLLEXPORT void jl_create_system_image(void **, jl_array_t *worklist, bool_t emit_split, ios_t **s, ios_t **z, jl_array_t **udeps, int64_t *srctextpos); -JL_DLLEXPORT void jl_restore_system_image(const char *fname); -JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len); +JL_DLLEXPORT void jl_restore_system_image(jl_image_t *image, jl_image_buf_t buf); JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int complete, const char *pkgimage); JL_DLLEXPORT jl_value_t *jl_object_top_module(jl_value_t* v) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index 72534aadafbb3..46d6404edc86f 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -3,6 +3,7 @@ #ifndef JL_INTERNAL_H #define JL_INTERNAL_H +#include "dtypes.h" #include "options.h" #include "julia_assert.h" #include "julia_locks.h" @@ -192,6 +193,9 @@ void JL_UV_LOCK(void); extern _Atomic(unsigned) _threadedregion; extern _Atomic(uint16_t) io_loop_tid; +JL_DLLEXPORT void jl_init_(jl_image_buf_t sysimage); +JL_DLLEXPORT void jl_enter_threaded_region(void); +JL_DLLEXPORT void jl_exit_threaded_region(void); int jl_running_under_rr(int recheck) JL_NOTSAFEPOINT; //-------------------------------------------------- @@ -1605,7 +1609,7 @@ void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT void *jl_get_library_(const char *f_lib, int throw_err); -void *jl_find_dynamic_library_by_addr(void *symbol); +void *jl_find_dynamic_library_by_addr(void *symbol, int throw_err); #define jl_get_library(f_lib) jl_get_library_(f_lib, 1) JL_DLLEXPORT void *jl_load_and_lookup(const char *f_lib, const char *f_name, _Atomic(void*) *hnd); JL_DLLEXPORT void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name); @@ -2008,17 +2012,6 @@ jl_sym_t *_jl_symbol(const char *str, size_t len) JL_NOTSAFEPOINT; #define JL_GC_ASSERT_LIVE(x) (void)(x) #endif -#ifdef _OS_WINDOWS_ -// On Windows, weak symbols do not default to 0 due to a GCC bug -// (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90826), use symbol -// aliases with a known value instead. -#define JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(sym) __attribute__((weak,alias(#sym))) -#define JL_WEAK_SYMBOL_DEFAULT(sym) &sym -#else -#define JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(sym) __attribute__((weak)) -#define JL_WEAK_SYMBOL_DEFAULT(sym) NULL -#endif - JL_DLLEXPORT uint32_t jl_crc32c(uint32_t crc, const char *buf, size_t len); // -- exports from codegen -- // diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index a76d076ebd6f3..02f77298513ee 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -216,7 +216,7 @@ static void annotate_module_clones(Module &M) { if (auto maybe_specs = get_target_specs(M)) { specs = std::move(*maybe_specs); } else { - auto full_specs = jl_get_llvm_clone_targets(); + auto full_specs = jl_get_llvm_clone_targets(jl_options.cpu_target); specs.reserve(full_specs.size()); for (auto &spec: full_specs) { specs.push_back(TargetSpec::fromSpec(spec)); diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index 15f5a5574a6d3..97c9d5a9551f5 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -170,11 +170,11 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter, *CFGModified = true; // emit slow branch code CallInst *adopt = cast(pgcstack->clone()); - Function *adoptFunc = M->getFunction(XSTR(jl_adopt_thread)); + Function *adoptFunc = M->getFunction(XSTR(jl_autoinit_and_adopt_thread)); if (adoptFunc == NULL) { adoptFunc = Function::Create(pgcstack_getter->getFunctionType(), pgcstack_getter->getLinkage(), pgcstack_getter->getAddressSpace(), - XSTR(jl_adopt_thread), M); + XSTR(jl_autoinit_and_adopt_thread), M); adoptFunc->copyAttributesFrom(pgcstack_getter); adoptFunc->copyMetadata(pgcstack_getter, 0); } diff --git a/src/module.c b/src/module.c index 54df487bcef22..9a456de0b11d8 100644 --- a/src/module.c +++ b/src/module.c @@ -136,15 +136,18 @@ static jl_binding_partition_t *jl_implicit_import_resolved(jl_binding_t *b, stru if (jl_is_binding_partition(gap.parent)) { // Check if we can merge this into the previous binding partition jl_binding_partition_t *prev = (jl_binding_partition_t *)gap.parent; + assert(new_max_world != ~(size_t)0); // It is inconsistent to have a gap with `gap.parent` set, but max_world == ~(size_t)0 size_t expected_prev_min_world = new_max_world + 1; if (prev->restriction == resolution.binding_or_const && prev->kind == new_kind) { +retry: if (!jl_atomic_cmpswap(&prev->min_world, &expected_prev_min_world, new_min_world)) { if (expected_prev_min_world <= new_min_world) { return prev; } else if (expected_prev_min_world <= new_max_world) { - // Concurrent modification by another thread - bail. - return NULL; + // Concurrent modification of the partition. However, our lookup is still valid, + // so we should still be able to extend the partition. + goto retry; } // There remains a gap - proceed } else { @@ -154,7 +157,7 @@ static jl_binding_partition_t *jl_implicit_import_resolved(jl_binding_t *b, stru for (;;) { // We've updated the previous partition - check if we've closed a gap size_t next_max_world = jl_atomic_load_relaxed(&next->max_world); - if (next_max_world == expected_prev_min_world-1 && next->kind == new_kind && next->restriction == resolution.binding_or_const) { + if (next_max_world >= expected_prev_min_world-1 && next->kind == new_kind && next->restriction == resolution.binding_or_const) { if (jl_atomic_cmpswap(&prev->min_world, &expected_prev_min_world, next_min_world)) { jl_binding_partition_t *nextnext = jl_atomic_load_relaxed(&next->next); if (!jl_atomic_cmpswap(&prev->next, &next, nextnext)) { @@ -370,7 +373,7 @@ JL_DLLEXPORT void jl_update_loaded_bpart(jl_binding_t *b, jl_binding_partition_t bpart->kind = resolution.ultimate_kind; } -STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_(jl_binding_t *b JL_PROPAGATES_ROOT, jl_value_t *parent, _Atomic(jl_binding_partition_t *)*insert, size_t world, modstack_t *st) JL_GLOBALLY_ROOTED +STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_(jl_binding_t *b JL_PROPAGATES_ROOT, jl_value_t *parent, _Atomic(jl_binding_partition_t *)*insert, size_t world, size_t max_world, modstack_t *st) JL_GLOBALLY_ROOTED { assert(jl_is_binding(b)); struct implicit_search_gap gap; @@ -378,7 +381,7 @@ STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_(jl_binding_t *b gap.insert = insert; gap.inherited_flags = 0; gap.min_world = 0; - gap.max_world = ~(size_t)0; + gap.max_world = max_world; while (1) { gap.replace = jl_atomic_load_relaxed(gap.insert); jl_binding_partition_t *bpart = jl_get_binding_partition__(b, world, &gap); @@ -395,15 +398,14 @@ jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) if (!b) return NULL; // Duplicate the code for the entry frame for branch prediction - return jl_get_binding_partition_(b, (jl_value_t*)b, &b->partitions, world, NULL); + return jl_get_binding_partition_(b, (jl_value_t*)b, &b->partitions, world, ~(size_t)0, NULL); } jl_binding_partition_t *jl_get_binding_partition_with_hint(jl_binding_t *b, jl_binding_partition_t *prev, size_t world) JL_GLOBALLY_ROOTED { // Helper for getting a binding partition for an older world after we've already looked up the partition for a newer world assert(b); - // TODO: Is it possible for a concurrent lookup to have expanded this bpart, making this false? - assert(jl_atomic_load_relaxed(&prev->min_world) > world); - return jl_get_binding_partition_(b, (jl_value_t*)prev, &prev->next, world, NULL); + size_t prev_min_world = jl_atomic_load_relaxed(&prev->min_world); + return jl_get_binding_partition_(b, (jl_value_t*)prev, &prev->next, world, prev_min_world-1, NULL); } jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b, size_t min_world, size_t max_world) { @@ -481,7 +483,8 @@ static jl_module_t *jl_new_module__(jl_sym_t *name, jl_module_t *parent) m->istopmod = 0; m->uuid = uuid_zero; static unsigned int mcounter; // simple counter backup, in case hrtime is not incrementing - m->build_id.lo = jl_hrtime() + (++mcounter); + // TODO: this is used for ir decompression and is liable to hash collisions so use more of the bits + m->build_id.lo = bitmix(jl_hrtime() + (++mcounter), jl_rand()); if (!m->build_id.lo) m->build_id.lo++; // build id 0 is invalid m->build_id.hi = ~(uint64_t)0; @@ -842,12 +845,15 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT { + if (jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) + jl_undefined_var_error(b->globalref->name, (jl_value_t*)b->globalref->mod); jl_safe_printf( "WARNING: Detected access to binding `%s.%s` in a world prior to its definition world.\n" " Julia 1.12 has introduced more strict world age semantics for global bindings.\n" " !!! This code may malfunction under Revise.\n" " !!! This code will error in future versions of Julia.\n" - "Hint: Add an appropriate `invokelatest` around the access to this binding.\n", + "Hint: Add an appropriate `invokelatest` around the access to this binding.\n" + "To make this warning an error, and hence obtain a stack trace, use `julia --depwarn=error`.\n", jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name)); } diff --git a/src/null_sysimage.c b/src/null_sysimage.c new file mode 100644 index 0000000000000..386842f0c4e77 --- /dev/null +++ b/src/null_sysimage.c @@ -0,0 +1,15 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include +#include "processor.h" + +/** + * These symbols support statically linking the sysimage with libjulia-internal. + * + * Here we provide dummy definitions that are used when these are not linked + * together (the default build configuration). The 0 value of jl_system_image_size + * is used as a sentinel to indicate that the sysimage should be loaded externally. + **/ +char jl_system_image_data = 0; +size_t jl_system_image_size = 0; +jl_image_pointers_t jl_image_pointers = { 0 }; diff --git a/src/precompile_utils.c b/src/precompile_utils.c index c602a15c1fb74..84619b714b624 100644 --- a/src/precompile_utils.c +++ b/src/precompile_utils.c @@ -275,7 +275,7 @@ static void *jl_precompile_(jl_array_t *m, int external_linkage) } else { assert(jl_is_simplevector(item)); - assert(jl_svec_len(item) == 2); + assert(jl_svec_len(item) == 2 || jl_svec_len(item) == 3); jl_array_ptr_1d_push(m2, item); } } diff --git a/src/processor.cpp b/src/processor.cpp index 3edebcc2f3ae6..6f95ee7f3790a 100644 --- a/src/processor.cpp +++ b/src/processor.cpp @@ -504,7 +504,8 @@ static inline llvm::SmallVector, 0> parse_cmdline(const char *option, F &&feature_cb) { if (!option) - option = "native"; + abort(); + llvm::SmallVector, 0> res; TargetData arg{}; auto reset_arg = [&] { @@ -612,36 +613,29 @@ parse_cmdline(const char *option, F &&feature_cb) // Cached version of command line parsing template -static inline llvm::SmallVector, 0> &get_cmdline_targets(F &&feature_cb) +static inline llvm::SmallVector, 0> &get_cmdline_targets(const char *cpu_target, F &&feature_cb) { static llvm::SmallVector, 0> targets = - parse_cmdline(jl_options.cpu_target, std::forward(feature_cb)); + parse_cmdline(cpu_target, std::forward(feature_cb)); return targets; } -extern "C" { -void *image_pointers_unavailable; -extern void * JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(image_pointers_unavailable) jl_image_pointers; -} - // Load sysimg, use the `callback` for dispatch and perform all relocations // for the selected target. template -static inline jl_image_t parse_sysimg(void *hdl, F &&callback) +static inline jl_image_t parse_sysimg(jl_image_buf_t image, F &&callback, void *ctx) { JL_TIMING(LOAD_IMAGE, LOAD_Processor); jl_image_t res{}; - const jl_image_pointers_t *pointers; - if (hdl == jl_exe_handle && &jl_image_pointers != JL_WEAK_SYMBOL_DEFAULT(image_pointers_unavailable)) - pointers = (const jl_image_pointers_t *)&jl_image_pointers; - else - jl_dlsym(hdl, "jl_image_pointers", (void**)&pointers, 1); + if (image.kind != JL_IMAGE_KIND_SO) + return res; + const jl_image_pointers_t *pointers = (const jl_image_pointers_t *)image.pointers; const void *ids = pointers->target_data; jl_value_t* rejection_reason = nullptr; JL_GC_PUSH1(&rejection_reason); - uint32_t target_idx = callback(ids, &rejection_reason); + uint32_t target_idx = callback(ctx, ids, &rejection_reason); if (target_idx == UINT32_MAX) { jl_error(jl_string_ptr(rejection_reason)); } @@ -799,17 +793,7 @@ static inline jl_image_t parse_sysimg(void *hdl, F &&callback) res.fptrs.nclones = clones.size(); } -#ifdef _OS_WINDOWS_ - res.base = (intptr_t)hdl; -#else - Dl_info dlinfo; - if (dladdr((void*)pointers, &dlinfo) != 0) { - res.base = (intptr_t)dlinfo.dli_fbase; - } - else { - res.base = 0; - } -#endif + res.base = image.base; { void *pgcstack_func_slot = pointers->ptls->pgcstack_func_slot; @@ -1029,7 +1013,7 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) } extern "C" JL_DLLEXPORT jl_value_t* jl_reflect_clone_targets() { - auto specs = jl_get_llvm_clone_targets(); + auto specs = jl_get_llvm_clone_targets(jl_options.cpu_target); const uint32_t base_flags = 0; llvm::SmallVector data; auto push_i32 = [&] (uint32_t v) { diff --git a/src/processor.h b/src/processor.h index 82a1121aaf7c4..65b634fd0ba26 100644 --- a/src/processor.h +++ b/src/processor.h @@ -64,6 +64,7 @@ JL_DLLEXPORT int jl_test_cpu_feature(jl_cpu_feature_t feature); static const uint32_t jl_sysimg_tag_mask = 0x80000000u; static const uint32_t jl_sysimg_val_mask = ~((uint32_t)0x80000000u); +// A parsed image file typedef struct _jl_image_fptrs_t { // number of functions uint32_t nptrs; @@ -82,14 +83,14 @@ typedef struct _jl_image_fptrs_t { const uint32_t *clone_idxs; } jl_image_fptrs_t; -typedef struct { +struct _jl_image_t { uint64_t base; const char *gvars_base; const int32_t *gvars_offsets; uint32_t ngvars; jl_image_fptrs_t fptrs; void **jl_small_typeof; -} jl_image_t; +}; // The header for each image // Details important counts about the image @@ -206,8 +207,8 @@ typedef struct { * * Return the data about the function pointers selected. */ -jl_image_t jl_init_processor_sysimg(void *hdl); -jl_image_t jl_init_processor_pkgimg(void *hdl); +jl_image_t jl_init_processor_sysimg(jl_image_buf_t image, const char *cpu_target); +jl_image_t jl_init_processor_pkgimg(jl_image_buf_t image); // Return the name of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); @@ -224,6 +225,18 @@ JL_DLLEXPORT int32_t jl_set_zero_subnormals(int8_t isZero); JL_DLLEXPORT int32_t jl_get_zero_subnormals(void); JL_DLLEXPORT int32_t jl_set_default_nans(int8_t isDefault); JL_DLLEXPORT int32_t jl_get_default_nans(void); + +/** + * System image contents. + * + * These symbols are typically dummy values, unless statically linking + * libjulia-* and the sysimage together (see null_sysimage.c), in which + * case they allow accessing the local copy of the sysimage. + **/ +extern char jl_system_image_data; +extern size_t jl_system_image_size; +extern jl_image_pointers_t jl_image_pointers; + #ifdef __cplusplus } @@ -239,7 +252,7 @@ extern JL_DLLEXPORT bool jl_processor_print_help; * If the detected/specified CPU name is not available on the LLVM version specified, * a fallback CPU name will be used. Unsupported features will be ignored. */ -extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) JL_NOTSAFEPOINT; +extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(const char *cpu_target, bool imaging, uint32_t &flags) JL_NOTSAFEPOINT; /** * Returns the CPU name and feature string to be used by LLVM disassembler. @@ -263,7 +276,7 @@ struct jl_target_spec_t { /** * Return the list of targets to clone */ -extern "C" JL_DLLEXPORT llvm::SmallVector jl_get_llvm_clone_targets(void) JL_NOTSAFEPOINT; +extern "C" JL_DLLEXPORT llvm::SmallVector jl_get_llvm_clone_targets(const char *cpu_target) JL_NOTSAFEPOINT; // NOLINTEND(clang-diagnostic-return-type-c-linkage) struct FeatureName { const char *name; diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index d28e527ed44e8..66704a718a14d 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1519,7 +1519,7 @@ static inline void disable_depends(FeatureList &features) ::disable_depends(features, Feature::deps, sizeof(Feature::deps) / sizeof(FeatureDep)); } -static const llvm::SmallVector, 0> &get_cmdline_targets(void) +static const llvm::SmallVector, 0> &get_cmdline_targets(const char *cpu_target) { auto feature_cb = [] (const char *str, size_t len, FeatureList &list) { #ifdef _CPU_AARCH64_ @@ -1536,7 +1536,7 @@ static const llvm::SmallVector, 0> &get_cmdline_targets(v set_bit(list, fbit, true); return true; }; - auto &targets = ::get_cmdline_targets(feature_cb); + auto &targets = ::get_cmdline_targets(cpu_target, feature_cb); for (auto &t: targets) { if (auto nname = normalize_cpu_name(t.name)) { t.name = nname; @@ -1599,10 +1599,11 @@ static int max_vector_size(const FeatureList &features) #endif } -static uint32_t sysimg_init_cb(const void *id, jl_value_t **rejection_reason) +static uint32_t sysimg_init_cb(void *ctx, const void *id, jl_value_t **rejection_reason) { // First see what target is requested for the JIT. - auto &cmdline = get_cmdline_targets(); + const char *cpu_target = (const char *)ctx; + auto &cmdline = get_cmdline_targets(cpu_target); TargetData target = arg_target_data(cmdline[0], true); // Then find the best match in the sysimg auto sysimg = deserialize_target_data((const uint8_t*)id); @@ -1626,7 +1627,7 @@ static uint32_t sysimg_init_cb(const void *id, jl_value_t **rejection_reason) return match.best_idx; } -static uint32_t pkgimg_init_cb(const void *id, jl_value_t **rejection_reason JL_REQUIRE_ROOTED_SLOT) +static uint32_t pkgimg_init_cb(void *ctx, const void *id, jl_value_t **rejection_reason JL_REQUIRE_ROOTED_SLOT) { TargetData target = jit_targets.front(); auto pkgimg = deserialize_target_data((const uint8_t*)id); @@ -1639,9 +1640,9 @@ static uint32_t pkgimg_init_cb(const void *id, jl_value_t **rejection_reason JL_ return match.best_idx; } -static void ensure_jit_target(bool imaging) +static void ensure_jit_target(const char *cpu_target, bool imaging) { - auto &cmdline = get_cmdline_targets(); + auto &cmdline = get_cmdline_targets(cpu_target); check_cmdline(cmdline, imaging); if (!jit_targets.empty()) return; @@ -1852,36 +1853,36 @@ JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) #endif } -jl_image_t jl_init_processor_sysimg(void *hdl) +jl_image_t jl_init_processor_sysimg(jl_image_buf_t image, const char *cpu_target) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); - return parse_sysimg(hdl, sysimg_init_cb); + return parse_sysimg(image, sysimg_init_cb, (void *)cpu_target); } -jl_image_t jl_init_processor_pkgimg(void *hdl) +jl_image_t jl_init_processor_pkgimg(jl_image_buf_t image) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); if (jit_targets.size() > 1) jl_error("Expected only one JIT target"); - return parse_sysimg(hdl, pkgimg_init_cb); + return parse_sysimg(image, pkgimg_init_cb, NULL); } JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char *data) { jl_value_t *rejection_reason = NULL; JL_GC_PUSH1(&rejection_reason); - uint32_t match_idx = pkgimg_init_cb(data, &rejection_reason); + uint32_t match_idx = pkgimg_init_cb(NULL, data, &rejection_reason); JL_GC_POP(); if (match_idx == UINT32_MAX) return rejection_reason; return jl_nothing; } -std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) +std::pair> jl_get_llvm_target(const char *cpu_target, bool imaging, uint32_t &flags) { - ensure_jit_target(imaging); + ensure_jit_target(cpu_target, imaging); flags = jit_targets[0].en.flags; return get_llvm_target_vec(jit_targets[0]); } @@ -1900,10 +1901,10 @@ const std::pair &jl_get_llvm_disasm_target(void) } #ifndef __clang_gcanalyzer__ -llvm::SmallVector jl_get_llvm_clone_targets(void) +llvm::SmallVector jl_get_llvm_clone_targets(const char *cpu_target) { - auto &cmdline = get_cmdline_targets(); + auto &cmdline = get_cmdline_targets(cpu_target); check_cmdline(cmdline, true); llvm::SmallVector, 0> image_targets; for (auto &arg: cmdline) { diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index f8d9eb9fd9e73..c8c8feb072345 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -13,12 +13,12 @@ static inline const std::string &host_cpu_name() return name; } -static const llvm::SmallVector, 0> &get_cmdline_targets(void) +static const llvm::SmallVector, 0> &get_cmdline_targets(const char *cpu_target) { auto feature_cb = [] (const char*, size_t, FeatureList<1>&) { return false; }; - return ::get_cmdline_targets<1>(feature_cb); + return ::get_cmdline_targets<1>(cpu_target, feature_cb); } static llvm::SmallVector, 0> jit_targets; @@ -36,10 +36,11 @@ static TargetData<1> arg_target_data(const TargetData<1> &arg, bool require_host return res; } -static uint32_t sysimg_init_cb(const void *id, jl_value_t **rejection_reason) +static uint32_t sysimg_init_cb(void *ctx, const void *id, jl_value_t **rejection_reason) { // First see what target is requested for the JIT. - auto &cmdline = get_cmdline_targets(); + const char *cpu_target = (const char *)ctx; + auto &cmdline = get_cmdline_targets(cpu_target); TargetData<1> target = arg_target_data(cmdline[0], true); // Find the last name match or use the default one. uint32_t best_idx = 0; @@ -54,7 +55,7 @@ static uint32_t sysimg_init_cb(const void *id, jl_value_t **rejection_reason) return best_idx; } -static uint32_t pkgimg_init_cb(const void *id, jl_value_t **rejection_reason) +static uint32_t pkgimg_init_cb(void *ctx, const void *id, jl_value_t **rejection_reason) { TargetData<1> target = jit_targets.front(); // Find the last name match or use the default one. @@ -70,9 +71,9 @@ static uint32_t pkgimg_init_cb(const void *id, jl_value_t **rejection_reason) return best_idx; } -static void ensure_jit_target(bool imaging) +static void ensure_jit_target(const char *cpu_target, bool imaging) { - auto &cmdline = get_cmdline_targets(); + auto &cmdline = get_cmdline_targets(cpu_target); check_cmdline(cmdline, imaging); if (!jit_targets.empty()) return; @@ -115,25 +116,25 @@ get_llvm_target_str(const TargetData<1> &data) using namespace Fallback; -jl_image_t jl_init_processor_sysimg(void *hdl) +jl_image_t jl_init_processor_sysimg(jl_image_buf_t image, const char *cpu_target) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); - return parse_sysimg(hdl, sysimg_init_cb); + return parse_sysimg(image, sysimg_init_cb, (void *)cpu_target); } -jl_image_t jl_init_processor_pkgimg(void *hdl) +jl_image_t jl_init_processor_pkgimg(jl_image_buf_t image) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); if (jit_targets.size() > 1) jl_error("Expected only one JIT target"); - return parse_sysimg(hdl, pkgimg_init_cb); + return parse_sysimg(image, pkgimg_init_cb, NULL); } -std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) +std::pair> jl_get_llvm_target(const char *cpu_target, bool imaging, uint32_t &flags) { - ensure_jit_target(imaging); + ensure_jit_target(cpu_target, imaging); flags = jit_targets[0].en.flags; return get_llvm_target_vec(jit_targets[0]); } @@ -145,10 +146,10 @@ const std::pair &jl_get_llvm_disasm_target(void) return res; } #ifndef __clang_gcanalyzer__ -llvm::SmallVector jl_get_llvm_clone_targets(void) +llvm::SmallVector jl_get_llvm_clone_targets(const char *cpu_target) { - auto &cmdline = get_cmdline_targets(); + auto &cmdline = get_cmdline_targets(cpu_target); check_cmdline(cmdline, true); llvm::SmallVector, 0> image_targets; for (auto &arg: cmdline) { @@ -192,7 +193,7 @@ JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char *data) { jl_value_t *rejection_reason = NULL; JL_GC_PUSH1(&rejection_reason); - uint32_t match_idx = pkgimg_init_cb(data, &rejection_reason); + uint32_t match_idx = pkgimg_init_cb(NULL, data, &rejection_reason); JL_GC_POP(); if (match_idx == UINT32_MAX) return rejection_reason; diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index bf765be160ed2..bd624943083ae 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -809,7 +809,7 @@ static inline void disable_depends(FeatureList &features) ::disable_depends(features, Feature::deps, sizeof(Feature::deps) / sizeof(FeatureDep)); } -static const llvm::SmallVector, 0> &get_cmdline_targets(void) +static const llvm::SmallVector, 0> &get_cmdline_targets(const char *cpu_target) { auto feature_cb = [] (const char *str, size_t len, FeatureList &list) { auto fbit = find_feature_bit(feature_names, nfeature_names, str, len); @@ -818,7 +818,7 @@ static const llvm::SmallVector, 0> &get_cmdline_targets(v set_bit(list, fbit, true); return true; }; - auto &targets = ::get_cmdline_targets(feature_cb); + auto &targets = ::get_cmdline_targets(cpu_target, feature_cb); for (auto &t: targets) { if (auto nname = normalize_cpu_name(t.name)) { t.name = nname; @@ -878,10 +878,11 @@ static int max_vector_size(const FeatureList &features) return 16; } -static uint32_t sysimg_init_cb(const void *id, jl_value_t** rejection_reason) +static uint32_t sysimg_init_cb(void *ctx, const void *id, jl_value_t** rejection_reason) { // First see what target is requested for the JIT. - auto &cmdline = get_cmdline_targets(); + const char *cpu_target = (const char *)ctx; + auto &cmdline = get_cmdline_targets(cpu_target); TargetData target = arg_target_data(cmdline[0], true); // Then find the best match in the sysimg auto sysimg = deserialize_target_data((const uint8_t*)id); @@ -924,7 +925,7 @@ static uint32_t sysimg_init_cb(const void *id, jl_value_t** rejection_reason) return match.best_idx; } -static uint32_t pkgimg_init_cb(const void *id, jl_value_t **rejection_reason) +static uint32_t pkgimg_init_cb(void *ctx, const void *id, jl_value_t **rejection_reason) { TargetData target = jit_targets.front(); auto pkgimg = deserialize_target_data((const uint8_t*)id); @@ -939,9 +940,9 @@ static uint32_t pkgimg_init_cb(const void *id, jl_value_t **rejection_reason) //This function serves as a fallback during bootstrapping, at that point we don't have a sysimage with native code // so we won't call sysimg_init_cb, else this function shouldn't do anything. -static void ensure_jit_target(bool imaging) +static void ensure_jit_target(const char *cpu_target, bool imaging) { - auto &cmdline = get_cmdline_targets(); + auto &cmdline = get_cmdline_targets(cpu_target); check_cmdline(cmdline, imaging); if (!jit_targets.empty()) return; @@ -1084,7 +1085,7 @@ JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char *data) { jl_value_t *rejection_reason = NULL; JL_GC_PUSH1(&rejection_reason); - uint32_t match_idx = pkgimg_init_cb(data, &rejection_reason); + uint32_t match_idx = pkgimg_init_cb(NULL, data, &rejection_reason); JL_GC_POP(); if (match_idx == UINT32_MAX) return rejection_reason; @@ -1101,25 +1102,25 @@ JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) return jl_false; } -jl_image_t jl_init_processor_sysimg(void *hdl) +jl_image_t jl_init_processor_sysimg(jl_image_buf_t image, const char *cpu_target) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); - return parse_sysimg(hdl, sysimg_init_cb); + return parse_sysimg(image, sysimg_init_cb, (void *)cpu_target); } -jl_image_t jl_init_processor_pkgimg(void *hdl) +jl_image_t jl_init_processor_pkgimg(jl_image_buf_t image) { if (jit_targets.empty()) jl_error("JIT targets not initialized"); if (jit_targets.size() > 1) jl_error("Expected only one JIT target"); - return parse_sysimg(hdl, pkgimg_init_cb); + return parse_sysimg(image, pkgimg_init_cb, NULL); } -std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) +std::pair> jl_get_llvm_target(const char *cpu_target, bool imaging, uint32_t &flags) { - ensure_jit_target(imaging); + ensure_jit_target(cpu_target, imaging); flags = jit_targets[0].en.flags; return get_llvm_target_vec(jit_targets[0]); } @@ -1132,9 +1133,10 @@ const std::pair &jl_get_llvm_disasm_target(void) } //This function parses the -C command line to figure out which targets to multiversion to. #ifndef __clang_gcanalyzer__ -llvm::SmallVector jl_get_llvm_clone_targets(void) +llvm::SmallVector jl_get_llvm_clone_targets(const char *cpu_target) { - auto &cmdline = get_cmdline_targets(); + + auto &cmdline = get_cmdline_targets(cpu_target); check_cmdline(cmdline, true); llvm::SmallVector, 0> image_targets; for (auto &arg: cmdline) { diff --git a/src/simplevector.c b/src/simplevector.c index 5f1fd744abd0c..d5b254f4a4409 100644 --- a/src/simplevector.c +++ b/src/simplevector.c @@ -56,6 +56,19 @@ JL_DLLEXPORT jl_svec_t *jl_svec2(void *a, void *b) return v; } +JL_DLLEXPORT jl_svec_t *jl_svec3(void *a, void *b, void *c) +{ + jl_task_t *ct = jl_current_task; + jl_svec_t *v = (jl_svec_t*)jl_gc_alloc(ct->ptls, sizeof(void*) * 4, + jl_simplevector_type); + jl_set_typetagof(v, jl_simplevector_tag, 0); + jl_svec_set_len_unsafe(v, 3); + jl_svec_data(v)[0] = (jl_value_t*)a; + jl_svec_data(v)[1] = (jl_value_t*)b; + jl_svec_data(v)[2] = (jl_value_t*)c; + return v; +} + JL_DLLEXPORT jl_svec_t *jl_alloc_svec_uninit(size_t n) { jl_task_t *ct = jl_current_task; diff --git a/src/staticdata.c b/src/staticdata.c index f24352bc4498f..4704b6527ec30 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -576,7 +576,6 @@ typedef struct { static jl_value_t *jl_bigint_type = NULL; static int gmp_limb_size = 0; -static jl_sym_t *jl_docmeta_sym = NULL; #ifdef _P64 #define RELOC_TAG_OFFSET 61 @@ -647,8 +646,7 @@ typedef struct { } pkgcachesizes; // --- Static Compile --- -static void *jl_sysimg_handle = NULL; -static jl_image_t sysimage; +static jl_image_buf_t jl_sysimage_buf = { JL_IMAGE_KIND_NONE }; static inline uintptr_t *sysimg_gvars(const char *base, const int32_t *offsets, size_t idx) { @@ -660,29 +658,6 @@ JL_DLLEXPORT int jl_running_on_valgrind(void) return RUNNING_ON_VALGRIND; } -void *system_image_data_unavailable; -extern void * JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(system_image_data_unavailable) jl_system_image_data; -extern void * JL_WEAK_SYMBOL_OR_ALIAS_DEFAULT(system_image_data_unavailable) jl_system_image_size; -static void jl_load_sysimg_so(void) -{ - const char *sysimg_data; - assert(sysimage.fptrs.ptrs); // jl_init_processor_sysimg should already be run - if (jl_sysimg_handle == jl_exe_handle && - &jl_system_image_data != JL_WEAK_SYMBOL_DEFAULT(system_image_data_unavailable)) - sysimg_data = (const char*)&jl_system_image_data; - else - jl_dlsym(jl_sysimg_handle, "jl_system_image_data", (void **)&sysimg_data, 1); - size_t *plen; - if (jl_sysimg_handle == jl_exe_handle && - &jl_system_image_size != JL_WEAK_SYMBOL_DEFAULT(system_image_data_unavailable)) - plen = (size_t *)&jl_system_image_size; - else - jl_dlsym(jl_sysimg_handle, "jl_system_image_size", (void **)&plen, 1); - jl_gc_notify_image_load(sysimg_data, *plen); - jl_restore_system_image_data(sysimg_data, *plen); -} - - // --- serializer --- #define NBOX_C 1024 @@ -810,39 +785,31 @@ static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_ jl_queue_for_serialization(s, m->parent); if (!jl_options.strip_metadata) jl_queue_for_serialization(s, m->file); + jl_queue_for_serialization(s, jl_atomic_load_relaxed(&m->bindingkeyset)); if (jl_options.trim) { jl_queue_for_serialization_(s, (jl_value_t*)jl_atomic_load_relaxed(&m->bindings), 0, 1); - } else { - jl_queue_for_serialization(s, jl_atomic_load_relaxed(&m->bindings)); - } - jl_queue_for_serialization(s, jl_atomic_load_relaxed(&m->bindingkeyset)); - if (jl_options.strip_metadata || jl_options.trim) { jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); for (size_t i = 0; i < jl_svec_len(table); i++) { jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; - if (jl_options.strip_metadata) { - jl_sym_t *name = b->globalref->name; - if (name == jl_docmeta_sym && jl_get_binding_value(b)) - record_field_change((jl_value_t**)&b->value, jl_nothing); - } - if (jl_options.trim) { - jl_value_t *val = jl_get_binding_value(b); - // keep binding objects that are defined and ... - if (val && - // ... point to modules ... - (jl_is_module(val) || - // ... or point to __init__ methods ... - !strcmp(jl_symbol_name(b->globalref->name), "__init__") || - // ... or point to Base functions accessed by the runtime - (m == jl_base_module && (!strcmp(jl_symbol_name(b->globalref->name), "wait") || - !strcmp(jl_symbol_name(b->globalref->name), "task_done_hook"))))) { - jl_queue_for_serialization(s, b); - } + jl_value_t *val = jl_get_binding_value_in_world(b, jl_atomic_load_relaxed(&jl_world_counter)); + // keep binding objects that are defined in the latest world and ... + if (val && + // ... point to modules ... + (jl_is_module(val) || + // ... or point to __init__ methods ... + !strcmp(jl_symbol_name(b->globalref->name), "__init__") || + // ... or point to Base functions accessed by the runtime + (m == jl_base_module && (!strcmp(jl_symbol_name(b->globalref->name), "wait") || + !strcmp(jl_symbol_name(b->globalref->name), "task_done_hook"))))) { + jl_queue_for_serialization(s, b); } } } + else { + jl_queue_for_serialization(s, jl_atomic_load_relaxed(&m->bindings)); + } for (size_t i = 0; i < module_usings_length(m); i++) { jl_queue_for_serialization(s, module_usings_getmod(m, i)); @@ -887,40 +854,51 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ } goto done_fields; // for now } - if (s->incremental && jl_is_method_instance(v)) { + if (jl_is_method_instance(v)) { jl_method_instance_t *mi = (jl_method_instance_t*)v; - jl_value_t *def = mi->def.value; - if (needs_uniquing(v, s->query_cache)) { - // we only need 3 specific fields of this (the rest are not used) - jl_queue_for_serialization(s, mi->def.value); - jl_queue_for_serialization(s, mi->specTypes); - jl_queue_for_serialization(s, (jl_value_t*)mi->sparam_vals); - goto done_fields; - } - else if (jl_is_method(def) && jl_object_in_image(def)) { - // we only need 3 specific fields of this (the rest are restored afterward, if valid) - // in particular, cache is repopulated by jl_mi_cache_insert for all foreign function, - // so must not be present here - record_field_change((jl_value_t**)&mi->backedges, NULL); - record_field_change((jl_value_t**)&mi->cache, NULL); + if (s->incremental) { + jl_value_t *def = mi->def.value; + if (needs_uniquing(v, s->query_cache)) { + // we only need 3 specific fields of this (the rest are not used) + jl_queue_for_serialization(s, mi->def.value); + jl_queue_for_serialization(s, mi->specTypes); + jl_queue_for_serialization(s, (jl_value_t*)mi->sparam_vals); + goto done_fields; + } + else if (jl_is_method(def) && jl_object_in_image(def)) { + // we only need 3 specific fields of this (the rest are restored afterward, if valid) + // in particular, cache is repopulated by jl_mi_cache_insert for all foreign function, + // so must not be present here + record_field_change((jl_value_t**)&mi->backedges, NULL); + record_field_change((jl_value_t**)&mi->cache, NULL); + } + else { + assert(!needs_recaching(v, s->query_cache)); + } + // n.b. opaque closures cannot be inspected and relied upon like a + // normal method since they can get improperly introduced by generated + // functions, so if they appeared at all, we will probably serialize + // them wrong and segfault. The jl_code_for_staged function should + // prevent this from happening, so we do not need to detect that user + // error now. } - else { - assert(!needs_recaching(v, s->query_cache)); + } + if (jl_is_mtable(v)) { + jl_methtable_t *mt = (jl_methtable_t*)v; + if (jl_options.trim || jl_options.strip_ir) { + record_field_change((jl_value_t**)&mt->backedges, NULL); } - // n.b. opaque closures cannot be inspected and relied upon like a - // normal method since they can get improperly introduced by generated - // functions, so if they appeared at all, we will probably serialize - // them wrong and segfault. The jl_code_for_staged function should - // prevent this from happening, so we do not need to detect that user - // error now. - } - if (s->incremental && jl_is_binding(v)) { - if (needs_uniquing(v, s->query_cache)) { - jl_binding_t *b = (jl_binding_t*)v; + } + if (jl_is_binding(v)) { + jl_binding_t *b = (jl_binding_t*)v; + if (s->incremental && needs_uniquing(v, s->query_cache)) { jl_queue_for_serialization(s, b->globalref->mod); jl_queue_for_serialization(s, b->globalref->name); goto done_fields; } + if (jl_options.trim || jl_options.strip_ir) { + record_field_change((jl_value_t**)&b->backedges, NULL); + } } if (s->incremental && jl_is_globalref(v)) { jl_globalref_t *gr = (jl_globalref_t*)v; @@ -1061,11 +1039,6 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ else if (jl_typetagis(v, jl_module_tag << 4)) { jl_queue_module_for_serialization(s, (jl_module_t*)v); } - else if (jl_is_binding_partition(v)) { - jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; - jl_queue_for_serialization_(s, bpart->restriction, 1, immediate); - jl_queue_for_serialization_(s, get_replaceable_field((jl_value_t**)&bpart->next, 0), 1, immediate); - } else if (layout->nfields > 0) { if (jl_options.trim) { if (jl_is_method(v)) { @@ -1083,15 +1056,23 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ record_field_change((jl_value_t **)&tn->mt, NULL); } } - else if (jl_is_binding(v)) { - record_field_change((jl_value_t**)&((jl_binding_t*)v)->backedges, NULL); - } + // TODO: prune any partitions and partition data that has been deleted in the current world + //else if (jl_is_binding(v)) { + // jl_binding_t *b = (jl_binding_t*)v; + //} + //else if (jl_is_binding_partition(v)) { + // jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; + //} } char *data = (char*)jl_data_ptr(v); size_t i, np = layout->npointers; + size_t fldidx = 1; for (i = 0; i < np; i++) { uint32_t ptr = jl_ptr_offset(t, i); - int mutabl = t->name->mutabl; + size_t offset = jl_ptr_offset(t, i) * sizeof(jl_value_t*); + while (offset >= (fldidx == layout->nfields ? jl_datatype_size(t) : jl_field_offset(t, fldidx))) + fldidx++; + int mutabl = !jl_field_isconst(t, fldidx - 1); jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], mutabl); jl_queue_for_serialization_(s, fld, 1, immediate); } @@ -1543,7 +1524,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED if (s->incremental) { if (needs_uniquing(v, s->query_cache)) { - if (jl_typetagis(v, jl_binding_type)) { + if (jl_is_binding(v)) { jl_binding_t *b = (jl_binding_t*)v; if (b->globalref == NULL) jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity @@ -1753,32 +1734,6 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED ios_write(s->const_data, (char*)pdata, nb); write_pointer(f); } - else if (jl_is_binding_partition(v)) { - jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; - write_pointerfield(s, bpart->restriction); - size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); - if (s->incremental) { - if (max_world == ~(size_t)0) { - // Still valid. Will be considered to be defined in jl_require_world - // after reload, which is the first world before new code runs. - // We use this as a quick check to determine whether a binding was - // invalidated. If a binding was first defined in or before - // jl_require_world, then we can assume that all precompile processes - // will have seen it consistently. - write_uint(f, jl_require_world); - write_uint(f, max_world); - } else { - // The world will not be reachable after loading - write_uint(f, 1); - write_uint(f, 0); - } - } else { - write_uint(f, jl_atomic_load_relaxed(&bpart->min_world)); - write_uint(f, max_world); - } - write_pointerfield(s, (jl_value_t*)jl_atomic_load_relaxed(&bpart->next)); - write_uint(f, bpart->kind); - } else { // Generic object::DataType serialization by field const char *data = (const char*)v; @@ -1818,9 +1773,12 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } size_t np = t->layout->npointers; + size_t fldidx = 1; for (i = 0; i < np; i++) { size_t offset = jl_ptr_offset(t, i) * sizeof(jl_value_t*); - int mutabl = t->name->mutabl; + while (offset >= (fldidx == nf ? jl_datatype_size(t) : jl_field_offset(t, fldidx))) + fldidx++; + int mutabl = !jl_field_isconst(t, fldidx - 1); jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], mutabl); size_t fld_pos = offset + reloc_offset; if (fld != NULL) { @@ -1840,15 +1798,33 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED jl_typemap_entry_t *newentry = (jl_typemap_entry_t*)&s->s->buf[reloc_offset]; if (jl_atomic_load_relaxed(&newentry->max_world) == ~(size_t)0) { if (jl_atomic_load_relaxed(&newentry->min_world) > 1) { - jl_atomic_store_release(&newentry->min_world, ~(size_t)0); - jl_atomic_store_release(&newentry->max_world, WORLD_AGE_REVALIDATION_SENTINEL); + jl_atomic_store_relaxed(&newentry->min_world, ~(size_t)0); + jl_atomic_store_relaxed(&newentry->max_world, WORLD_AGE_REVALIDATION_SENTINEL); arraylist_push(&s->fixup_objs, (void*)reloc_offset); } } else { // garbage newentry - delete it :( - jl_atomic_store_release(&newentry->min_world, 1); - jl_atomic_store_release(&newentry->max_world, 0); + jl_atomic_store_relaxed(&newentry->min_world, 1); + jl_atomic_store_relaxed(&newentry->max_world, 0); + } + } + else if (s->incremental && jl_is_binding_partition(v)) { + jl_binding_partition_t *newbpart = (jl_binding_partition_t*)&s->s->buf[reloc_offset]; + size_t max_world = jl_atomic_load_relaxed(&newbpart->max_world); + if (max_world == ~(size_t)0) { + // Still valid. Will be considered to be defined in jl_require_world + // after reload, which is the first world before new code runs. + // We use this as a quick check to determine whether a binding was + // invalidated. If a binding was first defined in or before + // jl_require_world, then we can assume that all precompile processes + // will have seen it consistently. + jl_atomic_store_relaxed(&newbpart->min_world, jl_require_world); + } + else { + // The world will not be reachable after loading + jl_atomic_store_relaxed(&newbpart->min_world, 1); + jl_atomic_store_relaxed(&newbpart->max_world, 0); } } else if (jl_is_method(v)) { @@ -2596,6 +2572,7 @@ static void jl_prune_type_cache_linear(jl_svec_t *cache) jl_svecset(cache, ins++, jl_nothing); } + uint_t bindingkey_hash(size_t idx, jl_value_t *data); static void jl_prune_module_bindings(jl_module_t * m) JL_GC_DISABLED @@ -2611,15 +2588,12 @@ static void jl_prune_module_bindings(jl_module_t * m) JL_GC_DISABLED if (ti == jl_nothing) continue; jl_binding_t *ref = ((jl_binding_t*)ti); - if (!((ptrhash_get(&serialization_order, ref) == HT_NOTFOUND) && - (ptrhash_get(&serialization_order, ref->globalref) == HT_NOTFOUND))) { - jl_svecset(bindings, i, jl_nothing); + if (ptrhash_get(&serialization_order, ref) != HT_NOTFOUND) arraylist_push(&bindings_list, ref); - } } - jl_genericmemory_t* bindingkeyset = jl_atomic_load_relaxed(&m->bindingkeyset); + jl_genericmemory_t *bindingkeyset = jl_atomic_load_relaxed(&m->bindingkeyset); _Atomic(jl_genericmemory_t*)bindingkeyset2; - jl_atomic_store_relaxed(&bindingkeyset2,(jl_genericmemory_t*)jl_an_empty_memory_any); + jl_atomic_store_relaxed(&bindingkeyset2, (jl_genericmemory_t*)jl_an_empty_memory_any); jl_svec_t *bindings2 = jl_alloc_svec_uninit(bindings_list.len); for (i = 0; i < bindings_list.len; i++) { jl_binding_t *ref = (jl_binding_t*)bindings_list.items[i]; @@ -2700,7 +2674,7 @@ static void strip_specializations_(jl_method_instance_t *mi) record_field_change((jl_value_t**)&codeinst->debuginfo, (jl_value_t*)jl_nulldebuginfo); codeinst = jl_atomic_load_relaxed(&codeinst->next); } - if (jl_options.strip_ir) { + if (jl_options.trim || jl_options.strip_ir) { record_field_change((jl_value_t**)&mi->backedges, NULL); } } @@ -2773,8 +2747,6 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) static int strip_all_codeinfos_(jl_methtable_t *mt, void *_env) { - if (jl_options.strip_ir && mt->backedges) - record_field_change((jl_value_t**)&mt->backedges, NULL); return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), strip_all_codeinfos__, NULL); } @@ -2783,6 +2755,63 @@ static void jl_strip_all_codeinfos(void) jl_foreach_reachable_mtable(strip_all_codeinfos_, NULL); } +static int strip_module(jl_module_t *m, jl_sym_t *docmeta_sym) +{ + size_t world = jl_atomic_load_relaxed(&jl_world_counter); + jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); + for (size_t i = 0; i < jl_svec_len(table); i++) { + jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); + if ((void*)b == jl_nothing) + break; + jl_sym_t *name = b->globalref->name; + jl_value_t *v = jl_get_binding_value_in_world(b, world); + if (v) { + if (jl_is_module(v)) { + jl_module_t *child = (jl_module_t*)v; + if (child != m && child->parent == m && child->name == name) { + // this is the original/primary binding for the submodule + if (!strip_module(child, docmeta_sym)) + return 0; + } + } + } + if (name == docmeta_sym) { + if (jl_atomic_load_relaxed(&b->value)) + record_field_change((jl_value_t**)&b->value, jl_nothing); + // TODO: this is a pretty stupidly unsound way to do this, but it is way to late here to do this correctly (by calling delete_binding and getting an updated world age then dropping all partitions from older worlds) + jl_binding_partition_t *bp = jl_atomic_load_relaxed(&b->partitions); + while (bp) { + if (jl_bkind_is_defined_constant(jl_binding_kind(bp))) { + // XXX: bp->kind = PARTITION_KIND_UNDEF_CONST; + record_field_change((jl_value_t**)&bp->restriction, NULL); + } + bp = jl_atomic_load_relaxed(&bp->next); + } + } + } + return 1; +} + + +static void jl_strip_all_docmeta(jl_array_t *mod_array) +{ + jl_sym_t *docmeta_sym = NULL; + if (jl_base_module) { + jl_value_t *docs = jl_get_global(jl_base_module, jl_symbol("Docs")); + if (docs && jl_is_module(docs)) { + docmeta_sym = (jl_sym_t*)jl_get_global((jl_module_t*)docs, jl_symbol("META")); + } + } + if (!docmeta_sym) + return; + for (size_t i = 0; i < jl_array_nrows(mod_array); i++) { + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); + assert(jl_is_module(m)); + if (m->parent == m) // some toplevel modules (really just Base) aren't actually + strip_module(m, docmeta_sym); + } +} + // --- entry points --- jl_genericmemory_t *jl_global_roots_list; @@ -2958,8 +2987,10 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, htable_new(&field_replace, 0); htable_new(&bits_replace, 0); // strip metadata and IR when requested - if (jl_options.strip_metadata || jl_options.strip_ir) + if (jl_options.strip_metadata || jl_options.strip_ir) { jl_strip_all_codeinfos(); + jl_strip_all_docmeta(mod_array); + } // collect needed methods and replace method tables that are in the tags array htable_new(&new_methtables, 0); arraylist_t MIs; @@ -3105,12 +3136,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, gmp_limb_size = jl_unbox_long(jl_get_global((jl_module_t*)jl_get_global(jl_base_module, jl_symbol("GMP")), jl_symbol("BITS_PER_LIMB"))) / 8; } - if (jl_base_module) { - jl_value_t *docs = jl_get_global(jl_base_module, jl_symbol("Docs")); - if (docs && jl_is_module(docs)) { - jl_docmeta_sym = (jl_sym_t*)jl_get_global((jl_module_t*)docs, jl_symbol("META")); - } - } jl_genericmemory_t *global_roots_list = NULL; jl_genericmemory_t *global_roots_keyset = NULL; @@ -3169,16 +3194,18 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_queue_for_serialization(&s, global_roots_keyset); jl_serialize_reachable(&s); } - // step 1.5: prune (garbage collect) some special weak references from - // built-in type caches too + // step 1.5: prune (garbage collect) some special weak references known caches for (i = 0; i < serialization_queue.len; i++) { jl_value_t *v = (jl_value_t*)serialization_queue.items[i]; if (jl_options.trim) { - if (jl_is_method(v)){ + if (jl_is_method(v)) { jl_method_t *m = (jl_method_t*)v; jl_value_t *specializations_ = jl_atomic_load_relaxed(&m->specializations); - if (!jl_is_svec(specializations_)) + if (!jl_is_svec(specializations_)) { + if (ptrhash_get(&serialization_order, specializations_) == HT_NOTFOUND) + record_field_change((jl_value_t **)&m->specializations, (jl_value_t*)jl_emptysvec); continue; + } jl_svec_t *specializations = (jl_svec_t *)specializations_; size_t l = jl_svec_len(specializations), i; @@ -3421,8 +3448,8 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli jl_query_cache query_cache; init_query_cache(&query_cache); + mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) if (worklist) { - mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) // Generate _native_data` if (_native_data != NULL) { jl_prepare_serialization_data(mod_array, newly_inferred, &extext_methods, &new_ext_cis, NULL, &query_cache); @@ -3508,33 +3535,107 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src); -// Takes in a path of the form "usr/lib/julia/sys.so" (jl_restore_system_image should be passed the same string) -JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname) +// Takes in a path of the form "usr/lib/julia/sys.so" +JL_DLLEXPORT jl_image_buf_t jl_preload_sysimg(const char *fname) { - if (jl_sysimg_handle) - return; // embedded target already called jl_set_sysimg_so + if (jl_sysimage_buf.kind != JL_IMAGE_KIND_NONE) + return jl_sysimage_buf; char *dot = (char*) strrchr(fname, '.'); int is_ji = (dot && !strcmp(dot, ".ji")); - // Get handle to sys.so - if (!is_ji) // .ji extension => load .ji file only - jl_set_sysimg_so(jl_load_dynamic_library(fname, JL_RTLD_LOCAL | JL_RTLD_NOW, 1)); + if (is_ji) { + // .ji extension => load .ji file only + ios_t f; + + if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) + jl_errorf("System image file \"%s\" not found.", fname); + ios_bufmode(&f, bm_none); + + ios_seek_end(&f); + size_t len = ios_pos(&f); + char *sysimg = (char*)jl_gc_perm_alloc(len, 0, 64, 0); + ios_seek(&f, 0); + + if (ios_readall(&f, sysimg, len) != len) + jl_errorf("Error reading system image file."); + + ios_close(&f); + + jl_sysimage_buf = (jl_image_buf_t) { + .kind = JL_IMAGE_KIND_JI, + .handle = NULL, + .pointers = NULL, + .data = sysimg, + .size = len, + .base = 0, + }; + return jl_sysimage_buf; + } else { + // Get handle to sys.so + return jl_set_sysimg_so(jl_load_dynamic_library(fname, JL_RTLD_LOCAL | JL_RTLD_NOW, 1)); + } } -// Allow passing in a module handle directly, rather than a path -JL_DLLEXPORT void jl_set_sysimg_so(void *handle) +// From a shared library handle, verify consistency and return a jl_image_buf_t +static jl_image_buf_t get_image_buf(void *handle, int is_pkgimage) { + size_t *plen; + const char *data; + const void *pointers; + uint64_t base; + + // verify that the linker resolved the symbols in this image against ourselves (libjulia-internal) void** (*get_jl_RTLD_DEFAULT_handle_addr)(void) = NULL; if (handle != jl_RTLD_DEFAULT_handle) { int symbol_found = jl_dlsym(handle, "get_jl_RTLD_DEFAULT_handle_addr", (void **)&get_jl_RTLD_DEFAULT_handle_addr, 0); if (!symbol_found || (void*)&jl_RTLD_DEFAULT_handle != (get_jl_RTLD_DEFAULT_handle_addr())) - jl_error("System image file failed consistency check: maybe opened the wrong version?"); + jl_error("Image file failed consistency check: maybe opened the wrong version?"); } - if (jl_options.cpu_target == NULL) - jl_options.cpu_target = "native"; - jl_sysimg_handle = handle; - sysimage = jl_init_processor_sysimg(handle); + + // verification passed, lookup the buffer pointers + if (jl_system_image_size == 0 || is_pkgimage) { + // in the usual case, the sysimage was not statically linked to libjulia-internal + // look up the external sysimage symbols via the dynamic linker + jl_dlsym(handle, "jl_system_image_size", (void **)&plen, 1); + jl_dlsym(handle, "jl_system_image_data", (void **)&data, 1); + jl_dlsym(handle, "jl_image_pointers", (void**)&pointers, 1); + } else { + // the sysimage was statically linked directly against libjulia-internal + // use the internal symbols + plen = &jl_system_image_size; + pointers = &jl_image_pointers; + data = &jl_system_image_data; + } + +#ifdef _OS_WINDOWS_ + base = (intptr_t)handle; +#else + Dl_info dlinfo; + if (dladdr((void*)pointers, &dlinfo) != 0) + base = (intptr_t)dlinfo.dli_fbase; + else + base = 0; +#endif + + return (jl_image_buf_t) { + .kind = JL_IMAGE_KIND_SO, + .handle = handle, + .pointers = pointers, + .data = data, + .size = *plen, + .base = base, + }; +} + +// Allow passing in a module handle directly, rather than a path +JL_DLLEXPORT jl_image_buf_t jl_set_sysimg_so(void *handle) +{ + if (jl_sysimage_buf.kind != JL_IMAGE_KIND_NONE) + return jl_sysimage_buf; + + jl_sysimage_buf = get_image_buf(handle, /* is_pkgimage */ 0); + return jl_sysimage_buf; } #ifndef JL_NDEBUG @@ -3641,7 +3742,8 @@ static int all_usings_unchanged_implicit(jl_module_t *mod) return unchanged_implicit; } -static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl_array_t *depmods, uint64_t checksum, +static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, + jl_array_t *depmods, uint64_t checksum, /* outputs */ jl_array_t **restored, jl_array_t **init_order, jl_array_t **extext_methods, jl_array_t **internal_methods, jl_array_t **new_ext_cis, jl_array_t **method_roots_list, @@ -4261,7 +4363,11 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i JL_SIGATOMIC_END(); // Add roots to methods - jl_copy_roots(method_roots_list, jl_worklist_key((jl_array_t*)restored)); + int failed = jl_copy_roots(method_roots_list, jl_worklist_key((jl_array_t*)restored)); + if (failed != 0) { + jl_printf(JL_STDERR, "Error copying roots to methods from Module: %s\n", pkgname); + abort(); + } // Insert method extensions and handle edges int new_methods = jl_array_nrows(extext_methods) > 0; if (!new_methods) { @@ -4311,16 +4417,16 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i return restored; } -static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image, uint32_t checksum) +static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image, void *image_handle, uint32_t checksum) { JL_TIMING(LOAD_IMAGE, LOAD_Sysimg); jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) +JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, jl_image_buf_t buf, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) { ios_t f; - ios_static_buffer(&f, (char*)buf, sz); + ios_static_buffer(&f, (char*)buf.data, buf.size); jl_value_t *ret = jl_restore_package_image_from_stream(pkgimage_handle, &f, image, depmods, completeinfo, pkgname, needs_permalloc); ios_close(&f); return ret; @@ -4339,47 +4445,22 @@ JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *d return ret; } -// TODO: need to enforce that the alignment of the buffer is suitable for vectors -JL_DLLEXPORT void jl_restore_system_image(const char *fname) +JL_DLLEXPORT void jl_restore_system_image(jl_image_t *image, jl_image_buf_t buf) { -#ifndef JL_NDEBUG - char *dot = fname ? (char*)strrchr(fname, '.') : NULL; - int is_ji = (dot && !strcmp(dot, ".ji")); - assert((is_ji || jl_sysimg_handle) && "System image file not preloaded"); -#endif + ios_t f; - if (jl_sysimg_handle) { - // load the pre-compiled sysimage from jl_sysimg_handle - jl_load_sysimg_so(); - } - else { - ios_t f; - if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) - jl_errorf("System image file \"%s\" not found.", fname); - ios_bufmode(&f, bm_none); - JL_SIGATOMIC_BEGIN(); - ios_seek_end(&f); - size_t len = ios_pos(&f); - char *sysimg = (char*)jl_gc_perm_alloc(len, 0, 64, 0); - ios_seek(&f, 0); - if (ios_readall(&f, sysimg, len) != len) - jl_errorf("Error reading system image file."); - ios_close(&f); - uint32_t checksum = jl_crc32c(0, sysimg, len); - ios_static_buffer(&f, sysimg, len); - jl_restore_system_image_from_stream(&f, &sysimage, checksum); - ios_close(&f); - JL_SIGATOMIC_END(); - } -} + if (buf.kind == JL_IMAGE_KIND_NONE) + return; + + if (buf.kind == JL_IMAGE_KIND_SO) + assert(image->fptrs.ptrs); // jl_init_processor_sysimg should already be run -JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len) -{ - ios_t f; JL_SIGATOMIC_BEGIN(); - ios_static_buffer(&f, (char*)buf, len); - uint32_t checksum = jl_crc32c(0, buf, len); - jl_restore_system_image_from_stream(&f, &sysimage, checksum); + ios_static_buffer(&f, (char *)buf.data, buf.size); + + uint32_t checksum = jl_crc32c(0, buf.data, buf.size); + jl_restore_system_image_from_stream(&f, image, buf.handle, checksum); + ios_close(&f); JL_SIGATOMIC_END(); } @@ -4398,13 +4479,13 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j #endif jl_errorf("Error opening package file %s: %s\n", fname, reason); } - const char *pkgimg_data; - jl_dlsym(pkgimg_handle, "jl_system_image_data", (void **)&pkgimg_data, 1); - size_t *plen; - jl_dlsym(pkgimg_handle, "jl_system_image_size", (void **)&plen, 1); - jl_gc_notify_image_load(pkgimg_data, *plen); - jl_image_t pkgimage = jl_init_processor_pkgimg(pkgimg_handle); + jl_image_buf_t buf = get_image_buf(pkgimg_handle, /* is_pkgimage */ 1); + + jl_gc_notify_image_load(buf.data, buf.size); + + // Despite the name, this function actually parses the pkgimage + jl_image_t pkgimage = jl_init_processor_pkgimg(buf); if (ignore_native) { // Must disable using native code in possible downstream users of this code: @@ -4413,7 +4494,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j IMAGE_NATIVE_CODE_TAINTED = 1; } - jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, 0); + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, buf, &pkgimage, depmods, completeinfo, pkgname, 0); return mod; } diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 95a64964eccba..c3f6a7e98a550 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -736,17 +736,31 @@ static void jl_activate_methods(jl_array_t *external, jl_array_t *internal, size } } -static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) +static int jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) { size_t i, l = jl_array_nrows(method_roots_list); + int failed = 0; for (i = 0; i < l; i+=2) { jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(method_roots_list, i); jl_array_t *roots = (jl_array_t*)jl_array_ptr_ref(method_roots_list, i+1); if (roots) { assert(jl_is_array(roots)); + if (m->root_blocks) { + // check for key collision + uint64_t *blocks = jl_array_data(m->root_blocks, uint64_t); + size_t nx2 = jl_array_nrows(m->root_blocks); + for (size_t i = 0; i < nx2; i+=2) { + if (blocks[i] == key) { + // found duplicate block + failed = -1; + } + } + } + jl_append_method_roots(m, key, roots); } } + return failed; } static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *depmods) diff --git a/src/subtype.c b/src/subtype.c index ed6a8818dc3f2..3d50dc492e190 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -1069,7 +1069,8 @@ static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e); static int subtype_tuple_varargs( jl_vararg_t *vtx, jl_vararg_t *vty, - size_t vx, size_t vy, + jl_value_t *lastx, jl_value_t *lasty, + size_t vx, size_t vy, size_t x_reps, jl_stenv_t *e, int param) { jl_value_t *xp0 = jl_unwrap_vararg(vtx); jl_value_t *xp1 = jl_unwrap_vararg_num(vtx); @@ -1111,12 +1112,30 @@ static int subtype_tuple_varargs( } } } - - // in Vararg{T1} <: Vararg{T2}, need to check subtype twice to - // simulate the possibility of multiple arguments, which is needed - // to implement the diagonal rule correctly. - if (!subtype(xp0, yp0, e, param)) return 0; - if (!subtype(xp0, yp0, e, 1)) return 0; + int x_same = vx > 1 || (lastx && obviously_egal(xp0, lastx)); + int y_same = vy > 1 || (lasty && obviously_egal(yp0, lasty)); + // keep track of number of consecutive identical subtyping + x_reps = y_same && x_same ? x_reps + 1 : 1; + if (x_reps > 2) { + // an identical type on the left doesn't need to be compared to the same + // element type on the right more than twice. + } + else if (x_same && e->Runions.depth == 0 && y_same && + !jl_has_free_typevars(xp0) && !jl_has_free_typevars(yp0)) { + // fast path for repeated elements + } + else if ((e->Runions.depth == 0 ? !jl_has_free_typevars(xp0) : jl_is_concrete_type(xp0)) && !jl_has_free_typevars(yp0)) { + // fast path for separable sub-formulas + if (!jl_subtype(xp0, yp0)) + return 0; + } + else { + // in Vararg{T1} <: Vararg{T2}, need to check subtype twice to + // simulate the possibility of multiple arguments, which is needed + // to implement the diagonal rule correctly. + if (!subtype(xp0, yp0, e, param)) return 0; + if (x_reps < 2 && !subtype(xp0, yp0, e, 1)) return 0; + } constrain_length: if (!yp1) { @@ -1246,7 +1265,8 @@ static int subtype_tuple_tail(jl_datatype_t *xd, jl_datatype_t *yd, int8_t R, jl return subtype_tuple_varargs( (jl_vararg_t*)xi, (jl_vararg_t*)yi, - vx, vy, e, param); + lastx, lasty, + vx, vy, x_reps, e, param); } if (j >= ly) @@ -1267,7 +1287,7 @@ static int subtype_tuple_tail(jl_datatype_t *xd, jl_datatype_t *yd, int8_t R, jl (yi == lastx && !vx && vy && jl_is_concrete_type(xi)))) { // fast path for repeated elements } - else if (e->Runions.depth == 0 && !jl_has_free_typevars(xi) && !jl_has_free_typevars(yi)) { + else if ((e->Runions.depth == 0 ? !jl_has_free_typevars(xi) : jl_is_concrete_type(xi)) && !jl_has_free_typevars(yi)) { // fast path for separable sub-formulas if (!jl_subtype(xi, yi)) return 0; diff --git a/src/threading.c b/src/threading.c index 690c5fafb5792..ecdc4ac1d51e8 100644 --- a/src/threading.c +++ b/src/threading.c @@ -1,11 +1,9 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license - #include #include #include #include #include - #include "julia.h" #include "julia_internal.h" #include "julia_assert.h" @@ -228,10 +226,6 @@ void jl_set_pgcstack(jl_gcframe_t **pgcstack) JL_NOTSAFEPOINT { *jl_pgcstack_key() = pgcstack; } -# if JL_USE_IFUNC -JL_DLLEXPORT __attribute__((weak)) -void jl_register_pgcstack_getter(void); -# endif static jl_gcframe_t **jl_get_pgcstack_init(void); static jl_get_pgcstack_func *jl_get_pgcstack_cb = jl_get_pgcstack_init; static jl_gcframe_t **jl_get_pgcstack_init(void) @@ -244,15 +238,8 @@ static jl_gcframe_t **jl_get_pgcstack_init(void) // This is clearly not thread-safe but should be fine since we // make sure the tls states callback is finalized before adding // multiple threads -# if JL_USE_IFUNC - if (jl_register_pgcstack_getter) - jl_register_pgcstack_getter(); - else -# endif - { - jl_get_pgcstack_cb = jl_get_pgcstack_fallback; - jl_pgcstack_key = &jl_pgcstack_addr_fallback; - } + jl_get_pgcstack_cb = jl_get_pgcstack_fallback; + jl_pgcstack_key = &jl_pgcstack_addr_fallback; return jl_get_pgcstack_cb(); } @@ -434,7 +421,6 @@ static void jl_init_task_lock(jl_task_t *ct) } } - JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) { // `jl_init_threadtls` puts us in a GC unsafe region, so ensure GC isn't running. @@ -460,6 +446,22 @@ JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) return &ct->gcstack; } +JL_DLLEXPORT jl_gcframe_t **jl_autoinit_and_adopt_thread(void) +{ + if (!jl_is_initialized()) { + void *retaddr = __builtin_extract_return_addr(__builtin_return_address(0)); + void *handle = jl_find_dynamic_library_by_addr(retaddr, 0); + if (handle == NULL) { + fprintf(stderr, "error: runtime auto-initialization failed due to bad sysimage lookup\n" + " (this should not happen, please file a bug report)\n"); + exit(1); + } + jl_init_with_image_handle(handle); + return &jl_get_current_task()->gcstack; + } + + return jl_adopt_thread(); +} void jl_safepoint_suspend_all_threads(jl_task_t *ct) { diff --git a/src/typemap.c b/src/typemap.c index b8b699e101fe5..6ee8f9c599f3a 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -23,29 +23,29 @@ static int jl_is_any(jl_value_t *t1) return t1 == (jl_value_t*)jl_any_type; } -static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT, int invariant) JL_NOTSAFEPOINT { if (jl_is_unionall(t1)) t1 = jl_unwrap_unionall(t1); if (jl_is_vararg(t1)) { - return jl_type_extract_name(jl_unwrap_vararg(t1)); + return jl_type_extract_name(jl_unwrap_vararg(t1), invariant); } else if (jl_is_typevar(t1)) { - return jl_type_extract_name(((jl_tvar_t*)t1)->ub); + return jl_type_extract_name(((jl_tvar_t*)t1)->ub, invariant); } else if (t1 == jl_bottom_type || t1 == (jl_value_t*)jl_typeofbottom_type || t1 == (jl_value_t*)jl_typeofbottom_type->super) { return (jl_value_t*)jl_typeofbottom_type->name; // put Union{} and typeof(Union{}) and Type{Union{}} together for convenience } else if (jl_is_datatype(t1)) { jl_datatype_t *dt = (jl_datatype_t*)t1; - if (!jl_is_kind(t1)) - return (jl_value_t*)dt->name; - return NULL; + if (jl_is_kind(t1) && !invariant) + return (jl_value_t*)jl_type_typename; + return (jl_value_t*)dt->name; } else if (jl_is_uniontype(t1)) { jl_uniontype_t *u1 = (jl_uniontype_t*)t1; - jl_value_t *tn1 = jl_type_extract_name(u1->a); - jl_value_t *tn2 = jl_type_extract_name(u1->b); + jl_value_t *tn1 = jl_type_extract_name(u1->a, invariant); + jl_value_t *tn2 = jl_type_extract_name(u1->b, invariant); if (tn1 == tn2) return tn1; // TODO: if invariant is false, instead find the nearest common ancestor @@ -71,7 +71,7 @@ static int jl_type_extract_name_precise(jl_value_t *t1, int invariant) } else if (jl_is_datatype(t1)) { jl_datatype_t *dt = (jl_datatype_t*)t1; - if ((invariant || !dt->name->abstract) && !jl_is_kind(t1)) + if (invariant || !dt->name->abstract || dt->name == jl_type_typename) return 1; return 0; } @@ -81,8 +81,8 @@ static int jl_type_extract_name_precise(jl_value_t *t1, int invariant) return 0; if (!jl_type_extract_name_precise(u1->b, invariant)) return 0; - jl_value_t *tn1 = jl_type_extract_name(u1->a); - jl_value_t *tn2 = jl_type_extract_name(u1->b); + jl_value_t *tn1 = jl_type_extract_name(u1->a, invariant); + jl_value_t *tn2 = jl_type_extract_name(u1->b, invariant); if (tn1 == tn2) return 1; return 0; @@ -469,7 +469,7 @@ static int jl_typemap_intersection_memory_visitor(jl_genericmemory_t *a, jl_valu tydt = (jl_datatype_t*)ttype; } else if (ttype) { - ttype = jl_type_extract_name(ttype); + ttype = jl_type_extract_name(ttype, tparam & 1); tydt = ttype ? (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)ttype)->wrapper) : NULL; } if (tydt == jl_any_type) @@ -641,7 +641,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (maybe_type && !maybe_kind) { typetype = jl_unwrap_unionall(ty); typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; - name = typetype ? jl_type_extract_name(typetype) : NULL; + name = typetype ? jl_type_extract_name(typetype, 1) : NULL; if (!typetype) exclude_typeofbottom = !jl_subtype((jl_value_t*)jl_typeofbottom_type, ty); else if (jl_is_typevar(typetype)) @@ -717,7 +717,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } } else { - jl_value_t *name = jl_type_extract_name(ty); + jl_value_t *name = jl_type_extract_name(ty, 0); if (name && jl_type_extract_name_precise(ty, 0)) { // direct lookup of leaf types jl_value_t *ml = mtcache_hash_lookup(cachearg1, name); @@ -782,7 +782,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } jl_genericmemory_t *name1 = jl_atomic_load_relaxed(&cache->name1); if (name1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { - jl_value_t *name = jl_type_extract_name(ty); + jl_value_t *name = jl_type_extract_name(ty, 0); if (name && jl_type_extract_name_precise(ty, 0)) { jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); // direct lookup of concrete types @@ -1003,7 +1003,7 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( // now look at the optimized TypeName caches jl_genericmemory_t *tname = jl_atomic_load_relaxed(&cache->tname); if (tname != (jl_genericmemory_t*)jl_an_empty_memory_any) { - jl_value_t *a0 = ty && jl_is_type_type(ty) ? jl_type_extract_name(jl_tparam0(ty)) : NULL; + jl_value_t *a0 = ty && jl_is_type_type(ty) ? jl_type_extract_name(jl_tparam0(ty), 1) : NULL; if (a0) { // TODO: if we start analyzing Union types in jl_type_extract_name, then a0 might be over-approximated here, leading us to miss possible subtypes jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)a0)->wrapper); while (1) { @@ -1042,7 +1042,7 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( jl_genericmemory_t *name1 = jl_atomic_load_relaxed(&cache->name1); if (name1 != (jl_genericmemory_t*)jl_an_empty_memory_any) { if (ty) { - jl_value_t *a0 = jl_type_extract_name(ty); + jl_value_t *a0 = jl_type_extract_name(ty, 0); if (a0) { // TODO: if we start analyzing Union types in jl_type_extract_name, then a0 might be over-approximated here, leading us to miss possible subtypes jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)a0)->wrapper); while (1) { @@ -1200,7 +1200,7 @@ jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_v } jl_genericmemory_t *tname = jl_atomic_load_relaxed(&cache->tname); if (jl_is_kind(ty) && tname != (jl_genericmemory_t*)jl_an_empty_memory_any) { - jl_value_t *name = jl_type_extract_name(a1); + jl_value_t *name = jl_type_extract_name(a1, 1); if (name) { if (ty != (jl_value_t*)jl_datatype_type) a1 = jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); @@ -1447,12 +1447,12 @@ static void jl_typemap_level_insert_( jl_value_t *a0; t1 = jl_unwrap_unionall(t1); if (jl_is_type_type(t1)) { - a0 = jl_type_extract_name(jl_tparam0(t1)); + a0 = jl_type_extract_name(jl_tparam0(t1), 1); jl_datatype_t *super = a0 ? (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)a0)->wrapper) : jl_any_type; jl_typemap_memory_insert_(map, &cache->tname, (jl_value_t*)super->name, newrec, (jl_value_t*)cache, 1, offs, NULL); return; } - a0 = jl_type_extract_name(t1); + a0 = jl_type_extract_name(t1, 0); if (a0 && a0 != (jl_value_t*)jl_any_type->name) { jl_typemap_memory_insert_(map, &cache->name1, a0, newrec, (jl_value_t*)cache, 0, offs, NULL); return; diff --git a/stdlib/FileWatching/src/FileWatching.jl b/stdlib/FileWatching/src/FileWatching.jl index 7c743ce634193..ebfdd9c8fea6b 100644 --- a/stdlib/FileWatching/src/FileWatching.jl +++ b/stdlib/FileWatching/src/FileWatching.jl @@ -488,12 +488,11 @@ end function getproperty(fdw::FDWatcher, s::Symbol) # support deprecated field names - s === :readable && return fdw.mask.readable - s === :writable && return fdw.mask.writable + s === :readable && return getfield(fdw, :mask).readable + s === :writable && return getfield(fdw, :mask).writable return getfield(fdw, s) end - close(t::_FDWatcher, mask::FDEvent) = close(t, mask.readable, mask.writable) function close(t::_FDWatcher, readable::Bool, writable::Bool) iolock_begin() diff --git a/stdlib/Manifest.toml b/stdlib/Manifest.toml index 1aa9d3b9082a7..0dcd58d217d0b 100644 --- a/stdlib/Manifest.toml +++ b/stdlib/Manifest.toml @@ -168,7 +168,7 @@ version = "0.8.5+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "Libdl"] uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.15+1" +version = "3.5.0+0" [[deps.PCRE2_jll]] deps = ["Artifacts", "Libdl"] diff --git a/stdlib/MozillaCACerts_jll/Project.toml b/stdlib/MozillaCACerts_jll/Project.toml index 2f9bf67e22a74..a951435168922 100644 --- a/stdlib/MozillaCACerts_jll/Project.toml +++ b/stdlib/MozillaCACerts_jll/Project.toml @@ -1,7 +1,7 @@ name = "MozillaCACerts_jll" uuid = "14a3606d-f60d-562e-9121-12d972cd8159" # Keep in sync with `deps/libgit2.version`. -version = "2024.12.31" +version = "2025.02.25" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/OpenSSL_jll/Project.toml b/stdlib/OpenSSL_jll/Project.toml index 7c8067261c253..28ecf86381213 100644 --- a/stdlib/OpenSSL_jll/Project.toml +++ b/stdlib/OpenSSL_jll/Project.toml @@ -1,6 +1,6 @@ name = "OpenSSL_jll" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.0.16+0" +version = "3.5.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/OpenSSL_jll/test/runtests.jl b/stdlib/OpenSSL_jll/test/runtests.jl index 8bf67288834c9..e5ae938b68311 100644 --- a/stdlib/OpenSSL_jll/test/runtests.jl +++ b/stdlib/OpenSSL_jll/test/runtests.jl @@ -6,5 +6,5 @@ using Test, Libdl, OpenSSL_jll major = ccall((:OPENSSL_version_major, libcrypto), Cuint, ()) minor = ccall((:OPENSSL_version_minor, libcrypto), Cuint, ()) patch = ccall((:OPENSSL_version_patch, libcrypto), Cuint, ()) - @test VersionNumber(major, minor, patch) == v"3.0.16" + @test VersionNumber(major, minor, patch) == v"3.5.0" end diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index cfb6e88a2663c..66b46154e78f1 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -474,6 +474,8 @@ function Base.showerror(io::IO, e::LimitIOException) print(io, "$LimitIOException: aborted printing after attempting to print more than $(Base.format_bytes(e.maxbytes)) within a `LimitIO`.") end +Base.displaysize(io::LimitIO) = _displaysize(io.io) + function Base.write(io::LimitIO, v::UInt8) io.n > io.maxbytes && throw(LimitIOException(io.maxbytes)) n_bytes = write(io.io, v) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 8880fe9decb1e..5b211d95b5385 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -729,24 +729,12 @@ function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool end MAX_ANY_METHOD_COMPLETIONS::Int = 10 -function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}()) - push!(exploredmodules, callee_module) - for name in names(callee_module; all=true, imported=true) - if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name) - func = getfield(callee_module, name) - if !isa(func, Module) - funct = Core.Typeof(func) - push!(seen, funct) - elseif isa(func, Module) && func ∉ exploredmodules - recursive_explore_names!(seen, func, initial_module, exploredmodules) - end - end - end -end -function recursive_explore_names(callee_module::Module, initial_module::Module) - seen = IdSet{Any}() - recursive_explore_names!(seen, callee_module, initial_module) - seen + +function accessible(mod::Module, private::Bool) + bindings = IdSet{Any}(Core.Typeof(getglobal(mod, s)) for s in names(mod; all=private, imported=private, usings=private) + if !Base.isdeprecated(mod, s) && !startswith(string(s), '#') && !startswith(string(s), '@') && isdefined(mod, s)) + delete!(bindings, Module) + return collect(bindings) end function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool) @@ -764,7 +752,7 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul # semicolon for the ".?(" syntax moreargs && push!(args_ex, Vararg{Any}) - for seen_name in recursive_explore_names(callee_module, callee_module) + for seen_name in accessible(callee_module, callee_module === context_module) complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false) end @@ -1273,20 +1261,20 @@ function dict_eval(@nospecialize(e), context_module::Module=Main) end function method_search(partial::AbstractString, context_module::Module, shift::Bool) - rexm = match(r"(\w+\.|)\?\((.*)$", partial) + rexm = match(r"([\w.]+.)?\?\((.*)$", partial) if rexm !== nothing # Get the module scope - if isempty(rexm.captures[1]) - callee_module = context_module - else - modname = Symbol(rexm.captures[1][1:end-1]) - if isdefined(context_module, modname) - callee_module = getfield(context_module, modname) - if !isa(callee_module, Module) - callee_module = context_module + callee_module = context_module + if !isnothing(rexm.captures[1]) + modnames = map(Symbol, split(something(rexm.captures[1]), '.')) + for m in modnames + if isdefined(callee_module, m) + callee_module = getfield(callee_module, m) + if !isa(callee_module, Module) + callee_module = context_module + break + end end - else - callee_module = context_module end end moreargs = !endswith(rexm.captures[2], ')') @@ -1296,7 +1284,8 @@ function method_search(partial::AbstractString, context_module::Module, shift::B end ex_org = Meta.parse(callstr, raise=false, depwarn=false) if isa(ex_org, Expr) - return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false + pos_q = isnothing(rexm.captures[1]) ? 1 : sizeof(something(rexm.captures[1]))+1 # position after ? + return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:pos_q) .+ rexm.offset, false end end end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index d1233ee00da1b..62cb6bbed8cb0 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1948,6 +1948,9 @@ end @test output == "…[printing stopped after displaying 0 bytes; $hint]" @test sprint(io -> show(REPL.LimitIO(io, 5), "abc")) == "\"abc\"" @test_throws REPL.LimitIOException(1) sprint(io -> show(REPL.LimitIO(io, 1), "abc")) + + # displaying objects at the REPL sometimes needs access to displaysize, like Dict + @test displaysize(IOContext(REPL.LimitIO(stdout, 100), stdout)) == displaysize(stdout) finally REPL.SHOW_MAXIMUM_BYTES = previous end diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 442dff90cc039..5569b93640bd8 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -17,6 +17,16 @@ let ex = module CompletionFoo using Random import Test + # make everything public, so that nothing gets hidden unintentionally from completions + public Test_y, Text_x, type_test, unicode_αΒγ, CompletionFoo2, bar, + foo, @foobar, @barfoo, @error_expanding, + @error_lowering_conditional, @error_throwing, NonStruct, x, + CustomDict, NoLengthDict, test, test1, test2, test3, test4, test5, + test6, test7, test8, test9, test10, test11, a, test!12, kwtest, + kwtest2, kwtest3, kwtest4, kwtest5, named, fmsoebelkv, array, + varfloat, tuple, test_y_array, test_dict, test_customdict, + @teststr_str, @tϵsτstρ_str, @testcmd_cmd, @tϵsτcmδ_cmd, + var"complicated symbol with spaces", WeirdNames, @ignoremacro mutable struct Test_y yy diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 6d4712c93a9c4..446102bc625d1 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -2191,6 +2191,22 @@ end @test one(Mat([1 2; 3 4])) == Mat([1 0; 0 1]) @test one(Mat([1 2; 3 4])) isa Mat + + @testset "SizedArray" begin + S = [1 2; 3 4] + A = SizedArrays.SizedArray{(2,2)}(S) + @test one(A) == one(typeof(A)) + @test oneunit(A) == oneunit(typeof(A)) + M = fill(A, 2, 2) + O = one(M) + for I in CartesianIndices(M) + if I[1] == I[2] + @test O[I] == one(S) + else + @test O[I] == zero(S) + end + end + end end @testset "copyto! with non-AbstractArray src" begin diff --git a/test/boundscheck_exec.jl b/test/boundscheck_exec.jl index 3b2f853999229..a79395c88231e 100644 --- a/test/boundscheck_exec.jl +++ b/test/boundscheck_exec.jl @@ -239,6 +239,13 @@ if bc_opt != bc_off @test_throws BoundsError BadVector20469([1,2,3])[:] end +# Accumulate: do not set inbounds context for user-supplied functions +if bc_opt != bc_off + Base.@propagate_inbounds op58200(a, b) = (1, 2)[a] + (1, 2)[b] + @test_throws BoundsError accumulate(op58200, 1:10) + @test_throws BoundsError Base.accumulate_pairwise(op58200, 1:10) +end + # Ensure iteration over arrays is vectorizable function g27079(X) r = 0 @@ -343,7 +350,7 @@ if bc_opt == bc_default m1 === m2 end no_alias_prove(1) - @test_broken (@allocated no_alias_prove(5)) == 0 + @test (@allocated no_alias_prove(5)) == 0 end end diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index f78e72f21b950..7934d9c60d54d 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -1056,7 +1056,7 @@ let exename = `$(Base.julia_cmd().exec[1]) -t 1` p = run(pipeline(`$exename --sysimage=$libjulia`, stderr=err), wait=false) close(err.in) let s = read(err, String) - @test s == "ERROR: System image file failed consistency check: maybe opened the wrong version?\n" + @test s == "ERROR: Image file failed consistency check: maybe opened the wrong version?\n" end @test errors_not_signals(p) @test p.exitcode == 1 diff --git a/test/core.jl b/test/core.jl index 8a98cc39b7af8..5c2950c83b2d3 100644 --- a/test/core.jl +++ b/test/core.jl @@ -26,6 +26,7 @@ for (T, c) in ( (Core.Memory, [:length, :ptr]), (Core.GenericMemoryRef, [:mem, :ptr_or_offset]), (Task, [:metrics_enabled]), + (Core.BindingPartition, [:restriction, :kind]), ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if isconst(T, i))) == Set(c) end @@ -44,6 +45,7 @@ for (T, c) in ( (Core.Memory, []), (Core.GenericMemoryRef, []), (Task, [:_state, :running_time_ns, :finished_at, :first_enqueued_at, :last_started_running_at]), + (Core.BindingPartition, [:min_world, :max_world, :next]), ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if Base.isfieldatomic(T, i))) == Set(c) end diff --git a/test/file.jl b/test/file.jl index 6425155c82965..85b9d1660510f 100644 --- a/test/file.jl +++ b/test/file.jl @@ -2165,3 +2165,5 @@ end @test dstat.total < 32PB @test dstat.used + dstat.available == dstat.total end + +@test Base.infer_return_type(stat, (String,)) == Base.Filesystem.StatStruct diff --git a/test/math.jl b/test/math.jl index 75ee928c62e65..9a8b3a16d8fb3 100644 --- a/test/math.jl +++ b/test/math.jl @@ -46,8 +46,7 @@ has_fma = Dict( @test clamp(100, Int8) === Int8(100) @test clamp(200, Int8) === typemax(Int8) - begin - x = [0.0, 1.0, 2.0, 3.0, 4.0] + let 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 @@ -59,12 +58,14 @@ has_fma = Dict( @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 + let x = big(2) ^ 100 + @test (@allocated clamp(x, Int16)) == 0 + end - x = clamp(2.0, BigInt) - @test x isa BigInt - @test x == big(2) + let x = clamp(2.0, BigInt) + @test x isa BigInt + @test x == big(2) + end end end diff --git a/test/opaque_closure.jl b/test/opaque_closure.jl index 7b02578a86621..0dc2ed95b8872 100644 --- a/test/opaque_closure.jl +++ b/test/opaque_closure.jl @@ -407,3 +407,6 @@ let f = f54357(+, Tuple{Int,Int}) @test g isa Core.OpaqueClosure @test g(32.0, 34.0) === 66.0 end + +# 49659: signature-scoped typevar shouldn't fail in lowering +@test_throws "must be a tuple type" @opaque ((x::T,y::T) where {T}) -> 123 diff --git a/test/precompile.jl b/test/precompile.jl index e118c7a8ee4bf..18e66e3172d2d 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -264,6 +264,7 @@ precompile_test_harness(false) do dir # check that @ccallable works from precompiled modules Base.@ccallable Cint f35014(x::Cint) = x+Cint(1) + Base.@ccallable "f35014_other" f35014_2(x::Cint)::Cint = x+Cint(1) # check that Tasks work from serialized state ch1 = Channel(x -> nothing) @@ -404,6 +405,8 @@ precompile_test_harness(false) do dir let foo_ptr = Libdl.dlopen(ocachefile::String, RTLD_NOLOAD) f35014_ptr = Libdl.dlsym(foo_ptr, :f35014) @test ccall(f35014_ptr, Int32, (Int32,), 3) == 4 + f35014_other_ptr = Libdl.dlsym(foo_ptr, :f35014_other) + @test ccall(f35014_other_ptr, Int32, (Int32,), 3) == 4 end else ocachefile = nothing diff --git a/test/subtype.jl b/test/subtype.jl index da69022b1466e..dbd761c7f5867 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -2782,3 +2782,7 @@ let Tvar1 = TypeVar(:Tvar1), Tvar2 = TypeVar(:Tvar2) V2 = UnionAll(Tvar2, Union{(@eval($(Symbol(:T58129, k)){$Tvar2}) for k in 1:100)...}) @test Set{<:V2} <: AbstractSet{<:V1} end + +#issue 58115 +@test Tuple{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{ Union{Tuple{}, Tuple{Tuple{}}}}}}}}}}}}} , Tuple{}} <: + Tuple{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Tuple{Vararg{Union{Tuple{}, Tuple{Tuple{}}}}}}}}}}}}}}}, Tuple{}} diff --git a/test/testhelpers/SizedArrays.jl b/test/testhelpers/SizedArrays.jl index e52e965a64859..11aeb24490646 100644 --- a/test/testhelpers/SizedArrays.jl +++ b/test/testhelpers/SizedArrays.jl @@ -54,6 +54,11 @@ Base.axes(a::SizedArray) = map(SOneTo, size(a)) Base.getindex(A::SizedArray, i...) = getindex(A.data, i...) Base.setindex!(A::SizedArray, v, i...) = setindex!(A.data, v, i...) Base.zero(::Type{T}) where T <: SizedArray = SizedArray{size(T)}(zeros(eltype(T), size(T))) +function Base.one(::Type{SizedMatrix{SZ,T,A}}) where {SZ,T,A} + allequal(SZ) || throw(DimensionMismatch("multiplicative identity defined only for square matrices")) + D = diagm(fill(one(T), SZ[1])) + SizedArray{SZ}(convert(A, D)) +end Base.parent(S::SizedArray) = S.data +(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = SizedArray{SZ}(S1.data + S2.data) ==(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = S1.data == S2.data diff --git a/test/threads.jl b/test/threads.jl index 52d0546f0e31b..fa0b33a6352f3 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -447,6 +447,12 @@ let once = OncePerProcess(() -> return [nothing]) @atomic once.state = 0x01 @test x === once() end +let once1 = OncePerProcess(BigFloat), once2 = OncePerProcess{BigFloat}(BigFloat) + # Using a type as a constructor should create a OncePerProcess with + # Type{...} as its initializer (rather than DataType) + @test typeof(once1) <: OncePerProcess{BigFloat,Type{BigFloat}} + @test typeof(once2) <: OncePerProcess{BigFloat,Type{BigFloat}} +end let once = OncePerProcess{Int}(() -> error("expected")) @test_throws ErrorException("expected") once() @test_throws ErrorException("OncePerProcess initializer failed previously") once() @@ -551,6 +557,12 @@ let e = Base.Event(true), @test_throws ArgumentError once[-1] end +let once1 = OncePerThread(BigFloat), once2 = OncePerThread{BigFloat}(BigFloat) + # Using a type as a constructor should create a OncePerThread with + # Type{...} as its initializer (rather than DataType) + @test typeof(once1) <: OncePerThread{BigFloat,Type{BigFloat}} + @test typeof(once2) <: OncePerThread{BigFloat,Type{BigFloat}} +end let once = OncePerThread{Int}(() -> error("expected")) @test_throws ErrorException("expected") once() @test_throws ErrorException("OncePerThread initializer failed previously") once() @@ -563,6 +575,12 @@ let once = OncePerTask(() -> return [nothing]) delete!(task_local_storage(), once) @test x !== once() === once() end +let once1 = OncePerTask(BigFloat), once2 = OncePerTask{BigFloat}(BigFloat) + # Using a type as a constructor should create a OncePerTask with + # Type{...} as its initializer (rather than DataType) + @test typeof(once1) <: OncePerTask{BigFloat,Type{BigFloat}} + @test typeof(once2) <: OncePerTask{BigFloat,Type{BigFloat}} +end let once = OncePerTask{Int}(() -> error("expected")) @test_throws ErrorException("expected") once() @test_throws ErrorException("expected") once() diff --git a/test/trimming/Makefile b/test/trimming/Makefile index bb2b64c2d0dd5..63114a2764570 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -32,27 +32,24 @@ LDFLAGS_ADD = -lm $(shell $(JULIA_CONFIG) --ldflags --ldlibs) -ljulia-internal release: hello$(EXE) basic_jll$(EXE) -hello.o: $(SRCDIR)/hello.jl $(BUILDSCRIPT) - $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.so --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $(SRCDIR)/hello.jl --output-exe true +hello-o.a: $(SRCDIR)/hello.jl $(BUILDSCRIPT) + $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $< --output-exe true -init.o: $(SRCDIR)/init.c - $(CC) -c -o $@ $< $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) +basic_jll-o.a: $(SRCDIR)/basic_jll.jl $(BUILDSCRIPT) + $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" + $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $< --output-exe true -basic_jll.o: $(SRCDIR)/basic_jll.jl $(BUILDSCRIPT) - $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.so --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" - $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.so --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $(SRCDIR)/basic_jll.jl --output-exe true +hello$(EXE): hello-o.a + $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) -hello$(EXE): hello.o init.o - $(CC) -o $@ $(WHOLE_ARCHIVE) hello.o $(NO_WHOLE_ARCHIVE) init.o $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) - -basic_jll$(EXE): basic_jll.o init.o - $(CC) -o $@ $(WHOLE_ARCHIVE) basic_jll.o $(NO_WHOLE_ARCHIVE) init.o $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) +basic_jll$(EXE): basic_jll-o.a + $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) check: hello$(EXE) basic_jll$(EXE) $(JULIA) --depwarn=error $(SRCDIR)/../runtests.jl $(SRCDIR)/trimming clean: - -rm -f hello$(EXE) basic_jll$(EXE) init.o hello.o basic_jll.o + -rm -f hello$(EXE) basic_jll$(EXE) hello-o.a basic_jll-o.a .PHONY: release clean check diff --git a/test/trimming/init.c b/test/trimming/init.c deleted file mode 100644 index ea1b02f8e5c8f..0000000000000 --- a/test/trimming/init.c +++ /dev/null @@ -1,9 +0,0 @@ -#include - -__attribute__((constructor)) void static_init(void) -{ - if (jl_is_initialized()) - return; - julia_init(JL_IMAGE_IN_MEMORY); - jl_exception_clear(); -}