Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Compiler/src/Compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali
_uncompressed_ir, datatype_min_ninitialized,
partialstruct_init_undefs, fieldcount_noerror, _eval_import, _eval_using,
get_ci_mi, get_methodtable, morespecific, specializations, has_image_globalref,
PARTITION_MASK_KIND, PARTITION_KIND_GUARD, PARTITION_FLAG_EXPORTED, PARTITION_FLAG_DEPRECATED,
PARTITION_MASK_KIND, PARTITION_KIND_GUARD, PARTITION_KIND_DECLARED_GUARD, PARTITION_FLAG_EXPORTED, PARTITION_FLAG_DEPRECATED,
BINDING_FLAG_ANY_IMPLICIT_EDGES, is_some_implicit, IteratorSize, SizeUnknown, get_require_world, JLOptions,
devnull, devnull as stdin

Expand Down
4 changes: 2 additions & 2 deletions Compiler/src/bindinginvalidations.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

using ..Compiler: _uncompressed_ir, specializations, get_ci_mi, convert, unsafe_load, cglobal, generating_output, has_image_globalref,
PARTITION_MASK_KIND, PARTITION_KIND_GUARD, PARTITION_FLAG_EXPORTED, PARTITION_FLAG_DEPRECATED,
PARTITION_MASK_KIND, PARTITION_KIND_GUARD, PARTITION_KIND_DECLARED_GUARD, PARTITION_FLAG_EXPORTED, PARTITION_FLAG_DEPRECATED,
BINDING_FLAG_ANY_IMPLICIT_EDGES, binding_kind, partition_restriction, is_some_imported,
is_some_binding_imported, is_some_implicit, SizeUnknown, maybe_add_binding_backedge!, walk_binding_partition, abstract_eval_partition_load, userefs
using .Core: SimpleVector, CodeInfo
Expand Down Expand Up @@ -83,7 +83,7 @@ function invalidate_method_for_globalref!(gr::GlobalRef, method::Method, invalid
end

export_affecting_partition_flags(bpart::Core.BindingPartition) =
((bpart.kind & PARTITION_MASK_KIND) == PARTITION_KIND_GUARD,
(let k = bpart.kind & PARTITION_MASK_KIND; k == PARTITION_KIND_GUARD || k == PARTITION_KIND_DECLARED_GUARD end,
(bpart.kind & PARTITION_FLAG_EXPORTED) != 0,
(bpart.kind & PARTITION_FLAG_DEPRECATED) != 0)

Expand Down
2 changes: 1 addition & 1 deletion base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,7 @@ function UndefVarError_hint(io::IO, ex::UndefVarError)
else
print(io, "\nSuggestion: check for spelling errors or missing imports.")
end
elseif kind === PARTITION_KIND_GLOBAL || kind === PARTITION_KIND_UNDEF_CONST || kind == PARTITION_KIND_DECLARED
elseif kind === PARTITION_KIND_GLOBAL || kind === PARTITION_KIND_UNDEF_CONST || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_DECLARED_GUARD
print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.")
elseif kind === PARTITION_KIND_FAILED
print(io, "\nHint: It looks like two or more modules export different ",
Expand Down
5 changes: 3 additions & 2 deletions base/runtime_internals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ const PARTITION_KIND_DECLARED = 0x8
const PARTITION_KIND_GUARD = 0x9
const PARTITION_KIND_UNDEF_CONST = 0xa
const PARTITION_KIND_BACKDATED_CONST = 0xb
const PARTITION_KIND_DECLARED_GUARD = 0xe

const PARTITION_FLAG_EXPORTED = 0x10
const PARTITION_FLAG_DEPRECATED = 0x20
Expand All @@ -265,10 +266,10 @@ const JL_MODULE_USING_REEXPORT = 0x1
is_defined_const_binding(kind::UInt8) = (kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_BACKDATED_CONST)
is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == PARTITION_KIND_UNDEF_CONST)
is_some_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED)
is_some_implicit(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED)
is_some_implicit(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_DECLARED_GUARD)
is_some_explicit_imported(kind::UInt8) = (kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED)
is_some_binding_imported(kind::UInt8) = is_some_explicit_imported(kind) || kind == PARTITION_KIND_IMPLICIT_GLOBAL
is_some_guard(kind::UInt8) = (kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_UNDEF_CONST)
is_some_guard(kind::UInt8) = (kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_UNDEF_CONST || kind == PARTITION_KIND_DECLARED_GUARD)

function lookup_binding_partition(world::UInt, b::Core.Binding)
ccall(:jl_get_binding_partition, Ref{Core.BindingPartition}, (Any, UInt), b, world)
Expand Down
2 changes: 2 additions & 0 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3356,6 +3356,8 @@ function print_partition(io::IO, partition::Core.BindingPartition)
print(io, "undefined const binding")
elseif kind == PARTITION_KIND_GUARD
print(io, "undefined binding - guard entry")
elseif kind == PARTITION_KIND_DECLARED_GUARD
print(io, "undefined binding - explicitly declared with `public` or `export`")
elseif kind == PARTITION_KIND_FAILED
print(io, "ambiguous binding - guard entry")
elseif kind == PARTITION_KIND_DECLARED
Expand Down
19 changes: 14 additions & 5 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,11 +667,11 @@ typedef struct _jl_weakref_t {
// PARTITION_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.
// The binding was explicitly declared without being given a value. These bindings may be
// replaced by a stronger binding and may be backdated by PARTITION_KIND_BACKDATED_CONST.
//
// PARTITION_KIND_DECLARED
// PARTITION_KIND_DECLARED (declared with `global x`)
// PARTITION_KIND_DECLARED_GUARD (declared with `public x` or `export x` without a value)
//
// 3. Strong Declared Bindings (Weak)
// All other bindings are explicitly declared using a keyword or global assignment.
Expand Down Expand Up @@ -702,6 +702,7 @@ typedef struct _jl_weakref_t {
// - PARTITION_KIND_GUARD
// - PARTITION_KIND_FAILED
// - PARTITION_KIND_DECLARED
// - PARTITION_KIND_DECLARED_GUARD
enum jl_partition_kind {
// Constant: This binding partition is a constant declared using `const _ = ...`
// ->restriction holds the constant value
Expand Down Expand Up @@ -747,7 +748,15 @@ enum jl_partition_kind {
// This is not a real binding kind, but can be used to ask for a re-resolution
// of the implicit binding kind
PARTITION_FAKE_KIND_IMPLICIT_RECOMPUTE = 0xc,
PARTITION_FAKE_KIND_CYCLE = 0xd
PARTITION_FAKE_KIND_CYCLE = 0xd,

// Declared Guard: The binding was declared using `public x` or `export x`, but no value
// has been assigned. Unlike PARTITION_KIND_GUARD, this kind is explicit (not implicit):
// it will not be replaced by implicit resolution and blocks implicit import resolution.
// Like PARTITION_KIND_DECLARED, this kind may be replaced by a stronger binding and may
// be backdated by PARTITION_KIND_BACKDATED_CONST.
// ->restriction is NULL
PARTITION_KIND_DECLARED_GUARD = 0xe
};

static const uint8_t PARTITION_MASK_KIND = 0x0f;
Expand Down
2 changes: 1 addition & 1 deletion src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,7 @@ STATIC_INLINE int jl_bkind_is_some_explicit_import(enum jl_partition_kind kind)
}

STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFEPOINT {
return kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_GUARD;
return kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_DECLARED_GUARD;
}

STATIC_INLINE int jl_bkind_is_some_implicit(enum jl_partition_kind kind) JL_NOTSAFEPOINT {
Expand Down
39 changes: 32 additions & 7 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@ static jl_binding_partition_t *jl_implicit_import_resolved(jl_binding_t *b, stru
size_t new_max_world = gap.max_world < resolution.max_world ? gap.max_world : resolution.max_world;
size_t new_min_world = gap.min_world > resolution.min_world ? gap.min_world : resolution.min_world;
jl_binding_partition_t *next = gap.replace;
if (jl_is_binding_partition(gap.parent)) {
// Check if we can merge this into the previous binding partition
if (jl_is_binding_partition(gap.parent) && !jl_bkind_is_some_guard((enum jl_partition_kind)(new_kind & PARTITION_MASK_KIND))) {
// Check if we can merge this into the previous binding partition.
// Guard partitions are not merged backward: the min_world of the earliest guard
// partition is semantically meaningful (it records the module's creation world,
// i.e. the world at which the using relationships were first established).
jl_binding_partition_t *prev = (jl_binding_partition_t *)gap.parent;
assert(new_max_world != ~(size_t)0); // It is inconsistent to have a gap with `gap.parent` set, but max_world == ~(size_t)0
size_t expected_prev_min_world = new_max_world + 1;
Expand Down Expand Up @@ -636,7 +639,10 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3(
size_t prev_bpart_min_world = jl_atomic_load_relaxed(&prev_bpart->min_world);
if (prev_bpart_min_world == 0)
break;
prev_bpart = jl_get_binding_partition(b, prev_bpart_min_world - 1);
struct implicit_search_gap bdate_gap;
prev_bpart = jl_get_binding_partition_if_present(b, prev_bpart_min_world - 1, &bdate_gap);
if (!prev_bpart)
break;
}
}
// If backdate is required, replace each existing partition by a new one.
Expand Down Expand Up @@ -1337,7 +1343,7 @@ JL_DLLEXPORT void jl_import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_
size_t world = jl_atomic_load_acquire(&jl_world_counter);
jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
enum jl_partition_kind kind = jl_binding_kind(bpart);
if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) {
if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED && kind != PARTITION_KIND_DECLARED_GUARD) {
// Unlike regular constant declaration, we allow this as long as we eventually end up at a constant.
jl_walk_binding_inplace(&b, &bpart, world);
if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) {
Expand Down Expand Up @@ -1506,8 +1512,27 @@ int jl_module_public_(jl_module_t *from, jl_sym_t *s, int exported, size_t new_w
jl_symbol_name(from->name), jl_symbol_name(s));
}
jl_atomic_fetch_or_relaxed(&b->flags, BINDING_FLAG_PUBLICP);
if (was_exported != exported) {
jl_replace_binding_locked2(b, bpart, bpart->restriction, bpart->kind | PARTITION_FLAG_EXPORTED, new_world);
enum jl_partition_kind kind = jl_binding_kind(bpart);
// If the binding is currently an implicit guard (unresolved lookup), replace it with an
// explicit declared-guard, which records that the user explicitly declared this name
// at a known world age, distinguishing it from purely implicit guard partitions.
int needs_declared_guard = (kind == PARTITION_KIND_GUARD);
if (was_exported != exported || needs_declared_guard) {
size_t new_kind;
jl_value_t *restriction;
if (needs_declared_guard) {
new_kind = (size_t)PARTITION_KIND_DECLARED_GUARD;
restriction = NULL;
}
else {
new_kind = (size_t)kind;
restriction = bpart->restriction;
}
// Preserve existing flags, updating the EXPORTED flag as needed
new_kind |= (bpart->kind & PARTITION_MASK_FLAG & ~PARTITION_FLAG_EXPORTED);
if (exported)
new_kind |= PARTITION_FLAG_EXPORTED;
jl_replace_binding_locked2(b, bpart, restriction, new_kind, new_world);
return 1;
}
return 0;
Expand Down Expand Up @@ -2117,7 +2142,7 @@ void append_module_names(jl_array_t* a, jl_module_t *m, int all, int imported, i
jl_bpart_is_exported(bpart->kind) ||
(imported && (kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_IMPORTED)) ||
(usings && kind == PARTITION_KIND_EXPLICIT) ||
((kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_DECLARED) && (all || main_public))) &&
((kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_DECLARED_GUARD) && (all || main_public))) &&
(all || (!(bpart->kind & PARTITION_FLAG_DEPRECATED) && !hidden)))
_append_symbol_to_bindings_array(a, asname);
}
Expand Down
2 changes: 1 addition & 1 deletion src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in
bpart = jl_get_binding_partition(b, new_world);
enum jl_partition_kind kind = jl_binding_kind(bpart);
if (kind != PARTITION_KIND_GLOBAL) {
if (jl_bkind_is_some_implicit(kind) || kind == PARTITION_KIND_DECLARED) {
if (jl_bkind_is_some_implicit(kind) || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_DECLARED_GUARD) {
if (kind == new_kind) {
if (!set_type)
goto done;
Expand Down
81 changes: 81 additions & 0 deletions test/rebinding.jl
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,84 @@ module ReexportTests
end
@test User3.same_name == 42
end

# Test PARTITION_KIND_DECLARED_GUARD: `export` or `public` on an undefined name creates
# a DECLARED_GUARD partition rather than leaving the binding as an implicit GUARD.
module DeclaredGuardTests
using Test

# `export` on an undefined name creates DECLARED_GUARD
export exported_undef
@test Base.binding_kind(@__MODULE__, :exported_undef) == Base.PARTITION_KIND_DECLARED_GUARD
@test Base.is_some_guard(Base.PARTITION_KIND_DECLARED_GUARD)
@test Base.is_some_implicit(Base.PARTITION_KIND_DECLARED_GUARD)

# `public` on an undefined name also creates DECLARED_GUARD
public public_undef
@test Base.binding_kind(@__MODULE__, :public_undef) == Base.PARTITION_KIND_DECLARED_GUARD

# The binding shows the right description via print_partition
bpart = Base.lookup_binding_partition(Base.tls_world_age(), GlobalRef(@__MODULE__, :exported_undef))
@test occursin("explicitly declared", sprint(Base.print_partition, bpart))

# The exported name appears in names() (exported = true by default with export keyword)
@test :exported_undef in names(@__MODULE__)

# UndefVarError hint says "declared but not assigned" for DECLARED_GUARD
Base.Experimental.register_error_hint(Base.UndefVarError_hint, UndefVarError)
ex = try; @eval exported_undef; catch e; e; end
@test ex isa UndefVarError
@test occursin("declared but not assigned", sprint(Base.showerror, ex))

# DECLARED_GUARD is re-resolvable via implicit import when a source binding appears
module DeclGuardSource
export resolvable_sym
end
module DeclGuardConsumer
using Test
using ..DeclGuardSource
export resolvable_sym # creates DECLARED_GUARD
@test Base.binding_kind(@__MODULE__, :resolvable_sym) == Base.PARTITION_KIND_DECLARED_GUARD
Core.eval(DeclGuardSource, :(resolvable_sym = 99))
@test resolvable_sym == 99
@test Base.is_some_imported(Base.binding_kind(@__MODULE__, :resolvable_sym))
end
end

@testset "Rebinding worlds" begin
# Test that partition lists preserve the world age of creation
w0 = Base.get_world_counter()
M = @eval module $(gensym())
export x
public y
end
w = Base.get_world_counter()
@eval M begin
const x = 1
const y = 2
end
bx = convert(Core.Binding, GlobalRef(M, :x))
by = convert(Core.Binding, GlobalRef(M, :y))
px = bx.partitions
@test px.min_world > w
@test (px.kind & Base.PARTITION_FLAG_EXPORTED) != 0
@test (px.kind & Base.PARTITION_MASK_KIND) == Base.PARTITION_KIND_CONST
px = px.next
@test w0 + 1 < px.min_world < w
@test px.max_world == w
@test (px.kind & Base.PARTITION_FLAG_EXPORTED) != 0
@test (px.kind & Base.PARTITION_MASK_KIND) == Base.PARTITION_KIND_BACKDATED_CONST
px = px.next
@test w0 + 1 == px.min_world
py = by.partitions
@test py.min_world > w
@test (py.kind & Base.PARTITION_FLAG_EXPORTED) == 0
@test (py.kind & Base.PARTITION_MASK_KIND) == Base.PARTITION_KIND_CONST
py = py.next
@test w0 + 1 < py.min_world <= w
@test py.max_world == w + 1
@test (py.kind & Base.PARTITION_FLAG_EXPORTED) == 0
@test (py.kind & Base.PARTITION_MASK_KIND) == Base.PARTITION_KIND_BACKDATED_CONST
py = py.next
@test w0 + 1 == py.min_world
end
39 changes: 39 additions & 0 deletions test/worlds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,42 @@ end
# Test that the hash function works without world age issues
@test hash(bar59429, UInt(0)) isa UInt
end

# Test that GUARD partition min_world is preserved as the module's creation timestamp
# and is not backdated when the binding is accessed from older worlds.
let world_before = Base.tls_world_age()
@eval module GuardMinWorldPreserveModule end
b = convert(Core.Binding, GlobalRef(GuardMinWorldPreserveModule, :undef_sym))
bpart = Base.lookup_binding_partition(Base.tls_world_age(), b)
guard_min_world = bpart.min_world
@test Base.binding_kind(bpart) == Base.PARTITION_KIND_GUARD
@test guard_min_world > world_before # reflects module creation world, not 0
# Accessing at an older world (before module creation) previously merged the
# GUARD partition backward, erasing the meaningful min_world boundary.
Base.lookup_binding_partition(UInt(world_before), b)
bpart2 = Base.lookup_binding_partition(Base.tls_world_age(), b)
@test Base.binding_kind(bpart2) == Base.PARTITION_KIND_GUARD
@test bpart2.min_world == guard_min_world # min_world must not be backdated to 0
end

# Test that the backdate loop in jl_declare_constant_val3 does not extend
# BACKDATED_CONST to worlds before the module existed.
# Previously, jl_get_binding_partition in the backdate loop triggered implicit
# resolution, creating pre-creation GUARD partitions that caused BACKDATED_CONST
# to cover worlds where the module did not yet exist.
let
@eval module BackdateLoopGuardModule end
module_creation_world = Base.tls_world_age()
# Declare a self-referential struct, which triggers jl_declare_constant_val3's
# backdate loop.
Core.eval(BackdateLoopGuardModule, quote
struct BackdateSelfRef
x::Vector{BackdateSelfRef}
BackdateSelfRef() = new(BackdateSelfRef[])
end
end)
b = convert(Core.Binding, GlobalRef(BackdateLoopGuardModule, :BackdateSelfRef))
# At world 0 (before the module existed), the binding must not be BACKDATED_CONST
bpart_at_0 = Base.lookup_binding_partition(UInt(0), b)
@test Base.binding_kind(bpart_at_0) != Base.PARTITION_KIND_BACKDATED_CONST
end