diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index 55293277d7f64..ba5f893a0e971 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -44,7 +44,7 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc typename, unsafe_write, write, stdout, stderr using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer, - PARTITION_KIND_GLOBAL, PARTITION_KIND_UNDEF_CONST, PARTITION_KIND_BACKDATED_CONST, PARTITION_KIND_DECLARED, + PARTITION_KIND_GLOBAL, PARTITION_KIND_UNDEF_CONST, PARTITION_KIND_BACKDATED_CONST, PARTITION_KIND_BACKDATED_IMPORT, PARTITION_KIND_BACKDATED_GLOBAL, PARTITION_KIND_DECLARED, PARTITION_FLAG_DEPWARN, Base, BitVector, Bottom, Callable, DataTypeFieldDesc, EffectsOverride, Filter, Generator, NUM_EFFECTS_OVERRIDES, @@ -55,7 +55,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali datatype_pointerfree, decode_effects_override, diff_names, fieldindex, visit, generating_output, get_nospecializeinfer_sig, get_world_counter, has_free_typevars, hasgenerator, hasintersect, indexed_iterate, isType, is_file_tracked, is_function_def, - is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_defined_const_binding, + is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_backdated, is_defined_const_binding, is_some_const_binding, is_some_guard, is_some_imported, is_some_explicit_imported, is_some_binding_imported, is_valid_intrinsic_elptr, isbitsunion, isconcretedispatch, isdispatchelem, isexpr, isfieldatomic, isidentityfree, iskindtype, ismutabletypename, ismutationfree, issingletontype, isvarargtype, isvatuple, diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 37d54e66d4ac6..bca8ea797600b 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3650,12 +3650,13 @@ function abstract_eval_partition_load(interp::Union{AbstractInterpreter,Nothing} end end + if is_backdated(kind) + # Infer this as guard. We do not want a later definition to retroactively improve + # inference results in an earlier world. + return RTEffects(Any, UndefVarError, local_getglobal_effects) + end + if is_defined_const_binding(kind) - if kind == PARTITION_KIND_BACKDATED_CONST - # Infer this as guard. We do not want a later const definition to retroactively improve - # inference results in an earlier world. - return RTEffects(Any, UndefVarError, local_getglobal_effects) - end rt = Const(partition_restriction(partition)) return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE, diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index ba224acf897d4..07e16d1e3394a 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -246,6 +246,8 @@ 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_BACKDATED_IMPORT = 0xc +const PARTITION_KIND_BACKDATED_GLOBAL = 0xd const PARTITION_FLAG_EXPORTED = 0x10 const PARTITION_FLAG_DEPRECATED = 0x20 @@ -256,11 +258,12 @@ const PARTITION_MASK_FLAG = 0xf0 const BINDING_FLAG_ANY_IMPLICIT_EDGES = 0x8 +is_backdated(kind::UInt8) = (kind == PARTITION_KIND_BACKDATED_CONST || kind == PARTITION_KIND_BACKDATED_IMPORT || kind == PARTITION_KIND_BACKDATED_GLOBAL) 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_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT) 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_explicit_imported(kind::UInt8) = (kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED) +is_some_explicit_imported(kind::UInt8) = (kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT) 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) diff --git a/base/show.jl b/base/show.jl index 1c3f270333f60..d5fe8a3723f2d 100644 --- a/base/show.jl +++ b/base/show.jl @@ -3309,6 +3309,12 @@ function print_partition(io::IO, partition::Core.BindingPartition) if kind == PARTITION_KIND_BACKDATED_CONST print(io, "backdated constant binding to ") print(io, partition_restriction(partition)) + elseif kind == PARTITION_KIND_BACKDATED_IMPORT + print(io, "backdated explicit `import` from ") + print(io, partition_restriction(partition).globalref) + elseif kind == PARTITION_KIND_BACKDATED_GLOBAL + print(io, "backdated global variable with type ") + print(io, partition_restriction(partition)) elseif kind == PARTITION_KIND_CONST print(io, "constant binding to ") print(io, partition_restriction(partition)) diff --git a/src/ast.c b/src/ast.c index e8cdf57ad8194..4b640d77b59b8 100644 --- a/src/ast.c +++ b/src/ast.c @@ -1224,9 +1224,10 @@ JL_DLLEXPORT jl_value_t *jl_fl_lower(jl_value_t *expr, jl_module_t *inmodule, JL_DLLEXPORT jl_value_t *jl_lower(jl_value_t *expr, jl_module_t *inmodule, const char *filename, int line, size_t world, bool_t warn) { - jl_value_t *core_lower = NULL; - if (jl_core_module) - core_lower = jl_get_global_value(jl_core_module, jl_symbol("_lower"), jl_current_task->world_age); + if (!jl_core_module || !jl_boundp(jl_core_module, jl_symbol("_lower"), jl_current_task->world_age)) { + return jl_fl_lower(expr, inmodule, filename, line, world, warn); + } + jl_value_t *core_lower = jl_get_global_value(jl_core_module, jl_symbol("_lower"), jl_current_task->world_age); if (!core_lower || core_lower == jl_nothing) { return jl_fl_lower(expr, inmodule, filename, line, world, warn); } diff --git a/src/julia.h b/src/julia.h index 4e0ed0d549f20..ddcd0b87fc9f7 100644 --- a/src/julia.h +++ b/src/julia.h @@ -694,11 +694,11 @@ typedef struct _jl_weakref_t { // // We may make this list more permissive in the future. // -// Finally, PARTITION_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 PARTITION_KIND_BACKDATED_CONST -// must be inferred accordingly. PARTITION_KIND_BACKDATED_CONST is intended as a temporary compatibility -// measure. The following kinds may be replaced by PARTITION_KIND_BACKDATED_CONST: +// Finally, PARTITION_KIND_BACKDATED_* are special cases, and the only cases where we may replace an +// existing partition by a different partition kind in the same world age. As such, they need special +// support in inference. Any partition kind that may be replaced by a PARTITION_KIND_BACKDATED_* +// must be inferred accordingly. PARTITION_KIND_BACKDATED_* are intended as a temporary compatibility +// measure. The following kinds may be replaced by any backdated partition kind: // - PARTITION_KIND_GUARD // - PARTITION_KIND_FAILED // - PARTITION_KIND_DECLARED @@ -743,11 +743,17 @@ enum jl_partition_kind { // Backated constant. A constant that was backdated for compatibility. In all other // ways equivalent to PARTITION_KIND_CONST, but prints a warning on access PARTITION_KIND_BACKDATED_CONST = 0xb, + // Backdated import. An explicit import that was backdated for compatibility. In all other + // ways equivalent to PARTITION_KIND_IMPORTED, but prints a warning on access + PARTITION_KIND_BACKDATED_IMPORT = 0xc, + // Backdated global. A global that was backdated for compatibility. In all other + // ways equivalent to PARTITION_KIND_GLOBAL, but prints a warning on access + PARTITION_KIND_BACKDATED_GLOBAL = 0xd, // 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_IMPLICIT_RECOMPUTE = 0xe, + PARTITION_FAKE_KIND_CYCLE = 0xf }; static const uint8_t PARTITION_MASK_KIND = 0x0f; diff --git a/src/julia_internal.h b/src/julia_internal.h index 4488388bb9875..b6fb6645c036e 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -979,6 +979,9 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b J jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind, size_t new_world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked2(jl_binding_t *b JL_PROPAGATES_ROOT, jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, size_t kind, size_t new_world) JL_GLOBALLY_ROOTED; +jl_binding_partition_t *jl_backdate_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, + jl_binding_partition_t *bpart, jl_binding_partition_t *new_bpart, + jl_value_t *restriction, enum jl_partition_kind backdated_kind, size_t new_world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT void jl_update_loaded_bpart(jl_binding_t *b, jl_binding_partition_t *bpart); extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; @@ -998,11 +1001,11 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source); STATIC_INLINE int jl_bkind_is_some_import(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED; + return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT; } STATIC_INLINE int jl_bkind_is_some_explicit_import(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED; + return kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED || kind == PARTITION_KIND_BACKDATED_IMPORT; } STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFEPOINT { @@ -1025,6 +1028,10 @@ STATIC_INLINE int jl_bkind_is_real_constant(enum jl_partition_kind kind) JL_NOTS return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT; } +STATIC_INLINE int jl_bkind_is_backdated(enum jl_partition_kind kind) JL_NOTSAFEPOINT { + return kind == PARTITION_KIND_BACKDATED_CONST || kind == PARTITION_KIND_BACKDATED_IMPORT || kind == PARTITION_KIND_BACKDATED_GLOBAL; +} + JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_with_hint(jl_binding_t *b JL_PROPAGATES_ROOT, jl_binding_partition_t *previous_part, size_t world) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_GLOBALLY_ROOTED; @@ -1059,6 +1066,21 @@ STATIC_INLINE void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partit } } +extern void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT; + +STATIC_INLINE void jl_walk_binding_inplace_stop_at_backdated(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world, int stop_at_backdated) JL_NOTSAFEPOINT +{ + while (1) { + enum jl_partition_kind kind = jl_binding_kind(*bpart); + if (stop_at_backdated && jl_bkind_is_backdated(kind)) + return; + if (!jl_bkind_is_some_explicit_import(kind) && kind != PARTITION_KIND_IMPLICIT_GLOBAL) + return; + *bnd = (jl_binding_t*)(*bpart)->restriction; + *bpart = jl_get_binding_partition(*bnd, world); + } +} + STATIC_INLINE void jl_walk_binding_inplace_depwarn(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world, int *depwarn) JL_NOTSAFEPOINT { int passed_explicit = 0; diff --git a/src/module.c b/src/module.c index 24ac81266da47..3ecd34fcb06e1 100644 --- a/src/module.c +++ b/src/module.c @@ -301,7 +301,7 @@ struct implicit_search_resolution jl_resolve_implicit_import(jl_binding_t *b, mo imp_resolution.binding_or_const = tempbpart->restriction; imp_resolution.debug_only_ultimate_binding = (jl_binding_t*)tempbpart->restriction; imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_GLOBAL; - } else if (kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_BACKDATED_CONST) { + } else if (kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_BACKDATED_CONST || kind == PARTITION_KIND_BACKDATED_GLOBAL) { imp_resolution.binding_or_const = (jl_value_t *)tempb; imp_resolution.debug_only_ultimate_binding = tempb; imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_GLOBAL; @@ -553,6 +553,86 @@ jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default return m; } +// Helper function to backdate a binding partition +// Precondition: world_counter_lock is held +// Returns the new backdated partition list head, or NULL if backdating is not needed +jl_binding_partition_t *jl_backdate_binding_partition( + jl_binding_t *b, jl_binding_partition_t *bpart, jl_binding_partition_t *new_bpart, + jl_value_t *restriction, enum jl_partition_kind backdated_kind, size_t new_world) +{ + // Check if we should backdate + int need_backdate = new_world; + 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 (;;) { + enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); + if (jl_bkind_is_some_constant(prev_kind) || prev_kind == PARTITION_KIND_GLOBAL || + jl_bkind_is_some_import(prev_kind)) { + need_backdate = 0; + break; + } + 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); + } + } + + if (!need_backdate) + return NULL; + + // If backdate is required, replace each existing partition by a new one. + // We can't use one binding to cover the entire range, because we need to + // keep the flags partitioned. + // Only overwrite partitions that are guards (GUARD, FAILED, or DECLARED). + // Do not overwrite implicit imports. + jl_binding_partition_t *prev_bpart = bpart; + jl_binding_partition_t *backdate_bpart = new_binding_partition(); + jl_binding_partition_t *new_prev_bpart = backdate_bpart; + while (1) { + enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); + enum jl_partition_kind kind_to_use; + + // Determine the appropriate backdated kind based on the original kind + // Only overwrite guard partitions (GUARD, FAILED, DECLARED) + // Do not overwrite implicit imports + if (jl_bkind_is_some_guard(prev_kind) || prev_kind == PARTITION_KIND_DECLARED) { + kind_to_use = backdated_kind; + } else if (prev_kind == PARTITION_KIND_IMPLICIT_CONST || prev_kind == PARTITION_KIND_IMPLICIT_GLOBAL) { + // Do not backdate implicit imports + kind_to_use = prev_kind; + } else { + kind_to_use = prev_kind; + } + + backdate_bpart->kind = (size_t)kind_to_use | (prev_bpart->kind & 0xf0); + if (kind_to_use == backdated_kind) { + backdate_bpart->restriction = restriction; + if (restriction) + jl_gc_wb_fresh(backdate_bpart, restriction); + } else { + backdate_bpart->restriction = prev_bpart->restriction; + if (prev_bpart->restriction) + jl_gc_wb_fresh(backdate_bpart, prev_bpart->restriction); + } + jl_atomic_store_relaxed(&backdate_bpart->min_world, + jl_atomic_load_relaxed(&prev_bpart->min_world)); + jl_atomic_store_relaxed(&backdate_bpart->max_world, + jl_atomic_load_relaxed(&prev_bpart->max_world)); + prev_bpart = jl_atomic_load_relaxed(&prev_bpart->next); + if (!prev_bpart) + break; + jl_binding_partition_t *next_prev_bpart = new_binding_partition(); + jl_atomic_store_relaxed(&backdate_bpart->next, next_prev_bpart); + jl_gc_wb(backdate_bpart, next_prev_bpart); + backdate_bpart = next_prev_bpart; + } + jl_atomic_store_release(&new_bpart->next, new_prev_bpart); + jl_gc_wb(new_bpart, new_prev_bpart); + return new_prev_bpart; +} // Precondition: world_counter_lock is held JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( @@ -595,49 +675,8 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( } else { new_bpart = jl_replace_binding_locked(b, bpart, val, constant_kind, new_world); } - 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 (;;) { - enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); - if (jl_bkind_is_some_constant(prev_kind) || prev_kind == PARTITION_KIND_GLOBAL || - jl_bkind_is_some_import(prev_kind)) { - need_backdate = 0; - break; - } - 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); - } - } - // If backdate is required, replace each existing partition by a new one. - // We can't use one binding to cover the entire range, because we need to - // keep the flags partitioned. - if (need_backdate) { - jl_binding_partition_t *prev_bpart = bpart; - jl_binding_partition_t *backdate_bpart = new_binding_partition(); - new_prev_bpart = backdate_bpart; - while (1) { - backdate_bpart->kind = (size_t)PARTITION_KIND_BACKDATED_CONST | (prev_bpart->kind & 0xf0); - backdate_bpart->restriction = val; - jl_atomic_store_relaxed(&backdate_bpart->min_world, - jl_atomic_load_relaxed(&prev_bpart->min_world)); - jl_gc_wb_fresh(backdate_bpart, val); - jl_atomic_store_relaxed(&backdate_bpart->max_world, - jl_atomic_load_relaxed(&prev_bpart->max_world)); - prev_bpart = jl_atomic_load_relaxed(&prev_bpart->next); - if (!prev_bpart) - break; - jl_binding_partition_t *next_prev_bpart = new_binding_partition(); - jl_atomic_store_relaxed(&backdate_bpart->next, next_prev_bpart); - jl_gc_wb(backdate_bpart, next_prev_bpart); - backdate_bpart = next_prev_bpart; - } - jl_atomic_store_release(&new_bpart->next, new_prev_bpart); - jl_gc_wb(new_bpart, new_prev_bpart); + if (new_world && val) { + new_prev_bpart = jl_backdate_binding_partition(b, bpart, new_bpart, val, PARTITION_KIND_BACKDATED_CONST, new_world); } } JL_GC_POP(); @@ -878,9 +917,9 @@ static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name)); } -static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT +void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT { - if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) { + if (__unlikely(jl_bkind_is_backdated(kind))) { // We don't want functions that inference executes speculatively to print this warning, so turn those into // an error for inference purposes. if (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) @@ -898,12 +937,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) JL_DLLEXPORT jl_value_t *jl_get_binding_value_in_world(jl_binding_t *b, size_t world) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); - jl_walk_binding_inplace(&b, &bpart, world); + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); + check_backdated_binding(b, jl_binding_kind(bpart)); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, world, stop_at_backdated); enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { - check_backdated_binding(b, kind); return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); @@ -919,14 +959,13 @@ static jl_value_t *jl_get_binding_value_depwarn(jl_binding_t *b, size_t world) if (needs_depwarn) jl_binding_deprecation_warning(b); } - else { - jl_walk_binding_inplace(&b, &bpart, world); - } + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); + check_backdated_binding(b, jl_binding_kind(bpart)); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, world, stop_at_backdated); enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { - check_backdated_binding(b, kind); return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); @@ -937,12 +976,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) { size_t world = jl_current_task->world_age; jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); - jl_walk_binding_inplace(&b, &bpart, world); + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); + check_backdated_binding(b, jl_binding_kind(bpart)); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, world, stop_at_backdated); enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { - check_backdated_binding(b, kind); return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); @@ -1270,9 +1310,15 @@ JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t * jl_binding_partition_t *btopart = jl_get_binding_partition(bto, new_world); enum jl_partition_kind btokind = jl_binding_kind(btopart); if (jl_bkind_is_some_implicit(btokind)) { - jl_binding_partition_t *new_bpart = jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, (explici != 0) ? PARTITION_KIND_IMPORTED : PARTITION_KIND_EXPLICIT, new_world); + enum jl_partition_kind import_kind = (explici != 0) ? PARTITION_KIND_IMPORTED : PARTITION_KIND_EXPLICIT; + jl_binding_partition_t *new_bpart = jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, import_kind, new_world); if (jl_atomic_load_relaxed(&new_bpart->max_world) == ~(size_t)0) jl_add_binding_backedge(b, (jl_value_t*)bto); + + // Backdate imports similar to constants + // Only overwrite guard partitions, not implicit imports + jl_backdate_binding_partition(bto, btopart, new_bpart, (jl_value_t*)b, PARTITION_KIND_BACKDATED_IMPORT, new_world); + jl_atomic_store_release(&jl_world_counter, new_world); } else { @@ -1471,20 +1517,20 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (!bpart) return 0; + int stop_at_backdated = (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); if (!allow_import) { if (!bpart || jl_bkind_is_some_import(jl_binding_kind(bpart))) return 0; } else { - jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + jl_walk_binding_inplace_stop_at_backdated(&b, &bpart, jl_current_task->world_age, stop_at_backdated); } enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return 0; + if (__unlikely(jl_bkind_is_backdated(kind))) { + return !stop_at_backdated; + } if (jl_bkind_is_defined_constant(kind)) { - if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) { - return !(jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR); - } - // N.B.: No backdated admonition for isdefined return 1; } return jl_atomic_load(&b->value) != NULL; diff --git a/src/toplevel.c b/src/toplevel.c index 4622a9e8b4ce0..99815978f2427 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -308,15 +308,20 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in goto check_type; } check_safe_newbinding(gm, gs); + jl_binding_partition_t *new_bpart = NULL; if (jl_atomic_load_relaxed(&bpart->min_world) == new_world) { bpart->kind = new_kind | (bpart->kind & PARTITION_MASK_FLAG); bpart->restriction = global_type; if (global_type) jl_gc_wb(bpart, global_type); - continue; + new_bpart = bpart; } else { - jl_replace_binding_locked(b, bpart, global_type, new_kind, new_world); + new_bpart = jl_replace_binding_locked(b, bpart, global_type, new_kind, new_world); } + + // Backdate globals similar to constants and imports + // Only overwrite guard partitions, not implicit imports + jl_backdate_binding_partition(b, bpart, new_bpart, global_type, PARTITION_KIND_BACKDATED_GLOBAL, new_world); break; } else if (set_type) { if (jl_bkind_is_some_constant(kind)) { diff --git a/test/worlds.jl b/test/worlds.jl index 96685129c9e99..03d300ac9339b 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -616,3 +616,89 @@ end # Test that the hash function works without world age issues @test hash(bar59429, UInt(0)) isa UInt end + +# Backdating tests +module BackdateConstTest + using Test + const world_before = Base.get_world_counter() + f() = BACKDATED_CONST # defined in old world + const world_after_f = Base.get_world_counter() + const BACKDATED_CONST = 42 # defined in new world + @test BACKDATED_CONST == 42 + if Base.JLOptions().depwarn <= 1 + # depwarn=no or yes: backdating works, emits warning + # Call f from the old world (before BACKDATED_CONST was defined) + @test_warn "Detected access to binding `BackdateConstTest.BACKDATED_CONST`" Base.invoke_in_world(world_after_f, f) + @test Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :BACKDATED_CONST) + else + # depwarn=error: backdating disabled + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :BACKDATED_CONST) + @test_throws UndefVarError Base.invoke_in_world(world_after_f, f) + end +end + +module BackdateImportTest + using Test + module Source + exported_value = 42 # Use a value, not a function + end + const world_before = Base.get_world_counter() + g() = exported_value # defined in old world + const world_after_g = Base.get_world_counter() + import .Source: exported_value # import in new world + @test exported_value == 42 + if Base.JLOptions().depwarn <= 1 + @test_warn "Detected access to binding `BackdateImportTest.exported_value`" Base.invoke_in_world(world_after_g, g) + @test Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :exported_value) + else + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :exported_value) + @test_throws UndefVarError Base.invoke_in_world(world_after_g, g) + end +end + +module BackdateGlobalTest + using Test + const world_before = Base.get_world_counter() + h() = backdated_global # defined in old world + const world_after_h = Base.get_world_counter() + global backdated_global::Int = 123 # defined in new world + @test backdated_global == 123 + if Base.JLOptions().depwarn <= 1 + @test_warn "Detected access to binding `BackdateGlobalTest.backdated_global`" Base.invoke_in_world(world_after_h, h) + @test Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :backdated_global) + else + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :backdated_global) + @test_throws UndefVarError Base.invoke_in_world(world_after_h, h) + end +end + +# Test case from issue #58511 +module BackdateIssue58511 + using Test + function foo() + eval(:(a = 42)) + return a + end + if Base.JLOptions().depwarn <= 1 + @test_warn "Detected access to binding `BackdateIssue58511.a`" foo() + else + @test_throws UndefVarError foo() + end +end + +# Test that implicit imports are NOT backdated +module BackdateNoImplicitTest + using Test + module Source1 + export shared_name + shared_name() = 1 + end + module Source2 + export shared_name + shared_name() = 2 + end + const world_before = Base.get_world_counter() + using .Source1, .Source2 + @test_throws UndefVarError shared_name() + @test !Base.invoke_in_world(world_before, isdefinedglobal, @__MODULE__, :shared_name) +end