From 27f16b5d769b9394afa8bc7e5cdf4a5804b7f547 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 13 Feb 2025 12:10:12 -0500 Subject: [PATCH 01/21] Compiler: fix unsoundness of getfield_tfunc on Tuple Types (#57275) This was noted in the original review of that PR by the PR author, but was for some reason not fixed at that time: https://github.com/JuliaLang/julia/pull/46693#discussion_r968785997 (cherry picked from commit db874ff6529d475834c83da3c7e202957ca4f6d6) --- Compiler/src/typeutils.jl | 10 ++-------- Compiler/test/inline.jl | 1 + 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Compiler/src/typeutils.jl b/Compiler/src/typeutils.jl index d588a9aee1a6c..50b3dc6b0c6f5 100644 --- a/Compiler/src/typeutils.jl +++ b/Compiler/src/typeutils.jl @@ -36,14 +36,8 @@ function isTypeDataType(@nospecialize t) isType(t) && return false # Could be Union{} at runtime t === Core.TypeofBottom && return false - if t.name === Tuple.name - # If we have a Union parameter, could have been redistributed at runtime, - # e.g. `Tuple{Union{Int, Float64}, Int}` is a DataType, but - # `Union{Tuple{Int, Int}, Tuple{Float64, Int}}` is typeequal to it and - # is not. - return all(isTypeDataType, t.parameters) - end - return true + # Return true if `t` is not covariant + return t.name !== Tuple.name end has_extended_info(@nospecialize x) = (!isa(x, Type) && !isvarargtype(x)) || isType(x) diff --git a/Compiler/test/inline.jl b/Compiler/test/inline.jl index b8ff14405391d..c5a7ab197e16e 100644 --- a/Compiler/test/inline.jl +++ b/Compiler/test/inline.jl @@ -1769,6 +1769,7 @@ let getfield_tfunc(@nospecialize xs...) = Compiler.getfield_tfunc(Compiler.fallback_lattice, xs...) @test getfield_tfunc(Type, Core.Const(:parameters)) !== Union{} @test !isa(getfield_tfunc(Type{Tuple{Union{Int, Float64}, Int}}, Core.Const(:name)), Core.Const) + @test !isa(getfield_tfunc(Type{Tuple{Any}}, Core.Const(:name)), Core.Const) end @test fully_eliminated(Base.ismutable, Tuple{Base.RefValue}) From 6f4c2208c3a91f92588093b3587431dbf8846ad5 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 13 Feb 2025 12:11:07 -0500 Subject: [PATCH 02/21] print admonition for auto-import only once per module (#57378) Quiets the build somewhat, after #57311 made it noisy. (cherry picked from commit 6b39a818bdcd302612c348785486e0a796610a83) --- src/julia.h | 3 ++- src/module.c | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/julia.h b/src/julia.h index 1fd709f42ee31..9f5c9ae586c99 100644 --- a/src/julia.h +++ b/src/julia.h @@ -744,7 +744,8 @@ typedef struct _jl_binding_t { uint8_t exportp:1; // `public foo` sets `publicp`, `export foo` sets both `publicp` and `exportp` uint8_t publicp:1; // exportp without publicp is not allowed. uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package - uint8_t padding:3; + uint8_t did_print_implicit_import_admonition:1; + uint8_t padding:2; } jl_binding_t; typedef struct { diff --git a/src/module.c b/src/module.c index 7fb2af14ef271..4dadd306a263b 100644 --- a/src/module.c +++ b/src/module.c @@ -378,6 +378,7 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) b->publicp = 0; b->deprecated = 0; b->did_print_backdate_admonition = 0; + b->did_print_implicit_import_admonition = 0; JL_GC_PUSH1(&b); b->globalref = jl_new_globalref(mod, name, b); jl_gc_wb(b, b->globalref); @@ -458,6 +459,7 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT { + b->did_print_backdate_admonition = 1; jl_safe_printf( "WARNING: Detected access to binding `%s.%s` in a world prior to its definition world.\n" " Julia 1.12 has introduced more strict world age semantics for global bindings.\n" @@ -465,7 +467,6 @@ static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT " !!! This code will error in future versions of Julia.\n" "Hint: Add an appropriate `invokelatest` around the access to this binding.\n", jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name)); - b->did_print_backdate_admonition = 1; } static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT @@ -632,10 +633,12 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ else if (kind != BINDING_KIND_IMPORTED) { int should_error = strcmp(jl_symbol_name(var), "=>") == 0; jl_module_t *from = jl_binding_dbgmodule(b, m, var); - if (should_error) + if (should_error) { jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", jl_module_debug_name(m), jl_module_debug_name(from), jl_symbol_name(var)); - else + } + else if (!b->did_print_implicit_import_admonition) { + b->did_print_implicit_import_admonition = 1; jl_printf(JL_STDERR, "WARNING: Constructor for type \"%s\" was extended in `%s` without explicit qualification or import.\n" " NOTE: Assumed \"%s\" refers to `%s.%s`. This behavior is deprecated and may differ in future versions.`\n" " NOTE: This behavior may have differed in Julia versions prior to 1.12.\n" @@ -645,6 +648,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ jl_symbol_name(var), jl_module_debug_name(from), jl_symbol_name(var), jl_symbol_name(var), jl_symbol_name(var), jl_module_debug_name(from), jl_symbol_name(var), jl_module_debug_name(from), jl_symbol_name(var)); + } } return ownerb; } From 94108c5ee0e924e0d5de01912678c612d56c5c59 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 13 Feb 2025 23:02:09 -0500 Subject: [PATCH 03/21] [LateLowerGCFrame] fix PlaceGCFrameReset for returns_twice (#57392) Using the right variable here should help quite a bit with the random GC segfaults we have seen. We already have the tests for this, but it is quite hard to make them just complex enough to trigger reliably. Fixes #57333 (cherry picked from commit 75dba048f49ddb4281c625ed4d12ffbab3e3c426) --- src/codegen.cpp | 5 ++--- src/llvm-late-gc-lowering.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 4a836b4f2a4c6..39380e3c74932 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3257,7 +3257,6 @@ static bool slot_eq(jl_value_t *e, int sl) // --- find volatile variables --- // assigned in a try block and used outside that try block - static bool local_var_occurs(jl_value_t *e, int sl) { if (slot_eq(e, sl)) { @@ -3297,13 +3296,13 @@ static bool have_try_block(jl_array_t *stmts) return 0; } -// conservative marking of all variables potentially used after a catch block that were assigned before it +// conservative marking of all variables potentially used after a catch block that were assigned after the try static void mark_volatile_vars(jl_array_t *stmts, SmallVectorImpl &slots, const std::set &bbstarts) { if (!have_try_block(stmts)) return; size_t slength = jl_array_dim0(stmts); - BitVector assigned_in_block(slots.size()); // conservatively only ignore slots assigned in the same basic block + BitVector assigned_in_block(slots.size()); // since we don't have domtree access, conservatively only ignore slots assigned in the same basic block for (int j = 0; j < (int)slength; j++) { if (bbstarts.count(j + 1)) assigned_in_block.reset(); diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 7d6fba65a79e7..2b9b918c4bd53 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -2291,7 +2291,7 @@ void LateLowerGCFrame::PlaceGCFrameStores(State &S, unsigned MinColorRoot, const LargeSparseBitVector &NowLive = S.LiveSets[*rit]; // reset slots which are no longer alive for (int Idx : *LastLive) { - if (Idx >= PreAssignedColors && !HasBitSet(NowLive, Idx)) { + if (Colors[Idx] >= PreAssignedColors && !HasBitSet(NowLive, Idx)) { PlaceGCFrameReset(S, Idx, MinColorRoot, Colors, GCFrame, S.ReverseSafepointNumbering[*rit]); } From 77e7199e7779652c9eb41b4b9ec43eb2f1a4e1ff Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 12 Feb 2025 15:30:47 -0500 Subject: [PATCH 04/21] lowering: Only try to define the method once (#57346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before: ``` :($(Expr(:thunk, CodeInfo( 1 ─ $(Expr(:thunk, CodeInfo( 1 ─ return $(Expr(:method, :(Main.f))) ))) │ $(Expr(:method, :(Main.f))) │ %3 = Main.f │ %4 = dynamic Core.Typeof(%3) │ %5 = builtin Core.svec(%4, Core.Any) │ %6 = builtin Core.svec() │ %7 = builtin Core.svec(%5, %6, $(QuoteNode(:(#= REPL[2]:1 =#)))) │ $(Expr(:method, :(Main.f), :(%7), CodeInfo( 1 ─ return 1 ))) │ $(Expr(:latestworld)) │ %10 = Main.f └── return %10 )))) ``` After: ``` julia> @Meta.lower f(x)=1 :($(Expr(:thunk, CodeInfo( 1 ─ $(Expr(:method, :(Main.f))) │ $(Expr(:latestworld)) │ Main.f │ $(Expr(:latestworld)) │ %5 = Main.f │ %6 = dynamic Core.Typeof(%5) │ %7 = builtin Core.svec(%6, Core.Any) │ %8 = builtin Core.svec() │ %9 = builtin Core.svec(%7, %8, $(QuoteNode(:(#= REPL[1]:1 =#)))) │ $(Expr(:method, :(Main.f), :(%9), CodeInfo( 1 ─ return 1 ))) │ $(Expr(:latestworld)) │ %12 = Main.f └── return %12 )))) ``` This doesn't really make a semantic difference, but if `f` is a type, we may now give a warning, so the prior definition would give the warning twice (https://github.com/JuliaLang/julia/pull/57311#issuecomment-2648045510). We may want to consider rate-limiting the warning independently, but for now at least give the correct number of warnings. (cherry picked from commit 512eb5e2c9202562321096c26c9b4fe5c6ac48f6) --- doc/src/manual/modules.md | 2 -- src/julia-syntax.scm | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index cf24474916bef..b4d1cde9527be 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -192,8 +192,6 @@ julia> nice(::Cat) = "nice 😸" ERROR: invalid method definition in Main: function NiceStuff.nice must be explicitly imported to be extended Stacktrace: [1] top-level scope - @ none:0 - [2] top-level scope @ none:1 ``` diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 035d2a84e729d..00057498bbf9b 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -4131,8 +4131,9 @@ f(x) = yt(x) `(toplevel-butfirst ;; wrap in toplevel-butfirst so it gets moved higher along with ;; closure type definitions + (unnecessary ,(cadr e)) ,e - (thunk (lambda () (() () 0 ()) (block (return ,e)))))))) + (latestworld))))) ((null? cvs) `(block ,@sp-inits From f63f36dfb3259f13785c969a1ba9d848158a8fd2 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 12 Feb 2025 18:01:06 -0500 Subject: [PATCH 05/21] bpart: When backdating replace the entire bpart chain (#57341) Rather than rewriting the `restriction` of the bparts. With this, I believe this removes that last point where `restriction` is overwritten after construction, hopefully allowing us to go back to the original design where `restriction` is `const` after construction. (cherry picked from commit 90046a055f39a0f1077d5073b6be47a33494ef49) --- src/module.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/toplevel.c | 76 ------------------------------------------------- 2 files changed, 75 insertions(+), 78 deletions(-) diff --git a/src/module.c b/src/module.c index 4dadd306a263b..437eac04c5924 100644 --- a/src/module.c +++ b/src/module.c @@ -248,6 +248,80 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui return m; } +// Precondition: world_counter_lock is held +JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( + jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, + enum jl_partition_kind constant_kind, size_t new_world) +{ + JL_GC_PUSH1(&val); + 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); + 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)) { + new_bpart = bpart; + break; + } + } 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 (bpart->min_world == new_world) { + if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { + continue; + } else if (val) { + jl_gc_wb(bpart, val); + } + new_bpart = bpart; + } 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 (;;) { + jl_ptr_kind_union_t prev_pku = jl_atomic_load_relaxed(&prev_bpart->restriction); + 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, create one new binding partition to cover + // the entire backdate range. + if (need_backdate) { + jl_binding_partition_t *backdate_bpart = new_binding_partition(); + jl_atomic_store_relaxed(&backdate_bpart->restriction, encode_restriction(val, BINDING_KIND_BACKDATED_CONST)); + jl_atomic_store_relaxed(&backdate_bpart->max_world, new_world - 1); + jl_atomic_store_release(&new_bpart->next, backdate_bpart); + jl_gc_wb(new_bpart, backdate_bpart); + } + } + JL_GC_POP(); + return new_bpart; +} + JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent) { return jl_new_module_(name, parent, 1); @@ -1175,7 +1249,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b, new_bpart->min_world = new_world; if (kind == BINDING_KIND_IMPLICIT_RECOMPUTE) { assert(!restriction_val); - jl_check_new_binding_implicit(new_bpart, b, NULL, new_world); + jl_check_new_binding_implicit(new_bpart /* callee rooted */, b, NULL, new_world); } else jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction(restriction_val, kind)); @@ -1185,7 +1259,6 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b, 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; diff --git a/src/toplevel.c b/src/toplevel.c index 321ef8c79dac0..904b7c15afcde 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -754,82 +754,6 @@ static void jl_eval_errorf(jl_module_t *m, const char *filename, int lineno, con JL_GC_POP(); } -JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( - jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, - enum jl_partition_kind constant_kind, size_t new_world) -{ - JL_GC_PUSH1(&val); - 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); - 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)) { - new_bpart = bpart; - break; - } - } 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 (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); - } - 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); - 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) { - // 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 new_bpart; -} - JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2( jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, enum jl_partition_kind constant_kind) From 92eb8ba86655cdca6183bc816101414e4559aacf Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 13 Feb 2025 00:38:51 -0500 Subject: [PATCH 06/21] staticdata: Set min validation world to require world (#57381) When we have implicit binding edges from literal GlobalRefs, these edges imply a implicit minimum world age of jl_require_age (which is what all bindings loaded from pkgimages get their definition age set to). In #57318, the `cpuid_llvm` minimum world age got set to `1` rather than `jl_require_world`, so codegen would refuse to perform the llvmcall special codegen, since it couldn't guarantee that the binding would actually resolve to `llvmcall` in all worlds. Fix that by adjusting staticdata.jl to set the appropriate minworld. That said, there are a few complicating factors here: 1. In general, most of our code doesn't handle world ranges with more than one partition. But for example, codegen could have checked the current world age and implicitly partitioned the binding at codegen time (in practice just adding an appropraite error check). 2. The included test case uses a cached inference. However, in the original issue it appeared that inlining was also creating new references to this replaced binding, which should not have been permitted. I have not fully investigated this behavior yet and this might be another bug. 3. In the original issue, the specialization had its max_world terminated a few ages past jl_require_world. I do not understand this behavior yet. Still, fixes #57318. (cherry picked from commit c6805e2c702e21477c3457f381e3ed81ef325cb0) --- base/loading.jl | 4 ++-- base/staticdata.jl | 14 +++++++++++--- test/precompile.jl | 25 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index 47741da26a1b2..818bd2366b2d0 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -3157,11 +3157,11 @@ This can be used to reduce package load times. Cache files are stored in `DEPOT_PATH[1]/compiled`. See [Module initialization and precompilation](@ref) for important notes. """ -function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing) +function compilecache(pkg::PkgId, internal_stderr::IO = stderr, internal_stdout::IO = stdout; flags::Cmd=``, cacheflags::CacheFlags=CacheFlags(), reasons::Union{Dict{String,Int},Nothing}=Dict{String,Int}(), loadable_exts::Union{Vector{PkgId},Nothing}=nothing) @nospecialize internal_stderr internal_stdout path = locate_package(pkg) path === nothing && throw(ArgumentError("$(repr("text/plain", pkg)) not found during precompilation")) - return compilecache(pkg, path, internal_stderr, internal_stdout; flags, reasons, loadable_exts) + return compilecache(pkg, path, internal_stderr, internal_stdout; flags, cacheflags, reasons, loadable_exts) end const MAX_NUM_PRECOMPILE_FILES = Ref(10) diff --git a/base/staticdata.jl b/base/staticdata.jl index 7283a93dd8b3b..45504622fe0ec 100644 --- a/base/staticdata.jl +++ b/base/staticdata.jl @@ -70,6 +70,8 @@ function verify_method_graph(codeinst::CodeInstance, stack::Vector{CodeInstance} nothing end +get_require_world() = unsafe_load(cglobal(:jl_require_world, UInt)) + # Test all edges relevant to a method: # - Visit the entire call graph, starting from edges[idx] to determine if that method is valid # - Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable @@ -81,7 +83,14 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi return 0, world, max_valid2 end end - local minworld::UInt, maxworld::UInt = 1, validation_world + # Implicitly referenced bindings in the current module do not get explicit edges. + # If they were invalidated, they'll be in `mwis`. If they weren't, they imply a minworld + # of `get_require_world`. In principle, this is only required for methods that do reference + # an implicit globalref. However, we already don't perform this validation for methods that + # don't have any (implicit or explicit) edges at all. The remaining corner case (some explicit, + # but no implicit edges) is rare and there would be little benefit to lower the minworld for it + # in any case, so we just always use `get_require_world` here. + local minworld::UInt, maxworld::UInt = get_require_world(), validation_world def = get_ci_mi(codeinst).def @assert def isa Method if haskey(visiting, codeinst) @@ -103,9 +112,8 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi # verify current edges if isempty(callees) # quick return: no edges to verify (though we probably shouldn't have gotten here from WORLD_AGE_REVALIDATION_SENTINEL) - elseif maxworld == unsafe_load(cglobal(:jl_require_world, UInt)) + elseif maxworld == get_require_world() # if no new worlds were allocated since serializing the base module, then no new validation is worth doing right now either - minworld = maxworld else j = 1 while j ≤ length(callees) diff --git a/test/precompile.jl b/test/precompile.jl index e6c987326e44c..7506af9ae50d9 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -2285,4 +2285,29 @@ precompile_test_harness("Constprop CodeInstance invalidation") do load_path end end +precompile_test_harness("llvmcall validation") do load_path + write(joinpath(load_path, "LLVMCall.jl"), + """ + module LLVMCall + using Base: llvmcall + @noinline do_llvmcall() = llvmcall("ret i32 0", UInt32, Tuple{}) + do_llvmcall2() = do_llvmcall() + do_llvmcall2() + end + """) + # Also test with --pkgimages=no + testcode = """ + insert!(LOAD_PATH, 1, $(repr(load_path))) + insert!(DEPOT_PATH, 1, $(repr(load_path))) + using LLVMCall + LLVMCall.do_llvmcall2() + """ + @test readchomp(`$(Base.julia_cmd()) --pkgimages=no -E $(testcode)`) == repr(UInt32(0)) + # Now the regular way + @eval using LLVMCall + invokelatest() do + @test LLVMCall.do_llvmcall2() == UInt32(0) + end +end + finish_precompile_test!() From ff12d71511b38bb7bbfd3376a6d8efa0c0fe0ccb Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 13 Feb 2025 00:53:09 -0500 Subject: [PATCH 07/21] Only implicitly `using` Base, not Core (#57357) Inspired by the question in https://github.com/JuliaLang/julia/pull/57311#issuecomment-2648144227, I want to revisit the basic setup where every module `using`s both `Core` and `Base`. In general, I think we mostly expect users to inferface with `Base`, not `Core`, so this PR changes things to only have new modules `using` Base (while re-exporting all `Core` names from `Base`). There should be little user-visible impact from these changes. The only situation I can think of where it makes a difference is if somebody were to make their own Base/toplevel module that does not re-export Core. However, we don't really support that situation in the first place, and I actually think it's a feature that such toplevel modules can more closely control the set of implicitly exposed names. (cherry picked from commit 20162ea3949c5e9cf8c803b5b2b7b249c57108e4) --- Compiler/src/typeinfer.jl | 1 + base/Base_compiler.jl | 5 ++++- base/boot.jl | 5 +++-- base/exports.jl | 39 +++++++++++++++++++++++++++++++++++++++ base/show.jl | 2 +- src/julia_internal.h | 1 + src/module.c | 26 ++++++++++++++++---------- src/toplevel.c | 5 ++++- test/misc.jl | 7 ++++++- 9 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index 427f485f9118b..ddcca9a6ffaa1 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -10,6 +10,7 @@ being used for this purpose alone. """ module Timings +using ..Core using ..Compiler: -, +, :, Vector, length, first, empty!, push!, pop!, @inline, @inbounds, copy, backtrace diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index 4ec6bae171d8f..5655d9bbedbab 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -2,6 +2,7 @@ baremodule Base +using Core using Core.Intrinsics, Core.IR # to start, we're going to use a very simple definition of `include` @@ -135,6 +136,9 @@ include("coreio.jl") import Core: @doc, @__doc__, WrappedException, @int128_str, @uint128_str, @big_str, @cmd +# Export list +include("exports.jl") + # core docsystem include("docs/core.jl") Core.atdoc!(CoreDocs.docm) @@ -142,7 +146,6 @@ Core.atdoc!(CoreDocs.docm) eval(x) = Core.eval(Base, x) eval(m::Module, x) = Core.eval(m, x) -include("exports.jl") include("public.jl") if false diff --git a/base/boot.jl b/base/boot.jl index 26a405f92f884..93d73679d65ed 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -238,7 +238,9 @@ export # method reflection applicable, invoke, # constants - nothing, Main + nothing, Main, + # backwards compatibility + arrayref, arrayset, arraysize, const_arrayref const getproperty = getfield # TODO: use `getglobal` for modules instead const setproperty! = setfield! @@ -1040,7 +1042,6 @@ const_arrayref(inbounds::Bool, A::Array, i::Int...) = Main.Base.getindex(A, i... arrayset(inbounds::Bool, A::Array{T}, x::Any, i::Int...) where {T} = Main.Base.setindex!(A, x::T, i...) arraysize(a::Array) = a.size arraysize(a::Array, i::Int) = sle_int(i, nfields(a.size)) ? getfield(a.size, i) : 1 -export arrayref, arrayset, arraysize, const_arrayref const check_top_bit = check_sign_bit # For convenience diff --git a/base/exports.jl b/base/exports.jl index d81067478dd55..2e0bb3ccfe4cf 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1,5 +1,44 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Re-exports from `Core` +export Core, + # key types + Any, DataType, Vararg, NTuple, + Tuple, Type, UnionAll, TypeVar, Union, Nothing, Cvoid, + AbstractArray, DenseArray, NamedTuple, Pair, + # special objects + Function, Method, Module, Symbol, Task, UndefInitializer, undef, WeakRef, VecElement, + Array, Memory, MemoryRef, AtomicMemory, AtomicMemoryRef, GenericMemory, GenericMemoryRef, + # numeric types + Number, Real, Integer, Bool, Ref, Ptr, + AbstractFloat, Float16, Float32, Float64, + Signed, Int, Int8, Int16, Int32, Int64, Int128, + Unsigned, UInt, UInt8, UInt16, UInt32, UInt64, UInt128, + # string types + AbstractChar, Char, AbstractString, String, IO, + # errors + ErrorException, BoundsError, DivideError, DomainError, Exception, + InterruptException, InexactError, OutOfMemoryError, ReadOnlyMemoryError, + OverflowError, StackOverflowError, SegmentationFault, UndefRefError, UndefVarError, + TypeError, ArgumentError, MethodError, AssertionError, LoadError, InitError, + UndefKeywordError, ConcurrencyViolationError, FieldError, + # AST representation + Expr, QuoteNode, LineNumberNode, GlobalRef, + # object model functions + fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, setfieldonce!, + nfields, throw, tuple, ===, isdefined, + # access to globals + getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!, isdefinedglobal, + # ifelse, sizeof # not exported, to avoid conflicting with Base + # type reflection + <:, typeof, isa, typeassert, + # method reflection + applicable, invoke, + # constants + nothing, Main, + # backwards compatibility + arrayref, arrayset, arraysize, const_arrayref + export # Modules Meta, diff --git a/base/show.jl b/base/show.jl index 6b27fe838c89b..122b3372b70ce 100644 --- a/base/show.jl +++ b/base/show.jl @@ -618,7 +618,7 @@ function make_typealias(@nospecialize(x::Type)) Any === x && return nothing x <: Tuple && return nothing mods = modulesof!(Set{Module}(), x) - Core in mods && push!(mods, Base) + replace!(mods, Core=>Base) aliases = Tuple{GlobalRef,SimpleVector}[] xenv = UnionAll[] for p in uniontypes(unwrap_unionall(x)) diff --git a/src/julia_internal.h b/src/julia_internal.h index 6d83000184880..02729b7a4c6e7 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1238,6 +1238,7 @@ _Atomic(jl_value_t*) *jl_table_peek_bp(jl_genericmemory_t *a, jl_value_t *key) J JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t*); +JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_using_core, uint8_t self_name); JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *module); JL_DLLEXPORT jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES_ROOT, size_t world, int mt_cache); jl_method_instance_t *jl_get_specialized(jl_method_t *m, jl_value_t *types, jl_svec_t *sp) JL_PROPAGATES_ROOT; diff --git a/src/module.c b/src/module.c index 437eac04c5924..dbe84ffc7e499 100644 --- a/src/module.c +++ b/src/module.c @@ -205,7 +205,7 @@ jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b, size_t min return bpart; } -JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_names) +JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_using_core, uint8_t self_name) { jl_task_t *ct = jl_current_task; const jl_uuid_t uuid_zero = {0, 0}; @@ -237,14 +237,20 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui jl_atomic_store_relaxed(&m->bindings, jl_emptysvec); jl_atomic_store_relaxed(&m->bindingkeyset, (jl_genericmemory_t*)jl_an_empty_memory_any); arraylist_new(&m->usings, 0); - if (jl_core_module && default_names) { - JL_GC_PUSH1(&m); - jl_module_using(m, jl_core_module); - // export own name, so "using Foo" makes "Foo" itself visible - jl_set_const(m, name, (jl_value_t*)m); - jl_module_public(m, name, 1); - JL_GC_POP(); + JL_GC_PUSH1(&m); + if (jl_core_module) { + // Bootstrap: Before jl_core_module is defined, we don't have enough infrastructure + // for bindings, so Core itself gets special handling in jltypes.c + if (default_using_core) { + jl_module_using(m, jl_core_module); + } + if (self_name) { + // export own name, so "using Foo" makes "Foo" itself visible + jl_set_const(m, name, (jl_value_t*)m); + jl_module_public(m, name, 1); + } } + JL_GC_POP(); return m; } @@ -324,7 +330,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent) { - return jl_new_module_(name, parent, 1); + return jl_new_module_(name, parent, 1, 1); } uint32_t jl_module_next_counter(jl_module_t *m) @@ -336,7 +342,7 @@ JL_DLLEXPORT jl_value_t *jl_f_new_module(jl_sym_t *name, uint8_t std_imports, ui { // TODO: should we prohibit this during incremental compilation? // TODO: the parent module is a lie - jl_module_t *m = jl_new_module_(name, jl_main_module, default_names); + jl_module_t *m = jl_new_module_(name, jl_main_module, default_names, default_names); JL_GC_PUSH1(&m); if (std_imports) jl_add_standard_imports(m); diff --git a/src/toplevel.c b/src/toplevel.c index 904b7c15afcde..e2c58b6345c8d 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -135,7 +135,10 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } int is_parent__toplevel__ = jl_is__toplevel__mod(parent_module); - jl_module_t *newm = jl_new_module(name, is_parent__toplevel__ ? NULL : parent_module); + // If we have `Base`, don't also try to import `Core` - the `Base` exports are a superset. + // While we allow multiple imports of the same binding from different modules, various error printing + // performs reflection on which module a binding came from and we'd prefer users see "Base" here. + jl_module_t *newm = jl_new_module_(name, is_parent__toplevel__ ? NULL : parent_module, std_imports && jl_base_module != NULL ? 0 : 1, 1); jl_value_t *form = (jl_value_t*)newm; JL_GC_PUSH1(&form); JL_LOCK(&jl_modules_mutex); diff --git a/test/misc.jl b/test/misc.jl index fef573e9fc747..bcc7ff69339a9 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1574,7 +1574,12 @@ end @testset "Base docstrings" begin undoc = Docs.undocumented_names(Base) @test_broken isempty(undoc) - @test undoc == [:BufferStream, :CanonicalIndexError, :CapturedException, :Filesystem, :IOServer, :InvalidStateException, :Order, :PipeEndpoint, :ScopedValues, :Sort, :TTY] + @test isempty(setdiff(undoc, [:BufferStream, :CanonicalIndexError, :CapturedException, :Filesystem, :IOServer, :InvalidStateException, :Order, :PipeEndpoint, :ScopedValues, :Sort, :TTY, :AtomicMemoryRef, :Exception, :GenericMemoryRef, :GlobalRef, :IO, :LineNumberNode, :MemoryRef, :Method, :SegmentationFault, :TypeVar, :arrayref, :arrayset, :arraysize, :const_arrayref])) +end + +exported_names(m) = filter(s -> Base.isexported(m, s), names(m)) +@testset "Base re-exports Core" begin + @test issubset(exported_names(Core), exported_names(Base)) end @testset "Base.Libc docstrings" begin From f572662ea6e31ba74f53aa7bcca9ea8e3e787129 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 13 Feb 2025 08:50:14 -0500 Subject: [PATCH 08/21] staticdata: Fix typo in recursive edge revalidation (#57383) Addresses mystery #3 in #57381 (and extends the test from that issue). (cherry picked from commit cff8bd673513623a9876a76a0526c0899a0018bb) --- src/staticdata.c | 2 +- test/precompile.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/staticdata.c b/src/staticdata.c index c0fa8931e27c8..9b7392f0f197a 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -4390,7 +4390,7 @@ JL_DLLEXPORT void _jl_promote_ci_to_current(jl_code_instance_t *ci, size_t valid jl_value_t *edge = jl_svecref(edges, i); if (!jl_is_code_instance(edge)) continue; - _jl_promote_ci_to_current(ci, validated_world); + _jl_promote_ci_to_current((jl_code_instance_t *)edge, validated_world); } } diff --git a/test/precompile.jl b/test/precompile.jl index 7506af9ae50d9..6d106acc185f5 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -2307,6 +2307,7 @@ precompile_test_harness("llvmcall validation") do load_path @eval using LLVMCall invokelatest() do @test LLVMCall.do_llvmcall2() == UInt32(0) + @test first(methods(LLVMCall.do_llvmcall)).specializations.cache.max_world === typemax(UInt) end end From 149a5f4a00fc3b97082f536630399545ef250b13 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 13 Feb 2025 08:56:47 -0500 Subject: [PATCH 09/21] bpart: Move kind enum into its intended place (#57385) The original design for the BindingPartition datastructure had ->restriction and ->kind as separate non-atomic fields. However, to support the old semantics, we created an intermediate state where both the restriciton and the kind were placed into ->restriction as a pointer-int-union (i.e. using the low three bits of the pointer to store an int). In #57341, I removed that last semantic place that needed to update these both atomically. This PR removes all the remaining non-semantic places and changes the datastructure back to its indended design. This is a necessary prerequisitve to be able to use more than three ->kind bits, which will be required for export invalidation (#57377), as well as some nicer error messages in failure cases. (cherry picked from commit 40fbc8893a1daf935b1a77d597a3f638db61b03d) --- src/ast.c | 2 +- src/clangsa/GCChecker.cpp | 1 - src/codegen.cpp | 63 ++++++------- src/gc-stock.c | 10 -- src/jltypes.c | 4 +- src/julia.h | 10 +- src/julia_internal.h | 86 +++-------------- src/method.c | 9 +- src/module.c | 192 +++++++++++++++++--------------------- src/staticdata.c | 42 +-------- src/toplevel.c | 54 +++-------- test/syntax.jl | 2 +- 12 files changed, 156 insertions(+), 319 deletions(-) diff --git a/src/ast.c b/src/ast.c index 0f24d96393f2f..f643f4a2f40fe 100644 --- a/src/ast.c +++ b/src/ast.c @@ -178,7 +178,7 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint jl_sym_t *var = scmsym_to_julia(fl_ctx, args[0]); jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - return (bpart != NULL && decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GLOBAL) ? fl_ctx->T : fl_ctx->F; + return (bpart != NULL && bpart->kind == BINDING_KIND_GLOBAL) ? fl_ctx->T : fl_ctx->F; } // Used to generate a unique suffix for a given symbol (e.g. variable or type name) diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index fdbe5ec9d9e29..af07ca2227839 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -856,7 +856,6 @@ bool GCChecker::isGCTrackedType(QualType QT) { Name.ends_with_insensitive("jl_stenv_t") || Name.ends_with_insensitive("jl_varbinding_t") || Name.ends_with_insensitive("set_world") || - Name.ends_with_insensitive("jl_ptr_kind_union_t") || Name.ends_with_insensitive("jl_codectx_t")) { return true; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 39380e3c74932..49fe9d8446473 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -940,7 +940,7 @@ static const auto jlcheckbpwritable_func = new JuliaFunction<>{ nullptr, }; static const auto jlgetbindingvalue_func = new JuliaFunction<>{ - XSTR(jl_reresolve_binding_value_seqcst), + XSTR(jl_get_binding_value_seqcst), [](LLVMContext &C) { auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -3138,9 +3138,9 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_sym_t *sym = (jl_sym_t*)ex; jl_binding_t *bnd = jl_get_module_binding(ctx.module, sym, 0); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) - return decode_restriction_value(pku); + jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); + if (bpart && jl_bkind_is_some_constant(bpart->kind)) + return bpart->restriction; return NULL; } if (jl_is_slotnumber(ex) || jl_is_argument(ex)) @@ -3163,10 +3163,10 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) s = jl_globalref_name(ex); jl_binding_t *bnd = jl_get_module_binding(jl_globalref_mod(ex), s, 0); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); + jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); jl_value_t *v = NULL; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) - v = decode_restriction_value(pku); + if (bpart && jl_bkind_is_some_constant(bpart->kind)) + v = bpart->restriction; if (v) { if (bnd->deprecated) cg_bdw(ctx, s, bnd); @@ -3190,10 +3190,10 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) if (s && jl_is_symbol(s)) { jl_binding_t *bnd = jl_get_module_binding(m, s, 0); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); + jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); jl_value_t *v = NULL; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) - v = decode_restriction_value(pku); + if (bpart && jl_bkind_is_some_constant(bpart->kind)) + v = bpart->restriction; if (v) { if (bnd->deprecated) cg_bdw(ctx, s, bnd); @@ -3442,50 +3442,44 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * if (!bpart) { return emit_globalref_runtime(ctx, bnd, mod, name); } - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - // try to look this up now. - // TODO: This is bad and we'd like to delete it. - jl_get_binding(mod, name); - } // bpart was updated in place - this will change with full partition - pku = jl_atomic_load_acquire(&bpart->restriction); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_guard(bpart->kind)) { // Redo the lookup at runtime return emit_globalref_runtime(ctx, bnd, mod, name); } else { while (true) { if (!bpart) break; - if (!jl_bkind_is_some_import(decode_restriction_kind(pku))) + if (!jl_bkind_is_some_import(bpart->kind)) break; if (bnd->deprecated) { cg_bdw(ctx, name, bnd); } - bnd = (jl_binding_t*)decode_restriction_value(pku); + bnd = (jl_binding_t*)bpart->restriction; bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); if (!bpart) break; - pku = jl_atomic_load_acquire(&bpart->restriction); } - enum jl_partition_kind kind = decode_restriction_kind(pku); - if (bpart && (jl_bkind_is_some_constant(kind) && kind != BINDING_KIND_BACKDATED_CONST)) { - jl_value_t *constval = decode_restriction_value(pku); - if (!constval) { - undef_var_error_ifnot(ctx, ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), name, (jl_value_t*)mod); - return jl_cgval_t(); + if (bpart) { + enum jl_partition_kind kind = bpart->kind; + if (jl_bkind_is_some_constant(kind) && kind != BINDING_KIND_BACKDATED_CONST) { + jl_value_t *constval = bpart->restriction; + if (!constval) { + undef_var_error_ifnot(ctx, ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), name, (jl_value_t*)mod); + return jl_cgval_t(); + } + return mark_julia_const(ctx, constval); } - return mark_julia_const(ctx, constval); } } - if (!bpart || decode_restriction_kind(pku) != BINDING_KIND_GLOBAL) { + if (!bpart || bpart->kind != BINDING_KIND_GLOBAL) { return emit_globalref_runtime(ctx, bnd, mod, name); } Value *bp = julia_binding_gv(ctx, bnd); if (bnd->deprecated) { cg_bdw(ctx, name, bnd); } - jl_value_t *ty = decode_restriction_value(pku); + jl_value_t *ty = bpart->restriction; bp = julia_binding_pvalue(ctx, bp); if (ty == nullptr) ty = (jl_value_t*)jl_any_type; @@ -3501,9 +3495,8 @@ static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *s jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); Value *bp = julia_binding_gv(ctx, bnd); if (bpart) { - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (decode_restriction_kind(pku) == BINDING_KIND_GLOBAL) { - jl_value_t *ty = decode_restriction_value(pku); + if (bpart->kind == BINDING_KIND_GLOBAL) { + jl_value_t *ty = bpart->restriction; if (ty != nullptr) { const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; if (!ismodifyglobal) { @@ -4188,8 +4181,8 @@ static jl_cgval_t emit_isdefinedglobal(jl_codectx_t &ctx, jl_module_t *modu, jl_ Value *isnull = NULL; jl_binding_t *bnd = allow_import ? jl_get_binding(modu, name) : jl_get_module_binding(modu, name, 0); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); - jl_ptr_kind_union_t pku = bpart ? jl_atomic_load_relaxed(&bpart->restriction) : encode_restriction(NULL, BINDING_KIND_GUARD); - if (decode_restriction_kind(pku) == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + enum jl_partition_kind kind = bpart ? bpart->kind : BINDING_KIND_GUARD; + if (kind == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(kind)) { if (jl_get_binding_value_if_const(bnd)) return mark_julia_const(ctx, jl_true); Value *bp = julia_binding_gv(ctx, bnd); diff --git a/src/gc-stock.c b/src/gc-stock.c index 72479d14e67a5..c9cb57b19a604 100644 --- a/src/gc-stock.c +++ b/src/gc-stock.c @@ -2448,16 +2448,6 @@ FORCE_INLINE void gc_mark_outrefs(jl_ptls_t ptls, jl_gc_markqueue_t *mq, void *_ if (npointers == 0) return; uintptr_t nptr = (npointers << 2 | (bits & GC_OLD)); - if (vt == jl_binding_partition_type) { - // BindingPartition has a special union of jl_value_t and flag bits - // but is otherwise regular. - jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_valueof(o); - jl_value_t *val = decode_restriction_value( - jl_atomic_load_relaxed(&bpart->restriction)); - if (val) - gc_heap_snapshot_record_binding_partition_edge((jl_value_t*)bpart, val); - gc_try_claim_and_push(mq, val, &nptr); - } assert((layout->nfields > 0 || layout->flags.fielddesc_type == 3) && "opaque types should have been handled specially"); if (layout->flags.fielddesc_type == 0) { diff --git a/src/jltypes.c b/src/jltypes.c index b94922ce9cf54..0e709936efc39 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3262,8 +3262,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_binding_partition_type = jl_new_datatype(jl_symbol("BindingPartition"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(5, "restriction", "min_world", "max_world", "next", "reserved"), - jl_svec(5, jl_uint64_type /* Special GC-supported union of Any and flags*/, + jl_perm_symsvec(5, "restriction", "min_world", "max_world", "next", "kind"), + jl_svec(5, jl_any_type, jl_ulong_type, jl_ulong_type, jl_any_type/*jl_binding_partition_type*/, jl_ulong_type), jl_emptysvec, 0, 1, 0); const static uint32_t binding_partition_atomicfields[] = { 0b01101 }; // Set fields 1, 3, 4 as atomic diff --git a/src/julia.h b/src/julia.h index 9f5c9ae586c99..bf049c909d833 100644 --- a/src/julia.h +++ b/src/julia.h @@ -706,12 +706,6 @@ enum jl_partition_kind { BINDING_KIND_IMPLICIT_RECOMPUTE = 0xb }; -#ifdef _P64 -// Union of a ptr and a 3 bit field. -typedef uintptr_t jl_ptr_kind_union_t; -#else -typedef struct __attribute__((aligned(8))) { jl_value_t *val; size_t kind; } jl_ptr_kind_union_t; -#endif typedef struct __attribute__((aligned(8))) _jl_binding_partition_t { JL_DATA_TYPE /* union { @@ -727,11 +721,11 @@ typedef struct __attribute__((aligned(8))) _jl_binding_partition_t { * * This field is updated atomically with both kind and restriction */ - _Atomic(jl_ptr_kind_union_t) restriction; + jl_value_t *restriction; size_t min_world; _Atomic(size_t) max_world; _Atomic(struct _jl_binding_partition_t *) next; - size_t reserved; // Reserved for ->kind. Currently this holds the low bits of ->restriction during serialization + enum jl_partition_kind kind; } jl_binding_partition_t; typedef struct _jl_binding_t { diff --git a/src/julia_internal.h b/src/julia_internal.h index 02729b7a4c6e7..0f52f82ac8230 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -932,62 +932,6 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva, int isinferred); JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source); -EXTERN_INLINE_DECLARE enum jl_partition_kind decode_restriction_kind(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT -{ -#ifdef _P64 - uint8_t bits = (pku & 0x7); - jl_value_t *val = (jl_value_t*)(pku & ~0x7); - - if (val == NULL) { - if (bits == BINDING_KIND_IMPLICIT) { - return BINDING_KIND_GUARD; - } - if (bits == BINDING_KIND_CONST) { - return BINDING_KIND_UNDEF_CONST; - } - } else { - if (bits == BINDING_KIND_DECLARED) { - return BINDING_KIND_BACKDATED_CONST; - } - } - - return (enum jl_partition_kind)bits; -#else - return (enum jl_partition_kind)pku.kind; -#endif -} - -STATIC_INLINE jl_value_t *decode_restriction_value(jl_ptr_kind_union_t JL_PROPAGATES_ROOT pku) JL_NOTSAFEPOINT -{ -#ifdef _P64 - jl_value_t *val = (jl_value_t*)(pku & ~0x7); - return val; -#else - return pku.val; -#endif -} - -STATIC_INLINE jl_ptr_kind_union_t encode_restriction(jl_value_t *val, enum jl_partition_kind kind) JL_NOTSAFEPOINT -{ -#ifdef _P64 - if (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST) - assert(val == NULL); - else if (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_CONST || kind == BINDING_KIND_BACKDATED_CONST) - assert(val != NULL); - if (kind == BINDING_KIND_GUARD) - kind = BINDING_KIND_IMPLICIT; - else if (kind == BINDING_KIND_UNDEF_CONST) - kind = BINDING_KIND_CONST; - else if (kind == BINDING_KIND_BACKDATED_CONST) - kind = BINDING_KIND_DECLARED; - assert((((uintptr_t)val) & 0x7) == 0); - return ((jl_ptr_kind_union_t)val) | kind; -#else - jl_ptr_kind_union_t ret = { val, kind }; - return ret; -#endif -} - STATIC_INLINE int jl_bkind_is_some_import(enum jl_partition_kind kind) JL_NOTSAFEPOINT { return kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED; } @@ -1008,35 +952,31 @@ JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL 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; EXTERN_INLINE_DECLARE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT { - return decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); + return (uint8_t)bpart->kind; } -STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; -STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; +STATIC_INLINE void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; +STATIC_INLINE void jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_NOTSAFEPOINT; #ifndef __clang_analyzer__ -STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world) JL_NOTSAFEPOINT +STATIC_INLINE void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world) JL_NOTSAFEPOINT { while (1) { - if (!*bpart) - return encode_restriction(NULL, BINDING_KIND_GUARD); - jl_ptr_kind_union_t pku = jl_atomic_load_acquire(&(*bpart)->restriction); - if (!jl_bkind_is_some_import(decode_restriction_kind(pku))) - return pku; - *bnd = (jl_binding_t*)decode_restriction_value(pku); + if (!jl_bkind_is_some_import((*bpart)->kind)) + return; + *bnd = (jl_binding_t*)(*bpart)->restriction; *bpart = jl_get_binding_partition(*bnd, world); } } -STATIC_INLINE jl_ptr_kind_union_t jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t min_world, size_t max_world) JL_NOTSAFEPOINT +STATIC_INLINE void jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t min_world, size_t max_world) JL_NOTSAFEPOINT { while (1) { - if (!*bpart) - return encode_restriction(NULL, BINDING_KIND_GUARD); - jl_ptr_kind_union_t pku = jl_atomic_load_acquire(&(*bpart)->restriction); - if (!jl_bkind_is_some_import(decode_restriction_kind(pku))) - return pku; - *bnd = (jl_binding_t*)decode_restriction_value(pku); + if (!(*bpart)) + return; + if (!jl_bkind_is_some_import((*bpart)->kind)) + return; + *bnd = (jl_binding_t*)(*bpart)->restriction; *bpart = jl_get_binding_partition_all(*bnd, min_world, max_world); } } diff --git a/src/method.c b/src/method.c index 68542fdacabb6..0fc0e0ca1f87d 100644 --- a/src/method.c +++ b/src/method.c @@ -1056,13 +1056,12 @@ JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_module_t *mod, jl_sym_t *name) size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1; jl_binding_t *b = jl_get_binding_for_method_def(mod, name, new_world); jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); jl_value_t *gf = NULL; - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; 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_walk_binding_inplace(&b, &bpart, new_world); + if (jl_bkind_is_some_constant(bpart->kind)) { + gf = bpart->restriction; JL_GC_PROMISE_ROOTED(gf); jl_check_gf(gf, b->globalref->name); JL_UNLOCK(&world_counter_lock); diff --git a/src/module.c b/src/module.c index dbe84ffc7e499..8adc99a5380d5 100644 --- a/src/module.c +++ b/src/module.c @@ -14,18 +14,15 @@ extern "C" { // In this translation unit and this translation unit only emit this symbol `extern` for use by julia EXTERN_INLINE_DEFINE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT; -extern inline enum jl_partition_kind decode_restriction_kind(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT; static jl_binding_partition_t *new_binding_partition(void) { jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_gc_alloc(jl_current_task->ptls, sizeof(jl_binding_partition_t), jl_binding_partition_type); - jl_atomic_store_relaxed(&bpart->restriction, encode_restriction(NULL, BINDING_KIND_GUARD)); + bpart->restriction = NULL; + bpart->kind = BINDING_KIND_GUARD; bpart->min_world = 0; jl_atomic_store_relaxed(&bpart->max_world, (size_t)-1); jl_atomic_store_relaxed(&bpart->next, NULL); -#ifdef _P64 - bpart->reserved = 0; -#endif return bpart; } @@ -38,12 +35,12 @@ static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_ 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)) + jl_walk_binding_inplace(&ownerb, &owner, world); + jl_walk_binding_inplace(&alias, &alias_bpart, world); + if (jl_bkind_is_some_constant(owner->kind) && + jl_bkind_is_some_constant(alias_bpart->kind) && + owner->restriction && + alias_bpart->restriction == owner->restriction) return 1; return owner == alias_bpart; } @@ -56,7 +53,8 @@ void jl_check_new_binding_implicit( 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 */)); + new_bpart->restriction = NULL; + new_bpart->kind = BINDING_KIND_FAILED; /* BINDING_KIND_CYCLE */ return; } } @@ -142,10 +140,13 @@ void jl_check_new_binding_implicit( 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)); + new_bpart->kind = BINDING_KIND_IMPLICIT; + new_bpart->restriction = (jl_value_t*)impb; + jl_gc_wb(new_bpart, impb); // TODO: World age constraints? } else { - jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction(NULL, guard_kind)); + new_bpart->kind = guard_kind; + new_bpart->restriction = NULL; } JL_GC_POP(); return; @@ -265,15 +266,14 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( } 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); while (!new_bpart) { - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_constant(kind)) { if (!val) { new_bpart = bpart; break; } - jl_value_t *old = decode_restriction_value(pku); + jl_value_t *old = bpart->restriction; JL_GC_PROMISE_ROOTED(old); if (jl_egal(val, old)) { new_bpart = bpart; @@ -287,11 +287,10 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( jl_symbol_name(mod->name), jl_symbol_name(var)); } if (bpart->min_world == new_world) { - if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { - continue; - } else if (val) { + bpart->kind = constant_kind; + bpart->restriction = val; + if (val) jl_gc_wb(bpart, val); - } new_bpart = bpart; } else { new_bpart = jl_replace_binding_locked(b, bpart, val, constant_kind, new_world); @@ -302,8 +301,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( // 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); - enum jl_partition_kind prev_kind = decode_restriction_kind(prev_pku); + enum jl_partition_kind prev_kind = prev_bpart->kind; if (jl_bkind_is_some_constant(prev_kind) || prev_kind == BINDING_KIND_GLOBAL || (jl_bkind_is_some_import(prev_kind))) { need_backdate = 0; @@ -318,7 +316,9 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( // the entire backdate range. if (need_backdate) { jl_binding_partition_t *backdate_bpart = new_binding_partition(); - jl_atomic_store_relaxed(&backdate_bpart->restriction, encode_restriction(val, BINDING_KIND_BACKDATED_CONST)); + backdate_bpart->kind = BINDING_KIND_BACKDATED_CONST; + backdate_bpart->restriction = val; + jl_gc_wb_fresh(backdate_bpart, val); jl_atomic_store_relaxed(&backdate_bpart->max_world, new_world - 1); jl_atomic_store_release(&new_bpart->next, backdate_bpart); jl_gc_wb(new_bpart, backdate_bpart); @@ -500,8 +500,7 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym JL_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module_t *m, jl_sym_t *s) { 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); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; 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" @@ -560,13 +559,13 @@ static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_ki JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - enum jl_partition_kind kind = decode_restriction_kind(pku); + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { check_backdated_binding(b, kind); - return decode_restriction_value(pku); + return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); return jl_atomic_load_relaxed(&b->value); @@ -575,13 +574,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - enum jl_partition_kind kind = decode_restriction_kind(pku); + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { check_backdated_binding(b, kind); - return decode_restriction_value(pku); + return bpart->restriction; } assert(!jl_bkind_is_some_import(kind)); return jl_atomic_load(&b->value); @@ -590,14 +589,14 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - enum jl_partition_kind kind = decode_restriction_kind(pku); + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_guard(kind)) return NULL; if (!jl_bkind_is_some_constant(kind)) return NULL; check_backdated_binding(b, kind); - return decode_restriction_value(pku); + return bpart->restriction; } JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved_and_const(jl_binding_t *b) @@ -612,14 +611,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved_and_const(jl_binding_t size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); if (bpart->min_world > jl_current_task->world_age || jl_current_task->world_age > max_world) return NULL; - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_guard(kind)) return NULL; if (!jl_bkind_is_some_constant(kind)) return NULL; check_backdated_binding(b, kind); - return decode_restriction_value(pku); + return bpart->restriction; } JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved(jl_binding_t *b) @@ -634,58 +632,44 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved(jl_binding_t *b) size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); if (bpart->min_world > jl_current_task->world_age || jl_current_task->world_age > max_world) return NULL; - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_import(kind)) return NULL; if (jl_bkind_is_some_constant(kind)) { check_backdated_binding(b, kind); - return decode_restriction_value(pku); + return bpart->restriction; } return jl_atomic_load_relaxed(&b->value); } JL_DLLEXPORT jl_value_t *jl_bpart_get_restriction_value(jl_binding_partition_t *bpart) { - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - jl_value_t *v = decode_restriction_value(pku); + jl_value_t *v = bpart->restriction; if (!v) jl_throw(jl_undefref_exception); return v; } -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); -} - // get binding for adding a method // like jl_get_binding_wr, but has different error paths and messages JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var, size_t new_world) { jl_binding_t *b = jl_get_module_binding(m, var, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - 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))) + enum jl_partition_kind kind = bpart->kind; + if (kind == BINDING_KIND_GLOBAL || kind == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(kind)) 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, new_world); + jl_walk_binding_inplace(&ownerb, &bpart, new_world); jl_value_t *f = NULL; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) - f = decode_restriction_value(pku); + if (jl_bkind_is_some_constant(bpart->kind)) + f = bpart->restriction; if (f == NULL) { if (kind == BINDING_KIND_IMPLICIT) { check_safe_newbinding(m, var); @@ -738,9 +722,8 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ 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); - 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; + if (jl_bkind_is_some_import(bpart->kind)) { + return ((jl_binding_t*)bpart->restriction)->globalref->mod; } return m; } @@ -754,17 +737,17 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); if (b == NULL) return jl_nothing; - jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (jl_bkind_is_some_guard(bpart->kind)) return jl_nothing; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_constant(bpart->kind)) { // TODO: We would like to return the type of the constant, but // currently code relies on this returning any to bypass conversion // before an attempted assignment to a constant. - // return jl_typeof(jl_atomic_load_relaxed(&bpart->restriction)); + // return bpart->restriction; return (jl_value_t*)jl_any_type; } - return decode_restriction_value(pku); + return bpart->restriction; } JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) @@ -796,7 +779,7 @@ JL_DLLEXPORT int jl_is_imported(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); - return b && decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_IMPORTED; + return b && bpart->kind == BINDING_KIND_IMPORTED; } extern const char *jl_filename; @@ -857,8 +840,6 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, { jl_binding_t *b = jl_get_binding(from, s); 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) @@ -881,9 +862,9 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, 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); + jl_walk_binding_inplace(&ownerb, &ownerbpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(decode_restriction_kind(owner_pku))) { + if (jl_bkind_is_some_guard(ownerbpart->kind)) { jl_printf(JL_STDERR, "WARNING: Imported binding %s.%s was undeclared at import time during import to %s.\n", jl_symbol_name(from->name), jl_symbol_name(s), @@ -898,10 +879,10 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, 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) { + enum jl_partition_kind btokind = btopart->kind; + if (btokind == BINDING_KIND_GUARD || + btokind == BINDING_KIND_IMPLICIT || + btokind == 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 (jl_atomic_load_relaxed(&new_bpart->max_world) == ~(size_t)0) @@ -911,12 +892,12 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, else { 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) { + if (btokind == 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); } } - else if (jl_bkind_is_some_import(decode_restriction_kind(bto_pku))) { + else if (jl_bkind_is_some_import(btokind)) { // already imported from somewhere else jl_printf(JL_STDERR, "WARNING: ignoring conflicting import of %s.%s into %s\n", @@ -1004,8 +985,7 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) jl_binding_t *tob = jl_get_module_binding(to, var, 0); if (tob) { 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); + enum jl_partition_kind kind = tobpart->kind; if (kind == BINDING_KIND_IMPLICIT || jl_bkind_is_some_guard(kind)) { jl_replace_binding_locked(tob, tobpart, NULL, BINDING_KIND_IMPLICIT_RECOMPUTE, new_world); } @@ -1059,16 +1039,15 @@ 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; - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); if (!allow_import) { - if (!bpart || jl_bkind_is_some_import(decode_restriction_kind(pku))) + if (!bpart || jl_bkind_is_some_import(bpart->kind)) return 0; } else { - pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); } - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + if (jl_bkind_is_some_guard(bpart->kind)) return 0; - if (jl_bkind_is_defined_constant(decode_restriction_kind(pku))) { + if (jl_bkind_is_defined_constant(bpart->kind)) { // N.B.: No backdated check for isdefined return 1; } @@ -1079,7 +1058,7 @@ JL_DLLEXPORT int jl_defines_or_exports_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); - return b && (b->exportp || decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GLOBAL); + return b && (b->exportp || bpart->kind == BINDING_KIND_GLOBAL); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) @@ -1192,7 +1171,8 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var jl_binding_partition_t *bpart = jl_get_binding_partition(bp, jl_current_task->world_age); bpart->min_world = 0; jl_atomic_store_release(&bpart->max_world, ~(size_t)0); - jl_atomic_store_release(&bpart->restriction, encode_restriction(val, BINDING_KIND_CONST)); + bpart->kind = BINDING_KIND_CONST; + bpart->restriction = val; jl_gc_wb(bpart, val); } @@ -1257,10 +1237,13 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b, assert(!restriction_val); jl_check_new_binding_implicit(new_bpart /* callee rooted */, b, NULL, new_world); } - else - jl_atomic_store_relaxed(&new_bpart->restriction, encode_restriction(restriction_val, kind)); + else { + new_bpart->kind = kind; + new_bpart->restriction = restriction_val; + jl_gc_wb_fresh(new_bpart, restriction_val); + } jl_atomic_store_relaxed(&new_bpart->next, old_bpart); - jl_gc_wb(new_bpart, old_bpart); + jl_gc_wb_fresh(new_bpart, old_bpart); jl_atomic_store_release(&b->partitions, new_bpart); jl_gc_wb(b, new_bpart); @@ -1303,7 +1286,7 @@ JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) 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))); + return jl_bkind_is_some_constant(bpart->kind); } JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) @@ -1313,7 +1296,7 @@ JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) 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) { + if (bpart->kind == BINDING_KIND_GUARD) { // Already guard return; } @@ -1327,8 +1310,8 @@ 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); - 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)); + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + return b && jl_bkind_is_some_constant(bpart->kind); } // set the deprecated flag for a binding: @@ -1379,10 +1362,9 @@ 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); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; if (jl_bkind_is_some_constant(kind)) { - jl_value_t *old = decode_restriction_value(pku); + jl_value_t *old = bpart->restriction; JL_GC_PROMISE_ROOTED(old); if (jl_egal(rhs, old)) { JL_GC_POP(); @@ -1392,7 +1374,7 @@ jl_value_t *jl_check_binding_assign_value(jl_binding_t *b JL_PROPAGATES_ROOT, jl jl_symbol_name(mod->name), jl_symbol_name(var)); } 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_value_t *old_ty = kind == BINDING_KIND_DECLARED ? (jl_value_t*)jl_any_type : bpart->restriction; 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)) @@ -1430,12 +1412,12 @@ JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, j JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs) { 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 = bpart->kind; + assert(!jl_bkind_is_some_guard(kind) && !jl_bkind_is_some_import(kind)); + if (jl_bkind_is_some_constant(kind)) 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_value_t *ty = bpart->restriction; JL_GC_PROMISE_ROOTED(ty); return modify_value(ty, &b->value, (jl_value_t*)b, op, rhs, 1, mod, var); } @@ -1482,7 +1464,7 @@ void append_module_names(jl_array_t* a, jl_module_t *m, int all, int imported, i int hidden = jl_symbol_name(asname)[0]=='#'; int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym)); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - enum jl_partition_kind kind = decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); + enum jl_partition_kind kind = bpart->kind; if (((b->publicp) || (imported && (kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_IMPORTED)) || (usings && kind == BINDING_KIND_EXPLICIT) || @@ -1569,7 +1551,7 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) if ((void*)b == jl_nothing) 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) { + if (bpart->kind == BINDING_KIND_IMPLICIT) { jl_atomic_store_relaxed(&b->partitions, NULL); } } diff --git a/src/staticdata.c b/src/staticdata.c index 9b7392f0f197a..ca10931b44393 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1001,7 +1001,7 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ } else if (jl_is_binding_partition(v)) { jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; - jl_queue_for_serialization_(s, decode_restriction_value(jl_atomic_load_relaxed(&bpart->restriction)), 1, immediate); + jl_queue_for_serialization_(s, bpart->restriction, 1, immediate); jl_queue_for_serialization_(s, get_replaceable_field((jl_value_t**)&bpart->next, 0), 1, immediate); } else if (layout->nfields > 0) { @@ -1680,13 +1680,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED } else if (jl_is_binding_partition(v)) { jl_binding_partition_t *bpart = (jl_binding_partition_t*)v; - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - jl_value_t *restriction_val = decode_restriction_value(pku); - static_assert(offsetof(jl_binding_partition_t, restriction) == 0, "BindingPartition layout mismatch"); - write_pointerfield(s, restriction_val); -#ifndef _P64 - write_uint(f, decode_restriction_kind(pku)); -#endif + write_pointerfield(s, bpart->restriction); size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); if (s->incremental) { if (max_world == ~(size_t)0) { @@ -1708,13 +1702,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED write_uint(f, max_world); } write_pointerfield(s, (jl_value_t*)jl_atomic_load_relaxed(&bpart->next)); -#ifdef _P64 - write_uint(f, decode_restriction_kind(pku)); // This will be moved back into place during deserialization (if necessary) - static_assert(sizeof(jl_binding_partition_t) == 5*sizeof(void*), "BindingPartition layout mismatch"); -#else - write_uint(f, 0); - static_assert(sizeof(jl_binding_partition_t) == 6*sizeof(void*), "BindingPartition layout mismatch"); -#endif + write_uint(f, bpart->kind); } else { // Generic object::DataType serialization by field @@ -3510,11 +3498,10 @@ static void jl_validate_binding_partition(jl_binding_t *b, jl_binding_partition_ if (jl_atomic_load_relaxed(&bpart->max_world) != ~(size_t)0) return; - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; if (!jl_bkind_is_some_import(kind)) return; - jl_binding_t *imported_binding = (jl_binding_t*)decode_restriction_value(pku); + jl_binding_t *imported_binding = (jl_binding_t*)bpart->restriction; jl_binding_partition_t *latest_imported_bpart = jl_atomic_load_relaxed(&imported_binding->partitions); if (!latest_imported_bpart) return; @@ -4015,25 +4002,6 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl } } } - // Move the binding bits back to their correct place -#ifdef _P64 - jl_svec_t *table = jl_atomic_load_relaxed(&mod->bindings); - for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); - if ((jl_value_t*)b == jl_nothing) - continue; - jl_binding_partition_t *bpart = jl_atomic_load_relaxed(&b->partitions); - while (bpart) { - jl_ptr_kind_union_t pku = encode_restriction( - (jl_value_t*)jl_atomic_load_relaxed(&bpart->restriction), - (enum jl_partition_kind)bpart->reserved); - jl_atomic_store_relaxed(&bpart->restriction, pku); - bpart->reserved = 0; - bpart = jl_atomic_load_relaxed(&bpart->next); - } - } - -#endif } else { abort(); diff --git a/src/toplevel.c b/src/toplevel.c index e2c58b6345c8d..9bb82a3451848 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -157,32 +157,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } } else { - jl_binding_t *b = jl_get_module_binding(parent_module, name, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); - jl_ptr_kind_union_t pku = encode_restriction(NULL, BINDING_KIND_UNDEF_CONST); - jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)newm, BINDING_KIND_CONST); - if (!jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) { - if (decode_restriction_kind(pku) != BINDING_KIND_CONST) { - jl_declare_constant_val(b, parent_module, name, (jl_value_t*)newm); - } else { - // As a special exception allow binding replacement of modules - if (!jl_is_module(decode_restriction_value(pku))) { - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(name)); - } - if (jl_generating_output()) - jl_errorf("cannot replace module %s during compilation", jl_symbol_name(name)); - jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(name)); - pku = jl_atomic_exchange(&bpart->restriction, new_pku); - } - jl_gc_wb(bpart, newm); - if (decode_restriction_value(pku) != NULL && jl_is_module(decode_restriction_value(pku))) { - // create a hidden gc root for the old module - JL_LOCK(&jl_modules_mutex); - uintptr_t *refcnt = (uintptr_t*)ptrhash_bp(&jl_current_modules, decode_restriction_value(pku)); - *refcnt += 1; - JL_UNLOCK(&jl_modules_mutex); - } - } + jl_declare_constant_val(NULL, parent_module, name, (jl_value_t*)newm); } if (parent_module == jl_main_module && name == jl_symbol("Base")) { @@ -332,29 +307,27 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in 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); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; 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 (kind == new_kind) { if (!set_type) goto done; goto check_type; } check_safe_newbinding(gm, gs); 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); + bpart->kind = new_kind; + bpart->restriction = global_type; + if (global_type) + jl_gc_wb(bpart, global_type); continue; } else { jl_replace_binding_locked(b, bpart, global_type, new_kind, new_world); } break; } else if (set_type) { - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (jl_bkind_is_some_constant(kind)) { jl_errorf("cannot set type for constant %s.%s.", jl_symbol_name(gm->name), jl_symbol_name(gs)); } else { @@ -366,7 +339,7 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in if (set_type) { check_type: ; - jl_value_t *old_ty = decode_restriction_value(pku); + jl_value_t *old_ty = bpart->restriction; 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.", @@ -682,14 +655,13 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym // TODO: this is a bit race-y with what error message we might print jl_binding_t *b = jl_get_module_binding(m, name, 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); - enum jl_partition_kind kind = decode_restriction_kind(pku); + enum jl_partition_kind kind = bpart->kind; if (kind != BINDING_KIND_GUARD && kind != BINDING_KIND_FAILED && kind != BINDING_KIND_DECLARED && kind != BINDING_KIND_IMPLICIT) { // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. - pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (decode_restriction_kind(pku) == BINDING_KIND_CONST || decode_restriction_kind(pku) == BINDING_KIND_BACKDATED_CONST || decode_restriction_kind(pku) == BINDING_KIND_CONST_IMPORT) { + jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + if (bpart->kind == BINDING_KIND_CONST || bpart->kind == BINDING_KIND_BACKDATED_CONST || bpart->kind == BINDING_KIND_CONST_IMPORT) { // Already declared (e.g. on another thread) or imported. - if (decode_restriction_value(pku) == (jl_value_t*)import) + if (bpart->restriction == (jl_value_t*)import) return; } jl_errorf("importing %s into %s conflicts with an existing global", diff --git a/test/syntax.jl b/test/syntax.jl index ebf6ed2e0b837..f3288168f3a3e 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3977,7 +3977,7 @@ module ReplacementContainer const x = 1 end const Old = ReplaceMe - @test_warn r"WARNING: replacing module ReplaceMe" @eval module ReplaceMe + @eval module ReplaceMe const x = 2 end end From 5e4a7fbd054595bab18c1d2c9258aad1508dd579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20G=C3=B6ttgens?= Date: Fri, 14 Feb 2025 12:22:38 +0100 Subject: [PATCH 10/21] Bump JuliaSyntax to v1.0.2 (#57388) Resolves https://github.com/JuliaLang/julia/issues/56950. This should get backported to julia-1.12. (cherry picked from commit b3198c9962c5893c53b7c0571422b9c38ec1e52e) --- deps/JuliaSyntax.version | 2 +- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/md5 create mode 100644 deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/sha512 delete mode 100644 deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/md5 delete mode 100644 deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/sha512 diff --git a/deps/JuliaSyntax.version b/deps/JuliaSyntax.version index 70dac3231f626..9487754d8a617 100644 --- a/deps/JuliaSyntax.version +++ b/deps/JuliaSyntax.version @@ -1,4 +1,4 @@ JULIASYNTAX_BRANCH = main -JULIASYNTAX_SHA1 = 86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb +JULIASYNTAX_SHA1 = 46723f071d5b2efcb21ca6757788028afb91cc13 JULIASYNTAX_GIT_URL := https://github.com/JuliaLang/JuliaSyntax.jl.git JULIASYNTAX_TAR_URL = https://api.github.com/repos/JuliaLang/JuliaSyntax.jl/tarball/$1 diff --git a/deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/md5 b/deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/md5 new file mode 100644 index 0000000000000..ff40f520dfe85 --- /dev/null +++ b/deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/md5 @@ -0,0 +1 @@ +2a0921e59edfab54554aa173f091c5b7 diff --git a/deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/sha512 b/deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/sha512 new file mode 100644 index 0000000000000..64e90d0edaba0 --- /dev/null +++ b/deps/checksums/JuliaSyntax-46723f071d5b2efcb21ca6757788028afb91cc13.tar.gz/sha512 @@ -0,0 +1 @@ +17050e23216335f6599f009f71e9614a11b6686e455554b1efd287cd8526a7ebece06dc473e34cd50f61bf52085ff72bb4279144a9fdb3a234d3d589a10fddaf diff --git a/deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/md5 b/deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/md5 deleted file mode 100644 index c5d3c249ca1e0..0000000000000 --- a/deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -a514fe65096a489bd4d3c06b675573c7 diff --git a/deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/sha512 b/deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/sha512 deleted file mode 100644 index bdb48e7ef87eb..0000000000000 --- a/deps/checksums/JuliaSyntax-86bc4331eaa08e08bf2af1ba7b50bbbf4af70cdb.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -95d45a27e427f2553da4d4e821edaee6896121977ce6572212c4234013c6f85bc69fc78d237b4dae5d4ed3451f3ba9e1a7172668025ef7bf8aad024293aa2865 From 22021d9541c6ca066c474dc1f2bb25504db99904 Mon Sep 17 00:00:00 2001 From: DilumAluthgeBot <43731525+DilumAluthgeBot@users.noreply.github.com> Date: Fri, 14 Feb 2025 06:23:28 -0500 Subject: [PATCH 11/21] =?UTF-8?q?=F0=9F=A4=96=20[master]=20Bump=20the=20St?= =?UTF-8?q?atistics=20stdlib=20from=20d49c2bf=20to=2077bd570=20(#57266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stdlib: Statistics URL: https://github.com/JuliaStats/Statistics.jl.git Stdlib branch: master Julia branch: master Old commit: d49c2bf New commit: 77bd570 Julia version: 1.13.0-DEV Statistics version: 1.11.2(Does not match) Bump invoked by: @nalimilan Powered by: [BumpStdlibs.jl](https://github.com/JuliaLang/BumpStdlibs.jl) Diff: https://github.com/JuliaStats/Statistics.jl/compare/d49c2bf4f81e1efb4980a35fe39c815ef8396297...77bd5707f143eb624721a7df28ddef470e70ecef ``` $ git log --oneline d49c2bf..77bd570 77bd570 Fix `quantile` doctest (#188) bfa5c6b Merge pull request #184 from JuliaStats/an/quantilemuladd 6bd1531 Update src/Statistics.jl 44d51c7 Use muladd in aleph calculation in _quantile to avoid some rounding errors. 793733e Bump codecov/codecov-action from 4 to 5 (#181) ``` Co-authored-by: nalimilan <1120448+nalimilan@users.noreply.github.com> (cherry picked from commit 504cbc36648640eb09898f1ee0ea4fc36f38a18e) --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/Statistics.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/md5 create mode 100644 deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/sha512 delete mode 100644 deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/md5 delete mode 100644 deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/sha512 diff --git a/deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/md5 b/deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/md5 new file mode 100644 index 0000000000000..600c561d0cf14 --- /dev/null +++ b/deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/md5 @@ -0,0 +1 @@ +5235ac479da042d5dc3c572c473b7219 diff --git a/deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/sha512 b/deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/sha512 new file mode 100644 index 0000000000000..2f663a3d7c44d --- /dev/null +++ b/deps/checksums/Statistics-77bd5707f143eb624721a7df28ddef470e70ecef.tar.gz/sha512 @@ -0,0 +1 @@ +0c02ccf1b4988fc701209afb949f27e6f675f37a628385d3f28dc9ea333fed38ce1ca77b001e58fdbe15af833bbe98598cbf478cef21a98b37d54acfe52270b6 diff --git a/deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/md5 b/deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/md5 deleted file mode 100644 index 3956c67f7fd47..0000000000000 --- a/deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -acf2bb0ea30132602e172e2f5f6274b4 diff --git a/deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/sha512 b/deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/sha512 deleted file mode 100644 index 051f2d0a862c3..0000000000000 --- a/deps/checksums/Statistics-d49c2bf4f81e1efb4980a35fe39c815ef8396297.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -5e879fe79bae19b62f81659a102602271c73a424faf4be069ab31fb50e30b536a8c7b3692127763000cc1dbab69c93ac3da7bace5f093d05dce2d652fb221d52 diff --git a/stdlib/Statistics.version b/stdlib/Statistics.version index 3df70d30ba7e6..e6f0a62b3cec5 100644 --- a/stdlib/Statistics.version +++ b/stdlib/Statistics.version @@ -1,4 +1,4 @@ STATISTICS_BRANCH = master -STATISTICS_SHA1 = d49c2bf4f81e1efb4980a35fe39c815ef8396297 +STATISTICS_SHA1 = 77bd5707f143eb624721a7df28ddef470e70ecef STATISTICS_GIT_URL := https://github.com/JuliaStats/Statistics.jl.git STATISTICS_TAR_URL = https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/$1 From 1152e9b5e31300ff7826649fe8e9fd2d3b43a99f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 14 Feb 2025 10:43:22 -0500 Subject: [PATCH 12/21] lowering: fix has_fcall computation (#57395) Previously didn't handle ccall or cfunction that were assigned to a variable. Required for inlining correctness. Fixes #57023 (cherry picked from commit 57b7d2e0a88d2e4006ad74ec4bd1db5586e2d0bf) --- src/method.c | 8 ++++++-- test/core.jl | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/method.c b/src/method.c index 0fc0e0ca1f87d..e14f00a4a32ae 100644 --- a/src/method.c +++ b/src/method.c @@ -485,8 +485,12 @@ jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir) is_flag_stmt = 1; else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_return_sym) jl_array_ptr_set(body, j, jl_new_struct(jl_returnnode_type, jl_exprarg(st, 0))); - else if (jl_is_expr(st) && (((jl_expr_t*)st)->head == jl_foreigncall_sym || ((jl_expr_t*)st)->head == jl_cfunction_sym)) - li->has_fcall = 1; + else { + if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_assign_sym) + st = jl_exprarg(st, 1); + if (jl_is_expr(st) && (((jl_expr_t*)st)->head == jl_foreigncall_sym || ((jl_expr_t*)st)->head == jl_cfunction_sym)) + li->has_fcall = 1; + } if (is_flag_stmt) jl_array_uint32_set(li->ssaflags, j, 0); else { diff --git a/test/core.jl b/test/core.jl index ee47eba0d2c7d..ef1d6784cac94 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8443,3 +8443,8 @@ f_call_me() = invoke(f_invoke_me, f_invoke_me_ci) f_invalidate_me() = 2 @test_throws ErrorException invoke(f_invoke_me, f_invoke_me_ci) @test_throws ErrorException f_call_me() + +myfun57023a(::Type{T}) where {T} = (x = @ccall mycfun()::Ptr{T}; x) +@test only(code_lowered(myfun57023a)).has_fcall +myfun57023b(::Type{T}) where {T} = (x = @cfunction myfun57023a Ptr{T} (Ref{T},); x) +@test only(code_lowered(myfun57023b)).has_fcall From 5e45856d6aaa4166c0d289bfa1d6015da0eb103a Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 14 Feb 2025 09:56:23 -0600 Subject: [PATCH 13/21] Clarify mathematical definition of `gcd` (#57204) See also: Triage request for this: https://github.com/JuliaLang/julia/pull/57067#issuecomment-2625399127 Similar PR for `lcm`: https://github.com/JuliaLang/julia/pull/56992 (cherry picked from commit 41a4dfa34086df0c43ec933235dc2cf9cb6f66bb) --- base/intfuncs.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index dc81f2bd3e489..bb0f454d9c574 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -8,6 +8,9 @@ Greatest common (positive) divisor (or zero if all arguments are zero). The arguments may be integer and rational numbers. +``a`` is a divisor of ``b`` if there exists an integer ``m`` such +that ``ma=b``. + !!! compat "Julia 1.4" Rational arguments require Julia 1.4 or later. From 69f1a812f621f353eef217c046511e8a8db8e4e7 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 14 Feb 2025 10:39:12 -0500 Subject: [PATCH 14/21] internals: add _defaultctor function for defining ctors (#57317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deferring this part of defining the ctor code allows it to do some value-based optimizations, without the awkwardness previously of needing to do conditional lowering of all of the possible versions that might be useful just from syntax transforms. This feels very like a generated function: how it expands just the function body, except it is flipped so that then we wrap the result in a single Method instead of being created from a Method! Avoids lowering inaccuracies where a variable appears but is not used. These warnings are now prevented: ```julia julia> struct Foo{T} x::(T; Any) end WARNING: method definition for Foo at REPL[1]:2 declares type variable T but does not use it. julia> struct Foo{T} x::Union{Any, T} end WARNING: method definition for Foo at REPL[1]:2 declares type variable T but does not use it. ``` Avoids hitting https://github.com/JuliaLang/julia/issues/31542. This lowering mistake is now avoided: ```julia julia> struct Foo{T} x::(Val{T} where T) end ERROR: syntax: invalid variable expression in "where" around REPL[1]:2 ``` As a minor optimization, this avoids generating a `convert` call when the user declares explicit `::Any` declarations, the same as if the user didn't annotate the field type: ```julia julia> struct Foo x::Any y::Int z end julia> code_lowered(Foo)[2] CodeInfo( @ REPL[1]:2 within `unknown scope` 1 ─ %1 = builtin Core.fieldtype(#ctor-self#, 2) │ %2 = y │ @_4 = %2 │ %4 = builtin @_4 isa %1 └── goto #3 if not %4 2 ─ goto #4 3 ─ @_4 = Base.convert(%1, @_4) 4 ┄ %8 = @_4 │ %9 = %new(#ctor-self#, x, %8, z) └── return %9 ) ``` The outer/inner names might be a bit historical at this point (predating where clauses allowing specifying them flexibly inside or outside of the struct def): they are really exact-type-type&convert-args-from-any / exact-arg-types&apply-type-from-args if named for precisely what they do. (cherry picked from commit f4a9d25b313b06836f4baa3e4badbcb0186c8540) --- src/ast.c | 28 +++++++++ src/builtin_proto.h | 1 + src/builtins.c | 8 +++ src/jlfrontend.scm | 6 ++ src/jltypes.c | 3 + src/julia-syntax.scm | 143 +++++++++++++++---------------------------- src/julia.h | 1 + src/julia_internal.h | 3 + src/method.c | 129 ++++++++++++++++++++++++++++++++++++++ src/staticdata.c | 4 +- src/toplevel.c | 2 +- 11 files changed, 231 insertions(+), 97 deletions(-) diff --git a/src/ast.c b/src/ast.c index f643f4a2f40fe..70bf6024a2dd5 100644 --- a/src/ast.c +++ b/src/ast.c @@ -1369,6 +1369,34 @@ JL_DLLEXPORT jl_value_t *jl_expand_stmt(jl_value_t *expr, jl_module_t *inmodule) return jl_expand_stmt_with_loc(expr, inmodule, "none", 0); } +jl_code_info_t *jl_outer_ctor_body(jl_value_t *thistype, size_t nfields, size_t nsparams, jl_module_t *inmodule, const char *file, int line) +{ + JL_TIMING(LOWERING, LOWERING); + jl_timing_show_location(file, line, inmodule, JL_TIMING_DEFAULT_BLOCK); + jl_expr_t *expr = jl_exprn(jl_empty_sym, 3); + JL_GC_PUSH1(&expr); + jl_exprargset(expr, 0, thistype); + jl_exprargset(expr, 1, jl_box_long(nfields)); + jl_exprargset(expr, 2, jl_box_long(nsparams)); + jl_code_info_t *ci = (jl_code_info_t*)jl_call_scm_on_ast_and_loc("jl-default-outer-ctor-body", (jl_value_t*)expr, inmodule, file, line); + JL_GC_POP(); + assert(jl_is_code_info(ci)); + return ci; +} + +jl_code_info_t *jl_inner_ctor_body(jl_array_t *fieldkinds, jl_module_t *inmodule, const char *file, int line) +{ + JL_TIMING(LOWERING, LOWERING); + jl_timing_show_location(file, line, inmodule, JL_TIMING_DEFAULT_BLOCK); + jl_expr_t *expr = jl_exprn(jl_empty_sym, 0); + JL_GC_PUSH1(&expr); + expr->args = fieldkinds; + jl_code_info_t *ci = (jl_code_info_t*)jl_call_scm_on_ast_and_loc("jl-default-inner-ctor-body", (jl_value_t*)expr, inmodule, file, line); + JL_GC_POP(); + assert(jl_is_code_info(ci)); + return ci; +} + //------------------------------------------------------------------------------ // Parsing API and utils for calling parser from runtime diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 77463ae4884cb..a543aa895fb97 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -80,6 +80,7 @@ JL_CALLABLE(jl_f__structtype); JL_CALLABLE(jl_f__abstracttype); JL_CALLABLE(jl_f__primitivetype); JL_CALLABLE(jl_f__setsuper); +JL_CALLABLE(jl_f__defaultctors); JL_CALLABLE(jl_f__equiv_typedef); JL_CALLABLE(jl_f_get_binding_type); JL_CALLABLE(jl_f__compute_sparams); diff --git a/src/builtins.c b/src/builtins.c index f3d2dfad42819..063b191510bfd 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2345,6 +2345,13 @@ JL_CALLABLE(jl_f__equiv_typedef) return equiv_type(args[0], args[1]) ? jl_true : jl_false; } +JL_CALLABLE(jl_f__defaultctors) +{ + JL_NARGS(_defaultctors, 2, 2); + jl_ctor_def(args[0], args[1]); + return jl_nothing; +} + // IntrinsicFunctions --------------------------------------------------------- static void (*runtime_fp[num_intrinsics])(void); @@ -2541,6 +2548,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin_func("_abstracttype", jl_f__abstracttype); add_builtin_func("_primitivetype", jl_f__primitivetype); add_builtin_func("_setsuper!", jl_f__setsuper); + add_builtin_func("_defaultctors", jl_f__defaultctors); jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody); add_builtin_func("_equiv_typedef", jl_f__equiv_typedef); jl_builtin_donotdelete = add_builtin_func("donotdelete", jl_f_donotdelete); diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index 9c69da199c0cd..c313a1e9b0db5 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -198,6 +198,12 @@ (error-wrap (lambda () (julia-expand-macroscope expr)))) +(define (jl-default-inner-ctor-body field-kinds file line) + (expand-to-thunk- (default-inner-ctor-body (cdr field-kinds) file line) file line)) + +(define (jl-default-outer-ctor-body args file line) + (expand-to-thunk- (default-outer-ctor-body (cadr args) (caddr args) (cadddr args) file line) file line)) + ; run whole frontend on a string. useful for testing. (define (fe str) (expand-toplevel-expr (julia-parse str) 'none 0)) diff --git a/src/jltypes.c b/src/jltypes.c index 0e709936efc39..7f89e15aa38f3 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3876,7 +3876,10 @@ void jl_init_types(void) JL_GC_DISABLED jl_string_type->ismutationfree = jl_string_type->isidentityfree = 1; jl_symbol_type->ismutationfree = jl_symbol_type->isidentityfree = 1; jl_simplevector_type->ismutationfree = jl_simplevector_type->isidentityfree = 1; + jl_typename_type->ismutationfree = 1; jl_datatype_type->ismutationfree = 1; + jl_uniontype_type->ismutationfree = 1; + jl_unionall_type->ismutationfree = 1; assert(((jl_datatype_t*)jl_array_any_type)->ismutationfree == 0); assert(((jl_datatype_t*)jl_array_uint8_type)->ismutationfree == 0); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 00057498bbf9b..b6dc7bb9c8a12 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -183,6 +183,7 @@ (meta ret-type ,R) ,@(list-tail body (+ 1 (length meta)))))))))) + ;; convert x<:T<:y etc. exprs into (name lower-bound upper-bound) ;; a bound is #f if not specified (define (analyze-typevar e) @@ -753,64 +754,34 @@ (params bounds) (sparam-name-bounds params) (struct-def-expr- name params bounds super (flatten-blocks fields) mut))) -;; replace field names with gensyms if they conflict with field-types -(define (safe-field-names field-names field-types) - (if (any (lambda (v) (contains (lambda (e) (eq? e v)) field-types)) - field-names) - (map (lambda (x) (gensy)) field-names) - ;; use a different name for a field called `_` - (map (lambda (x) (if (eq? x '_) (gensy) x)) field-names))) - -(define (with-wheres call wheres) - (if (pair? wheres) - `(where ,call ,@wheres) - call)) - -(define (default-inner-ctors name field-names field-types params bounds locs) - (let* ((field-names (safe-field-names field-names field-types)) - (all-ctor (if (null? params) - ;; definition with exact types for all arguments - `(function (call ,name - ,@(map make-decl field-names field-types)) - (block - ,@locs - (new (globalref (thismodule) ,name) ,@field-names))) - #f)) - (any-ctor (if (or (not all-ctor) (any (lambda (t) (not (equal? t '(core Any)))) - field-types)) - ;; definition with Any for all arguments - ;; only if any field type is not Any, checked at runtime - `(function (call (|::| |#ctor-self#| - ,(with-wheres - `(curly (core Type) ,(if (pair? params) - `(curly ,name ,@params) - name)) - (map (lambda (b) (cons 'var-bounds b)) bounds))) - ,@field-names) - (block - ,@locs - (call new ,@field-names))) ; this will add convert calls later - #f))) - (if all-ctor - (if any-ctor - (list all-ctor - `(if ,(foldl (lambda (t u) - `(&& ,u (call (core ===) (core Any) ,t))) - `(call (core ===) (core Any) ,(car field-types)) - (cdr field-types)) - '(block) - ,any-ctor)) - (list all-ctor)) - (list any-ctor)))) - -(define (default-outer-ctor name field-names field-types params bounds locs) - (let ((field-names (safe-field-names field-names field-types))) - `(function ,(with-wheres - `(call ,name ,@(map make-decl field-names field-types)) - (map (lambda (b) (cons 'var-bounds b)) bounds)) - (block - ,@locs - (new (curly ,name ,@params) ,@field-names))))) +;; definition with Any for all arguments (except type, which is exact) +;; field-kinds: +;; -1 no convert (e.g. because it is Any) +;; 0 normal convert to fieldtype +;; 1+ static_parameter N +(define (default-inner-ctor-body field-kinds file line) + (let* ((name '|#ctor-self#|) + (field-names (map (lambda (idx) (symbol (string "_" (+ idx 1)))) (iota (length field-kinds)))) + (field-convert (lambda (fld fty val) + (cond ((eq? fty -1) val) + ((> fty 0) (convert-for-type-decl val `(static_parameter ,fty) #f #f)) + (else (convert-for-type-decl val `(call (core fieldtype) ,name ,(+ fld 1)) #f #f))))) + (field-vals (map field-convert (iota (length field-names)) field-kinds field-names)) + (body `(block + (line ,line ,file) + (return (new ,name ,@field-vals))))) + `(lambda ,(cons name field-names) () (scope-block ,body)))) + +;; definition with exact types for all arguments (except type, which is not parameterized) +(define (default-outer-ctor-body thistype field-count sparam-count file line) + (let* ((name '|#ctor-self#|) + (field-names (map (lambda (idx) (symbol (string "_" (+ idx 1)))) (iota field-count))) + (sparams (map (lambda (idx) `(static_parameter ,(+ idx 1))) (iota sparam-count))) + (type (if (null? sparams) name `(curly ,thistype ,@sparams))) + (body `(block + (line ,line ,file) + (return (new ,type ,@field-names))))) + `(lambda ,(cons name field-names) () (scope-block ,body)))) (define (num-non-varargs args) (count (lambda (a) (not (vararg? a))) args)) @@ -993,14 +964,11 @@ fields))) (attrs (reverse attrs)) (defs (filter (lambda (x) (not (or (effect-free? x) (eq? (car x) 'string)))) defs)) - (locs (if (and (pair? fields0) (linenum? (car fields0))) - (list (car fields0)) - '())) + (loc (if (and (pair? fields0) (linenum? (car fields0))) + (car fields0) + '(line 0 ||))) (field-names (map decl-var fields)) (field-types (map decl-type fields)) - (defs2 (if (null? defs) - (default-inner-ctors name field-names field-types params bounds locs) - defs)) (min-initialized (min (ctors-min-initialized defs) (length fields))) (hasprev (make-ssavalue)) (prev (make-ssavalue)) @@ -1042,34 +1010,21 @@ (const (globalref (thismodule) ,name) ,newdef) (latestworld) (null))) - ;; "inner" constructors - (scope-block - (block - (hardscope) - (global ,name) - ,@(map (lambda (c) - (rewrite-ctor c name params field-names field-types)) - defs2))) - ;; "outer" constructors - ,@(if (and (null? defs) - (not (null? params)) - ;; To generate an outer constructor, each parameter must occur in a field - ;; type, or in the bounds of a subsequent parameter. - ;; Otherwise the constructor would not work, since the parameter values - ;; would never be specified. - (let loop ((root-types field-types) - (sp (reverse bounds))) - (or (null? sp) - (let ((p (car sp))) - (and (expr-contains-eq (car p) (cons 'list root-types)) - (loop (append (cdr p) root-types) - (cdr sp))))))) - `((scope-block - (block - (global ,name) - ,(default-outer-ctor name field-names field-types - params bounds locs)))) - '()) + ;; Always define ctors even if we didn't change the definition. + ;; If newdef===prev, then this is a bit suspect, since we don't know what might be + ;; changing about the old ctor definitions (we don't even track whether we're + ;; replacing defaultctors with identical ones). But it seems better to have the ctors + ;; added alongside (replacing) the old ones, than to not have them and need them. + ;; Commonly Revise.jl should be used to figure out actually which methods should + ;; actually be deleted or added anew. + ,(if (null? defs) + `(call (core _defaultctors) ,newdef (inert ,loc)) + `(scope-block + (block + (hardscope) + (global ,name) + ,@(map (lambda (c) (rewrite-ctor c name params field-names field-types)) defs)))) + (latestworld) (null))))) (define (abstract-type-def-expr name params super) @@ -4646,7 +4601,7 @@ f(x) = yt(x) ;; from the current function. (define (compile e break-labels value tail) (if (or (not (pair? e)) (memq (car e) '(null true false ssavalue quote inert top core copyast the_exception $ - globalref thismodule cdecl stdcall fastcall thiscall llvmcall))) + globalref thismodule cdecl stdcall fastcall thiscall llvmcall static_parameter))) (let ((e1 (if (and arg-map (symbol? e)) (get arg-map e e) e))) @@ -4657,7 +4612,7 @@ f(x) = yt(x) (cond (tail (emit-return tail e1)) (value e1) ((symbol? e1) (emit e1) #f) ;; keep symbols for undefined-var checking - ((and (pair? e1) (eq? (car e1) 'globalref)) (emit e1) #f) ;; keep globals for undefined-var checking + ((and (pair? e1) (memq (car e1) '(globalref static_parameter))) (emit e1) #f) ;; keep for undefined-var checking (else #f))) (case (car e) ((call new splatnew foreigncall cfunction new_opaque_closure) diff --git a/src/julia.h b/src/julia.h index bf049c909d833..3e730a44453ce 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1612,6 +1612,7 @@ static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT #define jl_is_quotenode(v) jl_typetagis(v,jl_quotenode_type) #define jl_is_newvarnode(v) jl_typetagis(v,jl_newvarnode_type) #define jl_is_linenode(v) jl_typetagis(v,jl_linenumbernode_type) +#define jl_is_linenumbernode(v) jl_typetagis(v,jl_linenumbernode_type) #define jl_is_method_instance(v) jl_typetagis(v,jl_method_instance_type) #define jl_is_code_instance(v) jl_typetagis(v,jl_code_instance_type) #define jl_is_code_info(v) jl_typetagis(v,jl_code_info_type) diff --git a/src/julia_internal.h b/src/julia_internal.h index 0f52f82ac8230..7fff59806f62f 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1214,6 +1214,9 @@ JL_DLLEXPORT int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, size_t lineno, size_t offset, jl_value_t *options); +jl_code_info_t *jl_inner_ctor_body(jl_array_t *fieldkinds, jl_module_t *inmodule, const char *file, int line); +jl_code_info_t *jl_outer_ctor_body(jl_value_t *thistype, size_t nfields, size_t nsparams, jl_module_t *inmodule, const char *file, int line); +void jl_ctor_def(jl_value_t *ty, jl_value_t *functionloc); //-------------------------------------------------- // Backtraces diff --git a/src/method.c b/src/method.c index e14f00a4a32ae..48edd8071e53d 100644 --- a/src/method.c +++ b/src/method.c @@ -1270,6 +1270,135 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, return m; } +void jl_ctor_def(jl_value_t *ty, jl_value_t *functionloc) +{ + jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(ty); + JL_TYPECHK(ctor_def, datatype, (jl_value_t*)dt); + JL_TYPECHK(ctor_def, linenumbernode, functionloc); + jl_svec_t *fieldtypes = jl_get_fieldtypes(dt); + size_t nfields = jl_svec_len(fieldtypes); + size_t nparams = jl_subtype_env_size(ty); + jl_module_t *inmodule = dt->name->module; + jl_sym_t *file = (jl_sym_t*)jl_linenode_file(functionloc); + if (!jl_is_symbol(file)) + file = jl_empty_sym; + int32_t line = jl_linenode_line(functionloc); + + // argdata is svec(svec(types...), svec(typevars...), functionloc) + jl_svec_t *argdata = jl_alloc_svec(3); + jl_array_t *fieldkinds = NULL; + jl_code_info_t *body = NULL; + JL_GC_PUSH3(&argdata, &fieldkinds, &body); + jl_svecset(argdata, 2, functionloc); + jl_svec_t *tvars = jl_alloc_svec(nparams); + jl_svecset(argdata, 1, tvars); + jl_unionall_t *ua = (jl_unionall_t*)ty; + for (size_t i = 0; i < nparams; i++) { + assert(jl_is_unionall(ua)); + jl_svecset(tvars, i, ua->var); + ua = (jl_unionall_t*)ua->body; + } + jl_svec_t *names = dt->name->names; + + // define outer constructor (if all typevars are present (thus not definitely unconstrained) by the fields or other typevars which themselves are constrained) + int constrains_all_tvars = 1; + for (size_t i = nparams; i > 0; i--) { + jl_tvar_t *tv = (jl_tvar_t*)jl_svecref(tvars, i - 1); + int constrains_tvar = 0; + for (size_t i = 0; i < nfields; i++) { + jl_value_t *ft = jl_svecref(fieldtypes, i); + if (jl_has_typevar(ft, tv)) { + constrains_tvar = 1; + break; + } + } + for (size_t j = i; j < nparams; j++) { + jl_tvar_t *tv2 = (jl_tvar_t*)jl_svecref(tvars, j); + if (jl_has_typevar(tv2->ub, tv)) { // lb doesn't constrain, but jl_has_typevar doesn't have a way to specify that we care about may-constrain and not merely containment + constrains_tvar = 1; + break; + } + if (tv2 == tv) { + constrains_tvar = 0; + break; + } + } + if (!constrains_tvar) { + constrains_all_tvars = 0; + break; + } + } + if (constrains_all_tvars) { + jl_svec_t *atypes = jl_alloc_svec(nfields + 1); + jl_svecset(argdata, 0, atypes); + jl_svecset(atypes, 0, jl_wrap_Type(ty)); + for (size_t i = 0; i < nfields; i++) { + jl_value_t *ft = jl_svecref(fieldtypes, i); + jl_svecset(atypes, i + 1, ft); + } + body = jl_outer_ctor_body(ty, nfields, nparams, inmodule, jl_symbol_name(file), line); + if (names) { + jl_array_t *slotnames = body->slotnames; + for (size_t i = 0; i < nfields; i++) { + jl_array_ptr_set(slotnames, i + 1, jl_svecref(names, i)); + } + } + jl_method_def(argdata, NULL, body, inmodule); + if (nparams == 0) { + int all_Any = 1; // check if all fields are Any and the type is not parameterized, since inner constructor would be the same signature and code + for (size_t i = 0; i < nfields; i++) { + jl_value_t *ft = jl_svecref(fieldtypes, i); + if (ft != (jl_value_t*)jl_any_type) { + all_Any = 0; + break; + } + } + if (all_Any) { + JL_GC_POP(); + return; + } + } + } + + // define inner constructor + jl_svec_t *atypes = jl_svec_fill(nfields + 1, (jl_value_t*)jl_any_type); + jl_svecset(argdata, 0, atypes); + jl_value_t *typedt = (jl_value_t*)jl_wrap_Type((jl_value_t*)dt); + jl_svecset(atypes, 0, typedt); + fieldkinds = jl_alloc_vec_any(nfields); + for (size_t i = 0; i < nfields; i++) { + jl_value_t *ft = jl_svecref(fieldtypes, i); + int kind = ft == (jl_value_t*)jl_any_type ? -1 : 0; + // TODO: if more efficient to do so, we could reference the sparam instead of fieldtype + //if (jl_is_typevar(ft)) { + // for (size_t i = 0; i < nparams; i++) { + // if (jl_svecref(tvars, i) == ft) { + // kind = i + 1; + // break; // if repeated, must consider only the innermost + // } + // } + //} + jl_array_ptr_set(fieldkinds, i, jl_box_long(kind)); + } + // rewrap_unionall(Type{dt}, ty) + for (size_t i = nparams; i > 0; i--) { + jl_value_t *tv = jl_svecref(tvars, i - 1); + typedt = jl_new_struct(jl_unionall_type, tv, typedt); + jl_svecset(atypes, 0, typedt); + } + tvars = jl_emptysvec; + jl_svecset(argdata, 1, tvars); + body = jl_inner_ctor_body(fieldkinds, inmodule, jl_symbol_name(file), line); + if (names) { + jl_array_t *slotnames = body->slotnames; + for (size_t i = 0; i < nfields; i++) { + jl_array_ptr_set(slotnames, i + 1, jl_svecref(names, i)); + } + } + jl_method_def(argdata, NULL, body, inmodule); + JL_GC_POP(); +} + // root blocks // This section handles method roots. Roots are GC-preserved items needed to diff --git a/src/staticdata.c b/src/staticdata.c index ca10931b44393..1956473a9fcad 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -515,8 +515,8 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_memoryrefset, &jl_f_memoryrefswap, &jl_f_memoryrefmodify, &jl_f_memoryrefreplace, &jl_f_memoryrefsetonce, &jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, - &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, - &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, + &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f__defaultctors, + &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, &jl_f_get_binding_type, &jl_f_getglobal, &jl_f_setglobal, &jl_f_swapglobal, &jl_f_modifyglobal, &jl_f_replaceglobal, &jl_f_setglobalonce, &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, &jl_f_current_scope, diff --git a/src/toplevel.c b/src/toplevel.c index 9bb82a3451848..5b5db28f113e8 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -433,7 +433,7 @@ static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int if (jl_is_intrinsic(called) && jl_unbox_int32(called) == (int)llvmcall) { *has_ccall = 1; } - if (called == jl_builtin__typebody) { + if (called == jl_builtin__typebody) { // TODO: rely on latestworld instead of function callee detection here (or add it to jl_is_toplevel_only_expr) *has_defs = 1; } } From 5918d429354995791ad98e6c39311d8f29b03d87 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 14 Feb 2025 10:05:01 -0600 Subject: [PATCH 15/21] Make `Pairs` public (#56794) This is the type of slurped keyword args and IMO was already public in 1.10. (cherry picked from commit be574cd1b441afdbc62fb40bfcc0cbec10475ad6) --- base/public.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/public.jl b/base/public.jl index 8777a454c920a..8d2a65f9a150c 100644 --- a/base/public.jl +++ b/base/public.jl @@ -20,6 +20,7 @@ public Generator, ImmutableDict, OneTo, + Pairs, LogRange, UUID, From 9002fe57a0052ec09041dbf4e8ad13d1b5f5dc76 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 14 Feb 2025 16:22:47 -0500 Subject: [PATCH 16/21] staticdata: corrected implementation of jl_collect_new_roots (#57407) This seems basically the same as #57212, but from computing and using `method_roots_list` wrong instead here (specifically the recursively part). Fix it by integrating `jl_collect_new_roots` with the places we actually need it. Fixes #56994 (cherry picked from commit f5f6d4115b5e4fc5e62169bfab006732358c6e84) --- src/staticdata.c | 95 ++++++++++++++++++++++++++++++------------ src/staticdata_utils.c | 45 -------------------- 2 files changed, 69 insertions(+), 71 deletions(-) diff --git a/src/staticdata.c b/src/staticdata.c index 1956473a9fcad..745b72026b9af 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -550,6 +550,9 @@ typedef struct { jl_array_t *link_ids_gctags; jl_array_t *link_ids_gvars; jl_array_t *link_ids_external_fnvars; + jl_array_t *method_roots_list; + htable_t method_roots_index; + uint64_t worklist_key; jl_ptls_t ptls; jl_image_t *image; int8_t incremental; @@ -931,22 +934,57 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ int is_relocatable = jl_is_code_info(inferred) || (jl_is_string(inferred) && jl_string_len(inferred) > 0 && jl_string_data(inferred)[jl_string_len(inferred) - 1]); if (!is_relocatable) { - record_field_change((jl_value_t**)&ci->inferred, jl_nothing); + inferred = jl_nothing; } else if (def->source == NULL) { // don't delete code from optimized opaque closures that can't be reconstructed (and builtins) } else if (jl_atomic_load_relaxed(&ci->max_world) != ~(size_t)0 || // delete all code that cannot run jl_atomic_load_relaxed(&ci->invoke) == jl_fptr_const_return) { // delete all code that just returns a constant - record_field_change((jl_value_t**)&ci->inferred, jl_nothing); + inferred = jl_nothing; } else if (native_functions && // don't delete any code if making a ji file (ci->owner == jl_nothing) && // don't delete code for external interpreters !effects_foldable(jl_atomic_load_relaxed(&ci->ipo_purity_bits)) && // don't delete code we may want for irinterp jl_ir_inlining_cost(inferred) == UINT16_MAX) { // don't delete inlineable code // delete the code now: if we thought it was worth keeping, it would have been converted to object code + inferred = jl_nothing; + } + if (inferred == jl_nothing) { record_field_change((jl_value_t**)&ci->inferred, jl_nothing); } + else if (jl_is_string(inferred)) { + // New roots for external methods + if (jl_object_in_image((jl_value_t*)def)) { + void **pfound = ptrhash_bp(&s->method_roots_index, def); + if (*pfound == HT_NOTFOUND) { + *pfound = def; + size_t nwithkey = nroots_with_key(def, s->worklist_key); + if (nwithkey) { + jl_array_ptr_1d_push(s->method_roots_list, (jl_value_t*)def); + jl_array_t *newroots = jl_alloc_vec_any(nwithkey); + jl_array_ptr_1d_push(s->method_roots_list, (jl_value_t*)newroots); + rle_iter_state rootiter = rle_iter_init(0); + uint64_t *rletable = NULL; + size_t nblocks2 = 0; + size_t nroots = jl_array_nrows(def->roots); + size_t k = 0; + if (def->root_blocks) { + rletable = jl_array_data(def->root_blocks, uint64_t); + nblocks2 = jl_array_nrows(def->root_blocks); + } + while (rle_iter_increment(&rootiter, nroots, rletable, nblocks2)) { + if (rootiter.key == s->worklist_key) { + jl_value_t *newroot = jl_array_ptr_ref(def->roots, rootiter.i); + jl_queue_for_serialization(s, newroot); + jl_array_ptr_set(newroots, k++, newroot); + } + } + assert(k == nwithkey); + } + } + } + } } } } @@ -2861,10 +2899,9 @@ JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val, int insert) return val; } -static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *newly_inferred, uint64_t worklist_key, +static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *newly_inferred, /* outputs */ jl_array_t **extext_methods JL_REQUIRE_ROOTED_SLOT, jl_array_t **new_ext_cis JL_REQUIRE_ROOTED_SLOT, - jl_array_t **method_roots_list JL_REQUIRE_ROOTED_SLOT, jl_array_t **edges JL_REQUIRE_ROOTED_SLOT) { // extext_methods: [method1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist @@ -2888,15 +2925,12 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new } if (edges) { + // Extract `edges` now (from info prepared by jl_collect_methcache_from_mod) size_t world = jl_atomic_load_acquire(&jl_world_counter); - // Extract `new_ext_cis` and `edges` now (from info prepared by jl_collect_methcache_from_mod) - *method_roots_list = jl_alloc_vec_any(0); - // Collect the new method roots for external specializations - jl_collect_new_roots(*method_roots_list, *new_ext_cis, worklist_key); *edges = jl_alloc_vec_any(0); jl_collect_internal_cis(*edges, world); } - internal_methods = NULL; + internal_methods = NULL; // global JL_GC_POP(); } @@ -2904,8 +2938,7 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new // In addition to the system image (where `worklist = NULL`), this can also save incremental images with external linkage static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_array_t *worklist, jl_array_t *extext_methods, - jl_array_t *new_ext_cis, jl_array_t *method_roots_list, - jl_array_t *edges) JL_GC_DISABLED + jl_array_t *new_ext_cis, jl_array_t *edges) { htable_new(&field_replace, 0); htable_new(&bits_replace, 0); @@ -3035,9 +3068,14 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, s.link_ids_gctags = jl_alloc_array_1d(jl_array_int32_type, 0); s.link_ids_gvars = jl_alloc_array_1d(jl_array_int32_type, 0); s.link_ids_external_fnvars = jl_alloc_array_1d(jl_array_int32_type, 0); + s.method_roots_list = NULL; + htable_new(&s.method_roots_index, 0); + if (worklist) { + s.method_roots_list = jl_alloc_vec_any(0); + s.worklist_key = jl_worklist_key(worklist); + } jl_value_t **const*const tags = get_tags(); // worklist == NULL ? get_tags() : NULL; - if (worklist == NULL) { // empty!(Core.ARGS) if (jl_core_module != NULL) { @@ -3082,8 +3120,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_queue_for_serialization(&s, extext_methods); // Queue the new specializations jl_queue_for_serialization(&s, new_ext_cis); - // Queue the new roots - jl_queue_for_serialization(&s, method_roots_list); // Queue the edges jl_queue_for_serialization(&s, edges); } @@ -3094,7 +3130,15 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, if (jl_options.trim) record_gvars(&s, &MIs); jl_serialize_reachable(&s); - // step 1.3: prune (garbage collect) special weak references from the jl_global_roots_list + // Beyond this point, all content should already have been visited, so now we can prune + // the rest and add some internal root arrays. + // step 1.3: include some other special roots + if (s.incremental) { + // Queue the new roots array + jl_queue_for_serialization(&s, s.method_roots_list); + jl_serialize_reachable(&s); + } + // step 1.4: prune (garbage collect) special weak references from the jl_global_roots_list if (worklist == NULL) { global_roots_list = jl_alloc_memory_any(0); global_roots_keyset = jl_alloc_memory_any(0); @@ -3110,7 +3154,7 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_queue_for_serialization(&s, global_roots_keyset); jl_serialize_reachable(&s); } - // step 1.4: prune (garbage collect) some special weak references from + // step 1.5: prune (garbage collect) some special weak references from // built-in type caches too for (i = 0; i < serialization_queue.len; i++) { jl_value_t *v = (jl_value_t*)serialization_queue.items[i]; @@ -3130,7 +3174,8 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, if (ptrhash_get(&serialization_order, mi) == HT_NOTFOUND) jl_svecset(specializations, i, jl_nothing); } - } else if (jl_is_module(v)) { + } + else if (jl_is_module(v)) { jl_prune_module_bindings((jl_module_t*)v); } } @@ -3262,7 +3307,7 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_write_value(&s, jl_module_init_order); jl_write_value(&s, extext_methods); jl_write_value(&s, new_ext_cis); - jl_write_value(&s, method_roots_list); + jl_write_value(&s, s.method_roots_list); jl_write_value(&s, edges); } write_uint32(f, jl_array_len(s.link_ids_gctags)); @@ -3293,6 +3338,7 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, arraylist_free(&s.gctags_list); arraylist_free(&gvars); arraylist_free(&external_fns); + htable_free(&s.method_roots_index); htable_free(&field_replace); htable_free(&bits_replace); htable_free(&serialization_order); @@ -3353,18 +3399,17 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli } jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_ext_cis = NULL; - jl_array_t *method_roots_list = NULL, *edges = NULL; + jl_array_t *edges = NULL; int64_t checksumpos = 0; int64_t checksumpos_ff = 0; int64_t datastartpos = 0; - JL_GC_PUSH5(&mod_array, &extext_methods, &new_ext_cis, &method_roots_list, &edges); + JL_GC_PUSH4(&mod_array, &extext_methods, &new_ext_cis, &edges); if (worklist) { mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) // Generate _native_data` if (_native_data != NULL) { - jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), - &extext_methods, &new_ext_cis, NULL, NULL); + jl_prepare_serialization_data(mod_array, newly_inferred, &extext_methods, &new_ext_cis, NULL); jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); *_native_data = jl_precompile_worklist(worklist, extext_methods, new_ext_cis); jl_precompile_toplevel_module = NULL; @@ -3395,8 +3440,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli assert((ct->reentrant_timing & 0b1110) == 0); ct->reentrant_timing |= 0b1000; if (worklist) { - jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), - &extext_methods, &new_ext_cis, &method_roots_list, &edges); + jl_prepare_serialization_data(mod_array, newly_inferred, &extext_methods, &new_ext_cis, &edges); if (!emit_split) { write_int32(f, 0); // No clone_targets write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); @@ -3408,7 +3452,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli } if (_native_data != NULL) native_functions = *_native_data; - jl_save_system_image_to_stream(ff, mod_array, worklist, extext_methods, new_ext_cis, method_roots_list, edges); + jl_save_system_image_to_stream(ff, mod_array, worklist, extext_methods, new_ext_cis, edges); if (_native_data != NULL) native_functions = NULL; // make sure we don't run any Julia code concurrently before this point @@ -4185,7 +4229,6 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i &edges, &base, &ccallable_list, &cachesizes); JL_SIGATOMIC_END(); - // No special processing of `new_ext_cis` is required because recaching handled it // Add roots to methods jl_copy_roots(method_roots_list, jl_worklist_key((jl_array_t*)restored)); // Insert method extensions and handle edges diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 1985357321a3a..163eb1ea9cad5 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -261,51 +261,6 @@ static jl_array_t *queue_external_cis(jl_array_t *list) return new_ext_cis; } -// New roots for external methods -static void jl_collect_new_roots(jl_array_t *roots, jl_array_t *new_ext_cis, uint64_t key) -{ - htable_t mset; - htable_new(&mset, 0); - size_t l = new_ext_cis ? jl_array_nrows(new_ext_cis) : 0; - for (size_t i = 0; i < l; i++) { - jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_ext_cis, i); - assert(jl_is_code_instance(ci)); - jl_method_t *m = jl_get_ci_mi(ci)->def.method; - assert(jl_is_method(m)); - ptrhash_put(&mset, (void*)m, (void*)m); - } - int nwithkey; - void *const *table = mset.table; - jl_array_t *newroots = NULL; - JL_GC_PUSH1(&newroots); - for (size_t i = 0; i < mset.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_method_t *m = (jl_method_t*)table[i]; - assert(jl_is_method(m)); - nwithkey = nroots_with_key(m, key); - if (nwithkey) { - jl_array_ptr_1d_push(roots, (jl_value_t*)m); - newroots = jl_alloc_vec_any(nwithkey); - jl_array_ptr_1d_push(roots, (jl_value_t*)newroots); - rle_iter_state rootiter = rle_iter_init(0); - uint64_t *rletable = NULL; - size_t nblocks2 = 0, nroots = jl_array_nrows(m->roots), k = 0; - if (m->root_blocks) { - rletable = jl_array_data(m->root_blocks, uint64_t); - nblocks2 = jl_array_nrows(m->root_blocks); - } - while (rle_iter_increment(&rootiter, nroots, rletable, nblocks2)) - if (rootiter.key == key) - jl_array_ptr_set(newroots, k++, jl_array_ptr_ref(m->roots, rootiter.i)); - assert(k == nwithkey); - } - } - } - JL_GC_POP(); - htable_free(&mset); -} - - // For every method: // - if the method is owned by a worklist module, add it to the list of things to be // verified on reloading From 67eb4535e58499f35a5395489bfa415e38260b7f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 15 Feb 2025 02:54:02 -0500 Subject: [PATCH 17/21] bpart: Also partition the export flag (#57405) Whether or not a binding is exported affects the binding resolution of any downstream modules that `using` the module that defines the binding. As such, it needs to fully participate in the invalidation mechanism, so that code which references bindings whose resolution may have changed get properly invalidated. To do this, move the `exportp` flag from Binding into a separate bitflag set that gets or'd into the BindingPartition `->kind` field. Note that we do not move `publicp` in the same way since it does not affect binding resolution. There is currently no mechanism to un-export a binding, although the system is set up to support this in the future (and Revise may want it). That said, at such a time, we may need to revisit the above decision on `publicp`. Lastly, I will note that this adds a fair number of additional invalidations. Most of these are unnecessary, as changing an export only affects the *downstream* users not the binding itself. I am planning to tackle this as part of a larger future PR that avoids invalidation when this is not required. Fixes #57377 (cherry picked from commit b27a24acbe831c3fab07828dba15fe149f05e486) --- base/invalidation.jl | 51 +++++++------- base/runtime_internals.jl | 2 + base/show.jl | 3 + src/ast.c | 2 +- src/codegen.cpp | 18 ++--- src/julia.h | 21 +++--- src/julia_internal.h | 8 ++- src/method.c | 4 +- src/module.c | 143 ++++++++++++++++++++++++-------------- src/staticdata.c | 6 +- src/toplevel.c | 8 +-- test/rebinding.jl | 25 ++++++- 12 files changed, 181 insertions(+), 110 deletions(-) diff --git a/base/invalidation.jl b/base/invalidation.jl index c0aed35aa90a0..462e348e09038 100644 --- a/base/invalidation.jl +++ b/base/invalidation.jl @@ -113,39 +113,36 @@ function invalidate_method_for_globalref!(gr::GlobalRef, method::Method, invalid end end -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)) + 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) + 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 - 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) + 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 end end end - if (b.flags & BINDING_FLAG_EXPORTP) != 0 + if (invalidated_bpart.kind & BINDING_FLAG_EXPORTED != 0) || (new_bpart !== nothing && (new_bpart.kind & BINDING_FLAG_EXPORTED != 0)) # This binding was exported - we need to check all modules that `using` us to see if they - # have an implicit binding to us. + # have a binding that is affected by this change. usings_backedges = ccall(:jl_get_module_usings_backedges, Any, (Any,), gr.mod) if usings_backedges !== nothing for user in usings_backedges::Vector{Any} @@ -154,8 +151,8 @@ function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core 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 + binding_kind(latest_bpart) in (BINDING_KIND_IMPLICIT, BINDING_KIND_FAILED, BINDING_KIND_GUARD) || continue + @atomic :release latest_bpart.max_world = new_max_world invalidate_code_for_globalref!(convert(Core.Binding, user_binding), latest_bpart, nothing, new_max_world) end end diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 36961f58c5c3f..3a66dbda97477 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -209,6 +209,8 @@ const BINDING_KIND_GUARD = 0x8 const BINDING_KIND_UNDEF_CONST = 0x9 const BINDING_KIND_BACKDATED_CONST = 0xa +const BINDING_FLAG_EXPORTED = 0x10 + 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) diff --git a/base/show.jl b/base/show.jl index 122b3372b70ce..6316f2811a34c 100644 --- a/base/show.jl +++ b/base/show.jl @@ -3368,6 +3368,9 @@ function print_partition(io::IO, partition::Core.BindingPartition) else print(io, max_world) end + if (partition.kind & BINDING_FLAG_EXPORTED) != 0 + print(io, " [exported]") + end print(io, " - ") kind = binding_kind(partition) if kind == BINDING_KIND_BACKDATED_CONST diff --git a/src/ast.c b/src/ast.c index 70bf6024a2dd5..eeff17162f3fc 100644 --- a/src/ast.c +++ b/src/ast.c @@ -178,7 +178,7 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint jl_sym_t *var = scmsym_to_julia(fl_ctx, args[0]); jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - return (bpart != NULL && bpart->kind == BINDING_KIND_GLOBAL) ? fl_ctx->T : fl_ctx->F; + return (bpart != NULL && jl_binding_kind(bpart) == BINDING_KIND_GLOBAL) ? fl_ctx->T : fl_ctx->F; } // Used to generate a unique suffix for a given symbol (e.g. variable or type name) diff --git a/src/codegen.cpp b/src/codegen.cpp index 49fe9d8446473..7fbd9e9e7659c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3139,7 +3139,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_t *bnd = jl_get_module_binding(ctx.module, sym, 0); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); - if (bpart && jl_bkind_is_some_constant(bpart->kind)) + if (bpart && jl_bkind_is_some_constant(jl_binding_kind(bpart))) return bpart->restriction; return NULL; } @@ -3165,7 +3165,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); jl_value_t *v = NULL; - if (bpart && jl_bkind_is_some_constant(bpart->kind)) + if (bpart && jl_bkind_is_some_constant(jl_binding_kind(bpart))) v = bpart->restriction; if (v) { if (bnd->deprecated) @@ -3192,7 +3192,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); jl_walk_binding_inplace_all(&bnd, &bpart, ctx.min_world, ctx.max_world); jl_value_t *v = NULL; - if (bpart && jl_bkind_is_some_constant(bpart->kind)) + if (bpart && jl_bkind_is_some_constant(jl_binding_kind(bpart))) v = bpart->restriction; if (v) { if (bnd->deprecated) @@ -3443,14 +3443,14 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * return emit_globalref_runtime(ctx, bnd, mod, name); } // bpart was updated in place - this will change with full partition - if (jl_bkind_is_some_guard(bpart->kind)) { + if (jl_bkind_is_some_guard(jl_binding_kind(bpart))) { // Redo the lookup at runtime return emit_globalref_runtime(ctx, bnd, mod, name); } else { while (true) { if (!bpart) break; - if (!jl_bkind_is_some_import(bpart->kind)) + if (!jl_bkind_is_some_import(jl_binding_kind(bpart))) break; if (bnd->deprecated) { cg_bdw(ctx, name, bnd); @@ -3461,7 +3461,7 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * break; } if (bpart) { - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_constant(kind) && kind != BINDING_KIND_BACKDATED_CONST) { jl_value_t *constval = bpart->restriction; if (!constval) { @@ -3472,7 +3472,7 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * } } } - if (!bpart || bpart->kind != BINDING_KIND_GLOBAL) { + if (!bpart || jl_binding_kind(bpart) != BINDING_KIND_GLOBAL) { return emit_globalref_runtime(ctx, bnd, mod, name); } Value *bp = julia_binding_gv(ctx, bnd); @@ -3495,7 +3495,7 @@ static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *s jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); Value *bp = julia_binding_gv(ctx, bnd); if (bpart) { - if (bpart->kind == BINDING_KIND_GLOBAL) { + if (jl_binding_kind(bpart) == BINDING_KIND_GLOBAL) { jl_value_t *ty = bpart->restriction; if (ty != nullptr) { const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; @@ -4181,7 +4181,7 @@ static jl_cgval_t emit_isdefinedglobal(jl_codectx_t &ctx, jl_module_t *modu, jl_ Value *isnull = NULL; jl_binding_t *bnd = allow_import ? jl_get_binding(modu, name) : jl_get_module_binding(modu, name, 0); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); - enum jl_partition_kind kind = bpart ? bpart->kind : BINDING_KIND_GUARD; + enum jl_partition_kind kind = bpart ? jl_binding_kind(bpart) : BINDING_KIND_GUARD; if (kind == BINDING_KIND_GLOBAL || jl_bkind_is_some_constant(kind)) { if (jl_get_binding_value_if_const(bnd)) return mark_julia_const(ctx, jl_true); diff --git a/src/julia.h b/src/julia.h index 3e730a44453ce..5edbf4a1c8955 100644 --- a/src/julia.h +++ b/src/julia.h @@ -706,6 +706,9 @@ enum jl_partition_kind { BINDING_KIND_IMPLICIT_RECOMPUTE = 0xb }; +// These are flags that get anded into the above +static const uint8_t BINDING_FLAG_EXPORTED = 0x10; + typedef struct __attribute__((aligned(8))) _jl_binding_partition_t { JL_DATA_TYPE /* union { @@ -716,18 +719,19 @@ typedef struct __attribute__((aligned(8))) _jl_binding_partition_t { * // For ->kind in (BINDING_KIND_IMPLICIT, BINDING_KIND_EXPLICIT, BINDING_KIND_IMPORT) * jl_binding_t *imported; * } restriction; - * - * Currently: Low 3 bits hold ->kind on _P64 to avoid needing >8 byte atomics - * - * This field is updated atomically with both kind and restriction */ jl_value_t *restriction; size_t min_world; _Atomic(size_t) max_world; _Atomic(struct _jl_binding_partition_t *) next; - enum jl_partition_kind kind; + size_t kind; } jl_binding_partition_t; +STATIC_INLINE enum jl_partition_kind jl_binding_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT +{ + return (enum jl_partition_kind)(bpart->kind & 0xf); +} + typedef struct _jl_binding_t { JL_DATA_TYPE jl_globalref_t *globalref; // cached GlobalRef for this binding @@ -735,11 +739,10 @@ typedef struct _jl_binding_t { _Atomic(jl_binding_partition_t*) partitions; jl_array_t *backedges; uint8_t did_print_backdate_admonition:1; - uint8_t exportp:1; // `public foo` sets `publicp`, `export foo` sets both `publicp` and `exportp` - uint8_t publicp:1; // exportp without publicp is not allowed. - uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package uint8_t did_print_implicit_import_admonition:1; - uint8_t padding:2; + uint8_t publicp:1; // `export` is tracked in partitions, but sets this as well + uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package + uint8_t padding:3; } jl_binding_t; typedef struct { diff --git a/src/julia_internal.h b/src/julia_internal.h index 7fff59806f62f..983df1ccdc4c7 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -915,6 +915,8 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_RO 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; +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; 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; @@ -952,7 +954,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL 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; EXTERN_INLINE_DECLARE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT { - return (uint8_t)bpart->kind; + return (uint8_t)(bpart->kind & 0xf); } STATIC_INLINE void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart JL_PROPAGATES_ROOT, size_t world) JL_NOTSAFEPOINT; @@ -962,7 +964,7 @@ STATIC_INLINE void jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_pa STATIC_INLINE void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **bpart, size_t world) JL_NOTSAFEPOINT { while (1) { - if (!jl_bkind_is_some_import((*bpart)->kind)) + if (!jl_bkind_is_some_import(jl_binding_kind(*bpart))) return; *bnd = (jl_binding_t*)(*bpart)->restriction; *bpart = jl_get_binding_partition(*bnd, world); @@ -974,7 +976,7 @@ STATIC_INLINE void jl_walk_binding_inplace_all(jl_binding_t **bnd, jl_binding_pa while (1) { if (!(*bpart)) return; - if (!jl_bkind_is_some_import((*bpart)->kind)) + if (!jl_bkind_is_some_import(jl_binding_kind(*bpart))) return; *bnd = (jl_binding_t*)(*bpart)->restriction; *bpart = jl_get_binding_partition_all(*bnd, min_world, max_world); diff --git a/src/method.c b/src/method.c index 48edd8071e53d..e4d44960018f3 100644 --- a/src/method.c +++ b/src/method.c @@ -1061,10 +1061,10 @@ JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_module_t *mod, jl_sym_t *name) jl_binding_t *b = jl_get_binding_for_method_def(mod, name, new_world); jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); jl_value_t *gf = NULL; - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (!jl_bkind_is_some_guard(kind) && kind != BINDING_KIND_DECLARED && kind != BINDING_KIND_IMPLICIT) { jl_walk_binding_inplace(&b, &bpart, new_world); - if (jl_bkind_is_some_constant(bpart->kind)) { + if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { gf = bpart->restriction; JL_GC_PROMISE_ROOTED(gf); jl_check_gf(gf, b->globalref->name); diff --git a/src/module.c b/src/module.c index 8adc99a5380d5..901561a9fb93f 100644 --- a/src/module.c +++ b/src/module.c @@ -19,7 +19,7 @@ static jl_binding_partition_t *new_binding_partition(void) { jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_gc_alloc(jl_current_task->ptls, sizeof(jl_binding_partition_t), jl_binding_partition_type); bpart->restriction = NULL; - bpart->kind = BINDING_KIND_GUARD; + bpart->kind = (size_t)BINDING_KIND_GUARD; bpart->min_world = 0; jl_atomic_store_relaxed(&bpart->max_world, (size_t)-1); jl_atomic_store_relaxed(&bpart->next, NULL); @@ -37,8 +37,8 @@ static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_ return 1; jl_walk_binding_inplace(&ownerb, &owner, world); jl_walk_binding_inplace(&alias, &alias_bpart, world); - if (jl_bkind_is_some_constant(owner->kind) && - jl_bkind_is_some_constant(alias_bpart->kind) && + if (jl_bkind_is_some_constant(jl_binding_kind(owner)) && + jl_bkind_is_some_constant(jl_binding_kind(alias_bpart)) && owner->restriction && alias_bpart->restriction == owner->restriction) return 1; @@ -90,7 +90,7 @@ void jl_check_new_binding_implicit( 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 (tempb != NULL) { if (data.min_world > min_world) min_world = data.min_world; if (data.max_world < min_world) @@ -105,6 +105,9 @@ void jl_check_new_binding_implicit( if (tempbmax_world < max_world) max_world = tempbmax_world; + if ((tempbpart->kind & BINDING_FLAG_EXPORTED) == 0) + continue; + if (impb) { if (tempb->deprecated) continue; @@ -179,6 +182,8 @@ STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_(jl_binding_t *b jl_atomic_store_relaxed(&new_bpart->max_world, max_world); JL_GC_PROMISE_ROOTED(new_bpart); // TODO: Analyzer doesn't understand MAYBE_UNROOTED properly jl_check_new_binding_implicit(new_bpart, b, st, world); + if (bpart && (bpart->kind & BINDING_FLAG_EXPORTED)) + new_bpart->kind |= BINDING_FLAG_EXPORTED; if (jl_atomic_cmpswap(insert, &bpart, new_bpart)) { jl_gc_wb(parent, new_bpart); return new_bpart; @@ -260,14 +265,15 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, enum jl_partition_kind constant_kind, size_t new_world) { - JL_GC_PUSH1(&val); + jl_binding_partition_t *new_prev_bpart = NULL; + JL_GC_PUSH2(&val, &new_prev_bpart); 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); while (!new_bpart) { - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_constant(kind)) { if (!val) { new_bpart = bpart; @@ -287,7 +293,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( jl_symbol_name(mod->name), jl_symbol_name(var)); } if (bpart->min_world == new_world) { - bpart->kind = constant_kind; + bpart->kind = constant_kind | (bpart->kind & 0xf0); bpart->restriction = val; if (val) jl_gc_wb(bpart, val); @@ -301,7 +307,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( // declared const, global, or imported. jl_binding_partition_t *prev_bpart = bpart; for (;;) { - enum jl_partition_kind prev_kind = prev_bpart->kind; + enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart); if (jl_bkind_is_some_constant(prev_kind) || prev_kind == BINDING_KIND_GLOBAL || (jl_bkind_is_some_import(prev_kind))) { need_backdate = 0; @@ -312,16 +318,30 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( prev_bpart = jl_get_binding_partition(b, prev_bpart->min_world - 1); } } - // If backdate is required, create one new binding partition to cover - // the entire backdate range. + // 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(); - backdate_bpart->kind = BINDING_KIND_BACKDATED_CONST; - backdate_bpart->restriction = val; - jl_gc_wb_fresh(backdate_bpart, val); - jl_atomic_store_relaxed(&backdate_bpart->max_world, new_world - 1); - jl_atomic_store_release(&new_bpart->next, backdate_bpart); - jl_gc_wb(new_bpart, backdate_bpart); + new_prev_bpart = backdate_bpart; + while (1) { + backdate_bpart->kind = (size_t)BINDING_KIND_BACKDATED_CONST | (prev_bpart->kind & 0xf0); + backdate_bpart->restriction = val; + backdate_bpart->min_world = 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); } } JL_GC_POP(); @@ -454,7 +474,6 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) jl_atomic_store_relaxed(&b->partitions, NULL); b->globalref = NULL; b->backedges = NULL; - b->exportp = 0; b->publicp = 0; b->deprecated = 0; b->did_print_backdate_admonition = 0; @@ -500,7 +519,7 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym JL_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module_t *m, jl_sym_t *s) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); 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" @@ -560,7 +579,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) { 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); - enum jl_partition_kind kind = bpart->kind; + 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)) { @@ -575,7 +594,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) { 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); - enum jl_partition_kind kind = bpart->kind; + 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)) { @@ -590,7 +609,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b) { 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); - enum jl_partition_kind kind = bpart->kind; + 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)) @@ -611,7 +630,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved_and_const(jl_binding_t size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); if (bpart->min_world > jl_current_task->world_age || jl_current_task->world_age > max_world) return NULL; - enum jl_partition_kind kind = bpart->kind; + 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)) @@ -632,7 +651,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved(jl_binding_t *b) size_t max_world = jl_atomic_load_relaxed(&bpart->max_world); if (bpart->min_world > jl_current_task->world_age || jl_current_task->world_age > max_world) return NULL; - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_guard(kind)) return NULL; if (jl_bkind_is_some_import(kind)) @@ -658,7 +677,7 @@ 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, new_world); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (kind == BINDING_KIND_GLOBAL || kind == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(kind)) return b; if (jl_bkind_is_some_guard(kind)) { @@ -668,7 +687,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ jl_binding_t *ownerb = b; jl_walk_binding_inplace(&ownerb, &bpart, new_world); jl_value_t *f = NULL; - if (jl_bkind_is_some_constant(bpart->kind)) + if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) f = bpart->restriction; if (f == NULL) { if (kind == BINDING_KIND_IMPLICIT) { @@ -722,7 +741,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ 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 (jl_bkind_is_some_import(bpart->kind)) { + if (jl_bkind_is_some_import(jl_binding_kind(bpart))) { return ((jl_binding_t*)bpart->restriction)->globalref->mod; } return m; @@ -738,9 +757,9 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) if (b == NULL) return jl_nothing; jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(bpart->kind)) + if (jl_bkind_is_some_guard(jl_binding_kind(bpart))) return jl_nothing; - if (jl_bkind_is_some_constant(bpart->kind)) { + if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { // TODO: We would like to return the type of the constant, but // currently code relies on this returning any to bypass conversion // before an attempted assignment to a constant. @@ -779,7 +798,7 @@ JL_DLLEXPORT int jl_is_imported(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); - return b && bpart->kind == BINDING_KIND_IMPORTED; + return b && jl_binding_kind(bpart) == BINDING_KIND_IMPORTED; } extern const char *jl_filename; @@ -864,7 +883,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_binding_partition_t *ownerbpart = bpart; jl_walk_binding_inplace(&ownerb, &ownerbpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(ownerbpart->kind)) { + if (jl_bkind_is_some_guard(jl_binding_kind(ownerbpart))) { jl_printf(JL_STDERR, "WARNING: Imported binding %s.%s was undeclared at import time during import to %s.\n", jl_symbol_name(from->name), jl_symbol_name(s), @@ -879,7 +898,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, 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); - enum jl_partition_kind btokind = btopart->kind; + enum jl_partition_kind btokind = jl_binding_kind(btopart); if (btokind == BINDING_KIND_GUARD || btokind == BINDING_KIND_IMPLICIT || btokind == BINDING_KIND_FAILED) { @@ -980,12 +999,13 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; - if (b->exportp) { + jl_binding_partition_t *frombpart = jl_get_binding_partition(b, new_world); + if (frombpart->kind & BINDING_FLAG_EXPORTED) { 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, new_world); - enum jl_partition_kind kind = tobpart->kind; + enum jl_partition_kind kind = jl_binding_kind(tobpart); if (kind == BINDING_KIND_IMPLICIT || jl_bkind_is_some_guard(kind)) { jl_replace_binding_locked(tob, tobpart, NULL, BINDING_KIND_IMPLICIT_RECOMPUTE, new_world); } @@ -1018,17 +1038,25 @@ JL_DLLEXPORT jl_value_t *jl_get_module_binding_or_nothing(jl_module_t *m, jl_sym JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) { jl_binding_t *b = jl_get_module_binding(from, s, 1); + JL_LOCK(&world_counter_lock); + size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world); + int was_exported = (bpart->kind & BINDING_FLAG_EXPORTED) != 0; if (b->publicp) { // check for conflicting declarations - if (b->exportp && !exported) + if (was_exported && !exported) jl_errorf("cannot declare %s.%s public; it is already declared exported", jl_symbol_name(from->name), jl_symbol_name(s)); - if (!b->exportp && exported) + if (!was_exported && exported) jl_errorf("cannot declare %s.%s exported; it is already declared public", jl_symbol_name(from->name), jl_symbol_name(s)); } b->publicp = 1; - b->exportp |= exported; + if (was_exported != exported) { + jl_replace_binding_locked2(b, bpart, bpart->restriction, bpart->kind | BINDING_FLAG_EXPORTED, new_world); + jl_atomic_store_release(&jl_world_counter, new_world); + } + JL_UNLOCK(&world_counter_lock); } JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // unlike most queries here, this is currently seq_cst @@ -1040,14 +1068,14 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u if (!bpart) return 0; if (!allow_import) { - if (!bpart || jl_bkind_is_some_import(bpart->kind)) + 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); } - if (jl_bkind_is_some_guard(bpart->kind)) + if (jl_bkind_is_some_guard(jl_binding_kind(bpart))) return 0; - if (jl_bkind_is_defined_constant(bpart->kind)) { + if (jl_bkind_is_defined_constant(jl_binding_kind(bpart))) { // N.B.: No backdated check for isdefined return 1; } @@ -1058,13 +1086,14 @@ JL_DLLEXPORT int jl_defines_or_exports_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); - return b && (b->exportp || bpart->kind == BINDING_KIND_GLOBAL); + return b && ((bpart->kind & BINDING_FLAG_EXPORTED) || jl_binding_kind(bpart) == BINDING_KIND_GLOBAL); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_module_binding(m, var, 0); - return b && b->exportp; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + return b && (bpart->kind & BINDING_FLAG_EXPORTED); } JL_DLLEXPORT int jl_module_public_p(jl_module_t *m, jl_sym_t *var) @@ -1171,7 +1200,7 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var jl_binding_partition_t *bpart = jl_get_binding_partition(bp, jl_current_task->world_age); bpart->min_world = 0; jl_atomic_store_release(&bpart->max_world, ~(size_t)0); - bpart->kind = BINDING_KIND_CONST; + bpart->kind = BINDING_KIND_CONST | (bpart->kind & 0xf0); bpart->restriction = val; jl_gc_wb(bpart, val); } @@ -1228,14 +1257,24 @@ JL_DLLEXPORT void jl_maybe_add_binding_backedge(jl_globalref_t *gr, jl_module_t 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) +{ + // Copy flags from old bpart + return jl_replace_binding_locked2(b, old_bpart, restriction_val, (size_t)kind | (size_t)(old_bpart->kind & 0xf0), + new_world); +} + +JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked2(jl_binding_t *b, + jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, size_t kind, size_t new_world) { 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(); + JL_GC_PUSH1(&new_bpart); new_bpart->min_world = new_world; - if (kind == BINDING_KIND_IMPLICIT_RECOMPUTE) { + if ((kind & 0x0f) == BINDING_KIND_IMPLICIT_RECOMPUTE) { assert(!restriction_val); jl_check_new_binding_implicit(new_bpart /* callee rooted */, b, NULL, new_world); + new_bpart->kind |= kind & 0xf0; } else { new_bpart->kind = kind; @@ -1247,6 +1286,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b, jl_atomic_store_release(&b->partitions, new_bpart); jl_gc_wb(b, new_bpart); + JL_GC_POP(); if (jl_typeinf_world != 1) { jl_task_t *ct = jl_current_task; @@ -1286,7 +1326,7 @@ JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) 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(bpart->kind); + return jl_bkind_is_some_constant(jl_binding_kind(bpart)); } JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) @@ -1296,7 +1336,7 @@ JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) 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->kind == BINDING_KIND_GUARD) { + if (jl_binding_kind(bpart) == BINDING_KIND_GUARD) { // Already guard return; } @@ -1311,7 +1351,7 @@ 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); jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - return b && jl_bkind_is_some_constant(bpart->kind); + return b && jl_bkind_is_some_constant(jl_binding_kind(bpart)); } // set the deprecated flag for a binding: @@ -1362,7 +1402,7 @@ 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); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (jl_bkind_is_some_constant(kind)) { jl_value_t *old = bpart->restriction; JL_GC_PROMISE_ROOTED(old); @@ -1412,7 +1452,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, j JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); assert(!jl_bkind_is_some_guard(kind) && !jl_bkind_is_some_import(kind)); if (jl_bkind_is_some_constant(kind)) jl_errorf("invalid assignment to constant %s.%s", @@ -1464,7 +1504,7 @@ void append_module_names(jl_array_t* a, jl_module_t *m, int all, int imported, i int hidden = jl_symbol_name(asname)[0]=='#'; int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym)); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (((b->publicp) || (imported && (kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_IMPORTED)) || (usings && kind == BINDING_KIND_EXPLICIT) || @@ -1481,7 +1521,8 @@ void append_exported_names(jl_array_t* a, jl_module_t *m, int all) jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); if ((void*)b == jl_nothing) break; - if (b->exportp && (all || !b->deprecated)) + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + if ((bpart->kind & BINDING_FLAG_EXPORTED) && (all || !b->deprecated)) _append_symbol_to_bindings_array(a, b->globalref->name); } } @@ -1551,7 +1592,7 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) if ((void*)b == jl_nothing) break; jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - if (bpart->kind == BINDING_KIND_IMPLICIT) { + if (jl_binding_kind(bpart) == BINDING_KIND_IMPLICIT) { jl_atomic_store_relaxed(&b->partitions, NULL); } } diff --git a/src/staticdata.c b/src/staticdata.c index 745b72026b9af..3e916a93c6382 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -3542,7 +3542,8 @@ static void jl_validate_binding_partition(jl_binding_t *b, jl_binding_partition_ if (jl_atomic_load_relaxed(&bpart->max_world) != ~(size_t)0) return; - enum jl_partition_kind kind = bpart->kind; + size_t raw_kind = bpart->kind; + enum jl_partition_kind kind = (enum jl_partition_kind)(raw_kind & 0x0f); if (!jl_bkind_is_some_import(kind)) return; jl_binding_t *imported_binding = (jl_binding_t*)bpart->restriction; @@ -3551,6 +3552,7 @@ static void jl_validate_binding_partition(jl_binding_t *b, jl_binding_partition_ return; if (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_FAILED) { jl_check_new_binding_implicit(bpart, b, NULL, jl_atomic_load_relaxed(&jl_world_counter)); + bpart->kind |= (raw_kind & 0xf0); if (bpart->min_world > jl_require_world) goto invalidated; } @@ -3581,7 +3583,7 @@ static void jl_validate_binding_partition(jl_binding_t *b, jl_binding_partition_ jl_validate_binding_partition(bedge, jl_atomic_load_relaxed(&bedge->partitions), mod_idx); } } - if (b->exportp) { + if (bpart->kind & BINDING_FLAG_EXPORTED) { jl_module_t *mod = b->globalref->mod; jl_sym_t *name = b->globalref->name; JL_LOCK(&mod->lock); diff --git a/src/toplevel.c b/src/toplevel.c index 5b5db28f113e8..d931b59178ce6 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -307,7 +307,7 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in global_type = (jl_value_t*)jl_any_type; while (1) { bpart = jl_get_binding_partition(b, new_world); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (kind != BINDING_KIND_GLOBAL) { if (jl_bkind_is_some_guard(kind) || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_IMPLICIT) { if (kind == new_kind) { @@ -317,7 +317,7 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type, in } check_safe_newbinding(gm, gs); if (bpart->min_world == new_world) { - bpart->kind = new_kind; + bpart->kind = new_kind | (bpart->kind & 0xf0); bpart->restriction = global_type; if (global_type) jl_gc_wb(bpart, global_type); @@ -655,11 +655,11 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym // TODO: this is a bit race-y with what error message we might print jl_binding_t *b = jl_get_module_binding(m, name, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - enum jl_partition_kind kind = bpart->kind; + enum jl_partition_kind kind = jl_binding_kind(bpart); if (kind != BINDING_KIND_GUARD && kind != BINDING_KIND_FAILED && kind != BINDING_KIND_DECLARED && kind != BINDING_KIND_IMPLICIT) { // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (bpart->kind == BINDING_KIND_CONST || bpart->kind == BINDING_KIND_BACKDATED_CONST || bpart->kind == BINDING_KIND_CONST_IMPORT) { + if (jl_binding_kind(bpart) == BINDING_KIND_CONST || jl_binding_kind(bpart) == BINDING_KIND_BACKDATED_CONST || jl_binding_kind(bpart) == BINDING_KIND_CONST_IMPORT) { // Already declared (e.g. on another thread) or imported. if (bpart->restriction == (jl_value_t*)import) return; diff --git a/test/rebinding.jl b/test/rebinding.jl index 23feb8ded0e56..2f343fd86eb9a 100644 --- a/test/rebinding.jl +++ b/test/rebinding.jl @@ -193,10 +193,31 @@ module RebindingPrecompile Core.eval(Export2, :(const import_me2 = 22)) end invokelatest() do - # Currently broken - # @test_throws UndefVarError ImportTest.f_use_binding2() + @test_throws UndefVarError ImportTest.f_use_binding2() end end finish_precompile_test!() end + +module Regression + using Test + + # Issue #57377 + module GeoParams57377 + module B + using ...GeoParams57377 + export S + struct S end + module C + using ..GeoParams57377 + h() = S() + x -> nothing + end + end + + using .B + export S + end + @test GeoParams57377.B.C.h() == GeoParams57377.B.C.S() +end From 0a7da082d262fde555b407e956f1542b2c5930c6 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 15 Feb 2025 03:13:17 -0500 Subject: [PATCH 18/21] Compiler: Fix check for IRShow definedness (#57420) Should fix the error part of #57329. I also cannot reproduce the underlying assertion error on master, so that was likely fixed in one of the other PRs. Closes #57329 as a result, but of course there could be other issues with the same symptoms, which can get their own issues. (cherry picked from commit 88b292d11027c3c8b2cd4b66e1f348affd24d761) --- Compiler/src/ssair/verify.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Compiler/src/ssair/verify.jl b/Compiler/src/ssair/verify.jl index 974690885a2e4..6b9863a87ec05 100644 --- a/Compiler/src/ssair/verify.jl +++ b/Compiler/src/ssair/verify.jl @@ -1,7 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +irshow_was_loaded() = invokelatest(isdefined, Compiler.IRShow, :debuginfo_firstline) function maybe_show_ir(ir::IRCode) - if isdefined(Core, :Main) && isdefined(Core.Main, :Base) + if irshow_was_loaded() # ensure we use I/O that does not yield, as this gets called during compilation invokelatest(Core.Main.Base.show, Core.stdout, "text/plain", ir) else @@ -104,15 +105,16 @@ function count_int(val::Int, arr::Vector{Int}) n end +_debuginfo_firstline(debuginfo::Union{DebugInfo,DebugInfoStream}) = IRShow.debuginfo_firstline(debuginfo) function verify_ir(ir::IRCode, print::Bool=true, allow_frontend_forms::Bool=false, 𝕃ₒ::AbstractLattice = SimpleInferenceLattice.instance, mi::Union{Nothing,MethodInstance}=nothing) function raise_error() error_args = Any["IR verification failed."] - if isdefined(Core, :Main) && isdefined(Core.Main, :Base) + if irshow_was_loaded() # ensure we use I/O that does not yield, as this gets called during compilation - firstline = invokelatest(IRShow.debuginfo_firstline, ir.debuginfo) + firstline = invokelatest(_debuginfo_firstline, ir.debuginfo) else firstline = nothing end From 41e0464bbf4590a3fd48f3f2547f92a3e015ed24 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 15 Feb 2025 12:52:42 -0500 Subject: [PATCH 19/21] fix `(-Inf)^-1` inconsistency (#55875) fixes https://github.com/JuliaLang/julia/issues/55831 (cherry picked from commit 2364719a3b1e6ae360857a1ea88107d958cdcbe0) --- base/math.jl | 3 ++- test/math.jl | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/base/math.jl b/base/math.jl index 650fc6bc0cef0..0e5f9dd41bdca 100644 --- a/base/math.jl +++ b/base/math.jl @@ -1217,7 +1217,8 @@ end # this method is only reliable for -2^20 < n < 2^20 (cf. #53881 #53886) @assume_effects :terminates_locally @noinline function pow_body(x::Float64, n::Integer) y = 1.0 - xnlo = ynlo = 0.0 + xnlo = -0.0 + ynlo = 0.0 n == 3 && return x*x*x # keep compatibility with literal_pow if n < 0 rx = inv(x) diff --git a/test/math.jl b/test/math.jl index 7070fe63ba931..d9cfd411124ed 100644 --- a/test/math.jl +++ b/test/math.jl @@ -1505,7 +1505,19 @@ end @test E^n == Inf @test E^float(n) == Inf - # #55633 + # issue #55831 + @testset "literal pow zero sign" begin + @testset "T: $T" for T ∈ (Float16, Float32, Float64, BigFloat) + @testset "literal `-1`" begin + @test -0.0 === Float64(T(-Inf)^-1) + end + @testset "`Int(-1)`" begin + @test -0.0 === Float64(T(-Inf)^Int(-1)) + end + end + end + + # issue #55633 struct Issue55633_1 <: Number end struct Issue55633_3 <: Number end struct Issue55633_9 <: Number end From 423cb563ad6319a5b0d6eb027266ec0cff367ba0 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 15 Feb 2025 13:34:58 -0500 Subject: [PATCH 20/21] bpart: Ignore guard bindings for ambiguity purposes (#57406) This makes non-guard bindings stronger than guard bindings for ambiguity purposes. Note that both of these are yet stronger than deprecated bindings, so if there's a "non-guard deprecated" binding and a "guard non-deprecated" binding, the latter will win and the access will be UndefVarError. I think this is the closest to the 1.11 behavior without relying on resolvedness. Fixes #57404 This PR is against #57405 just because that PR touches the common interface, but is conceptually independent. (cherry picked from commit a371899c38a60e21c2727d1f50de9716be181d7d) --- src/module.c | 10 ++++++++++ test/syntax.jl | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/module.c b/src/module.c index 901561a9fb93f..604e0b8e659fc 100644 --- a/src/module.c +++ b/src/module.c @@ -65,6 +65,7 @@ void jl_check_new_binding_implicit( jl_binding_t *deprecated_impb = NULL; jl_binding_t *impb = NULL; + jl_binding_partition_t *impbpart = NULL; size_t min_world = new_bpart->min_world; size_t max_world = jl_atomic_load_relaxed(&new_bpart->max_world); @@ -111,6 +112,14 @@ void jl_check_new_binding_implicit( if (impb) { if (tempb->deprecated) continue; + if (jl_binding_kind(tempbpart) == BINDING_KIND_GUARD && + jl_binding_kind(impbpart) != BINDING_KIND_GUARD) + continue; + if (jl_binding_kind(impbpart) == BINDING_KIND_GUARD) { + impb = tempb; + impbpart = tempbpart; + continue; + } if (eq_bindings(tempbpart, impb, world)) continue; // Binding is ambiguous @@ -132,6 +141,7 @@ void jl_check_new_binding_implicit( } else { impb = tempb; + impbpart = tempbpart; } } } diff --git a/test/syntax.jl b/test/syntax.jl index f3288168f3a3e..366b7f5e63679 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -4089,3 +4089,17 @@ abstract type A57267{S, T} end B57267{S} = A57267{S, 1} const C57267 = B57267 end + +# #57404 - Binding ambiguity resolution ignores guard bindings +module Ambig57404 + module A + export S + end + using .A + module B + const S = 1 + export S + end + using .B +end +@test Ambig57404.S == 1 From 5da257d9a3120d4d6518031f7a227090824776d6 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sat, 15 Feb 2025 18:51:44 +0100 Subject: [PATCH 21/21] Allow for :foreigncall to transition to GC safe automatically (#49933) This has been bouncing around as a idea for a while. One of the challenges around time-to-safepoint has been Julia code that is calling libraries. Since foreign code will not include safepoints we see increased latency when one thread is running a foreign-call and another wants to trigger GC. The open design question here is: - Do we expose this as an option the user must "opt-in", e.g. by using a keyword arg to `@ccall` or a specific calling-convetion. - Or do we turn this on for all ccall, except for Julia runtime calls. There is relativly little code outside the Julia runtime that needs to be "GC unsafe", exception are programs that directly use the Julia C-API. Incidentially `jl_adopt_thread` and `@cfunction`/`@ccallable` do the right thing and transition to "GC unsafe", regardless of what state the thread currently is in. I still need to figure out how to reliably detect Julia runtime calls, but I think we can switch all other calls to "GC safe". We should also consider optimizations that mark large regions of code without Julia runtime interactions as "GC safe" in particular numeric for-loops. Closes #57057 --------- Co-authored-by: Gabriel Baraldi (cherry picked from commit 85458a04f0eab6c70d9e9a5b575d67c46a65f991) --- Compiler/src/abstractinterpretation.jl | 2 +- Compiler/src/validation.jl | 2 +- NEWS.md | 1 + base/c.jl | 50 ++++++++++++++++++++++---- base/meta.jl | 2 +- base/strings/string.jl | 2 +- doc/src/devdocs/ast.md | 4 +-- src/ccall.cpp | 21 +++++++---- src/codegen.cpp | 4 +-- src/llvm-codegen-shared.h | 14 +++----- src/llvm-late-gc-lowering.cpp | 49 ++++++++++++++++++++----- src/llvm-pass-helpers.cpp | 21 +++++++++++ src/llvm-pass-helpers.h | 5 ++- src/llvm-ptls.cpp | 7 +++- src/method.c | 5 +-- test/ccall.jl | 15 +++++++- 16 files changed, 160 insertions(+), 44 deletions(-) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 8a7e8aee715a6..73949068ce8c6 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3409,7 +3409,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, sstate: abstract_eval_value(interp, x, sstate, sv) end cconv = e.args[5] - if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16})) + if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16, Bool})) override = decode_effects_override(v[2]) effects = override_effects(effects, override) end diff --git a/Compiler/src/validation.jl b/Compiler/src/validation.jl index 9bde405a49956..4f9362e97b30d 100644 --- a/Compiler/src/validation.jl +++ b/Compiler/src/validation.jl @@ -23,7 +23,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :meta => 0:typemax(Int), :global => 1:1, :globaldecl => 1:2, - :foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects), args..., roots... + :foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects, gc_safe), args..., roots... :cfunction => 5:5, :isdefined => 1:2, :code_coverage_effect => 0:0, diff --git a/NEWS.md b/NEWS.md index 3a29ba1a10eac..30782942fd612 100644 --- a/NEWS.md +++ b/NEWS.md @@ -25,6 +25,7 @@ New language features * Support for Unicode 16 ([#56925]). * `Threads.@spawn` now takes a `:samepool` argument to specify the same threadpool as the caller. `Threads.@spawn :samepool foo()` which is shorthand for `Threads.@spawn Threads.threadpool() foo()` ([#57109]). +* The `@ccall` macro can now take a `gc_safe` argument, that if set to true allows the runtime to run garbage collection concurrently to the `ccall` Language changes ---------------- diff --git a/base/c.jl b/base/c.jl index c1b34579e0a0b..77ef631e295a3 100644 --- a/base/c.jl +++ b/base/c.jl @@ -268,7 +268,31 @@ The above input outputs this: (:printf, :Cvoid, [:Cstring, :Cuint], ["%d", :value]) """ -function ccall_macro_parse(expr::Expr) +function ccall_macro_parse(exprs) + gc_safe = false + expr = nothing + if exprs isa Expr + expr = exprs + elseif length(exprs) == 1 + expr = exprs[1] + elseif length(exprs) == 2 + gc_expr = exprs[1] + expr = exprs[2] + if gc_expr.head == :(=) && gc_expr.args[1] == :gc_safe + if gc_expr.args[2] == true + gc_safe = true + elseif gc_expr.args[2] == false + gc_safe = false + else + throw(ArgumentError("gc_safe must be true or false")) + end + else + throw(ArgumentError("@ccall option must be `gc_safe=true` or `gc_safe=false`")) + end + else + throw(ArgumentError("@ccall needs a function signature with a return type")) + end + # setup and check for errors if !isexpr(expr, :(::)) throw(ArgumentError("@ccall needs a function signature with a return type")) @@ -328,12 +352,11 @@ function ccall_macro_parse(expr::Expr) pusharg!(a) end end - - return func, rettype, types, args, nreq + return func, rettype, types, args, gc_safe, nreq end -function ccall_macro_lower(convention, func, rettype, types, args, nreq) +function ccall_macro_lower(convention, func, rettype, types, args, gc_safe, nreq) statements = [] # if interpolation was used, ensure the value is a function pointer at runtime. @@ -351,9 +374,15 @@ function ccall_macro_lower(convention, func, rettype, types, args, nreq) else func = esc(func) end + cconv = nothing + if convention isa Tuple + cconv = Expr(:cconv, (convention..., gc_safe), nreq) + else + cconv = Expr(:cconv, (convention, UInt16(0), gc_safe), nreq) + end return Expr(:block, statements..., - Expr(:call, :ccall, func, Expr(:cconv, convention, nreq), esc(rettype), + Expr(:call, :ccall, func, cconv, esc(rettype), Expr(:tuple, map(esc, types)...), map(esc, args)...)) end @@ -404,9 +433,16 @@ Example using an external library: The string literal could also be used directly before the function name, if desired `"libglib-2.0".g_uri_escape_string(...` + +It's possible to declare the ccall as `gc_safe` by using the `gc_safe = true` option: + @ccall gc_safe=true strlen(s::Cstring)::Csize_t +This allows the garbage collector to run concurrently with the ccall, which can be useful whenever +the `ccall` may block outside of julia. +WARNING: This option should be used with caution, as it can lead to undefined behavior if the ccall +calls back into the julia runtime. (`@cfunction`/`@ccallables` are safe however) """ -macro ccall(expr) - return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) +macro ccall(exprs...) + return ccall_macro_lower((:ccall), ccall_macro_parse(exprs)...) end macro ccall_effects(effects::UInt16, expr) diff --git a/base/meta.jl b/base/meta.jl index 36875b8e2c625..4807b910c494a 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -427,7 +427,7 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any}, elseif i == 4 @assert isa(x.args[4], Int) elseif i == 5 - @assert isa((x.args[5]::QuoteNode).value, Union{Symbol, Tuple{Symbol, UInt8}}) + @assert isa((x.args[5]::QuoteNode).value, Union{Symbol, Tuple{Symbol, UInt16, Bool}}) else x.args[i] = _partially_inline!(x.args[i], slot_replacements, type_signature, static_param_values, diff --git a/base/strings/string.jl b/base/strings/string.jl index 9f3c3d00e4b81..79ec12d11cb94 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -107,7 +107,7 @@ end # but the macro is not available at this time in bootstrap, so we write it manually. const _string_n_override = 0x04ee @eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, - :(Core.svec(Csize_t)), 1, QuoteNode((:ccall, _string_n_override)), :(convert(Csize_t, n)))) + :(Core.svec(Csize_t)), 1, QuoteNode((:ccall, _string_n_override, false)), :(convert(Csize_t, n)))) """ String(s::AbstractString) diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index fe63dfe35edac..706dfc34875fa 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -498,9 +498,9 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form. The number of required arguments for a varargs function definition. - * `args[5]::QuoteNode{<:Union{Symbol,Tuple{Symbol,UInt16}}`: calling convention + * `args[5]::QuoteNode{<:Union{Symbol,Tuple{Symbol,UInt16}, Tuple{Symbol,UInt16,Bool}}`: calling convention - The calling convention for the call, optionally with effects. + The calling convention for the call, optionally with effects, and `gc_safe` (safe to execute concurrently to GC.). * `args[6:5+length(args[3])]` : arguments diff --git a/src/ccall.cpp b/src/ccall.cpp index 6c03f9532d9a8..c35979eb85b1d 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -1134,6 +1134,7 @@ class function_sig_t { AttributeList attributes; // vector of function call site attributes Type *lrt; // input parameter of the llvm return type (from julia_struct_to_llvm) bool retboxed; // input parameter indicating whether lrt is jl_value_t* + bool gc_safe; // input parameter indicating whether the call is safe to execute concurrently to GC Type *prt; // out parameter of the llvm return type for the function signature int sret; // out parameter for indicating whether return value has been moved to the first argument position std::string err_msg; @@ -1146,8 +1147,8 @@ class function_sig_t { size_t nreqargs; // number of required arguments in ccall function definition jl_codegen_params_t *ctx; - function_sig_t(const char *fname, Type *lrt, jl_value_t *rt, bool retboxed, jl_svec_t *at, jl_unionall_t *unionall_env, size_t nreqargs, CallingConv::ID cc, bool llvmcall, jl_codegen_params_t *ctx) - : lrt(lrt), retboxed(retboxed), + function_sig_t(const char *fname, Type *lrt, jl_value_t *rt, bool retboxed, bool gc_safe, jl_svec_t *at, jl_unionall_t *unionall_env, size_t nreqargs, CallingConv::ID cc, bool llvmcall, jl_codegen_params_t *ctx) + : lrt(lrt), retboxed(retboxed), gc_safe(gc_safe), prt(NULL), sret(0), cc(cc), llvmcall(llvmcall), at(at), rt(rt), unionall_env(unionall_env), nccallargs(jl_svec_len(at)), nreqargs(nreqargs), @@ -1295,6 +1296,7 @@ std::string generate_func_sig(const char *fname) RetAttrs = RetAttrs.addAttribute(LLVMCtx, Attribute::NonNull); if (rt == jl_bottom_type) FnAttrs = FnAttrs.addAttribute(LLVMCtx, Attribute::NoReturn); + assert(attributes.isEmpty()); attributes = AttributeList::get(LLVMCtx, FnAttrs, RetAttrs, paramattrs); return ""; @@ -1412,7 +1414,7 @@ static const std::string verify_ccall_sig(jl_value_t *&rt, jl_value_t *at, const int fc_args_start = 6; -// Expr(:foreigncall, pointer, rettype, (argtypes...), nreq, [cconv | (cconv, effects)], args..., roots...) +// Expr(:foreigncall, pointer, rettype, (argtypes...), nreq, gc_safe, [cconv | (cconv, effects)], args..., roots...) static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) { JL_NARGSV(ccall, 5); @@ -1424,11 +1426,13 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) assert(jl_is_quotenode(args[5])); jl_value_t *jlcc = jl_quotenode_value(args[5]); jl_sym_t *cc_sym = NULL; + bool gc_safe = false; if (jl_is_symbol(jlcc)) { cc_sym = (jl_sym_t*)jlcc; } else if (jl_is_tuple(jlcc)) { cc_sym = (jl_sym_t*)jl_get_nth_field_noalloc(jlcc, 0); + gc_safe = jl_unbox_bool(jl_get_nth_field_checked(jlcc, 2)); } assert(jl_is_symbol(cc_sym)); native_sym_arg_t symarg = {}; @@ -1547,7 +1551,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } if (rt != args[2] && rt != (jl_value_t*)jl_any_type) jl_temporary_root(ctx, rt); - function_sig_t sig("ccall", lrt, rt, retboxed, + function_sig_t sig("ccall", lrt, rt, retboxed, gc_safe, (jl_svec_t*)at, unionall, nreqargs, cc, llvmcall, &ctx.emission_context); for (size_t i = 0; i < nccallargs; i++) { @@ -2158,11 +2162,16 @@ jl_cgval_t function_sig_t::emit_a_ccall( } } - OperandBundleDef OpBundle("jl_roots", gc_uses); + // Potentially we could drop `jl_roots(gc_uses)` in the presence of `gc-transition(gc_uses)` + SmallVector bundles; + if (!gc_uses.empty()) + bundles.push_back(OperandBundleDef("jl_roots", gc_uses)); + if (gc_safe) + bundles.push_back(OperandBundleDef("gc-transition", ArrayRef {})); // the actual call CallInst *ret = ctx.builder.CreateCall(functype, llvmf, argvals, - ArrayRef(&OpBundle, gc_uses.empty() ? 0 : 1)); + bundles); ((CallInst*)ret)->setAttributes(attributes); if (cc != CallingConv::C) diff --git a/src/codegen.cpp b/src/codegen.cpp index 7fbd9e9e7659c..2f5a12aad326d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8041,7 +8041,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con if (rt != declrt && rt != (jl_value_t*)jl_any_type) jl_temporary_root(ctx, rt); - function_sig_t sig("cfunction", lrt, rt, retboxed, argt, unionall_env, false, CallingConv::C, false, &ctx.emission_context); + function_sig_t sig("cfunction", lrt, rt, retboxed, false, argt, unionall_env, false, CallingConv::C, false, &ctx.emission_context); assert(sig.fargt.size() + sig.sret == sig.fargt_sig.size()); if (!sig.err_msg.empty()) { emit_error(ctx, sig.err_msg); @@ -8181,7 +8181,7 @@ const char *jl_generate_ccallable(Module *llvmmod, void *sysimg_handle, jl_value } jl_value_t *err; { // scope block for sig - function_sig_t sig("cfunction", lcrt, crt, toboxed, + function_sig_t sig("cfunction", lcrt, crt, toboxed, false, argtypes, NULL, false, CallingConv::C, false, ¶ms); if (sig.err_msg.empty()) { if (sysimg_handle) { diff --git a/src/llvm-codegen-shared.h b/src/llvm-codegen-shared.h index ff6f5a97299d7..d474fb4f61183 100644 --- a/src/llvm-codegen-shared.h +++ b/src/llvm-codegen-shared.h @@ -244,21 +244,17 @@ static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::T unsigned offset = offsetof(jl_tls_states_t, gc_state); Value *gc_state = builder.CreateConstInBoundsGEP1_32(T_int8, ptls, offset, "gc_state"); if (old_state == nullptr) { - old_state = builder.CreateLoad(T_int8, gc_state); + old_state = builder.CreateLoad(T_int8, gc_state, "old_state"); cast(old_state)->setOrdering(AtomicOrdering::Monotonic); } builder.CreateAlignedStore(state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); if (auto *C = dyn_cast(old_state)) - if (C->isZero()) - return old_state; - if (auto *C = dyn_cast(state)) - if (!C->isZero()) - return old_state; + if (auto *C2 = dyn_cast(state)) + if (C->getZExtValue() == C2->getZExtValue()) + return old_state; BasicBlock *passBB = BasicBlock::Create(builder.getContext(), "safepoint", builder.GetInsertBlock()->getParent()); BasicBlock *exitBB = BasicBlock::Create(builder.getContext(), "after_safepoint", builder.GetInsertBlock()->getParent()); - Constant *zero8 = ConstantInt::get(T_int8, 0); - builder.CreateCondBr(builder.CreateOr(builder.CreateICmpEQ(old_state, zero8), // if (!old_state || !state) - builder.CreateICmpEQ(state, zero8)), + builder.CreateCondBr(builder.CreateICmpEQ(old_state, state, "is_new_state"), // Safepoint whenever we change the GC state passBB, exitBB); builder.SetInsertPoint(passBB); MDNode *tbaa = get_tbaa_const(builder.getContext()); diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 2b9b918c4bd53..1b7551f33ebcd 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -2181,16 +2181,47 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { NewCall->copyMetadata(*CI); CI->replaceAllUsesWith(NewCall); UpdatePtrNumbering(CI, NewCall, S); - } else if (CI->arg_size() == CI->getNumOperands()) { - /* No operand bundle to lower */ - ++it; - continue; } else { - CallInst *NewCall = CallInst::Create(CI, None, CI); - NewCall->takeName(CI); - NewCall->copyMetadata(*CI); - CI->replaceAllUsesWith(NewCall); - UpdatePtrNumbering(CI, NewCall, S); + SmallVector bundles; + CI->getOperandBundlesAsDefs(bundles); + bool gc_transition = false; + for (auto &bundle: bundles) + if (bundle.getTag() == "gc-transition") + gc_transition = true; + + // In theory LLVM wants us to lower this using RewriteStatepointsForGC + if (gc_transition) { + // Insert the operations to switch to gc_safe if necessary. + IRBuilder<> builder(CI); + Value *pgcstack = getOrAddPGCstack(F); + assert(pgcstack); + // We dont use emit_state_set here because safepoints are unconditional for any code that reaches this + // We are basically guaranteed to go from gc_unsafe to gc_safe and back, and both transitions need a safepoint + // We also can't add any BBs here, so just avoiding the branches is good + Value *ptls = get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, pgcstack), tbaa_gcframe); + unsigned offset = offsetof(jl_tls_states_t, gc_state); + Value *gc_state = builder.CreateConstInBoundsGEP1_32(Type::getInt8Ty(builder.getContext()), ptls, offset, "gc_state"); + LoadInst *last_gc_state = builder.CreateAlignedLoad(Type::getInt8Ty(builder.getContext()), gc_state, Align(sizeof(void*))); + last_gc_state->setOrdering(AtomicOrdering::Monotonic); + builder.CreateAlignedStore(builder.getInt8(JL_GC_STATE_SAFE), gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); + MDNode *tbaa = get_tbaa_const(builder.getContext()); + emit_gc_safepoint(builder, T_size, ptls, tbaa, false); + builder.SetInsertPoint(CI->getNextNode()); + builder.CreateAlignedStore(last_gc_state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); + emit_gc_safepoint(builder, T_size, ptls, tbaa, false); + } + if (CI->arg_size() == CI->getNumOperands()) { + /* No operand bundle to lower */ + ++it; + continue; + } else { + // remove operand bundle + CallInst *NewCall = CallInst::Create(CI, None, CI); + NewCall->takeName(CI); + NewCall->copyMetadata(*CI); + CI->replaceAllUsesWith(NewCall); + UpdatePtrNumbering(CI, NewCall, S); + } } if (!CI->use_empty()) { CI->replaceAllUsesWith(UndefValue::get(CI->getType())); diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index ca25251040fb2..9d415d923ecb6 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -88,6 +88,27 @@ llvm::CallInst *JuliaPassContext::getPGCstack(llvm::Function &F) const return nullptr; } +llvm::CallInst *JuliaPassContext::getOrAddPGCstack(llvm::Function &F) +{ + if (pgcstack_getter || adoptthread_func) + for (auto &I : F.getEntryBlock()) { + if (CallInst *callInst = dyn_cast(&I)) { + Value *callee = callInst->getCalledOperand(); + if ((pgcstack_getter && callee == pgcstack_getter) || + (adoptthread_func && callee == adoptthread_func)) { + return callInst; + } + } + } + IRBuilder<> builder(&F.getEntryBlock().front()); + if (pgcstack_getter) + return builder.CreateCall(pgcstack_getter); + auto FT = FunctionType::get(PointerType::get(F.getContext(), 0), false); + auto F2 = Function::Create(FT, Function::ExternalLinkage, "julia.get_pgcstack", F.getParent()); + pgcstack_getter = F2; + return builder.CreateCall( F2); +} + llvm::Function *JuliaPassContext::getOrNull( const jl_intrinsics::IntrinsicDescription &desc) const { diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index d46f1f46634e6..ac08cda2d61e0 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -87,7 +87,10 @@ struct JuliaPassContext { // point of the given function, if there exists such a call. // Otherwise, `nullptr` is returned. llvm::CallInst *getPGCstack(llvm::Function &F) const; - + // Gets a call to the `julia.get_pgcstack' intrinsic in the entry + // point of the given function, if there exists such a call. + // Otherwise, creates a new call to the intrinsic + llvm::CallInst *getOrAddPGCstack(llvm::Function &F); // Gets the intrinsic or well-known function that conforms to // the given description if it exists in the module. If not, // `nullptr` is returned. diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index e36136859517a..15f5a5574a6d3 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -196,8 +196,13 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter, last_gc_state->addIncoming(prior, fastTerm->getParent()); for (auto &BB : *pgcstack->getParent()->getParent()) { if (isa(BB.getTerminator())) { + // Don't use emit_gc_safe_leave here, as that introduces a new BB while iterating BBs builder.SetInsertPoint(BB.getTerminator()); - emit_gc_unsafe_leave(builder, T_size, get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, phi), tbaa), last_gc_state, true); + Value *ptls = get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, phi), tbaa_gcframe); + unsigned offset = offsetof(jl_tls_states_t, gc_state); + Value *gc_state = builder.CreateConstInBoundsGEP1_32(Type::getInt8Ty(builder.getContext()), ptls, offset, "gc_state"); + builder.CreateAlignedStore(last_gc_state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); + emit_gc_safepoint(builder, T_size, ptls, tbaa, true); } } } diff --git a/src/method.c b/src/method.c index e4d44960018f3..6f53a6ff55c49 100644 --- a/src/method.c +++ b/src/method.c @@ -138,7 +138,7 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod return expr; } if (e->head == jl_foreigncall_sym) { - JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects)) + JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects, gc_safe)) jl_task_t *ct = jl_current_task; jl_value_t *rt = jl_exprarg(e, 1); jl_value_t *at = jl_exprarg(e, 2); @@ -172,11 +172,12 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod jl_value_t *cc = jl_quotenode_value(jl_exprarg(e, 4)); if (!jl_is_symbol(cc)) { JL_TYPECHK(ccall method definition, tuple, cc); - if (jl_nfields(cc) != 2) { + if (jl_nfields(cc) != 3) { jl_error("In ccall calling convention, expected two argument tuple or symbol."); } JL_TYPECHK(ccall method definition, symbol, jl_get_nth_field(cc, 0)); JL_TYPECHK(ccall method definition, uint16, jl_get_nth_field(cc, 1)); + JL_TYPECHK(ccall method definition, bool, jl_get_nth_field(cc, 2)); } } if (e->head == jl_call_sym && nargs > 0 && diff --git a/test/ccall.jl b/test/ccall.jl index b10504de21abc..f193d16fc09e2 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1745,6 +1745,7 @@ using Base: ccall_macro_parse, ccall_macro_lower :Cvoid, # returntype Any[:Cstring, :Cstring, :Cint], # argument types Any["%s = %d\n", :name, :value], # argument symbols + false, # is gc_safe 1 # number of required arguments (for varargs) ) end @@ -1757,7 +1758,7 @@ end )::Cstring))...) @test call == Base.remove_linenums!( quote - ccall($(Expr(:escape, :((:func, libstring)))), $(Expr(:cconv, :ccall, 0)), $(Expr(:escape, :Cstring)), ($(Expr(:escape, :Cstring)), $(Expr(:escape, :Cint)), $(Expr(:escape, :Cint))), $(Expr(:escape, :str)), $(Expr(:escape, :num1)), $(Expr(:escape, :num2))) + ccall($(Expr(:escape, :((:func, libstring)))), $(Expr(:cconv, (:ccall, UInt16(0), false), 0)), $(Expr(:escape, :Cstring)), ($(Expr(:escape, :Cstring)), $(Expr(:escape, :Cint)), $(Expr(:escape, :Cint))), $(Expr(:escape, :str)), $(Expr(:escape, :num1)), $(Expr(:escape, :num2))) end) local fptr = :x @@ -1966,3 +1967,15 @@ let llvm = sprint(code_llvm, world_counter, ()) # the world age should be -1 in generated functions (or other pure contexts) @test (generated_world_counter() == reinterpret(UInt, -1)) end + +function gc_safe_ccall() + # jl_rand is marked as JL_NOTSAFEPOINT + @ccall gc_safe=true jl_rand()::UInt64 +end + +let llvm = sprint(code_llvm, gc_safe_ccall, ()) + # check that the call works + @test gc_safe_ccall() isa UInt64 + # check for the gc_safe store + @test occursin("store atomic i8 2", llvm) +end