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