From 9697863018aab836351c84ae725c9d30c5660ccd Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 3 Feb 2025 23:49:19 +0000 Subject: [PATCH] bpart: Fully switch to partitioned semantics This is the final PR in the binding partitions series (modulo bugs and tweaks), i.e. it closes #54654 and thus closes #40399, which was the original design sketch. This thus activates the full designed semantics for binding partitions, in particular allowing safe replacement of const bindings. It in particular allows struct redefinitions. This thus closes timholy/Revise.jl#18 and also closes #38584. The biggest semantic change here is probably that this gets rid of the notion of "resolvedness" of a binding. Previously, a lot of the behavior of our implementation depended on when bindings were "resolved", which could happen at basically an arbitrary point (in the compiler, in REPL completion, in a different thread), making a lot of the semantics around bindings ill- or at least implementation-defined. There are several related issues in the bugtracker, so this closes #14055 #44604 #46354 #30277 It is also the last step to close #24569. It also supports bindings for undef->defined transitions and thus closes #53958 #54733 - however, this is not activated yet for performance reasons and may need some further optimization. Since resolvedness no longer exists, we need to replace it with some hopefully more well-defined semantics. I will describe the semantics below, but before I do I will make two notes: 1. There are a number of cases where these semantics will behave slightly differently than the old semantics absent some other task going around resolving random bindings. 2. The new behavior (except for the replacement stuff) was generally permissible under the old semantics if the bindings happened to be resolved at the right time. With all that said, there are essentially three "strengths" of bindings: 1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no binding", plus slightly more exotic corner cases around conflicts 2. Weakly declared bindings: Declared using `global sym` and nothing else 3. Strongly declared bindings: Declared using `global sym::T`, `const sym=val`, `import Mod: sym`, `using Mod: sym` or as an implicit strong global declaration in `sym=val`, where `sym` is known to be global (either by being at toplevle or as `global sym=val` inside a function). In general, you always allowed to syntactically replace a weaker binding by a stronger one (although the runtime permits arbitrary binding deletion now, this is just a syntactic constraint to catch errors). Second, any implicit binding can be replaced by other implicit bindings as the result of changing the `using`'ed module. And lastly, any constants may be replaced by any other constants (irrespective of type). We do not currently allow replacing globals, but may consider changing that in 1.13. This is mostly how things used to work, as well in the absence of any stray external binding resolutions. The most prominent difference is probably this one: ``` set_foo!() = global foo = 1 ``` In the above terminology, this now always declares a "strongly declared binding", whereas before it declared a "weakly declared binding" that would become strongly declared on first write to the global (unless of course somebody had created a different strongly declared global in the meantime). To see the difference, this is now disallowed: ``` julia> set_foo!() = global foo = 1 set_foo! (generic function with 1 method) julia> const foo = 1 ERROR: cannot declare Main.foo constant; it was already declared global Stacktrace: [1] top-level scope @ REPL[2]:1 ``` Before it would depend on the order of binding resolution (although it just crashes on current master for some reason - whoops, probably my fault). Another major change is the ambiguousness of imports. In: ``` module M1; export x; x=1; end module M2; export x; x=2; end using .M1, .M2 ``` the binding `Main.x` is now always ambiguous and will throw on access. Before which binding you get, would depend on resolution order. To choose one, use an explicit import (which was the behavior you would previously get if neither binding was resolved before both imports). --- Compiler/src/Compiler.jl | 2 +- Compiler/src/abstractinterpretation.jl | 32 +- Compiler/src/ssair/verify.jl | 1 - Compiler/src/validation.jl | 2 +- Compiler/test/codegen.jl | 4 + Compiler/test/effects.jl | 31 +- Compiler/test/inference.jl | 2 +- base/boot.jl | 2 +- base/client.jl | 3 +- base/deprecated.jl | 25 + base/invalidation.jl | 64 +- base/reflection.jl | 86 --- base/runtime_internals.jl | 118 ++- base/show.jl | 26 +- contrib/generate_precompile.jl | 7 +- src/builtins.c | 75 +- src/codegen.cpp | 15 +- src/gc-stock.c | 2 + src/interpreter.c | 9 +- src/jl_exported_funcs.inc | 4 +- src/jlapi.c | 40 + src/julia-syntax.scm | 60 +- src/julia.h | 77 +- src/julia_internal.h | 6 +- src/method.c | 45 +- src/module.c | 718 ++++++++++-------- src/staticdata.c | 8 +- src/toplevel.c | 158 ++-- .../InteractiveUtils/src/InteractiveUtils.jl | 4 +- stdlib/REPL/src/REPL.jl | 1 - stdlib/REPL/src/REPLCompletions.jl | 3 +- stdlib/REPL/src/docview.jl | 9 +- stdlib/REPL/test/replcompletions.jl | 2 - stdlib/Test/src/Test.jl | 4 +- test/ambiguous.jl | 4 - test/atomics.jl | 18 +- test/core.jl | 105 ++- test/errorshow.jl | 7 +- test/misc.jl | 4 +- test/precompile.jl | 1 - test/rebinding.jl | 28 +- test/reflection.jl | 4 - test/show.jl | 9 - test/staged.jl | 2 +- test/syntax.jl | 12 +- test/worlds.jl | 25 +- 46 files changed, 1078 insertions(+), 786 deletions(-) diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index 0fc7bd6e328e74..ba6d1607ded55f 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -49,7 +49,7 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc using Base using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer, - BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_CONST, + BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_CONST, BINDING_KIND_DECLARED, Base, BitVector, Bottom, Callable, DataTypeFieldDesc, EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES, OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME, diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index fe7cc1dbf5680e..8a7e8aee715a67 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3464,14 +3464,7 @@ world_range(ci::CodeInfo) = WorldRange(ci.min_world, ci.max_world) world_range(ci::CodeInstance) = WorldRange(ci.min_world, ci.max_world) world_range(compact::IncrementalCompact) = world_range(compact.ir) -function force_binding_resolution!(g::GlobalRef, world::UInt) - # Force resolution of the binding - # TODO: This will go away once we switch over to fully partitioned semantics - ccall(:jl_force_binding_resolution, Cvoid, (Any, Csize_t), g, world) - return nothing -end - -function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact}, retry_after_resolve::Bool=true) +function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact}) worlds = world_range(src) partition = lookup_binding_partition(min_world(worlds), g) partition.max_world < max_world(worlds) && return Any @@ -3480,25 +3473,18 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, partition = lookup_binding_partition(min_world(worlds), imported_binding) partition.max_world < max_world(worlds) && return Any end - if is_some_guard(binding_kind(partition)) - if retry_after_resolve - # This method is surprisingly hot. For performance, don't ask the runtime to resolve - # the binding unless necessary - doing so triggers an additional lookup, which though - # not super expensive is hot enough to show up in benchmarks. - force_binding_resolution!(g, min_world(worlds)) - return abstract_eval_globalref_type(g, src, false) - end + kind = binding_kind(partition) + if is_some_guard(kind) # return Union{} return Any end - if is_some_const_binding(binding_kind(partition)) + if is_some_const_binding(kind) return Const(partition_restriction(partition)) end - return partition_restriction(partition) + return kind == BINDING_KIND_DECLARED ? Any : partition_restriction(partition) end function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) - force_binding_resolution!(g, get_inference_world(interp)) partition = lookup_binding_partition(get_inference_world(interp), g) update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world)) partition @@ -3541,7 +3527,11 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE)) end - rt = partition_restriction(partition) + if kind == BINDING_KIND_DECLARED + rt = Any + else + rt = partition_restriction(partition) + end return RTEffects(rt, UndefVarError, generic_getglobal_effects) end @@ -3580,7 +3570,7 @@ function global_assignment_binding_rt_exct(interp::AbstractInterpreter, partitio elseif is_some_const_binding(kind) return Pair{Any,Any}(Bottom, ErrorException) end - ty = partition_restriction(partition) + ty = kind == BINDING_KIND_DECLARED ? Any : partition_restriction(partition) wnewty = widenconst(newty) if !hasintersect(wnewty, ty) return Pair{Any,Any}(Bottom, TypeError) diff --git a/Compiler/src/ssair/verify.jl b/Compiler/src/ssair/verify.jl index 2d6d59cc4e22b9..974690885a2e4b 100644 --- a/Compiler/src/ssair/verify.jl +++ b/Compiler/src/ssair/verify.jl @@ -61,7 +61,6 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, raise_error() end elseif isa(op, GlobalRef) - force_binding_resolution!(op, min_world(ir.valid_worlds)) bpart = lookup_binding_partition(min_world(ir.valid_worlds), op) while is_some_imported(binding_kind(bpart)) && max_world(ir.valid_worlds) <= bpart.max_world imported_binding = partition_restriction(bpart)::Core.Binding diff --git a/Compiler/src/validation.jl b/Compiler/src/validation.jl index 6700aa8d4508f2..9bde405a49956b 100644 --- a/Compiler/src/validation.jl +++ b/Compiler/src/validation.jl @@ -22,7 +22,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :copyast => 1:1, :meta => 0:typemax(Int), :global => 1:1, - :globaldecl => 2:2, + :globaldecl => 1:2, :foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects), args..., roots... :cfunction => 5:5, :isdefined => 1:2, diff --git a/Compiler/test/codegen.jl b/Compiler/test/codegen.jl index 57a9c26aefac67..4d0020ea58d4d1 100644 --- a/Compiler/test/codegen.jl +++ b/Compiler/test/codegen.jl @@ -689,6 +689,10 @@ U41096 = Term41096{:U}(Modulate41096(:U, false)) # test that we can start julia with libjulia-codegen removed; PR #41936 mktempdir() do pfx + # First check that ordinary running won't try to compile things. If it does that indicates an insufficient precompile or invalidation. + @test success(`$(Base.julia_cmd()) --startup-file=no --trace-compile="$(pfx)/compiles.txt" -e 'print("no codegen!\n")'`) + @test !isfile(joinpath(pfx, "compiles.txt")) + cp(dirname(Sys.BINDIR), pfx; force=true) libpath = relpath(dirname(dlpath(libjulia_codegen_name())), dirname(Sys.BINDIR)) libs_deleted = 0 diff --git a/Compiler/test/effects.jl b/Compiler/test/effects.jl index b8a841b6b74b71..082d954b4a9bca 100644 --- a/Compiler/test/effects.jl +++ b/Compiler/test/effects.jl @@ -378,32 +378,27 @@ let effects = Base.infer_effects(; optimize=false) do end # we should taint `nothrow` if the binding doesn't exist and isn't fixed yet, -# as the cached effects can be easily wrong otherwise -# since the inference currently doesn't track "world-age" of global variables -@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42 setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42) -let effects = Base.infer_effects() do - global_assignment_undefinedyet() - end +let effects = Base.infer_effects(setglobal!_nothrow_undefinedyet) @test !Compiler.is_nothrow(effects) end -let effects = Base.infer_effects() do - setglobal!_nothrow_undefinedyet() - end - @test !Compiler.is_nothrow(effects) +@test_throws ErrorException setglobal!_nothrow_undefinedyet() +# This declares the binding as ::Any +@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42 +let effects = Base.infer_effects(global_assignment_undefinedyet) + @test Compiler.is_nothrow(effects) end -global UNDEFINEDYET::String = "0" -let effects = Base.infer_effects() do - global_assignment_undefinedyet() - end +# Again with type mismatch +global UNDEFINEDYET2::String = "0" +setglobal!_nothrow_undefinedyet2() = setglobal!(@__MODULE__, :UNDEFINEDYET2, 42) +@eval global_assignment_undefinedyet2() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET2)) = 42 +let effects = Base.infer_effects(global_assignment_undefinedyet2) @test !Compiler.is_nothrow(effects) end -let effects = Base.infer_effects() do - setglobal!_nothrow_undefinedyet() - end +let effects = Base.infer_effects(setglobal!_nothrow_undefinedyet2) @test !Compiler.is_nothrow(effects) end -@test_throws Union{ErrorException,TypeError} setglobal!_nothrow_undefinedyet() # TODO: what kind of error should this be? +@test_throws TypeError setglobal!_nothrow_undefinedyet2() # Nothrow for setfield! mutable struct SetfieldNothrow diff --git a/Compiler/test/inference.jl b/Compiler/test/inference.jl index d4ea990e7d1485..563828ac77296b 100644 --- a/Compiler/test/inference.jl +++ b/Compiler/test/inference.jl @@ -6160,7 +6160,7 @@ end === Int swapglobal!(@__MODULE__, :swapglobal!_xxx, x) end === Union{} -global swapglobal!_must_throw +eval(Expr(:const, :swapglobal!_must_throw)) @newinterp SwapGlobalInterp Compiler.InferenceParams(::SwapGlobalInterp) = Compiler.InferenceParams(; assume_bindings_static=true) function func_swapglobal!_must_throw(x) diff --git a/base/boot.jl b/base/boot.jl index e50d74659d399f..26a405f92f884b 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -229,7 +229,7 @@ export Expr, QuoteNode, LineNumberNode, GlobalRef, # object model functions fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, setfieldonce!, - nfields, throw, tuple, ===, isdefined, eval, + nfields, throw, tuple, ===, isdefined, # access to globals getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!, isdefinedglobal, # ifelse, sizeof # not exported, to avoid conflicting with Base diff --git a/base/client.jl b/base/client.jl index 2527d382c695de..3449e2fbf2e62c 100644 --- a/base/client.jl +++ b/base/client.jl @@ -267,9 +267,8 @@ function exec_options(opts) let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed")) Core.eval(MainInclude, :(const Distributed = $Distributed)) Core.eval(Main, :(using Base.MainInclude.Distributed)) + invokelatest(Distributed.process_opts, opts) end - - invokelatest(Main.Distributed.process_opts, opts) end interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY) diff --git a/base/deprecated.jl b/base/deprecated.jl index cffff05d954d1d..f72698ad470081 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -531,4 +531,29 @@ end # BEGIN 1.12 deprecations +@deprecate isbindingresolved(m::Module, var::Symbol) true false + +""" + isbindingresolved(m::Module, s::Symbol) -> Bool + +Returns whether the binding of a symbol in a module is resolved. + +See also: [`isexported`](@ref), [`ispublic`](@ref), [`isdeprecated`](@ref) + +```jldoctest +julia> module Mod + foo() = 17 + end +Mod + +julia> Base.isbindingresolved(Mod, :foo) +true +``` + +!!! warning + This function is deprecated. The concept of binding "resolvedness" was removed in Julia 1.12. + The function now always returns `true`. +""" +isbindingresolved + # END 1.12 deprecations diff --git a/base/invalidation.jl b/base/invalidation.jl index 36b867ede28682..c0aed35aa90a00 100644 --- a/base/invalidation.jl +++ b/base/invalidation.jl @@ -113,32 +113,56 @@ function invalidate_method_for_globalref!(gr::GlobalRef, method::Method, invalid end end -function invalidate_code_for_globalref!(gr::GlobalRef, invalidated_bpart::Core.BindingPartition, new_max_world::UInt) - b = convert(Core.Binding, gr) - try - valid_in_valuepos = false - foreach_module_mtable(gr.mod, new_max_world) do mt::Core.MethodTable - for method in MethodList(mt) - invalidate_method_for_globalref!(gr, method, invalidated_bpart, new_max_world) +const BINDING_FLAG_EXPORTP = 0x2 + +function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core.BindingPartition, new_bpart::Union{Core.BindingPartition, Nothing}, new_max_world::UInt) + gr = b.globalref + if is_some_guard(binding_kind(invalidated_bpart)) + # TODO: We may want to invalidate for these anyway, since they have performance implications + return + end + foreach_module_mtable(gr.mod, new_max_world) do mt::Core.MethodTable + for method in MethodList(mt) + invalidate_method_for_globalref!(gr, method, invalidated_bpart, new_max_world) + end + return true + end + if isdefined(b, :backedges) + for edge in b.backedges + if isa(edge, CodeInstance) + ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), edge, new_max_world) + elseif isa(edge, Core.Binding) + isdefined(edge, :partitions) || continue + latest_bpart = edge.partitions + latest_bpart.max_world == typemax(UInt) || continue + is_some_imported(binding_kind(latest_bpart)) || continue + partition_restriction(latest_bpart) === b || continue + invalidate_code_for_globalref!(edge, latest_bpart, nothing, new_max_world) + else + invalidate_method_for_globalref!(gr, edge::Method, invalidated_bpart, new_max_world) end - return true end - b = convert(Core.Binding, gr) - if isdefined(b, :backedges) - for edge in b.backedges - if isa(edge, CodeInstance) - ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), edge, new_max_world) - else - invalidate_method_for_globalref!(gr, edge::Method, invalidated_bpart, new_max_world) - end + end + if (b.flags & BINDING_FLAG_EXPORTP) != 0 + # This binding was exported - we need to check all modules that `using` us to see if they + # have an implicit binding to us. + usings_backedges = ccall(:jl_get_module_usings_backedges, Any, (Any,), gr.mod) + if usings_backedges !== nothing + for user in usings_backedges::Vector{Any} + user_binding = ccall(:jl_get_module_binding_or_nothing, Any, (Any, Any), user, gr.name) + user_binding === nothing && continue + isdefined(user_binding, :partitions) || continue + latest_bpart = user_binding.partitions + latest_bpart.max_world == typemax(UInt) || continue + is_some_imported(binding_kind(latest_bpart)) || continue + partition_restriction(latest_bpart) === b || continue + invalidate_code_for_globalref!(convert(Core.Binding, user_binding), latest_bpart, nothing, new_max_world) end end - catch err - bt = catch_backtrace() - invokelatest(Base.println, "Internal Error during invalidation:") - invokelatest(Base.display_error, err, bt) end end +invalidate_code_for_globalref!(gr::GlobalRef, invalidated_bpart::Core.BindingPartition, new_bpart::Core.BindingPartition, new_max_world::UInt) = + invalidate_code_for_globalref!(convert(Core.Binding, gr), invalidated_bpart, new_bpart, new_max_world) gr_needs_backedge_in_module(gr::GlobalRef, mod::Module) = gr.mod !== mod diff --git a/base/reflection.jl b/base/reflection.jl index 78e701692a2a76..c98f6244cc89f7 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -46,92 +46,6 @@ function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool= return ret end -# high-level, more convenient method lookup functions - -function visit(f, mt::Core.MethodTable) - mt.defs !== nothing && visit(f, mt.defs) - nothing -end -function visit(f, mc::Core.TypeMapLevel) - function avisit(f, e::Memory{Any}) - for i in 2:2:length(e) - isassigned(e, i) || continue - ei = e[i] - if ei isa Memory{Any} - for j in 2:2:length(ei) - isassigned(ei, j) || continue - visit(f, ei[j]) - end - else - visit(f, ei) - end - end - end - if mc.targ !== nothing - avisit(f, mc.targ::Memory{Any}) - end - if mc.arg1 !== nothing - avisit(f, mc.arg1::Memory{Any}) - end - if mc.tname !== nothing - avisit(f, mc.tname::Memory{Any}) - end - if mc.name1 !== nothing - avisit(f, mc.name1::Memory{Any}) - end - mc.list !== nothing && visit(f, mc.list) - mc.any !== nothing && visit(f, mc.any) - nothing -end -function visit(f, d::Core.TypeMapEntry) - while d !== nothing - f(d.func) - d = d.next - end - nothing -end -struct MethodSpecializations - specializations::Union{Nothing, Core.MethodInstance, Core.SimpleVector} -end -""" - specializations(m::Method) → itr - -Return an iterator `itr` of all compiler-generated specializations of `m`. -""" -specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations) ? m.specializations : nothing) -function iterate(specs::MethodSpecializations) - s = specs.specializations - s === nothing && return nothing - isa(s, Core.MethodInstance) && return (s, nothing) - return iterate(specs, 0) -end -iterate(specs::MethodSpecializations, ::Nothing) = nothing -function iterate(specs::MethodSpecializations, i::Int) - s = specs.specializations::Core.SimpleVector - n = length(s) - i >= n && return nothing - item = nothing - while i < n && item === nothing - item = s[i+=1] - end - item === nothing && return nothing - return (item, i) -end -length(specs::MethodSpecializations) = count(Returns(true), specs) - -function length(mt::Core.MethodTable) - n = 0 - visit(mt) do m - n += 1 - end - return n::Int -end -isempty(mt::Core.MethodTable) = (mt.defs === nothing) - -uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m) : - isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : - error("Code for this Method is not available.") - # for backwards compat const uncompressed_ast = uncompressed_ir const _uncompressed_ast = _uncompressed_ir diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index b61e24c11f3f93..36961f58c5c3fa 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -177,28 +177,6 @@ ispublic(m::Module, s::Symbol) = ccall(:jl_module_public_p, Cint, (Any, Any), m, # `Base.deprecate`, not the @deprecated macro: isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0 -""" - isbindingresolved(m::Module, s::Symbol) -> Bool - -Returns whether the binding of a symbol in a module is resolved. - -See also: [`isexported`](@ref), [`ispublic`](@ref), [`isdeprecated`](@ref) - -```jldoctest -julia> module Mod - foo() = 17 - end -Mod - -julia> Base.isbindingresolved(Mod, :foo) -true - -julia> Base.isbindingresolved(Mod, :bar) -false -``` -""" -isbindingresolved(m::Module, var::Symbol) = ccall(:jl_binding_resolved_p, Cint, (Any, Any), m, var) != 0 - function binding_module(m::Module, s::Symbol) p = ccall(:jl_get_module_of_binding, Ptr{Cvoid}, (Any, Any), m, s) p == C_NULL && return m @@ -234,7 +212,7 @@ const BINDING_KIND_BACKDATED_CONST = 0xa is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_BACKDATED_CONST) is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == BINDING_KIND_UNDEF_CONST) is_some_imported(kind::UInt8) = (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED) -is_some_guard(kind::UInt8) = (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST) +is_some_guard(kind::UInt8) = (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST) function lookup_binding_partition(world::UInt, b::Core.Binding) ccall(:jl_get_binding_partition, Ref{Core.BindingPartition}, (Any, UInt), b, world) @@ -412,8 +390,13 @@ parentmodule(t::UnionAll) = parentmodule(unwrap_unionall(t)) """ isconst(m::Module, s::Symbol) -> Bool + isconst(g::GlobalRef) -Determine whether a global is declared `const` in a given module `m`. +Determine whether a global is `const` in a given module `m`, either +because it was declared constant or because it was imported from a +constant binding. Note that constant-ness is specific to a particular +world age, so the result of this function may not be assumed to hold +after a world age update. """ isconst(m::Module, s::Symbol) = ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0 @@ -1584,3 +1567,90 @@ hasintersect(@nospecialize(a), @nospecialize(b)) = typeintersect(a, b) !== Botto ########### _topmod(m::Module) = ccall(:jl_base_relative_to, Any, (Any,), m)::Module + + +# high-level, more convenient method lookup functions + +function visit(f, mt::Core.MethodTable) + mt.defs !== nothing && visit(f, mt.defs) + nothing +end +function visit(f, mc::Core.TypeMapLevel) + function avisit(f, e::Memory{Any}) + for i in 2:2:length(e) + isassigned(e, i) || continue + ei = e[i] + if ei isa Memory{Any} + for j in 2:2:length(ei) + isassigned(ei, j) || continue + visit(f, ei[j]) + end + else + visit(f, ei) + end + end + end + if mc.targ !== nothing + avisit(f, mc.targ::Memory{Any}) + end + if mc.arg1 !== nothing + avisit(f, mc.arg1::Memory{Any}) + end + if mc.tname !== nothing + avisit(f, mc.tname::Memory{Any}) + end + if mc.name1 !== nothing + avisit(f, mc.name1::Memory{Any}) + end + mc.list !== nothing && visit(f, mc.list) + mc.any !== nothing && visit(f, mc.any) + nothing +end +function visit(f, d::Core.TypeMapEntry) + while d !== nothing + f(d.func) + d = d.next + end + nothing +end +struct MethodSpecializations + specializations::Union{Nothing, Core.MethodInstance, Core.SimpleVector} +end +""" + specializations(m::Method) → itr + +Return an iterator `itr` of all compiler-generated specializations of `m`. +""" +specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations) ? m.specializations : nothing) +function iterate(specs::MethodSpecializations) + s = specs.specializations + s === nothing && return nothing + isa(s, Core.MethodInstance) && return (s, nothing) + return iterate(specs, 0) +end +iterate(specs::MethodSpecializations, ::Nothing) = nothing +function iterate(specs::MethodSpecializations, i::Int) + s = specs.specializations::Core.SimpleVector + n = length(s) + i >= n && return nothing + item = nothing + while i < n && item === nothing + item = s[i+=1] + end + item === nothing && return nothing + return (item, i) +end +length(specs::MethodSpecializations) = count(Returns(true), specs) + +function length(mt::Core.MethodTable) + n = 0 + visit(mt) do m + n += 1 + end + return n::Int +end +isempty(mt::Core.MethodTable) = (mt.defs === nothing) + +uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m) : + isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") : + error("Code for this Method is not available.") diff --git a/base/show.jl b/base/show.jl index 42788f05eceb5d..6b27fe838c89bd 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1021,18 +1021,24 @@ end # If an object with this name exists in 'from', we need to check that it's the same binding # and that it's not deprecated. function isvisible(sym::Symbol, parent::Module, from::Module) - owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), parent, sym) - from_owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), from, sym) - return owner !== C_NULL && from_owner === owner && - !isdeprecated(parent, sym) && - isdefinedglobal(from, sym) # if we're going to return true, force binding resolution + isdeprecated(parent, sym) && return false + isdefinedglobal(from, sym) || return false + parent_binding = convert(Core.Binding, GlobalRef(parent, sym)) + from_binding = convert(Core.Binding, GlobalRef(from, sym)) + while true + from_binding === parent_binding && return true + partition = lookup_binding_partition(tls_world_age(), from_binding) + is_some_imported(binding_kind(partition)) || break + from_binding = partition_restriction(partition)::Core.Binding + end + return false end function is_global_function(tn::Core.TypeName, globname::Union{Symbol,Nothing}) if globname !== nothing globname_str = string(globname::Symbol) if ('#' ∉ globname_str && '@' ∉ globname_str && isdefined(tn, :module) && - isbindingresolved(tn.module, globname) && isdefinedglobal(tn.module, globname) && + isdefinedglobal(tn.module, globname) && isconcretetype(tn.wrapper) && isa(getglobal(tn.module, globname), tn.wrapper)) return true end @@ -3377,16 +3383,16 @@ function print_partition(io::IO, partition::Core.BindingPartition) elseif kind == BINDING_KIND_FAILED print(io, "ambiguous binding - guard entry") elseif kind == BINDING_KIND_DECLARED - print(io, "undefined, but declared using `global` - guard entry") + print(io, "weak global binding declared using `global` (implicit type Any)") elseif kind == BINDING_KIND_IMPLICIT print(io, "implicit `using` from ") - print(io, partition_restriction(partition)) + print(io, partition_restriction(partition).globalref) elseif kind == BINDING_KIND_EXPLICIT print(io, "explicit `using` from ") - print(io, partition_restriction(partition)) + print(io, partition_restriction(partition).globalref) elseif kind == BINDING_KIND_IMPORTED print(io, "explicit `import` from ") - print(io, partition_restriction(partition)) + print(io, partition_restriction(partition).globalref) else @assert kind == BINDING_KIND_GLOBAL print(io, "global variable with type ") diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index b075223d9c7e4a..f12e94e22fca5d 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -107,6 +107,9 @@ precompile(Base.CoreLogging.env_override_minlevel, (Symbol, Module)) precompile(Base.StackTraces.lookup, (Ptr{Nothing},)) precompile(Tuple{typeof(Base.run_module_init), Module, Int}) +# Presence tested in the tests +precompile(Tuple{typeof(Base.print), Base.IOStream, String}) + # precompilepkgs precompile(Tuple{typeof(Base.get), Type{Array{String, 1}}, Base.Dict{String, Any}, String}) precompile(Tuple{typeof(Base.get), Type{Base.Dict{String, Any}}, Base.Dict{String, Any}, String}) @@ -352,10 +355,10 @@ generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printe PrecompileStagingArea = Module() for (_pkgid, _mod) in Base.loaded_modules if !(_pkgid.name in ("Main", "Core", "Base")) - eval(PrecompileStagingArea, :(const $(Symbol(_mod)) = $_mod)) + Core.eval(PrecompileStagingArea, :(const $(Symbol(_mod)) = $_mod)) end end - eval(PrecompileStagingArea, :(const Compiler = Base.Compiler)) + Core.eval(PrecompileStagingArea, :(const Compiler = Base.Compiler)) n_succeeded = 0 # Make statements unique diff --git a/src/builtins.c b/src/builtins.c index f67ef65d353569..ecbf0a229270f5 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2197,11 +2197,13 @@ static int references_name(jl_value_t *p, jl_typename_t *name, int affects_layou JL_CALLABLE(jl_f__typebody) { - JL_NARGS(_typebody!, 1, 2); - jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(args[0]); + JL_NARGS(_typebody!, 2, 3); + jl_value_t *prev = args[0]; + jl_value_t *tret = args[1]; + jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(args[1]); JL_TYPECHK(_typebody!, datatype, (jl_value_t*)dt); - if (nargs == 2) { - jl_value_t *ft = args[1]; + if (nargs == 3) { + jl_value_t *ft = args[2]; JL_TYPECHK(_typebody!, simplevector, ft); size_t nf = jl_svec_len(ft); for (size_t i = 0; i < nf; i++) { @@ -2212,30 +2214,52 @@ JL_CALLABLE(jl_f__typebody) (jl_value_t*)jl_type_type, elt); } } - if (dt->types != NULL) { - if (!equiv_field_types((jl_value_t*)dt->types, ft)) - jl_errorf("invalid redefinition of type %s", jl_symbol_name(dt->name->name)); - } - else { - dt->types = (jl_svec_t*)ft; - jl_gc_wb(dt, ft); - // If a supertype can reference the same type, then we may not be - // able to compute the layout of the object before needing to - // publish it, so we must assume it cannot be inlined, if that - // check passes, then we also still need to check the fields too. - if (!dt->name->mutabl && (nf == 0 || !references_name((jl_value_t*)dt->super, dt->name, 0, 1))) { - int mayinlinealloc = 1; - size_t i; - for (i = 0; i < nf; i++) { - jl_value_t *fld = jl_svecref(ft, i); - if (references_name(fld, dt->name, 1, 1)) { - mayinlinealloc = 0; - break; + // Optimization: To avoid lots of unnecessary churning, lowering contains an optimization + // that re-uses the typevars of an existing definition (if any exists) for compute the field + // types. If such a previous type exists, there are two possibilities: + // 1. The field types are identical, we don't need to do anything and can proceed with the + // old type as if it was the new one. + // 2. The field types are not identical, in which case we need to rename the typevars + // back to their equivalents in the new type before proceeding. + if (prev == jl_false) { + if (dt->types != NULL) + jl_errorf("Internal Error: Expected type fields to be unset"); + } else { + jl_datatype_t *prev_dt = (jl_datatype_t*)jl_unwrap_unionall(prev); + JL_TYPECHK(_typebody!, datatype, (jl_value_t*)prev_dt); + if (equiv_field_types((jl_value_t*)prev_dt->types, ft)) { + tret = prev; + goto have_type; + } else { + if (jl_svec_len(prev_dt->parameters) != jl_svec_len(dt->parameters)) + jl_errorf("Internal Error: Types should not have been considered equivalent"); + for (size_t i = 0; i < nf; i++) { + jl_value_t *elt = jl_svecref(ft, i); + for (int j = 0; j < jl_svec_len(prev_dt->parameters); ++j) { + // Only the last svecset matters for semantics, but we re-use the GC root + jl_svecset(ft, i, jl_substitute_var(elt, (jl_tvar_t *)jl_svecref(prev_dt->parameters, j), jl_svecref(dt->parameters, j))); } } - dt->name->mayinlinealloc = mayinlinealloc; } } + dt->types = (jl_svec_t*)ft; + jl_gc_wb(dt, ft); + // If a supertype can reference the same type, then we may not be + // able to compute the layout of the object before needing to + // publish it, so we must assume it cannot be inlined, if that + // check passes, then we also still need to check the fields too. + if (!dt->name->mutabl && (nf == 0 || !references_name((jl_value_t*)dt->super, dt->name, 0, 1))) { + int mayinlinealloc = 1; + size_t i; + for (i = 0; i < nf; i++) { + jl_value_t *fld = jl_svecref(ft, i); + if (references_name(fld, dt->name, 1, 1)) { + mayinlinealloc = 0; + break; + } + } + dt->name->mayinlinealloc = mayinlinealloc; + } } JL_TRY { @@ -2248,7 +2272,8 @@ JL_CALLABLE(jl_f__typebody) if (jl_is_structtype(dt)) jl_compute_field_offsets(dt); - return jl_nothing; +have_type: + return tret; } // this is a heuristic for allowing "redefining" a type to something identical diff --git a/src/codegen.cpp b/src/codegen.cpp index e9e4275672c7e6..ddbac31badd7b9 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -918,7 +918,7 @@ static const auto jldeclareglobal_func = new JuliaFunction<>{ auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); return FunctionType::get(getVoidTy(C), - {T_pjlvalue, T_pjlvalue, T_prjlvalue}, false); }, + {T_pjlvalue, T_pjlvalue, T_prjlvalue, getInt32Ty(C)}, false); }, nullptr, }; static const auto jlgetbindingorerror_func = new JuliaFunction<>{ @@ -6958,16 +6958,21 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } } else if (head == jl_globaldecl_sym) { - assert(nargs == 2); + assert(nargs <= 2 && nargs >= 1); jl_sym_t *sym = (jl_sym_t*)args[0]; jl_module_t *mod = ctx.module; if (jl_is_globalref(sym)) { mod = jl_globalref_mod(sym); sym = jl_globalref_name(sym); } - jl_cgval_t typ = emit_expr(ctx, args[1]); - ctx.builder.CreateCall(prepare_call(jldeclareglobal_func), - { literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, typ) }); + if (nargs == 2) { + jl_cgval_t typ = emit_expr(ctx, args[1]); + ctx.builder.CreateCall(prepare_call(jldeclareglobal_func), + { literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, typ), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1) }); + } else { + ctx.builder.CreateCall(prepare_call(jldeclareglobal_func), + { literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), ConstantPointerNull::get(cast(ctx.types().T_prjlvalue)), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1) }); + } } else if (head == jl_new_sym) { bool is_promotable = false; diff --git a/src/gc-stock.c b/src/gc-stock.c index 8118b3c5629ae9..e7ce48c1730b4f 100644 --- a/src/gc-stock.c +++ b/src/gc-stock.c @@ -2144,6 +2144,8 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent gc_heap_snapshot_record_module_to_binding(mb_parent, bindings, bindingkeyset); gc_assert_parent_validity((jl_value_t *)mb_parent, (jl_value_t *)mb_parent->parent); gc_try_claim_and_push(mq, (jl_value_t *)mb_parent->parent, &nptr); + gc_assert_parent_validity((jl_value_t *)mb_parent, (jl_value_t *)mb_parent->usings_backedges); + gc_try_claim_and_push(mq, (jl_value_t *)mb_parent->usings_backedges, &nptr); size_t nusings = module_usings_length(mb_parent); if (nusings > 0) { // this is only necessary because bindings for "using" modules diff --git a/src/interpreter.c b/src/interpreter.c index 35c70a9ead2f1a..513fe58f7b5ccd 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -634,9 +634,12 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, s->locals[jl_source_nslots(s->src) + s->ip] = res; } else if (head == jl_globaldecl_sym) { - jl_value_t *val = eval_value(jl_exprarg(stmt, 1), s); - s->locals[jl_source_nslots(s->src) + s->ip] = val; // temporarily root - jl_declare_global(s->module, jl_exprarg(stmt, 0), val); + jl_value_t *val = NULL; + if (jl_expr_nargs(stmt) >= 2) { + val = eval_value(jl_exprarg(stmt, 1), s); + s->locals[jl_source_nslots(s->src) + s->ip] = val; // temporarily root + } + jl_declare_global(s->module, jl_exprarg(stmt, 0), val, 1); s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing; } else if (head == jl_const_sym) { diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 4d1ab94644e39d..4454ffb4cca3f8 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -42,7 +42,6 @@ XX(jl_atomic_swap_bits) \ XX(jl_backtrace_from_here) \ XX(jl_base_relative_to) \ - XX(jl_binding_resolved_p) \ XX(jl_bitcast) \ XX(jl_boundp) \ XX(jl_bounds_error) \ @@ -72,6 +71,7 @@ XX(jl_call1) \ XX(jl_call2) \ XX(jl_call3) \ + XX(jl_call4) \ XX(jl_calloc) \ XX(jl_call_in_typeinf_world) \ XX(jl_capture_interp_frame) \ @@ -213,6 +213,8 @@ XX(jl_get_module_infer) \ XX(jl_get_module_of_binding) \ XX(jl_get_module_optlevel) \ + XX(jl_get_module_usings_backedges) \ + XX(jl_get_module_binding_or_nothing) \ XX(jl_get_next_task) \ XX(jl_get_nth_field) \ XX(jl_get_nth_field_checked) \ diff --git a/src/jlapi.c b/src/jlapi.c index b8fbda801f43b6..e205a4a4dc723c 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -437,6 +437,46 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, return v; } +/** + * @brief Call a Julia function with three arguments. + * + * A specialized case of `jl_call` for simpler scenarios. + * + * @param f A pointer to `jl_function_t` representing the Julia function to call. + * @param a A pointer to `jl_value_t` representing the first argument. + * @param b A pointer to `jl_value_t` representing the second argument. + * @param c A pointer to `jl_value_t` representing the third argument. + * @param d A pointer to `jl_value_t` representing the fourth argument. + * @return A pointer to `jl_value_t` representing the result of the function call. + */ +JL_DLLEXPORT jl_value_t *jl_call4(jl_function_t *f, jl_value_t *a, + jl_value_t *b, jl_value_t *c, + jl_value_t *d) +{ + jl_value_t *v; + jl_task_t *ct = jl_current_task; + JL_TRY { + jl_value_t **argv; + JL_GC_PUSHARGS(argv, 5); + argv[0] = f; + argv[1] = a; + argv[2] = b; + argv[3] = c; + argv[4] = d; + size_t last_age = ct->world_age; + ct->world_age = jl_get_world_counter(); + v = jl_apply(argv, 5); + ct->world_age = last_age; + JL_GC_POP(); + _jl_exception_clear(ct); + } + JL_CATCH { + ct->ptls->previous_exception = jl_current_exception(ct); + v = NULL; + } + return v; +} + /** * @brief Get a field from a Julia object. * diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 97d76e7762a9e0..726312a13d10f7 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1002,7 +1002,9 @@ (default-inner-ctors name field-names field-types params bounds locs) defs)) (min-initialized (min (ctors-min-initialized defs) (length fields))) - (prev (make-ssavalue))) + (hasprev (make-ssavalue)) + (prev (make-ssavalue)) + (newdef (make-ssavalue))) (let ((dups (has-dups field-names))) (if dups (error (string "duplicate field name: \"" (car dups) "\" is not unique")))) (for-each (lambda (v) @@ -1023,22 +1025,21 @@ (call (core svec) ,@attrs) ,mut ,min-initialized)) (call (core _setsuper!) ,name ,super) - (if (call (core isdefinedglobal) (thismodule) (inert ,name) (false)) - (block - (= ,prev (globalref (thismodule) ,name)) - (if (call (core _equiv_typedef) ,prev ,name) - ;; if this is compatible with an old definition, use the existing type object - ;; and its parameters - (block (= ,name ,prev) - ,@(if (pair? params) - `((= (tuple ,@params) (|.| - ,(foldl (lambda (_ x) `(|.| ,x (quote body))) - prev - params) - (quote parameters)))) - '()))))) - (call (core _typebody!) ,name (call (core svec) ,@(insert-struct-shim field-types name))) - (const (globalref (thismodule) ,name) ,name) + (= ,hasprev (&& (call (core isdefinedglobal) (thismodule) (inert ,name) (false)) (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name))) + (= ,prev (if ,hasprev (globalref (thismodule) ,name) (false))) + (if ,hasprev + ;; if this is compatible with an old definition, use the old parameters, but the + ;; new object. This will fail to capture recursive cases, but the call to typebody! + ;; below is permitted to choose either type definition to put into the binding table + (block ,@(if (pair? params) + `((= (tuple ,@params) (|.| + ,(foldl (lambda (_ x) `(|.| ,x (quote body))) + prev + params) + (quote parameters)))) + '()))) + (= ,newdef (call (core _typebody!) ,prev ,name (call (core svec) ,@(insert-struct-shim field-types name)))) + (const (globalref (thismodule) ,name) ,newdef) (latestworld) (null))) ;; "inner" constructors @@ -1084,7 +1085,7 @@ (toplevel-only abstract_type) (= ,name (call (core _abstracttype) (thismodule) (inert ,name) (call (core svec) ,@params))) (call (core _setsuper!) ,name ,super) - (call (core _typebody!) ,name) + (call (core _typebody!) (false) ,name) (if (&& (call (core isdefinedglobal) (thismodule) (inert ,name) (false)) (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) @@ -1105,7 +1106,7 @@ (toplevel-only primitive_type) (= ,name (call (core _primitivetype) (thismodule) (inert ,name) (call (core svec) ,@params) ,n)) (call (core _setsuper!) ,name ,super) - (call (core _typebody!) ,name) + (call (core _typebody!) (false) ,name) (if (&& (call (core isdefinedglobal) (thismodule) (inert ,name) (false)) (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) @@ -3525,7 +3526,7 @@ f(x) = yt(x) (false) ,(length fields))) (call (core _setsuper!) ,s ,super) (const (globalref (thismodule) ,name) ,s) - (call (core _typebody!) ,s (call (core svec) ,@types)) + (call (core _typebody!) (false) ,s (call (core svec) ,@types)) (return (null))))))))) (define (type-for-closure name fields super) @@ -3539,7 +3540,7 @@ f(x) = yt(x) (false) ,(length fields))) (call (core _setsuper!) ,s ,super) (const (globalref (thismodule) ,name) ,s) - (call (core _typebody!) ,s + (call (core _typebody!) (false) ,s (call (core svec) ,@(map (lambda (v) '(core Box)) fields))) (return (null))))))))) @@ -3641,8 +3642,8 @@ f(x) = yt(x) rhs1)) (ex `(= ,var ,rhs))) (if (eq? rhs1 rhs0) - `(block ,ex ,rhs0) - `(block (= ,rhs1 ,rhs0) + `(block (globaldecl ,var) ,ex ,rhs0) + `(block (globaldecl ,var) (= ,rhs1 ,rhs0) ,ex ,rhs1)))) @@ -4624,11 +4625,7 @@ f(x) = yt(x) tests)) (define (emit-assignment-or-setglobal lhs rhs) (if (globalref? lhs) - (begin - (emit `(global ,lhs)) - (if (null? (cadr lam)) - (emit `(latestworld))) - (emit `(call (top setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs))) + (emit `(call (top setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs)) (emit `(= ,lhs ,rhs)))) (define (emit-assignment lhs rhs) (if rhs @@ -4951,11 +4948,12 @@ f(x) = yt(x) (emit `(latestworld)))) ((globaldecl) (if value (error "misplaced \"global\" declaration")) - (if (atom? (caddr e)) (begin (emit e) (emit `(latestworld))) + (if (or (length= e 2) (atom? (caddr e))) (emit e) (let ((rr (make-ssavalue))) (emit `(= ,rr ,(caddr e))) - (emit `(globaldecl ,(cadr e) ,rr)) - (emit `(latestworld))))) + (emit `(globaldecl ,(cadr e) ,rr)))) + (if (null? (cadr lam)) + (emit `(latestworld)))) ((local-def) #f) ((local) #f) ((moved-local) diff --git a/src/julia.h b/src/julia.h index a80a69049ccb2e..fe0c42c2e740b3 100644 --- a/src/julia.h +++ b/src/julia.h @@ -616,6 +616,52 @@ typedef struct _jl_weakref_t { } jl_weakref_t; // N.B: Needs to be synced with runtime_internals.jl +// We track essentially three levels of binding strength: +// +// 1. Implicit Bindings (Weakest) +// These binding kinds depend solely on the set of using'd packages and are not explicitly +// declared: +// +// BINDING_KIND_IMPLICIT +// BINDING_KIND_GUARD +// BINDING_KIND_FAILED +// +// 2. Weakly Declared Bindings (Weak) +// The binding was declared using `global`. It is treated as a mutable, `Any` type global +// for almost all purposes, except that it receives slightly worse optimizations, since it +// may be replaced. +// +// BINDING_KIND_DECLARED +// +// 3. Strong Declared Bindings (Weak) +// All other bindings are explicitly declared using a keyword or global assignment. +// These are considered strongest: +// +// BINDING_KIND_CONST +// BINDING_KIND_CONST_IMPORT +// BINDING_KIND_EXPLICIT +// BINDING_KIND_IMPORTED +// BINDING_KIND_GLOBAL +// BINDING_KIND_UNDEF_CONST +// +// The runtime supports syntactic invalidation (by raising the world age and changing the partition type +// in the new world age) from any partition kind to any other. +// +// However, not all transitions are allowed syntactically. We have the following rules for SYNTACTIC invalidation: +// 1. It is always syntactically permissable to replace a weaker binding by a stronger binding +// 2. Implicit bindings can be syntactically changed other other implicit bindings by chaing the `using` set. +// 3. Finally, we syntactically permit replacing one BINDING_KIND_CONST(_IMPORT) by another of a different value. +// +// We may make this list more permissive in the future. +// +// Finally, BINDING_KIND_BACKDATED_CONST is a special case, and the only case where we may replace an +// existing partition by a different partition kind in the same world age. As such, it needs special +// support in inference. Any partition kind that may be replaced by a BINDING_KIND_BACKDATED_CONST +// must be inferred accordingly. BINDING_KIND_BACKDATED_CONST is intended as a temporary compatibility +// measure. The following kinds may be replaced by BINDING_KIND_BACKDATED_CONST: +// - BINDING_KIND_GUARD +// - BINDING_KIND_FAILED +// - BINDING_KIND_DECLARED enum jl_partition_kind { // Constant: This binding partition is a constant declared using `const _ = ...` // ->restriction holds the constant value @@ -623,7 +669,8 @@ enum jl_partition_kind { // Import Constant: This binding partition is a constant declared using `import A` // ->restriction holds the constant value BINDING_KIND_CONST_IMPORT = 0x1, - // Global: This binding partition is a global variable. + // Global: This binding partition is a global variable. It was declared either using + // `global x::T` to implicitly through a syntactic global assignment. // -> restriction holds the type restriction BINDING_KIND_GLOBAL = 0x2, // Implicit: The binding was implicitly imported from a `using`'d module. @@ -638,7 +685,9 @@ enum jl_partition_kind { // Failed: We attempted to import the binding, but the import was ambiguous // ->restriction is NULL. BINDING_KIND_FAILED = 0x6, - // Declared: The binding was declared using `global` or similar + // Declared: The binding was declared using `global` or similar. This acts in most ways like + // BINDING_KIND_GLOBAL with an `Any` restriction, except that it may be redefined to a stronger + // binding like `const` or an explicit import. // ->restriction is NULL. BINDING_KIND_DECLARED = 0x7, // Guard: The binding was looked at, but no global or import was resolved at the time @@ -651,6 +700,10 @@ enum jl_partition_kind { // Backated constant. A constant that was backdated for compatibility. In all other // ways equivalent to BINDING_KIND_CONST, but prints a warning on access BINDING_KIND_BACKDATED_CONST = 0xa, + + // This is not a real binding kind, but can be used to ask for a re-resolution + // of the implicit binding kind + BINDING_KIND_IMPLICIT_RECOMPUTE = 0xb }; #ifdef _P64 @@ -672,17 +725,7 @@ typedef struct __attribute__((aligned(8))) _jl_binding_partition_t { * * Currently: Low 3 bits hold ->kind on _P64 to avoid needing >8 byte atomics * - * This field is updated atomically with both kind and restriction. The following - * transitions are allowed and modeled by the system: - * - * GUARD -> any - * (DECLARED, FAILED) -> any non-GUARD - * IMPLICIT -> {EXPLICIT, IMPORTED} (->restriction unchanged only) - * - * In addition, we permit (with warning about undefined behavior) changing the restriction - * pointer for CONST(_IMPORT). - * - * All other kind or restriction transitions are disallowed. + * This field is updated atomically with both kind and restriction */ _Atomic(jl_ptr_kind_union_t) restriction; size_t min_world; @@ -717,6 +760,7 @@ typedef struct _jl_module_t { _Atomic(jl_genericmemory_t*) bindingkeyset; // index lookup by name into bindings jl_sym_t *file; int32_t line; + jl_value_t *usings_backedges; // hidden fields: arraylist_t usings; /* arraylist of struct jl_module_using */ // modules with all bindings potentially imported jl_uuid_t build_id; @@ -1996,6 +2040,9 @@ JL_DLLEXPORT void jl_set_module_infer(jl_module_t *self, int value); JL_DLLEXPORT int jl_get_module_infer(jl_module_t *m); JL_DLLEXPORT void jl_set_module_max_methods(jl_module_t *self, int value); JL_DLLEXPORT int jl_get_module_max_methods(jl_module_t *m); +JL_DLLEXPORT jl_value_t *jl_get_module_usings_backedges(jl_module_t *m); +JL_DLLEXPORT jl_value_t *jl_get_module_binding_or_nothing(jl_module_t *m, jl_sym_t *s); + // get binding for reading JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); @@ -2007,7 +2054,6 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import); JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); -JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr); JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr); @@ -2253,6 +2299,9 @@ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED, jl_value_t *c JL_MAYBE_UNROOTED); +JL_DLLEXPORT jl_value_t *jl_call4(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t *a JL_MAYBE_UNROOTED, + jl_value_t *b JL_MAYBE_UNROOTED, jl_value_t *c JL_MAYBE_UNROOTED, + jl_value_t *d JL_MAYBE_UNROOTED); // async signal handling ------------------------------------------------------ diff --git a/src/julia_internal.h b/src/julia_internal.h index 9817c8cc8263b1..9c0b7e8029bf3c 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -843,7 +843,7 @@ int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL_NOTSAFEPOINT JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t *val); void jl_binding_set_type(jl_binding_t *b, jl_module_t *mod, jl_sym_t *sym, jl_value_t *ty); void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); -JL_DLLEXPORT void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type); +JL_DLLEXPORT void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, int strong); JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind, size_t new_world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno); @@ -905,6 +905,8 @@ void jl_compute_field_offsets(jl_datatype_t *st); void jl_module_run_initializer(jl_module_t *m); JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *sym, jl_binding_t *b); +JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b JL_PROPAGATES_ROOT, + jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind, size_t new_world) JL_GLOBALLY_ROOTED; extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT jl_module_t *jl_precompile_toplevel_module JL_GLOBALLY_ROOTED; @@ -991,7 +993,7 @@ STATIC_INLINE int jl_bkind_is_defined_constant(enum jl_partition_kind kind) JL_N } STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == BINDING_KIND_FAILED || kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED; + return kind == BINDING_KIND_FAILED || kind == BINDING_KIND_GUARD; } JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world) JL_GLOBALLY_ROOTED; diff --git a/src/method.c b/src/method.c index 8a14eb00182b1c..1b38a16649d8a1 100644 --- a/src/method.c +++ b/src/method.c @@ -62,6 +62,11 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod jl_eval_global_expr(module, e, 1); return jl_nothing; } + if (e->head == jl_globaldecl_sym && binding_effects) { + assert(jl_expr_nargs(e) == 1); + jl_declare_global(module, jl_exprarg(e, 0), NULL, 1); + return jl_nothing; + } // These exprs are not fully linearized if (e->head == jl_assign_sym) { jl_exprargset(e, 1, resolve_definition_effects(jl_exprarg(e, 1), module, sparam_vals, binding_edge, binding_effects, eager_resolve)); @@ -180,26 +185,24 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod jl_value_t *fe = jl_exprarg(e, 0); jl_module_t *fe_mod = jl_globalref_mod(fe); jl_sym_t *fe_sym = jl_globalref_name(fe); - if (jl_binding_resolved_p(fe_mod, fe_sym)) { - // look at some known called functions - jl_binding_t *b = jl_get_binding(fe_mod, fe_sym); - if (jl_get_binding_value_if_const(b) == jl_builtin_tuple) { - size_t j; - for (j = 1; j < nargs; j++) { - if (!jl_is_quotenode(jl_exprarg(e, j))) - break; + // look at some known called functions + jl_binding_t *b = jl_get_binding(fe_mod, fe_sym); + if (jl_get_binding_value_if_const(b) == jl_builtin_tuple) { + size_t j; + for (j = 1; j < nargs; j++) { + if (!jl_is_quotenode(jl_exprarg(e, j))) + break; + } + if (j == nargs) { + jl_value_t *val = NULL; + JL_TRY { + val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals); } - if (j == nargs) { - jl_value_t *val = NULL; - JL_TRY { - val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals); - } - JL_CATCH { - val = NULL; // To make the analyzer happy see #define JL_TRY - } - if (val) - return val; + JL_CATCH { + val = NULL; // To make the analyzer happy see #define JL_TRY } + if (val) + return val; } } } @@ -1052,9 +1055,11 @@ JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_binding_t *b, jl_module_t *mod, JL_LOCK(&world_counter_lock); size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1; jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, new_world); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); jl_value_t *gf = NULL; - if (!jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (!jl_bkind_is_some_guard(kind) && kind != BINDING_KIND_DECLARED && kind != BINDING_KIND_IMPLICIT) { + pku = jl_walk_binding_inplace(&b, &bpart, new_world); if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { gf = decode_restriction_value(pku); JL_GC_PROMISE_ROOTED(gf); diff --git a/src/module.c b/src/module.c index b2a4018519fca6..0783a7ba44ebdb 100644 --- a/src/module.c +++ b/src/module.c @@ -29,7 +29,134 @@ static jl_binding_partition_t *new_binding_partition(void) return bpart; } -jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) { + +typedef struct _modstack_t { + jl_binding_t *b; + struct _modstack_t *prev; +} modstack_t; + +static jl_binding_partition_t *jl_get_binding_partition2(jl_binding_t *b, size_t world, modstack_t *st); + +static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_t world) +{ + jl_binding_t *ownerb = NULL; + jl_binding_partition_t *alias_bpart = jl_get_binding_partition(alias, world); + if (owner == alias_bpart) + return 1; + jl_ptr_kind_union_t owner_pku = jl_walk_binding_inplace(&ownerb, &owner, world); + jl_ptr_kind_union_t alias_pku = jl_walk_binding_inplace(&alias, &alias_bpart, world); + if (jl_bkind_is_some_constant(decode_restriction_kind(owner_pku)) && + jl_bkind_is_some_constant(decode_restriction_kind(alias_pku)) && + decode_restriction_value(owner_pku) && + decode_restriction_value(alias_pku) == decode_restriction_value(owner_pku)) + return 1; + return owner == alias_bpart; +} + +// find a binding from a module's `usings` list +static void check_new_binding_implicit( + jl_binding_partition_t *new_bpart JL_MAYBE_UNROOTED, jl_binding_t *b, modstack_t *st, int warn, size_t world) +{ + modstack_t top = { b, st }; + modstack_t *tmp = st; + for (; tmp != NULL; tmp = tmp->prev) { + if (tmp->b == b) { + jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction(NULL, BINDING_KIND_FAILED /* BINDING_KIND_CYCLE */)); + return; + } + } + + JL_GC_PUSH1(&new_bpart); + jl_module_t *m = b->globalref->mod; + jl_sym_t *var = b->globalref->name; + + jl_binding_t *deprecated_impb = NULL; + jl_binding_t *impb = NULL; + + size_t min_world = new_bpart->min_world; + size_t max_world = jl_atomic_load_relaxed(&new_bpart->max_world); + + JL_LOCK(&m->lock); + int i = (int)module_usings_length(m) - 1; + JL_UNLOCK(&m->lock); + enum jl_partition_kind guard_kind = BINDING_KIND_GUARD; + for (; i >= 0; --i) { + JL_LOCK(&m->lock); + struct _jl_module_using data = *module_usings_getidx(m, i); + JL_UNLOCK(&m->lock); + if (data.min_world > world) { + if (max_world > data.min_world) + max_world = data.min_world - 1; + continue; + } + if (data.max_world < world) { + if (min_world < data.max_world) + min_world = data.max_world + 1; + continue; + } + jl_module_t *imp = data.mod; + JL_GC_PROMISE_ROOTED(imp); + jl_binding_t *tempb = jl_get_module_binding(imp, var, 0); + if (tempb != NULL && tempb->exportp) { + if (data.min_world > min_world) + min_world = data.min_world; + if (data.max_world < min_world) + max_world = data.max_world; + + jl_binding_partition_t *tempbpart = jl_get_binding_partition2(tempb, world, &top); + JL_GC_PROMISE_ROOTED(tempbpart); + + size_t tempbmax_world = jl_atomic_load_relaxed(&tempbpart->max_world); + if (tempbpart->min_world > min_world) + min_world = tempbpart->min_world; + if (tempbmax_world < max_world) + max_world = tempbmax_world; + + if (impb) { + if (eq_bindings(tempbpart, impb, world)) + continue; + // Binding is ambiguous + // TODO: Even for eq bindings, this may need to further constrain the world age. + impb = NULL; + guard_kind = BINDING_KIND_FAILED; + break; + } + + if (tempb->deprecated) { + if (deprecated_impb) { + if (!eq_bindings(tempbpart, deprecated_impb, world)) { + guard_kind = BINDING_KIND_FAILED; + deprecated_impb = NULL; + } + } + else if (guard_kind == BINDING_KIND_GUARD) { + deprecated_impb = tempb; + } + } + else { + impb = tempb; + } + } + } + + if (deprecated_impb && !impb) + impb = deprecated_impb; + + assert(min_world <= max_world); + new_bpart->min_world = min_world; + jl_atomic_store_relaxed(&new_bpart->max_world, max_world); + if (impb) { + jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction((jl_value_t*)impb, BINDING_KIND_IMPLICIT)); + // TODO: World age constraints? + } else { + jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction(NULL, guard_kind)); + } + JL_GC_POP(); + return; +} + +STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world, modstack_t *st) JL_GLOBALLY_ROOTED +{ if (!b) return NULL; assert(jl_is_binding(b)); @@ -53,6 +180,8 @@ jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) jl_gc_wb_fresh(new_bpart, bpart); new_bpart->min_world = bpart ? jl_atomic_load_relaxed(&bpart->max_world) + 1 : 0; jl_atomic_store_relaxed(&new_bpart->max_world, max_world); + JL_GC_PROMISE_ROOTED(new_bpart); // TODO: Analyzer doesn't understand MAYBE_UNROOTED properly + check_new_binding_implicit(new_bpart, b, st, 0, world); if (jl_atomic_cmpswap(insert, &bpart, new_bpart)) { jl_gc_wb(parent, new_bpart); return new_bpart; @@ -60,6 +189,15 @@ jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) } } +jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) { + // Duplicate the code for the entry frame for branch prediction + return jl_get_binding_partition_(b, world, NULL); +} + +jl_binding_partition_t *jl_get_binding_partition2(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world, modstack_t *st) JL_GLOBALLY_ROOTED { + return jl_get_binding_partition_(b, world, st); +} + jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b, size_t min_world, size_t max_world) { if (!b) return NULL; @@ -89,6 +227,7 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui m->build_id.lo++; // build id 0 is invalid m->build_id.hi = ~(uint64_t)0; jl_atomic_store_relaxed(&m->counter, 1); + m->usings_backedges = jl_nothing; m->nospecialize = 0; m->optlevel = -1; m->compile = -1; @@ -255,7 +394,7 @@ extern jl_mutex_t jl_modules_mutex; extern void check_safe_newbinding(jl_module_t *m, jl_sym_t *var) { if (jl_current_task->ptls->in_pure_callback) - jl_errorf("new globals cannot be created in a generated function"); + jl_errorf("new strong globals cannot be created in a generated function. Delare them outside using `global x::Any`."); if (jl_options.incremental && jl_generating_output()) { JL_LOCK(&jl_modules_mutex); int open = ptrhash_has(&jl_current_modules, (void*)m); @@ -285,20 +424,14 @@ JL_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); -retry: - if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - if (decode_restriction_kind(pku) != BINDING_KIND_DECLARED) { - jl_errorf("Global %s.%s does not exist and cannot be assigned.\n" - "Note: Julia 1.9 and 1.10 inadvertently omitted this error check (#56933).\n" - "Hint: Declare it using `global %s` inside `%s` before attempting assignment.", - jl_symbol_name(m->name), jl_symbol_name(s), - jl_symbol_name(s), jl_symbol_name(m->name)); - } - jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)jl_any_type, BINDING_KIND_GLOBAL); - if (!jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) - goto retry; - jl_gc_wb_knownold(bpart, jl_any_type); + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (kind != BINDING_KIND_GLOBAL && kind != BINDING_KIND_DECLARED && !jl_bkind_is_some_constant(kind)) { + if (jl_bkind_is_some_guard(kind)) { + jl_errorf("Global %s.%s does not exist and cannot be assigned.\n" + "Note: Julia 1.9 and 1.10 inadvertently omitted this error check (#56933).\n" + "Hint: Declare it using `global %s` inside `%s` before attempting assignment.", + jl_symbol_name(m->name), jl_symbol_name(s), + jl_symbol_name(s), jl_symbol_name(m->name)); } else { jl_module_t *from = jl_binding_dbgmodule(b, m, s); if (from == m) @@ -321,10 +454,10 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, // return module of binding JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_binding(m, var); - if (b == NULL) - return NULL; - return b->globalref->mod; // TODO: deprecate this? + jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + return b ? b->globalref->mod : m; } static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT @@ -358,6 +491,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) check_backdated_binding(b, kind); return decode_restriction_value(pku); } + assert(!jl_bkind_is_some_import(kind)); return jl_atomic_load_relaxed(&b->value); } @@ -372,6 +506,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) check_backdated_binding(b, kind); return decode_restriction_value(pku); } + assert(!jl_bkind_is_some_import(kind)); return jl_atomic_load(&b->value); } @@ -444,19 +579,14 @@ JL_DLLEXPORT jl_value_t *jl_bpart_get_restriction_value(jl_binding_partition_t * return v; } -typedef struct _modstack_t { - jl_module_t *m; - jl_sym_t *var; - struct _modstack_t *prev; -} modstack_t; -static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st, size_t world); - JL_DLLEXPORT jl_value_t *jl_reresolve_binding_value_seqcst(jl_binding_t *b) { + /* jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (jl_bkind_is_some_guard(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))) { jl_resolve_owner(b, b->globalref->mod, b->globalref->name, NULL, jl_current_task->world_age); } + */ return jl_get_binding_value_seqcst(b); } @@ -467,91 +597,48 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ jl_binding_t *b = jl_get_module_binding(m, var, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - if (decode_restriction_kind(pku) != BINDING_KIND_DECLARED) { - check_safe_newbinding(m, var); - } + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (kind == BINDING_KIND_GLOBAL || kind == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(pku))) + return b; + if (jl_bkind_is_some_guard(kind)) { + check_safe_newbinding(m, var); + return b; + } + jl_binding_t *ownerb = b; + pku = jl_walk_binding_inplace(&ownerb, &bpart, jl_current_task->world_age); + jl_value_t *f = NULL; + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + f = decode_restriction_value(pku); + if (f == NULL) { + if (kind == BINDING_KIND_IMPLICIT) { + check_safe_newbinding(m, var); return b; } - jl_value_t *f = jl_get_binding_value_if_const(b); - if (f == NULL) { - jl_module_t *from = jl_binding_dbgmodule(b, m, var); - // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from - jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", - jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from + jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", + jl_symbol_name(m->name), from ? jl_symbol_name(from->name) : "", jl_symbol_name(var)); + } + int istype = f && jl_is_type(f); + if (!istype) { + if (kind == BINDING_KIND_IMPLICIT) { + check_safe_newbinding(m, var); + return b; } - // TODO: we might want to require explicitly importing types to add constructors - // or we might want to drop this error entirely - if (decode_restriction_kind(pku) != BINDING_KIND_IMPORTED && !(f && jl_is_type(f) && strcmp(jl_symbol_name(var), "=>") != 0)) { + else if (kind != BINDING_KIND_IMPORTED) { + // TODO: we might want to require explicitly importing types to add constructors + // or we might want to drop this error entirely jl_module_t *from = jl_binding_dbgmodule(b, m, var); jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", - jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + jl_symbol_name(m->name), from ? jl_symbol_name(from->name) : "", jl_symbol_name(var)); } - return b; } - return b; -} - -static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_t world) -{ - jl_ptr_kind_union_t owner_pku = jl_atomic_load_relaxed(&owner->restriction); - assert(decode_restriction_kind(owner_pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(owner_pku) == BINDING_KIND_DECLARED || - jl_bkind_is_some_constant(decode_restriction_kind(owner_pku))); - jl_binding_partition_t *alias_bpart = jl_get_binding_partition(alias, world); - if (owner == alias_bpart) - return 1; - jl_ptr_kind_union_t alias_pku = jl_walk_binding_inplace(&alias, &alias_bpart, world); - if (jl_bkind_is_some_constant(decode_restriction_kind(owner_pku)) && - jl_bkind_is_some_constant(decode_restriction_kind(alias_pku)) && - decode_restriction_value(owner_pku) && - decode_restriction_value(alias_pku) == decode_restriction_value(owner_pku)) - return 1; - return owner == alias_bpart; -} - -// find a binding from a module's `usings` list -static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, jl_module_t **from, modstack_t *st, int warn, size_t world) -{ - jl_binding_t *b = NULL; - jl_binding_partition_t *bpart = NULL; - jl_module_t *owner = NULL; - JL_LOCK(&m->lock); - int i = (int)module_usings_length(m) - 1; - JL_UNLOCK(&m->lock); - for (; i >= 0; --i) { - JL_LOCK(&m->lock); - jl_module_t *imp = module_usings_getmod(m, i); - JL_UNLOCK(&m->lock); - jl_binding_t *tempb = jl_get_module_binding(imp, var, 0); - if (tempb != NULL && tempb->exportp) { - tempb = jl_resolve_owner(NULL, imp, var, st, world); // find the owner for tempb - if (tempb == NULL) - // couldn't resolve; try next using (see issue #6105) - continue; - jl_binding_partition_t *tempbpart = jl_get_binding_partition(tempb, world); - jl_ptr_kind_union_t tempb_pku = jl_atomic_load_relaxed(&tempbpart->restriction); - assert(jl_bkind_is_some_guard(decode_restriction_kind(tempb_pku)) || decode_restriction_kind(tempb_pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(tempb_pku) == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(tempb_pku))); - (void)tempb_pku; - if (bpart != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempbpart, b, world)) { - if (warn) { - // set usingfailed=1 to avoid repeating this warning - // the owner will still be NULL, so it can be later imported or defined - tempb = jl_get_module_binding(m, var, 1); - tempbpart = jl_get_binding_partition(tempb, world); - jl_atomic_store_release(&tempbpart->restriction, encode_restriction(NULL, BINDING_KIND_FAILED)); - } - return NULL; - } - if (owner == NULL || !tempb->deprecated) { - owner = imp; - b = tempb; - bpart = tempbpart; - } - } + else if (strcmp(jl_symbol_name(var), "=>") == 0 && (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT)) { + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", + jl_symbol_name(m->name), from ? jl_symbol_name(from->name) : "", jl_symbol_name(var)); } - *from = owner; - return b; + return ownerb; } // for error message printing: look up the module that exported a binding to m as var @@ -559,100 +646,15 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) != BINDING_KIND_GLOBAL) { - // for implicitly imported globals, try to re-resolve it to find the module we got it from most directly - jl_module_t *from = NULL; - jl_binding_t *b2 = using_resolve_binding(m, var, &from, NULL, 0, jl_current_task->world_age); - if (b2) { - jl_binding_partition_t *b2part = jl_get_binding_partition(b2, jl_current_task->world_age); - if (eq_bindings(b2part, b, jl_current_task->world_age)) - return from; - // if we did not find it (or accidentally found a different one), ignore this - } + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (jl_bkind_is_some_import(decode_restriction_kind(pku))) { + return ((jl_binding_t*)decode_restriction_value(pku))->globalref->mod; } return m; } static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t *b); -// get binding for reading. might return NULL for unbound. -static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st, size_t world) -{ - if (b == NULL) - b = jl_get_module_binding(m, var, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); -retry: - if (decode_restriction_kind(pku) == BINDING_KIND_FAILED) - return NULL; - if (decode_restriction_kind(pku) == BINDING_KIND_DECLARED) { - return b; - } - if (decode_restriction_kind(pku) == BINDING_KIND_GUARD) { - jl_binding_t *b2 = NULL; - modstack_t top = { m, var, st }; - modstack_t *tmp = st; - for (; tmp != NULL; tmp = tmp->prev) { - if (tmp->m == m && tmp->var == var) { - // import cycle without finding actual location - return NULL; - } - } - jl_module_t *from = NULL; // for error message printing - b2 = using_resolve_binding(m, var, &from, &top, 1, world); - if (b2 == NULL) - return NULL; - assert(from); - JL_GC_PROMISE_ROOTED(from); // gc-analysis does not understand output parameters - JL_GC_PROMISE_ROOTED(b2); - if (b2->deprecated) { - if (jl_get_binding_value(b2) == jl_nothing) { - // silently skip importing deprecated values assigned to nothing (to allow later mutation) - return NULL; - } - } - // do a full import to prevent the result of this lookup from - // changing, for example if this var is assigned to later. - if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction((jl_value_t*)b2, BINDING_KIND_IMPLICIT))) - goto retry; - jl_gc_wb(bpart, b2); - if (b2->deprecated) { - b->deprecated = 1; // we will warn about this below, but we might want to warn at the use sites too - if (m != jl_main_module && m != jl_base_module && - jl_options.depwarn != JL_OPTIONS_DEPWARN_OFF) { - /* with #22763, external packages wanting to replace - deprecated Base bindings should simply export the new - binding */ - jl_printf(JL_STDERR, - "WARNING: using deprecated binding %s.%s in %s.\n", - jl_symbol_name(from->name), jl_symbol_name(var), - jl_symbol_name(m->name)); - jl_binding_dep_message(from, var, b2); - } - } - return b2; - } - jl_walk_binding_inplace(&b, &bpart, world); - return b; -} - -// get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) -JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) -{ - jl_binding_t *b = jl_get_module_binding(m, var, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_module_t *from = m; - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (decode_restriction_kind(pku) == BINDING_KIND_GUARD) { - b = using_resolve_binding(m, var, &from, NULL, 0, jl_current_task->world_age); - bpart = jl_get_binding_partition(b, jl_current_task->world_age); - } - pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) - return NULL; - return b; -} - // get type of binding m.var, without resolving the binding JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { @@ -675,7 +677,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { - return jl_resolve_owner(NULL, m, var, NULL, jl_current_task->world_age); + return jl_get_module_binding(m, var, 1); } JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var) @@ -762,81 +764,82 @@ static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici) { jl_binding_t *b = jl_get_binding(from, s); - if (b == NULL) { + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + (void)pku; + if (b->deprecated) { + if (jl_get_binding_value(b) == jl_nothing) { + // silently skip importing deprecated values assigned to nothing (to allow later mutation) + return; + } + else if (to != jl_main_module && to != jl_base_module && + jl_options.depwarn != JL_OPTIONS_DEPWARN_OFF) { + /* with #22763, external packages wanting to replace + deprecated Base bindings should simply export the new + binding */ + jl_printf(JL_STDERR, + "WARNING: importing deprecated binding %s.%s into %s%s%s.\n", + jl_symbol_name(from->name), jl_symbol_name(s), + jl_symbol_name(to->name), + asname == s ? "" : " as ", + asname == s ? "" : jl_symbol_name(asname)); + jl_binding_dep_message(from, s, b); + } + } + + jl_binding_t *ownerb = b; + jl_binding_partition_t *ownerbpart = bpart; + jl_ptr_kind_union_t owner_pku = jl_walk_binding_inplace(&ownerb, &ownerbpart, jl_current_task->world_age); + + if (jl_bkind_is_some_guard(decode_restriction_kind(owner_pku))) { jl_printf(JL_STDERR, - "WARNING: could not import %s.%s into %s\n", + "WARNING: Imported binding %s.%s was undeclared at import time during import to %s.\n", jl_symbol_name(from->name), jl_symbol_name(s), jl_symbol_name(to->name)); } + + jl_binding_t *bto = jl_get_module_binding(to, asname, 1); + if (bto == b) { + // importing a binding on top of itself. harmless. + return; + } + JL_LOCK(&world_counter_lock); + size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1; + jl_binding_partition_t *btopart = jl_get_binding_partition(bto, new_world); + jl_ptr_kind_union_t bto_pku = jl_atomic_load_relaxed(&btopart->restriction); + if (decode_restriction_kind(bto_pku) == BINDING_KIND_GUARD || + decode_restriction_kind(bto_pku) == BINDING_KIND_IMPLICIT || + decode_restriction_kind(bto_pku) == BINDING_KIND_FAILED) { + + jl_binding_partition_t *new_bpart = jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, (explici != 0) ? BINDING_KIND_IMPORTED : BINDING_KIND_EXPLICIT, new_world); + if (new_bpart->max_world == ~(size_t)0) + jl_add_binding_backedge(b, (jl_value_t*)bto); + jl_atomic_store_release(&jl_world_counter, new_world); + } else { - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - assert(decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(pku) == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(pku))); - (void)pku; - if (b->deprecated) { - if (jl_get_binding_value(b) == jl_nothing) { - // silently skip importing deprecated values assigned to nothing (to allow later mutation) - return; - } - else if (to != jl_main_module && to != jl_base_module && - jl_options.depwarn != JL_OPTIONS_DEPWARN_OFF) { - /* with #22763, external packages wanting to replace - deprecated Base bindings should simply export the new - binding */ - jl_printf(JL_STDERR, - "WARNING: importing deprecated binding %s.%s into %s%s%s.\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name), - asname == s ? "" : " as ", - asname == s ? "" : jl_symbol_name(asname)); - jl_binding_dep_message(from, s, b); + if (eq_bindings(bpart, bto, new_world)) { + // already imported - potentially upgrade _EXPLICIT to _IMPORTED + if (decode_restriction_kind(bto_pku) == BINDING_KIND_EXPLICIT && explici != 0) { + jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, BINDING_KIND_IMPORTED, new_world); + jl_atomic_store_release(&jl_world_counter, new_world); } } - - jl_binding_t *bto = jl_get_module_binding(to, asname, 1); - if (bto == b) { - // importing a binding on top of itself. harmless. - return; - } - jl_binding_partition_t *btopart = jl_get_binding_partition(bto, jl_current_task->world_age); - jl_ptr_kind_union_t bto_pku = jl_atomic_load_relaxed(&btopart->restriction); -retry: - if (decode_restriction_kind(bto_pku) == BINDING_KIND_GUARD || - decode_restriction_kind(bto_pku) == BINDING_KIND_IMPLICIT || - decode_restriction_kind(bto_pku) == BINDING_KIND_FAILED) { - - jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)b, (explici != 0) ? BINDING_KIND_IMPORTED : BINDING_KIND_EXPLICIT); - if (!jl_atomic_cmpswap(&btopart->restriction, &bto_pku, new_pku)) - goto retry; - jl_gc_wb(btopart, b); - bto->deprecated |= b->deprecated; // we already warned about this above, but we might want to warn at the use sites too + else if (jl_bkind_is_some_import(decode_restriction_kind(bto_pku))) { + // already imported from somewhere else + jl_printf(JL_STDERR, + "WARNING: ignoring conflicting import of %s.%s into %s\n", + jl_symbol_name(from->name), jl_symbol_name(s), + jl_symbol_name(to->name)); } else { - if (eq_bindings(bpart, bto, jl_current_task->world_age)) { - // already imported - potentially upgrade to _IMPORTED or _EXPLICIT - if (jl_bkind_is_some_import(decode_restriction_kind(bto_pku))) { - jl_ptr_kind_union_t new_pku = encode_restriction(decode_restriction_value(bto_pku), (explici != 0) ? BINDING_KIND_IMPORTED : BINDING_KIND_EXPLICIT); - if (!jl_atomic_cmpswap(&btopart->restriction, &bto_pku, new_pku)) - goto retry; - // No wb, because the value is unchanged - } - } - else if (jl_bkind_is_some_import(decode_restriction_kind(bto_pku))) { - // already imported from somewhere else - jl_printf(JL_STDERR, - "WARNING: ignoring conflicting import of %s.%s into %s\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); - } - else { - // conflict with name owned by destination module - jl_printf(JL_STDERR, - "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); - } + // conflict with name owned by destination module + jl_printf(JL_STDERR, + "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", + jl_symbol_name(from->name), jl_symbol_name(s), + jl_symbol_name(to->name)); } } + JL_UNLOCK(&world_counter_lock); } JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t *s) @@ -863,53 +866,76 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) { if (to == from) return; + JL_LOCK(&world_counter_lock); JL_LOCK(&to->lock); for (size_t i = 0; i < module_usings_length(to); i++) { if (from == module_usings_getmod(to, i)) { JL_UNLOCK(&to->lock); + JL_UNLOCK(&world_counter_lock); return; } } + + size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1; struct _jl_module_using new_item = { .mod = from, - .min_world = 0, - .max_world = (size_t)-1 + .min_world = new_world, + .max_world = ~(size_t)0 }; arraylist_grow(&to->usings, sizeof(struct _jl_module_using)/sizeof(void*)); memcpy(&to->usings.items[to->usings.len-3], &new_item, sizeof(struct _jl_module_using)); jl_gc_wb(to, from); + JL_UNLOCK(&to->lock); - // print a warning if something visible via this "using" conflicts with - // an existing identifier. note that an identifier added later may still - // silently override a "using" name. see issue #2054. + // Go through all exported bindings. If we have a binding for this in the + // importing module and it is some import or guard, we need to recompute + // it. jl_svec_t *table = jl_atomic_load_relaxed(&from->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_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (b->exportp && (decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || decode_restriction_kind(pku) == BINDING_KIND_IMPORTED)) { + if (b->exportp) { jl_sym_t *var = b->globalref->name; jl_binding_t *tob = jl_get_module_binding(to, var, 0); if (tob) { - jl_binding_partition_t *tobpart = jl_get_binding_partition(tob, jl_current_task->world_age); - jl_ptr_kind_union_t tobpku = jl_walk_binding_inplace(&tob, &tobpart, jl_current_task->world_age); - if (tob && decode_restriction_kind(tobpku) != BINDING_KIND_GUARD && - // don't warn for conflicts with the module name itself. - // see issue #4715 - var != to->name && - !eq_bindings(tobpart, b, jl_current_task->world_age)) { - jl_printf(JL_STDERR, - "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", - jl_symbol_name(from->name), jl_symbol_name(var), - jl_symbol_name(to->name)); + jl_binding_partition_t *tobpart = jl_get_binding_partition(tob, new_world); + jl_ptr_kind_union_t tobpku = jl_atomic_load_relaxed(&tobpart->restriction); + enum jl_partition_kind kind = decode_restriction_kind(tobpku); + if (kind == BINDING_KIND_IMPLICIT || jl_bkind_is_some_guard(kind)) { + jl_replace_binding_locked(tob, tobpart, NULL, BINDING_KIND_IMPLICIT_RECOMPUTE, new_world); } } } table = jl_atomic_load_relaxed(&from->bindings); } + + JL_LOCK(&from->lock); + if (from->usings_backedges == jl_nothing) { + from->usings_backedges = (jl_value_t*)jl_alloc_vec_any(0); + jl_gc_wb(from, from->usings_backedges); + } + jl_array_ptr_1d_push((jl_array_t*)from->usings_backedges, (jl_value_t*)to); + JL_UNLOCK(&from->lock); + + jl_atomic_store_release(&jl_world_counter, new_world); + JL_UNLOCK(&world_counter_lock); +} + +JL_DLLEXPORT jl_value_t *jl_get_module_usings_backedges(jl_module_t *m) +{ + // We assume the caller holds the world_counter_lock, which is the only place we set this + // TODO: We may want to make this more precise with the module lock + return m->usings_backedges; +} + +JL_DLLEXPORT jl_value_t *jl_get_module_binding_or_nothing(jl_module_t *m, jl_sym_t *s) +{ + jl_binding_t *b = jl_get_module_binding(m, s, 0); + if (!b) + return jl_nothing; + return (jl_value_t*)b; } JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) @@ -941,9 +967,6 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u if (!bpart || jl_bkind_is_some_import(decode_restriction_kind(pku))) return 0; } else { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - jl_resolve_owner(b, b->globalref->mod, b->globalref->name, NULL, jl_current_task->world_age); - } pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); } if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) @@ -974,16 +997,6 @@ JL_DLLEXPORT int jl_module_public_p(jl_module_t *m, jl_sym_t *var) return b && b->publicp; } -JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) -{ - jl_binding_t *b = jl_get_module_binding(m, var, 0); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - if (!bpart) - return 0; - enum jl_partition_kind kind = decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); - return kind == BINDING_KIND_DECLARED || !jl_bkind_is_some_guard(kind); -} - uint_t bindingkey_hash(size_t idx, jl_value_t *data) { jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); // This must always happen inside the lock @@ -1052,7 +1065,8 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL, jl_current_task->world_age); + if (!b) + b = jl_get_module_binding(gr->mod, gr->name, 1); // ignores b->deprecated return b == NULL ? NULL : jl_get_binding_value(b); } @@ -1079,23 +1093,27 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var // this function is mostly only used during initialization, so the data races here are not too important to us jl_binding_t *bp = jl_get_module_binding(m, var, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(bp, jl_current_task->world_age); - assert(jl_bkind_is_some_guard(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))); + bpart->min_world = 0; + bpart->max_world = ~(size_t)0; jl_atomic_store_release(&bpart->restriction, encode_restriction(val, BINDING_KIND_CONST)); jl_gc_wb(bpart, val); } -void jl_invalidate_binding_refs(jl_globalref_t *ref, jl_binding_partition_t *invalidated_bpart, size_t new_world) +void jl_invalidate_binding_refs(jl_globalref_t *ref, jl_binding_partition_t *invalidated_bpart, jl_binding_partition_t *new_bpart, size_t new_world) { static jl_value_t *invalidate_code_for_globalref = NULL; if (invalidate_code_for_globalref == NULL && jl_base_module != NULL) invalidate_code_for_globalref = jl_get_global(jl_base_module, jl_symbol("invalidate_code_for_globalref!")); if (!invalidate_code_for_globalref) jl_error("Binding invalidation is not permitted during bootstrap."); - if (jl_generating_output()) - jl_error("Binding invalidation is not permitted during image generation."); - jl_value_t *boxed_world = jl_box_ulong(new_world); - JL_GC_PUSH1(&boxed_world); - jl_call3((jl_function_t*)invalidate_code_for_globalref, (jl_value_t*)ref, (jl_value_t*)invalidated_bpart, boxed_world); + jl_value_t **fargs; + JL_GC_PUSHARGS(fargs, 5); + fargs[0] = (jl_function_t*)invalidate_code_for_globalref; + fargs[1] = (jl_value_t*)ref; + fargs[2] = (jl_value_t*)invalidated_bpart; + fargs[3] = (jl_value_t*)new_bpart; + fargs[4] = jl_box_ulong(new_world); + jl_apply(fargs, 5); JL_GC_POP(); } @@ -1131,50 +1149,90 @@ JL_DLLEXPORT void jl_maybe_add_binding_backedge(jl_globalref_t *gr, jl_module_t jl_add_binding_backedge(b, edge); } -JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) +JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b, + jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind, size_t new_world) { - jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL, jl_current_task->world_age); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + assert(jl_atomic_load_relaxed(&b->partitions) == old_bpart); + jl_atomic_store_release(&old_bpart->max_world, new_world-1); + jl_binding_partition_t *new_bpart = new_binding_partition(); + new_bpart->min_world = new_world; + if (kind == BINDING_KIND_IMPLICIT_RECOMPUTE) { + assert(!restriction_val); + check_new_binding_implicit(new_bpart, b, NULL, 0, new_world); + } + else + jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction(restriction_val, kind)); + jl_atomic_store_relaxed(&new_bpart->next, old_bpart); + jl_gc_wb(new_bpart, old_bpart); - if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GUARD) { - // Already guard - return; + jl_atomic_store_release(&b->partitions, new_bpart); + jl_gc_wb(b, new_bpart); + + + if (jl_typeinf_world != 1) { + jl_task_t *ct = jl_current_task; + size_t last_world = ct->world_age; + ct->world_age = jl_typeinf_world; + jl_invalidate_binding_refs(b->globalref, old_bpart, new_bpart, new_world-1); + ct->world_age = last_world; } + return new_bpart; +} + +JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding(jl_binding_t *b, + jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind) { + JL_LOCK(&world_counter_lock); - jl_task_t *ct = jl_current_task; - size_t last_world = ct->world_age; - size_t new_max_world = jl_atomic_load_acquire(&jl_world_counter); - jl_atomic_store_release(&bpart->max_world, new_max_world); - ct->world_age = jl_typeinf_world; - jl_invalidate_binding_refs(gr, bpart, new_max_world); - ct->world_age = last_world; - jl_atomic_store_release(&jl_world_counter, new_max_world + 1); + + if (jl_atomic_load_relaxed(&b->partitions) != old_bpart) { + JL_UNLOCK(&world_counter_lock); + return NULL; + } + + size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1; + jl_binding_partition_t *bpart = jl_replace_binding_locked(b, old_bpart, restriction_val, kind, new_world); + if (bpart && bpart->min_world == new_world) + jl_atomic_store_release(&jl_world_counter, new_world); + JL_UNLOCK(&world_counter_lock); + return bpart; } JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; - b = jl_resolve_owner(b, gr->mod, gr->name, NULL, jl_current_task->world_age); + if (!b) + b = jl_get_module_binding(gr->mod, gr->name, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (!bpart) return 0; return jl_bkind_is_some_constant(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction))); } -JL_DLLEXPORT void jl_force_binding_resolution(jl_globalref_t *gr, size_t world) +JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; - jl_resolve_owner(b, gr->mod, gr->name, NULL, world); + if (!b) + b = jl_get_module_binding(gr->mod, gr->name, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + + if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GUARD) { + // Already guard + return; + } + + for (;;) + if (jl_replace_binding(b, bpart, NULL, BINDING_KIND_GUARD)) + break; } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - return b && jl_bkind_is_some_constant(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction))); + jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + return b && jl_bkind_is_some_constant(decode_restriction_kind(pku)); } // set the deprecated flag for a binding: @@ -1188,12 +1246,9 @@ JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag) JL_DLLEXPORT int jl_is_binding_deprecated(jl_module_t *m, jl_sym_t *var) { - if (jl_binding_resolved_p(m, var)) { - // XXX: this only considers if the original is deprecated, not this precise binding - jl_binding_t *b = jl_get_binding(m, var); - return b && b->deprecated; - } - return 0; + // XXX: this only considers if the original is deprecated, not this precise binding + jl_binding_t *b = jl_get_binding(m, var); + return b && b->deprecated; } void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b) @@ -1229,22 +1284,19 @@ jl_value_t *jl_check_binding_assign_value(jl_binding_t *b JL_PROPAGATES_ROOT, jl JL_GC_PUSH1(&rhs); // callee-rooted jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - assert(!jl_bkind_is_some_guard(decode_restriction_kind(pku)) && !jl_bkind_is_some_import(decode_restriction_kind(pku))); - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_constant(kind)) { jl_value_t *old = decode_restriction_value(pku); JL_GC_PROMISE_ROOTED(old); if (jl_egal(rhs, old)) { JL_GC_POP(); return NULL; } - if (jl_typeof(rhs) == jl_typeof(old)) - jl_errorf("invalid redefinition of constant %s.%s. This redefinition may be permitted using the `const` keyword.", - jl_symbol_name(mod->name), jl_symbol_name(var)); - else - jl_errorf("invalid redefinition of constant %s.%s.", - jl_symbol_name(mod->name), jl_symbol_name(var)); + jl_errorf("invalid assignment to constant %s.%s. This redefinition may be permitted using the `const` keyword.", + jl_symbol_name(mod->name), jl_symbol_name(var)); } - jl_value_t *old_ty = decode_restriction_value(pku); + assert(kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_GLOBAL); + jl_value_t *old_ty = kind == BINDING_KIND_DECLARED ? (jl_value_t*)jl_any_type : decode_restriction_value(pku); JL_GC_PROMISE_ROOTED(old_ty); if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty) { if (!jl_isa(rhs, old_ty)) @@ -1285,7 +1337,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); assert(!jl_bkind_is_some_guard(decode_restriction_kind(pku)) && !jl_bkind_is_some_import(decode_restriction_kind(pku))); if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) - jl_errorf("invalid redefinition of constant %s.%s", + jl_errorf("invalid assignment to constant %s.%s", jl_symbol_name(mod->name), jl_symbol_name(var)); jl_value_t *ty = decode_restriction_value(pku); JL_GC_PROMISE_ROOTED(ty); @@ -1422,7 +1474,7 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) break; jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_IMPLICIT) { - jl_atomic_store_relaxed(&bpart->restriction, encode_restriction(NULL, BINDING_KIND_GUARD)); + jl_atomic_store_relaxed(&b->partitions, NULL); } } JL_UNLOCK(&m->lock); diff --git a/src/staticdata.c b/src/staticdata.c index cb1dc54d26d50b..d4bfcd8ecb69d5 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -827,6 +827,8 @@ static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_ for (size_t i = 0; i < module_usings_length(m); i++) { jl_queue_for_serialization(s, module_usings_getmod(m, i)); } + + jl_queue_for_serialization(s, m->usings_backedges); } // Anything that requires uniquing or fixing during deserialization needs to be "toplevel" @@ -1299,10 +1301,14 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t newm->file = NULL; arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, file))); arraylist_push(&s->relocs_list, (void*)backref_id(s, m->file, s->link_ids_relocs)); + newm->usings_backedges = NULL; + arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, usings_backedges))); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->usings_backedges, s->link_ids_relocs)); // write out the usings list memset(&newm->usings._space, 0, sizeof(newm->usings._space)); if (m->usings.items == &m->usings._space[0]) { + newm->usings.items = &newm->usings._space[0]; // Push these relocations here, to keep them in order. This pairs with the `newm->usings.items = ` below. arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, usings.items))); arraylist_push(&s->relocs_list, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + item)); @@ -1316,7 +1322,7 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t if (s->incremental) { if (data->max_world != (size_t)-1) newm_data->max_world = 0; - newm_data->min_world = 0; + newm_data->min_world = jl_require_world; } arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, usings._space[3*i]))); arraylist_push(&s->relocs_list, (void*)backref_id(s, data->mod, s->link_ids_relocs)); diff --git a/src/toplevel.c b/src/toplevel.c index cdd390b9b49edd..d9ab4011904ebb 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -303,7 +303,7 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f } extern void check_safe_newbinding(jl_module_t *m, jl_sym_t *var); -void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { +void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, int strong) { // create uninitialized mutable binding for "global x" decl sometimes or probably jl_module_t *gm; jl_sym_t *gs; @@ -321,42 +321,58 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1; jl_binding_t *b = jl_get_module_binding(gm, gs, 1); jl_binding_partition_t *bpart = NULL; - jl_ptr_kind_union_t new_pku = encode_restriction(set_type, set_type == NULL ? BINDING_KIND_DECLARED : BINDING_KIND_GLOBAL); + if (!strong && set_type) + jl_error("Weak global definitions cannot have types"); + enum jl_partition_kind new_kind = strong ? BINDING_KIND_GLOBAL : BINDING_KIND_DECLARED; + jl_value_t *global_type = set_type; + if (strong && !global_type) + global_type = (jl_value_t*)jl_any_type; while (1) { bpart = jl_get_binding_partition(b, new_world); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL) { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - if (decode_restriction_kind(pku) == BINDING_KIND_DECLARED && !set_type) - goto done; + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (kind != BINDING_KIND_GLOBAL) { + if (jl_bkind_is_some_guard(kind) || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_IMPLICIT) { + if (decode_restriction_kind(pku) == new_kind) { + if (!set_type) + goto done; + goto check_type; + } check_safe_newbinding(gm, gs); - if (jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) { - break; + if (bpart->min_world == new_world) { + if (jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(global_type, new_kind))) { + break; + } + if (set_type) + jl_gc_wb(bpart, set_type); + continue; + } else { + jl_replace_binding_locked(b, bpart, global_type, new_kind, new_world); } - continue; + break; } else if (set_type) { if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { - jl_errorf("cannot set type for imported constant %s.%s.", + jl_errorf("cannot set type for constant %s.%s.", jl_symbol_name(gm->name), jl_symbol_name(gs)); } else { - jl_errorf("cannot set type for imported global %s.%s.", + jl_errorf("cannot set type for imported binding %s.%s.", jl_symbol_name(gm->name), jl_symbol_name(gs)); } } } - if (!set_type) - goto done; - jl_value_t *old_ty = decode_restriction_value(pku); - JL_GC_PROMISE_ROOTED(old_ty); - if (!jl_types_equal(set_type, old_ty)) { - jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", - jl_symbol_name(gm->name), jl_symbol_name(gs)); + if (set_type) + { +check_type: + jl_value_t *old_ty = decode_restriction_value(pku); + JL_GC_PROMISE_ROOTED(old_ty); + if (!jl_types_equal(set_type, old_ty)) { + jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", + jl_symbol_name(gm->name), jl_symbol_name(gs)); + } + } goto done; } - if (set_type) - jl_gc_wb(bpart, set_type); - bpart->min_world = new_world; jl_atomic_store_release(&jl_world_counter, new_world); done: JL_UNLOCK(&world_counter_lock); @@ -367,7 +383,7 @@ void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) size_t i, l = jl_array_nrows(ex->args); for (i = 0; i < l; i++) { jl_value_t *arg = jl_exprarg(ex, i); - jl_declare_global(m, arg, NULL); + jl_declare_global(m, arg, NULL, 0); } } @@ -431,10 +447,8 @@ static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int if (jl_is_globalref(f)) { jl_module_t *mod = jl_globalref_mod(f); jl_sym_t *name = jl_globalref_name(f); - if (jl_binding_resolved_p(mod, name)) { - jl_binding_t *b = jl_get_binding(mod, name); - called = jl_get_binding_value_if_const(b); - } + jl_binding_t *b = jl_get_binding(mod, name); + called = jl_get_binding_value_if_const(b); } else if (jl_is_quotenode(f)) { called = jl_quotenode_value(f); @@ -747,72 +761,72 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( if (!b) { b = jl_get_module_binding(mod, var, 1); } + jl_binding_partition_t *new_bpart = NULL; jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - int did_warn = 0; - while (1) { + while (!new_bpart) { enum jl_partition_kind kind = decode_restriction_kind(pku); if (jl_bkind_is_some_constant(kind)) { if (!val) { + new_bpart = bpart; break; } jl_value_t *old = decode_restriction_value(pku); JL_GC_PROMISE_ROOTED(old); - if (jl_egal(val, old)) + if (jl_egal(val, old)) { + new_bpart = bpart; break; - if (!did_warn) { - if (jl_typeof(val) != jl_typeof(old) || jl_is_type(val) || jl_is_module(val)) - jl_errorf("invalid redefinition of constant %s.%s", - jl_symbol_name(mod->name), - jl_symbol_name(var)); - else - jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", - jl_symbol_name(mod->name), - jl_symbol_name(var)); - did_warn = 1; - } - if (new_world > bpart->min_world) { - // TODO: Invoke invalidation logic here - jl_atomic_store_relaxed(&bpart->max_world, new_world - 1); - bpart = jl_get_binding_partition(b, new_world); - pku = jl_atomic_load_relaxed(&bpart->restriction); - } - } else if (!jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - if (jl_bkind_is_some_import(decode_restriction_kind(pku))) { - jl_errorf("cannot declare %s.%s constant; it was already declared as an import", - jl_symbol_name(mod->name), jl_symbol_name(var)); - } else { - jl_errorf("cannot declare %s.%s constant; it was already declared global", - jl_symbol_name(mod->name), jl_symbol_name(var)); } + } else if (jl_bkind_is_some_import(kind) && kind != BINDING_KIND_IMPLICIT) { + jl_errorf("cannot declare %s.%s constant; it was already declared as an import", + jl_symbol_name(mod->name), jl_symbol_name(var)); + } else if (kind == BINDING_KIND_GLOBAL) { + jl_errorf("cannot declare %s.%s constant; it was already declared global", + jl_symbol_name(mod->name), jl_symbol_name(var)); } - if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { - continue; + if (bpart->min_world == new_world) { + if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { + continue; + } + jl_gc_wb(bpart, val); + new_bpart = bpart; + } else { + new_bpart = jl_replace_binding_locked(b, bpart, val, constant_kind, new_world); } - jl_gc_wb(bpart, val); - size_t prev_min_world = bpart->min_world; - bpart->min_world = new_world; - int need_backdate = 0; - if (new_world && val) { - if (prev_min_world == 0) { - need_backdate = 1; - } else if (kind == BINDING_KIND_DECLARED) { - jl_binding_partition_t *prev_bpart = jl_get_binding_partition(b, prev_min_world-1); + int need_backdate = new_world && val; + if (need_backdate) { + // We will backdate as long as this partition was never explicitly + // declared const, global, or imported. + jl_binding_partition_t *prev_bpart = bpart; + for (;;) { jl_ptr_kind_union_t prev_pku = jl_atomic_load_relaxed(&prev_bpart->restriction); - if (prev_bpart->min_world == 0 && decode_restriction_kind(prev_pku) == BINDING_KIND_GUARD) { - // Just keep it simple and use one backdated const entry for both previous guard partition - // ranges. - jl_atomic_store_relaxed(&prev_bpart->max_world, new_world-1); - need_backdate = 1; + enum jl_partition_kind prev_kind = decode_restriction_kind(prev_pku); + if (jl_bkind_is_some_constant(prev_kind) || prev_kind == BINDING_KIND_GLOBAL || + (jl_bkind_is_some_import(prev_kind))) { + need_backdate = 0; + break; } + if (prev_bpart->min_world == 0) + break; + prev_bpart = jl_get_binding_partition(b, prev_bpart->min_world - 1); } } + // If backdate is required, rewrite all previous binding partitions to + // backdated const if (need_backdate) { - jl_declare_constant_val3(b, mod, var, val, BINDING_KIND_BACKDATED_CONST, 0); + // We will backdate as long as this partition was never explicitly + // declared const, global, or *explicitly* imported. + jl_binding_partition_t *prev_bpart = bpart; + for (;;) { + jl_atomic_store_relaxed(&prev_bpart->restriction, encode_restriction(val, BINDING_KIND_BACKDATED_CONST)); + if (prev_bpart->min_world == 0) + break; + prev_bpart = jl_get_binding_partition(b, prev_bpart->min_world - 1); + } } } JL_GC_POP(); - return bpart; + return new_bpart; } JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2( @@ -1035,7 +1049,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val size_t i, l = jl_array_nrows(ex->args); for (i = 0; i < l; i++) { jl_value_t *arg = jl_exprarg(ex, i); - jl_declare_global(m, arg, NULL); + jl_declare_global(m, arg, NULL, 0); } JL_GC_POP(); return jl_nothing; diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index 4a320282610cda..6b75a228b2761d 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -17,7 +17,7 @@ export apropos, edit, less, code_warntype, code_llvm, code_native, methodswith, import Base.Docs.apropos using Base: unwrap_unionall, rewrap_unionall, isdeprecated, Bottom, summarysize, - signature_type, format_bytes, isbindingresolved + signature_type, format_bytes using Base.Libc using Markdown @@ -264,7 +264,7 @@ function _subtypes_in!(mods::Array, x::Type) m = pop!(mods) xt = xt::DataType for s in names(m, all = true) - if isbindingresolved(m, s) && !isdeprecated(m, s) && isdefined(m, s) + if !isdeprecated(m, s) && isdefined(m, s) t = getfield(m, s) dt = isa(t, UnionAll) ? unwrap_unionall(t) : t if isa(dt, DataType) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index cc4f4f00cf8f61..561e4bcd4feb2d 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -72,7 +72,6 @@ function UndefVarError_hint(io::IO, ex::UndefVarError) end function _UndefVarError_warnfor(io::IO, m::Module, var::Symbol) - Base.isbindingresolved(m, var) || return false (Base.isexported(m, var) || Base.ispublic(m, var)) || return false active_mod = Base.active_module() print(io, "\nHint: ") diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index eadc2672dd29bb..e70eb8dd97927d 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -240,8 +240,7 @@ function complete_symbol!(suggestions::Vector{Completion}, return suggestions end -completes_module(mod::Module, x::Symbol) = - Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module) +completes_module(mod::Module, x::Symbol) = isdefined(mod, x) && isa(getglobal(mod, x), Module) function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t)) if isa(t, Union) diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index 313994505b3eef..c25b7a748ccfbb 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -312,11 +312,7 @@ function summarize(binding::Binding, sig) else println(io, "No documentation found.\n") quot = any(isspace, sprint(print, binding)) ? "'" : "" - if Base.isbindingresolved(binding.mod, binding.var) - println(io, "Binding ", quot, "`", binding, "`", quot, " exists, but has not been assigned a value.") - else - println(io, "Binding ", quot, "`", binding, "`", quot, " does not exist.") - end + println(io, "Binding ", quot, "`", binding, "`", quot, " exists, but has not been assigned a value.") end md = Markdown.parse(seekstart(io)) # Save metadata in the generated markdown. @@ -567,8 +563,7 @@ function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main, internal_ac quote repl_latex($io, $str) repl_search($io, $str, $mod) - $(if !isdefined(mod, s) && !Base.isbindingresolved(mod, s) && !haskey(keywords, s) && !Base.isoperator(s) - # n.b. we call isdefined for the side-effect of resolving the binding, if possible + $(if !isdefined(mod, s) && !haskey(keywords, s) && !Base.isoperator(s) :(repl_corrections($io, $str, $mod)) end) $(_repl(s, brief, mod, internal_accesses)) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 77d056b63655da..59e994f88945b2 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -214,8 +214,6 @@ end let s = "using REP" c, r = test_complete_32377(s) @test count(isequal("REPL"), c) == 1 - # issue #30234 - @test !Base.isbindingresolved(M32377, :tanh) # check what happens if REPL is already imported M32377.eval(:(using REPL)) c, r = test_complete_32377(s) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 88fd055f7bd58b..e8d670b3d7d000 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -2158,7 +2158,7 @@ function detect_ambiguities(mods::Module...; while !isempty(work) mod = pop!(work) for n in names(mod, all = true) - (!Base.isbindingresolved(mod, n) || Base.isdeprecated(mod, n)) && continue + Base.isdeprecated(mod, n) && continue if !isdefined(mod, n) if is_in_mods(mod, recursive, mods) if allowed_undefineds === nothing || GlobalRef(mod, n) ∉ allowed_undefineds @@ -2229,7 +2229,7 @@ function detect_unbound_args(mods...; while !isempty(work) mod = pop!(work) for n in names(mod, all = true) - (!Base.isbindingresolved(mod, n) || Base.isdeprecated(mod, n)) && continue + Base.isdeprecated(mod, n) && continue if !isdefined(mod, n) if is_in_mods(mod, recursive, mods) if allowed_undefineds === nothing || GlobalRef(mod, n) ∉ allowed_undefineds diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 5f859e773f5d28..0f29817e74dd55 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -171,12 +171,8 @@ module UnboundAmbig55868 using .B export C, D end -@test !Base.isbindingresolved(UnboundAmbig55868, :C) -@test !Base.isbindingresolved(UnboundAmbig55868, :D) @test isempty(detect_unbound_args(UnboundAmbig55868)) @test isempty(detect_ambiguities(UnboundAmbig55868)) -@test !Base.isbindingresolved(UnboundAmbig55868, :C) -@test !Base.isbindingresolved(UnboundAmbig55868, :D) # Test that Core and Base are free of ambiguities # not using isempty so this prints more information when it fails diff --git a/test/atomics.jl b/test/atomics.jl index adfe4c87138cdb..7e9f29c23ca108 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -23,22 +23,32 @@ mutable struct Refxy{T} end modname = String(nameof(@__MODULE__)) -@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} +const orig_Refxy = Refxy +const orig_ARefxy = ARefxy +mutable struct ARefxy{T} @atomic x::T @atomic y::T end -@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} +@test orig_ARefxy !== ARefxy +const ARefxy = orig_ARefxy +mutable struct ARefxy{T} x::T y::T end -@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} +@test orig_ARefxy !== ARefxy +const ARefxy = orig_ARefxy +mutable struct ARefxy{T} x::T @atomic y::T end -@test_throws ErrorException("invalid redefinition of constant $modname.Refxy") @eval mutable struct Refxy{T} +@test orig_ARefxy !== ARefxy +const ARefxy = orig_ARefxy +mutable struct Refxy{T} x::T @atomic y::T end +@test orig_Refxy !== Refxy +const Refxy = orig_Refxy copy(r::Union{Refxy,ARefxy}) = typeof(r)(r.x, r.y) function add(x::T, y)::T where {T}; x + y; end diff --git a/test/core.jl b/test/core.jl index 5e2677f0f075ff..853ab697a72956 100644 --- a/test/core.jl +++ b/test/core.jl @@ -63,20 +63,7 @@ mutable struct ABCDconst c const d::Union{Int,Nothing} end -@test_throws(ErrorException("invalid redefinition of constant $(nameof(curmod)).ABCDconst"), - mutable struct ABCDconst - const a - const b::Int - c - d::Union{Int,Nothing} - end) -@test_throws(ErrorException("invalid redefinition of constant $(nameof(curmod)).ABCDconst"), - mutable struct ABCDconst - a - b::Int - c - d::Union{Int,Nothing} - end) + let abcd = ABCDconst(1, 2, 3, 4) @test (1, 2, 3, 4) === (abcd.a, abcd.b, abcd.c, abcd.d) @test_throws(ErrorException("setfield!: const field .a of type ABCDconst cannot be changed"), @@ -113,6 +100,21 @@ let abcd = ABCDconst(1, 2, 3, 4) abcd.d = nothing) @test (1, 2, "not constant", 4) === (abcd.a, abcd.b, abcd.c, abcd.d) end +const orig_ABCDconst = ABCDconst +mutable struct ABCDconst + const a + const b::Int + c + d::Union{Int,Nothing} +end +@test ABCDconst !== orig_ABCDconst +mutable struct ABCDconst + a + b::Int + c + d::Union{Int,Nothing} +end +@test ABCDconst !== orig_ABCDconst # Issue #52686 struct A52686{T} end struct B52686{T, S} @@ -1210,10 +1212,6 @@ let A = [1] @test x == 1 end -# Make sure that `Module` is not resolved to `Core.Module` during sysimg generation -# so that users can define their own binding named `Module` in Main. -@test success(`$(Base.julia_cmd()) -e '@assert !Base.isbindingresolved(Main, :Module)'`) - # Module() constructor @test names(Module(:anonymous), all = true, imported = true) == [:anonymous] @test names(Module(:anonymous, false), all = true, imported = true) == [:anonymous] @@ -3885,11 +3883,13 @@ end struct NInitializedTestType a end +const orig_NInitializedTestType = NInitializedTestType -@test_throws ErrorException @eval struct NInitializedTestType +struct NInitializedTestType a NInitializedTestType() = new() end +@test orig_NInitializedTestType !== NInitializedTestType # issue #12394 mutable struct Empty12394 end @@ -5578,76 +5578,94 @@ struct A16424 x y end +const orig_A16424 = A16424 struct A16424 # allowed x y end +@test A16424 === orig_A16424 -@test_throws ErrorException @eval struct A16424 +struct A16424 x z end +@test A16424 !== orig_A16424 +const A16424 = orig_A16424 -@test_throws ErrorException @eval struct A16424 +struct A16424 x y::Real end +@test A16424 !== orig_A16424 +const A16424 = orig_A16424 struct B16424{T} a end +const orig_B16424 = B16424 struct B16424{T} a end +@test B16424 === orig_B16424 -@test_throws ErrorException @eval struct B16424{S} +struct B16424{S} a end +@test B16424 !== orig_B16424 struct C16424{T,S} x::T y::S end +const orig_C16424 = C16424 struct C16424{T,S} x::T y::S end +@test C16424 === orig_C16424 -@test_throws ErrorException @eval struct C16424{T,S} +struct C16424{T,S} x::S y::T end +@test C16424 !== orig_C16424 struct D16424{T<:Real,S<:T} x::Vector{S} y::Vector{T} end +const orig_D16424 = D16424 struct D16424{T<:Real,S<:T} x::Vector{S} y::Vector{T} end +@test D16424 === orig_D16424 -@test_throws ErrorException struct D16424{T<:Real,S<:Real} +struct D16424{T<:Real,S<:Real} x::Vector{S} y::Vector{T} end +@test D16424 !== orig_D16424 # issue #20999, allow more type redefinitions struct T20999 x::Array{T} where T<:Real end +const orig_T20999 = T20999 struct T20999 x::Array{T} where T<:Real end +@test T20999 === orig_T20999 -@test_throws ErrorException struct T20999 +struct T20999 x::Array{T} where T<:Integer end +@test T20999 !== orig_T20999 # issue #54757, type redefinitions with recursive reference in supertype struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A,N}},Vararg{Y,N}} where {X,Y<:T54757}, N} @@ -5655,20 +5673,27 @@ struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Unio y::Union{A,T54757{A,N}} z::T54757{A} end +const orig_T54757 = T54757 struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A,N}},Vararg{Y,N}} where {X,Y<:T54757}, N} x::A y::Union{A,T54757{A,N}} z::T54757{A} end +# The type is identical - either answer is semantically allowed here +# However, knowing that the type is identical would require reasoning about the purity of the +# field definitions exprs, which we do not do. Thus, simply check that this doesn't error and +# then reset to the original for the next test. +const T54757 = orig_T54757 -@test_throws ErrorException struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A}},Vararg{Y,N}} where {X,Y<:T54757}, N} +struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A}},Vararg{Y,N}} where {X,Y<:T54757}, N} x::A y::Union{A,T54757{A,N}} z::T54757{A} end +@test orig_T54757 !== T54757 - +# initialization of Vector{Core.TypeofBottom} let a = Vector{Core.TypeofBottom}(undef, 2) @test a[1] == Union{} @test a == [Union{}, Union{}] @@ -7676,29 +7701,35 @@ struct S36104{K,V} # check that redefining it works S36104{K,V}() where {K,V} = new() S36104{K,V}(x::S36104) where {K,V} = new(x) end -# with a gensymmed unionall -struct Symmetric{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T} + +# with a gensymmed unionall (#39778) +struct Symmetric39778{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T} data::S uplo::Char end -struct Symmetric{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T} +const orig_Symmetric39778 = Symmetric39778 +struct Symmetric39778{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T} data::S uplo::Char end -@test_throws ErrorException begin - struct Symmetric{T,S<:AbstractMatrix{T}} <: AbstractMatrix{T} - data::S - uplo::Char - end -end +@test Symmetric39778 === orig_Symmetric39778 +struct Symmetric39778{T,S<:AbstractMatrix{T}} <: AbstractMatrix{T} + data::S + uplo::Char end +@test Symmetric39778 !== orig_Symmetric39778 + +end # module M36104 + @test fieldtypes(M36104.T36104) == (Vector{M36104.T36104},) @test_throws ErrorException("expected") @eval(struct X36104; x::error("expected"); end) @test !@isdefined(X36104) struct X36104; x::Int; end @test fieldtypes(X36104) == (Int,) primitive type P36104 8 end -@test_throws ErrorException("invalid redefinition of constant $(nameof(curmod)).P36104") @eval(primitive type P36104 16 end) +const orig_P36104 = P36104 +primitive type P36104 16 end +@test P36104 !== orig_P36104 # Malformed invoke f_bad_invoke(x::Int) = invoke(x, (Any,), x) diff --git a/test/errorshow.jl b/test/errorshow.jl index f83bbe31b7cc45..8f7482ce3235ec 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -216,13 +216,14 @@ Base.show_method_candidates(buf, try bad_vararg_decl("hello", 3) catch e e end) macro except_str(expr, err_type) source_info = __source__ + errmsg = "expected failure, but no exception thrown for $expr" return quote let err = nothing try $(esc(expr)) catch err end - err === nothing && error("expected failure, but no exception thrown") + err === nothing && error($errmsg) @testset let expr=$(repr(expr)) $(Expr(:macrocall, Symbol("@test"), source_info, :(typeof(err) === $(esc(err_type))))) end @@ -255,6 +256,7 @@ end macro except_stackframe(expr, err_type) source_info = __source__ + errmsg = "expected failure, but no exception thrown for $expr" return quote let err = nothing local st @@ -263,7 +265,7 @@ macro except_stackframe(expr, err_type) catch err st = stacktrace(catch_backtrace()) end - err === nothing && error("expected failure, but no exception thrown") + err === nothing && error($errmsg) @testset let expr=$(repr(expr)) $(Expr(:macrocall, Symbol("@test"), source_info, :(typeof(err) === $(esc(err_type))))) end @@ -297,6 +299,7 @@ err_str = @except_str 1 + 2 MethodError err_str = @except_str Float64[](1) MethodError @test !occursin("import Base.Array", err_str) +global Array Array() = 1 err_str = @except_str Array([1]) MethodError @test occursin("import Base.Array", err_str) diff --git a/test/misc.jl b/test/misc.jl index 070952db890322..fef573e9fc747b 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1218,8 +1218,8 @@ end @test readlines(`$(Base.julia_cmd()) --startup-file=no -e 'foreach(println, names(Main))'`) == ["Base","Core","Main"] # issue #26310 -@test_warn "could not import" Core.eval(@__MODULE__, :(import .notdefined_26310__)) -@test_warn "could not import" Core.eval(Main, :(import ........notdefined_26310__)) +@test_warn "undeclared at import time" Core.eval(@__MODULE__, :(import .notdefined_26310__)) +@test_warn "undeclared at import time" Core.eval(Main, :(import ........notdefined_26310__)) @test_nowarn Core.eval(Main, :(import .Main)) @test_nowarn Core.eval(Main, :(import ....Main)) diff --git a/test/precompile.jl b/test/precompile.jl index 194f88719642e9..a8154090c0360a 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1359,7 +1359,6 @@ precompile_test_harness("package_callbacks") do dir """) Base.compilecache(Base.PkgId("$(Test2_module)")) - @test !Base.isbindingresolved(Main, Test2_module) Base.require(Main, Test2_module) @test take!(loaded_modules) == Test1_module @test take!(loaded_modules) == Test2_module diff --git a/test/rebinding.jl b/test/rebinding.jl index aee866facaf023..5e50d7f022d061 100644 --- a/test/rebinding.jl +++ b/test/rebinding.jl @@ -62,6 +62,26 @@ module Rebinding @test f_generated_return_delete_me() == 4 Base.delete_binding(@__MODULE__, :delete_me) @test_throws UndefVarError f_generated_return_delete_me() + + module DeleteMeModule + export delete_me_implicit + const delete_me_explicit = 5 + const delete_me_implicit = 6 + end + + # + via import + using .DeleteMeModule: delete_me_explicit + f_return_delete_me_explicit() = delete_me_explicit + @test f_return_delete_me_explicit() == 5 + Base.delete_binding(DeleteMeModule, :delete_me_explicit) + @test_throws UndefVarError f_return_delete_me_explicit() + + # + via using + using .DeleteMeModule + f_return_delete_me_implicit() = delete_me_implicit + @test f_return_delete_me_implicit() == 6 + Base.delete_binding(DeleteMeModule, :delete_me_implicit) + @test_throws UndefVarError f_return_delete_me_implicit() end module RebindingPrecompile @@ -95,9 +115,11 @@ module RebindingPrecompile """) Base.compilecache(Base.PkgId("UseTheBindings")) @eval using LotsOfBindingsToDelete - # Delete some bindings before loading the dependent package - Base.delete_binding(LotsOfBindingsToDelete, :delete_me_1) - Base.delete_binding(LotsOfBindingsToDelete, :delete_me_3) + invokelatest() do + # Delete some bindings before loading the dependent package + Base.delete_binding(LotsOfBindingsToDelete, :delete_me_1) + Base.delete_binding(LotsOfBindingsToDelete, :delete_me_3) + end # Load the dependent package @eval using UseTheBindings invokelatest() do diff --git a/test/reflection.jl b/test/reflection.jl index 9aa8fe512cd7c0..57c32d19de6294 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -969,10 +969,6 @@ f20872(::Val, ::Val) = false @test_throws ErrorException which(f20872, Tuple{Any,Val{N}} where N) @test which(Tuple{typeof(f20872), Val{1}, Val{2}}).sig == Tuple{typeof(f20872), Val, Val} -module M29962 end -# make sure checking if a binding is deprecated does not resolve it -@test !Base.isdeprecated(M29962, :sin) && !Base.isbindingresolved(M29962, :sin) - # @locals using Base: @locals let diff --git a/test/show.jl b/test/show.jl index 75f04c1e02096c..99bb14df15b234 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1903,15 +1903,6 @@ end b = IOBuffer() show(IOContext(b, :module => @__MODULE__), TypeA) @test String(take!(b)) == "TypeA" - - # issue #26354; make sure testing for symbol visibility doesn't cause - # spurious binding resolutions - show(IOContext(b, :module => TestShowType), Base.Pair) - @test !Base.isbindingresolved(TestShowType, :Pair) - @test String(take!(b)) == "Core.Pair" - show(IOContext(b, :module => TestShowType), Base.Complex) - @test Base.isbindingresolved(TestShowType, :Complex) - @test String(take!(b)) == "Complex" end @testset "typeinfo" begin diff --git a/test/staged.jl b/test/staged.jl index f3dbdcd73d8115..6811bc05a9b68b 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -312,7 +312,7 @@ end :(global x33243 = 2) end @test_throws ErrorException f33243() -global x33243 +global x33243::Any @test f33243() === 2 @test x33243 === 2 diff --git a/test/syntax.jl b/test/syntax.jl index f29dd4978d3097..ef8999c6b86377 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -2652,10 +2652,10 @@ using ..Mod end @test Mod3.f(10) == 21 @test !isdefined(Mod3, :func) -@test_throws ErrorException("invalid method definition in Mod3: function Mod3.f must be explicitly imported to be extended") Core.eval(Mod3, :(f(x::Int) = x)) +@test_throws ErrorException("invalid method definition in Mod3: function Mod.f must be explicitly imported to be extended") Core.eval(Mod3, :(f(x::Int) = x)) @test !isdefined(Mod3, :always_undef) # resolve this binding now in Mod3 -@test_throws ErrorException("invalid method definition in Mod3: exported function Mod.always_undef does not exist") Core.eval(Mod3, :(always_undef(x::Int) = x)) -@test_throws ErrorException("cannot declare Mod3.always_undef constant; it was already declared as an import") Core.eval(Mod3, :(const always_undef = 3)) +@test Core.eval(Mod3, :(always_undef(x::Int) = x)) == invokelatest(getglobal, Mod3, :always_undef) +@test Core.eval(Mod3, :(const always_undef = 3)) == invokelatest(getglobal, Mod3, :always_undef) @test_throws ErrorException("cannot declare Mod3.f constant; it was already declared as an import") Core.eval(Mod3, :(const f = 3)) @test_throws ErrorException("cannot declare Mod.maybe_undef constant; it was already declared global") Core.eval(Mod, :(const maybe_undef = 3)) @@ -4046,3 +4046,9 @@ function fs56711() return f end @test !@isdefined(x_should_not_be_defined) + +# Test that importing twice is allowed without warning +@test_nowarn baremodule ImportTwice + import ..Base + using .Base: zero, zero +end diff --git a/test/worlds.jl b/test/worlds.jl index 48fb6593d3a379..6f0a5e8bbfa1ed 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -107,7 +107,7 @@ end g265() = [f265(x) for x in 1:3.] wc265 = get_world_counter() wc265_41332a = Task(tls_world_age) -@test tls_world_age() == wc265 + 2 +@test tls_world_age() == wc265 + 1 (function () global wc265_41332b = Task(tls_world_age) @eval f265(::Any) = 1.0 @@ -115,15 +115,15 @@ wc265_41332a = Task(tls_world_age) global wc265_41332d = Task(tls_world_age) nothing end)() -@test wc265 + 10 == get_world_counter() == tls_world_age() +@test wc265 + 9 == get_world_counter() == tls_world_age() schedule(wc265_41332a) schedule(wc265_41332b) schedule(wc265_41332c) schedule(wc265_41332d) @test wc265 + 1 == fetch(wc265_41332a) -@test wc265 + 8 == fetch(wc265_41332b) -@test wc265 + 10 == fetch(wc265_41332c) -@test wc265 + 8 == fetch(wc265_41332d) +@test wc265 + 7 == fetch(wc265_41332b) +@test wc265 + 9 == fetch(wc265_41332c) +@test wc265 + 7 == fetch(wc265_41332d) chnls, tasks = Base.channeled_tasks(2, wfunc) t265 = tasks[1] @@ -509,3 +509,18 @@ struct FooBackdated FooBackdated() = new(FooBackdated[]) end @test Base.invoke_in_world(before_backdate_age, isdefined, @__MODULE__, :FooBackdated) + +# Test that ambiguous binding intersect the using'd binding's world ranges +module AmbigWorldTest + using Test + module M1; export x; end + module M2; export x; end + using .M1, .M2 + Core.eval(M1, :(x=1)) + Core.eval(M2, :(x=2)) + @test_throws UndefVarError x + @test convert(Core.Binding, GlobalRef(@__MODULE__, :x)).partitions.min_world == max( + convert(Core.Binding, GlobalRef(M1, :x)).partitions.min_world, + convert(Core.Binding, GlobalRef(M2, :x)).partitions.min_world + ) +end