From e20b41ba986131e5fb9562a31e92b833889f809f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 26 Jul 2025 12:38:00 -0400 Subject: [PATCH 01/25] add array element mutex offset in print and gc (#58997) The layout, printing, and gc logic need to correctly offset and align the inset fields to account for the per-element mutex of an atomic array with large elements. Fix #58993 (cherry picked from commit c5ba4e28f2c9c49be01ed7409efedef8627fe96d) --- base/runtime_internals.jl | 6 +++- src/cgutils.cpp | 9 +++--- src/codegen.cpp | 19 ++++++------- src/datatype.c | 59 ++++++++++++++++++++++++++------------- src/genericmemory.c | 15 ++++------ src/julia.h | 6 +++- src/rtutils.c | 5 ++++ test/atomics.jl | 24 ++++++++++------ 8 files changed, 90 insertions(+), 53 deletions(-) diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 7a06a8fcbdb75..cabedc7b46964 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -528,6 +528,10 @@ struct DataTypeLayout # fielddesc_type : 2; # arrayelem_isboxed : 1; # arrayelem_isunion : 1; + # arrayelem_isatomic : 1; + # arrayelem_islocked : 1; + # isbitsegal : 1; + # padding : 8; end """ @@ -600,7 +604,7 @@ function datatype_isbitsegal(dt::DataType) @_foldable_meta dt.layout == C_NULL && throw(UndefRefError()) flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags - return (flags & (1<<5)) != 0 + return (flags & (1<<7)) != 0 end """ diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 945e125c71be2..47a26718166d4 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -3257,7 +3257,7 @@ static Value *emit_genericmemoryelsize(jl_codectx_t &ctx, Value *v, jl_value_t * if (jl_is_genericmemoryref_type(sty)) sty = (jl_datatype_t*)jl_field_type_concrete(sty, 1); size_t sz = sty->layout->size; - if (sty->layout->flags.arrayelem_isunion) + if (sty->layout->flags.arrayelem_isunion && add_isunion) sz++; auto elsize = ConstantInt::get(ctx.types().T_size, sz); return elsize; @@ -4711,9 +4711,10 @@ static jl_cgval_t emit_memoryref(jl_codectx_t &ctx, const jl_cgval_t &ref, jl_cg setName(ctx.emission_context, ovflw, "memoryref_ovflw"); } #endif - Type *elty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jl_tparam1(ref.typ)); - newdata = ctx.builder.CreateGEP(elty, data, offset); - setName(ctx.emission_context, newdata, "memoryref_data_offset"); + boffset = ctx.builder.CreateMul(offset, elsz); + setName(ctx.emission_context, boffset, "memoryref_byteoffset"); + newdata = ctx.builder.CreateGEP(getInt8Ty(ctx.builder.getContext()), data, boffset); + setName(ctx.emission_context, newdata, "memoryref_data_byteoffset"); (void)boffset; // LLVM is very bad at handling GEP with types different from the load if (bc) { BasicBlock *failBB, *endBB; diff --git a/src/codegen.cpp b/src/codegen.cpp index 2dfcdd39aa665..81d371d7f0804 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3869,8 +3869,8 @@ static bool emit_f_opmemory(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; bool isboxed = layout->flags.arrayelem_isboxed; bool isunion = layout->flags.arrayelem_isunion; - bool isatomic = kind == (jl_value_t*)jl_atomic_sym; - bool needlock = isatomic && layout->size > MAX_ATOMIC_SIZE; + bool isatomic = layout->flags.arrayelem_isatomic || layout->flags.arrayelem_islocked; + bool needlock = layout->flags.arrayelem_islocked; size_t elsz = layout->size; size_t al = layout->alignment; if (al > JL_HEAP_ALIGNMENT) @@ -4229,7 +4229,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, size_t al = layout->alignment; if (al > JL_HEAP_ALIGNMENT) al = JL_HEAP_ALIGNMENT; - bool needlock = isatomic && !isboxed && elsz > MAX_ATOMIC_SIZE; + bool needlock = layout->flags.arrayelem_islocked; AtomicOrdering Order = (needlock || order <= jl_memory_order_notatomic) ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) : get_llvm_atomic_order(order); @@ -4315,7 +4315,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, *ret = jl_cgval_t(); // unreachable return true; } - bool isatomic = kind == (jl_value_t*)jl_atomic_sym; + const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; + bool isatomic = layout->flags.arrayelem_isatomic || layout->flags.arrayelem_islocked; if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { emit_atomic_error(ctx, "memoryref_isassigned: non-atomic memory cannot be accessed atomically"); *ret = jl_cgval_t(); // unreachable @@ -4331,13 +4332,12 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } jl_value_t *boundscheck = argv[3].constant; emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, fname); - const jl_datatype_layout_t *layout = ((jl_datatype_t*)mty_dt)->layout; Value *mem = emit_memoryref_mem(ctx, ref, layout); Value *mlen = emit_genericmemorylen(ctx, mem, ref.typ); Value *oob = bounds_check_enabled(ctx, boundscheck) ? ctx.builder.CreateIsNull(mlen) : nullptr; bool isboxed = layout->flags.arrayelem_isboxed; if (isboxed || layout->first_ptr >= 0) { - bool needlock = isatomic && !isboxed && layout->size > MAX_ATOMIC_SIZE; + bool needlock = layout->flags.arrayelem_islocked; AtomicOrdering Order = (needlock || order <= jl_memory_order_notatomic) ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) : get_llvm_atomic_order(order); @@ -4357,13 +4357,12 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, ctx.builder.SetInsertPoint(passBB); } Value *elem = emit_memoryref_ptr(ctx, ref, layout); - if (needlock) { + if (!isboxed) + elem = emit_ptrgep(ctx, elem, layout->first_ptr * sizeof(void*)); + else if (needlock) // n.b. no actual lock acquire needed, as the check itself only needs to load a single pointer and check for null // elem += sizeof(lock); elem = emit_ptrgep(ctx, elem, LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT)); - } - if (!isboxed) - elem = emit_ptrgep(ctx, elem, layout->first_ptr * sizeof(void*)); // emit this using the same type as BUILTIN(memoryrefget) // so that LLVM may be able to load-load forward them and fold the result auto tbaa = isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf; diff --git a/src/datatype.c b/src/datatype.c index 0dbba03e30343..f6cdf21c81afb 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -239,8 +239,10 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t sz, flddesc->flags.haspadding = haspadding; flddesc->flags.isbitsegal = isbitsegal; flddesc->flags.fielddesc_type = fielddesc_type; - flddesc->flags.arrayelem_isboxed = arrayelem == 1; - flddesc->flags.arrayelem_isunion = arrayelem == 2; + flddesc->flags.arrayelem_isboxed = (arrayelem & 1) != 0; + flddesc->flags.arrayelem_isunion = (arrayelem & 2) != 0; + flddesc->flags.arrayelem_isatomic = (arrayelem & 4) != 0; + flddesc->flags.arrayelem_islocked = (arrayelem & 8) != 0; flddesc->flags.padding = 0; flddesc->npointers = npointers; flddesc->first_ptr = first_ptr; @@ -537,6 +539,7 @@ void jl_get_genericmemory_layout(jl_datatype_t *st) uint32_t *pointers = &first_ptr; int needlock = 0; + const jl_datatype_layout_t *el_layout = NULL; if (isunboxed) { elsz = LLT_ALIGN(elsz, al); if (kind == (jl_value_t*)jl_atomic_sym) { @@ -551,12 +554,12 @@ void jl_get_genericmemory_layout(jl_datatype_t *st) else { assert(jl_is_datatype(eltype)); zi = ((jl_datatype_t*)eltype)->zeroinit; - const jl_datatype_layout_t *layout = ((jl_datatype_t*)eltype)->layout; - if (layout->first_ptr >= 0) { - first_ptr = layout->first_ptr; - npointers = layout->npointers; - if (layout->flags.fielddesc_type == 2) { - pointers = (uint32_t*)jl_dt_layout_ptrs(layout); + el_layout = ((jl_datatype_t*)eltype)->layout; + if (el_layout->first_ptr >= 0) { + first_ptr = el_layout->first_ptr; + npointers = el_layout->npointers; + if (el_layout->flags.fielddesc_type == 2 && !needlock) { + pointers = (uint32_t*)jl_dt_layout_ptrs(el_layout); } else { pointers = (uint32_t*)alloca(npointers * sizeof(uint32_t)); @@ -568,10 +571,22 @@ void jl_get_genericmemory_layout(jl_datatype_t *st) } if (needlock) { assert(al <= JL_SMALL_BYTE_ALIGNMENT); - size_t offset = LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT); - elsz += offset; + size_t lock_offset = LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT); + elsz += lock_offset; + if (al < sizeof(void*)) { + al = sizeof(void*); + elsz = LLT_ALIGN(elsz, al); + } haspadding = 1; zi = 1; + // Adjust pointer offsets to account for the lock at the beginning + if (first_ptr != -1) { + uint32_t lock_offset_words = lock_offset / sizeof(void*); + first_ptr += lock_offset_words; + for (int j = 0; j < npointers; j++) { + pointers[j] += lock_offset_words; + } + } } } else { @@ -580,13 +595,17 @@ void jl_get_genericmemory_layout(jl_datatype_t *st) zi = 1; } - int arrayelem; + // arrayelem is a bitfield: 1=isboxed, 2=isunion, 4=isatomic, 8=islocked + int arrayelem = 0; if (!isunboxed) - arrayelem = 1; - else if (isunion) - arrayelem = 2; - else - arrayelem = 0; + arrayelem |= 1; // arrayelem_isboxed + if (isunion) + arrayelem |= 2; // arrayelem_isunion + if (kind == (jl_value_t*)jl_atomic_sym) { + arrayelem |= 4; // arrayelem_isatomic + if (needlock) + arrayelem |= 8; // arrayelem_islocked + } assert(!st->layout); st->layout = jl_get_layout(elsz, nfields, npointers, al, haspadding, isbitsegal, arrayelem, NULL, pointers); st->zeroinit = zi; @@ -647,17 +666,17 @@ void jl_compute_field_offsets(jl_datatype_t *st) // if we have no fields, we can trivially skip the rest if (st == jl_symbol_type || st == jl_string_type) { // opaque layout - heap-allocated blob - static const jl_datatype_layout_t opaque_byte_layout = {0, 0, 1, -1, 1, { .haspadding = 0, .fielddesc_type=0, .isbitsegal=1, .arrayelem_isboxed=0, .arrayelem_isunion=0 }}; + static const jl_datatype_layout_t opaque_byte_layout = {0, 0, 1, -1, 1, { .isbitsegal=1 }}; st->layout = &opaque_byte_layout; return; } else if (st == jl_simplevector_type || st == jl_module_type) { - static const jl_datatype_layout_t opaque_ptr_layout = {0, 0, 1, -1, sizeof(void*), { .haspadding = 0, .fielddesc_type=0, .isbitsegal=1, .arrayelem_isboxed=0, .arrayelem_isunion=0 }}; + static const jl_datatype_layout_t opaque_ptr_layout = {0, 0, 1, -1, sizeof(void*), { .isbitsegal=1 }}; st->layout = &opaque_ptr_layout; return; } else { - static const jl_datatype_layout_t singleton_layout = {0, 0, 0, -1, 1, { .haspadding = 0, .fielddesc_type=0, .isbitsegal=1, .arrayelem_isboxed=0, .arrayelem_isunion=0 }}; + static const jl_datatype_layout_t singleton_layout = {0, 0, 0, -1, 1, { .isbitsegal=1 }}; st->layout = &singleton_layout; } } @@ -1001,6 +1020,8 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, layout->flags.padding = 0; layout->flags.arrayelem_isboxed = 0; layout->flags.arrayelem_isunion = 0; + layout->flags.arrayelem_isatomic = 0; + layout->flags.arrayelem_islocked = 0; jl_fielddescdyn_t * desc = (jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout)); desc->markfunc = markfunc; diff --git a/src/genericmemory.c b/src/genericmemory.c index e435ec3b63c9f..e35ed5e5ac5b3 100644 --- a/src/genericmemory.c +++ b/src/genericmemory.c @@ -302,8 +302,8 @@ JL_DLLEXPORT void jl_genericmemory_copyto(jl_genericmemory_t *dest, char* destda JL_DLLEXPORT jl_value_t *jl_genericmemoryref(jl_genericmemory_t *mem, size_t i) { - int isatomic = (jl_tparam0(jl_typetagof(mem)) == (jl_value_t*)jl_atomic_sym); const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(mem))->layout; + int isatomic = layout->flags.arrayelem_isatomic || layout->flags.arrayelem_islocked; jl_genericmemoryref_t m; m.mem = mem; m.ptr_or_offset = (layout->flags.arrayelem_isunion || layout->size == 0) ? (void*)i : (void*)((char*)mem->ptr + layout->size * i); @@ -379,8 +379,8 @@ static jl_value_t *jl_ptrmemrefget(jl_genericmemoryref_t m JL_PROPAGATES_ROOT, i JL_DLLEXPORT jl_value_t *jl_memoryrefget(jl_genericmemoryref_t m, int isatomic) { - assert(isatomic == (jl_tparam0(jl_typetagof(m.mem)) == (jl_value_t*)jl_atomic_sym)); const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + assert(isatomic == (layout->flags.arrayelem_isatomic || layout->flags.arrayelem_islocked)); if (layout->flags.arrayelem_isboxed) return jl_ptrmemrefget(m, isatomic); jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); @@ -402,7 +402,7 @@ JL_DLLEXPORT jl_value_t *jl_memoryrefget(jl_genericmemoryref_t m, int isatomic) assert(data - (char*)m.mem->ptr < layout->size * m.mem->length); jl_value_t *r; size_t fsz = jl_datatype_size(eltype); - int needlock = isatomic && fsz > MAX_ATOMIC_SIZE; + int needlock = layout->flags.arrayelem_islocked; if (isatomic && !needlock) { r = jl_atomic_new_bits(eltype, data); } @@ -430,9 +430,6 @@ static int _jl_memoryref_isassigned(jl_genericmemoryref_t m, int isatomic) if (layout->flags.arrayelem_isboxed) { } else if (layout->first_ptr >= 0) { - int needlock = isatomic && layout->size > MAX_ATOMIC_SIZE; - if (needlock) - elem = elem + LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT) / sizeof(jl_value_t*); elem = &elem[layout->first_ptr]; } else { @@ -448,7 +445,8 @@ JL_DLLEXPORT jl_value_t *jl_memoryref_isassigned(jl_genericmemoryref_t m, int is JL_DLLEXPORT void jl_memoryrefset(jl_genericmemoryref_t m JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, int isatomic) { - assert(isatomic == (jl_tparam0(jl_typetagof(m.mem)) == (jl_value_t*)jl_atomic_sym)); + const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; + assert(isatomic == (layout->flags.arrayelem_isatomic || layout->flags.arrayelem_islocked)); jl_value_t *eltype = jl_tparam1(jl_typetagof(m.mem)); if (eltype != (jl_value_t*)jl_any_type && !jl_typeis(rhs, eltype)) { JL_GC_PUSH1(&rhs); @@ -456,7 +454,6 @@ JL_DLLEXPORT void jl_memoryrefset(jl_genericmemoryref_t m JL_ROOTING_ARGUMENT, j jl_type_error("memoryrefset!", eltype, rhs); JL_GC_POP(); } - const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m.mem))->layout; if (layout->flags.arrayelem_isboxed) { assert((char*)m.ptr_or_offset - (char*)m.mem->ptr < sizeof(jl_value_t*) * m.mem->length); if (isatomic) @@ -486,7 +483,7 @@ JL_DLLEXPORT void jl_memoryrefset(jl_genericmemoryref_t m JL_ROOTING_ARGUMENT, j } if (layout->size != 0) { assert(data - (char*)m.mem->ptr < layout->size * m.mem->length); - int needlock = isatomic && layout->size > MAX_ATOMIC_SIZE; + int needlock = layout->flags.arrayelem_islocked; size_t fsz = jl_datatype_size((jl_datatype_t*)jl_typeof(rhs)); // need to shrink-wrap the final copy if (isatomic && !needlock) { jl_atomic_store_bits(data, rhs, fsz); diff --git a/src/julia.h b/src/julia.h index 094a518f9f86d..9bbdb2a092b97 100644 --- a/src/julia.h +++ b/src/julia.h @@ -583,10 +583,12 @@ typedef struct { // metadata bit only for GenericMemory eltype layout uint16_t arrayelem_isboxed : 1; uint16_t arrayelem_isunion : 1; + uint16_t arrayelem_isatomic : 1; + uint16_t arrayelem_islocked : 1; // If set, this type's egality can be determined entirely by comparing // the non-padding bits of this datatype. uint16_t isbitsegal : 1; - uint16_t padding : 10; + uint16_t padding : 8; } flags; // union { // jl_fielddesc8_t field8[nfields]; @@ -1689,6 +1691,8 @@ static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT #define jl_is_addrspacecore(v) jl_typetagis(v,jl_addrspacecore_type) #define jl_is_abioverride(v) jl_typetagis(v,jl_abioverride_type) #define jl_genericmemory_isbitsunion(a) (((jl_datatype_t*)jl_typetagof(a))->layout->flags.arrayelem_isunion) +#define jl_genericmemory_isatomic(a) (((jl_datatype_t*)jl_typetagof(a))->layout->flags.arrayelem_isatomic) +#define jl_genericmemory_islocked(a) (((jl_datatype_t*)jl_typetagof(a))->layout->flags.arrayelem_islocked) #define jl_is_array_any(v) jl_typetagis(v,jl_array_any_type) JL_DLLEXPORT int jl_subtype(jl_value_t *a, jl_value_t *b); diff --git a/src/rtutils.c b/src/rtutils.c index e3672ab5d887e..a64f2e2e757c0 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -1294,6 +1294,11 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else { char *ptr = ((char*)m->ptr) + j * layout->size; + if (layout->flags.arrayelem_islocked) { + // Skip the lock at the beginning for locked arrays + size_t lock_size = sizeof(jl_mutex_t); + ptr += lock_size; + } n += jl_static_show_x_(out, (jl_value_t*)ptr, (jl_datatype_t*)(typetagdata ? jl_nth_union_component(el_type, typetagdata[j]) : el_type), depth, ctx); diff --git a/test/atomics.jl b/test/atomics.jl index 2d4a713b1d30d..3572824741459 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -111,6 +111,7 @@ Base.show(io::IO, x::Int24) = print(io, "Int24(", Core.Intrinsics.zext_int(Int, ## Fields @noinline function _test_field_operators(r) + GC.gc(false) r = r[] TT = fieldtype(typeof(r), :x) T = typeof(getfield(r, :x)) @@ -147,6 +148,7 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) @noinline function _test_field_orderings(r, x, y) @nospecialize x y + GC.gc(false) r = r[] TT = fieldtype(typeof(r), :x) @@ -328,8 +330,9 @@ test_field_orderings(ARefxy{Any}(true, false), true, false) test_field_orderings(ARefxy{Union{Nothing,Missing}}(nothing, missing), nothing, missing) test_field_orderings(ARefxy{Union{Nothing,Int}}(nothing, 123_1), nothing, 123_1) test_field_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) -test_field_orderings(Complex{Real}(10, 30), Complex{Real}(20, 40)) +test_field_orderings(Complex{Real}(10.5, 30.5), Complex{Real}(20.5, 40.5)) test_field_orderings(Complex{Rational{Integer}}(10, 30), Complex{Rational{Integer}}(20, 40)) +test_field_orderings(Pair{NTuple{3,Float64},NTuple{3,Real}}((10.5,11.5,12.5), (30.5,40.5,50.5)), Pair{NTuple{3,Float64},NTuple{3,Real}}((110.5,111.5,112.5), (130.5,140.5,150.5))) test_field_orderings(10.0, 20.0) test_field_orderings(NaN, Inf) @@ -705,7 +708,7 @@ test_global_orderings(Any, true, false) test_global_orderings(Union{Nothing,Missing}, nothing, missing) test_global_orderings(Union{Nothing,Int}, nothing, 123_1) test_global_orderings(Complex{Int128}, Complex{Int128}(10, 30), Complex{Int128}(20, 40)) -test_global_orderings(Complex{Real}, Complex{Real}(10, 30), Complex{Real}(20, 40)) +test_global_orderings(Complex{Real}, Complex{Real}(10.5, 30.5), Complex{Real}(20.5, 40.5)) test_global_orderings(Float64, 10.0, 20.0) test_global_orderings(Float64, NaN, Inf) @@ -1024,15 +1027,17 @@ test_memory_operators(Float64) end @noinline function test_memory_orderings(T::Type, x, y) @nospecialize - xr = GenericMemoryRef(AtomicMemory{T}(undef, 1)) - memoryrefset!(xr, x, :unordered, true) # @atomic xr[] = x - yr = GenericMemoryRef(Memory{T}(undef, 1)) + xr = GenericMemoryRef(AtomicMemory{T}(undef, 2), 2) + memoryrefset!(xr, x, :unordered, true) # @atomic xr[2] = x + yr = GenericMemoryRef(Memory{T}(undef, 2), 2) yr[] = y + GC.gc(false) _test_memory_orderings(Ref(xr), Ref(yr), x, y) - xr = GenericMemoryRef(AtomicMemory{T}(undef, 1)) - memoryrefset!(xr, x, :unordered, true) # @atomic xr[] = x - yr = GenericMemoryRef(Memory{T}(undef, 1)) + xr = GenericMemoryRef(AtomicMemory{T}(undef, 2), 2) + memoryrefset!(xr, x, :unordered, true) # @atomic xr[2] = x + yr = GenericMemoryRef(Memory{T}(undef, 2), 2) yr[] = y + GC.gc(false) _test_memory_orderings(Ref{Any}(xr), Ref{Any}(yr), x, y) nothing end @@ -1047,7 +1052,8 @@ test_memory_orderings(Any, true, false) test_memory_orderings(Union{Nothing,Missing}, nothing, missing) test_memory_orderings(Union{Nothing,Int}, nothing, 123_1) test_memory_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) -test_memory_orderings(Complex{Real}(10, 30), Complex{Real}(20, 40)) +test_memory_orderings(Complex{Real}(10.5, 30.5), Complex{Real}(20.5, 40.5)) +test_memory_orderings(Pair{NTuple{3,Float64},NTuple{3,Real}}((10.5,11.5,12.5), (30.5,40.5,50.5)), Pair{NTuple{3,Float64},NTuple{3,Real}}((110.5,111.5,112.5), (130.5,140.5,150.5))) test_memory_orderings(10.0, 20.0) test_memory_orderings(NaN, Inf) From a602e1ab156f7255a4941692598d3137b7bbefb5 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 29 Jul 2025 16:27:00 -0400 Subject: [PATCH 02/25] update libblastrampoline_jll to fix julia compat --- deps/checksums/blastrampoline | 72 +++++++++++------------ stdlib/libblastrampoline_jll/Project.toml | 4 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/deps/checksums/blastrampoline b/deps/checksums/blastrampoline index 7870242560f34..eef1653cf353a 100644 --- a/deps/checksums/blastrampoline +++ b/deps/checksums/blastrampoline @@ -1,38 +1,38 @@ blastrampoline-f26278e83ddc9035ae7695da597f1a5b26a4c62b.tar.gz/md5/855b7723a6e9eb8885876eb675d48329 blastrampoline-f26278e83ddc9035ae7695da597f1a5b26a4c62b.tar.gz/sha512/29cbd060c8f5eb17ef486d0a10ee4b221eeceec3a2ab0f9f98f60880f3d19a2247d93ac0dc0d32ec568ef876acd30f6c0642aaf704757580c2e17884e425607f -libblastrampoline.v5.13.1+0.aarch64-apple-darwin.tar.gz/md5/d8dc0f092f86b379b2fb9da97382be70 -libblastrampoline.v5.13.1+0.aarch64-apple-darwin.tar.gz/sha512/d9fc0439565afaabe53f56f64c20aeddb846c991dafeafdef6c2369bd7a359c1a6b49cdf8d63eaae2730a336509854b5c306e630eb520445712efc4e41c0263e -libblastrampoline.v5.13.1+0.aarch64-linux-gnu.tar.gz/md5/c181e51a6ca4cde0da3d036d561e24dc -libblastrampoline.v5.13.1+0.aarch64-linux-gnu.tar.gz/sha512/fe4a86bb4c94ef86c2307adad528bb58d0508a33c194c64190fffe7902f5b915592567d9e0cc35414633c5ab9067def2fa20cf669a2f4309265744180a5ec51a -libblastrampoline.v5.13.1+0.aarch64-linux-musl.tar.gz/md5/6f9eb8d73a0e61f3a2b97dba7105086e -libblastrampoline.v5.13.1+0.aarch64-linux-musl.tar.gz/sha512/9c3db080155729a91b5dd47df91d3852539aefc331d4dc51167fccaf3b01e601b36911ec259c53e211fe192c108e839a1f14b837009fa4f7d88ed82d658f80ff -libblastrampoline.v5.13.1+0.aarch64-unknown-freebsd.tar.gz/md5/68f65db9da9938929d510eea3540335b -libblastrampoline.v5.13.1+0.aarch64-unknown-freebsd.tar.gz/sha512/2fc7b375a751f3bb201504e0417828602fe014a2c8626137779c09ca7264ac6d39d44db0d1d32e0dc506284f56b49e23791922b0cc1237021473fb505fbf06bd -libblastrampoline.v5.13.1+0.armv6l-linux-gnueabihf.tar.gz/md5/a377fa4e5751fbeb3c42f319cb6341de -libblastrampoline.v5.13.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/9ddb1e2f4daab45d65b66dafc00df6ca7f788cb919cd6699c4aa0deca3e99a86d9ced10c3741610a6e480093d483e8a02c1d9165f91a7179632c1e2ae1abcfb7 -libblastrampoline.v5.13.1+0.armv6l-linux-musleabihf.tar.gz/md5/42c841baa05f80f17ea1b1d4f3405bef -libblastrampoline.v5.13.1+0.armv6l-linux-musleabihf.tar.gz/sha512/0c3ed42bd48f8f1ee9b1dc18faa7afa6e2fb27cffc59b9a420e29b5e6cdf8fb3bde36b82f3086075f8f7f329614aeb91ca5f173b1683e30e9530076f341ea2e0 -libblastrampoline.v5.13.1+0.armv7l-linux-gnueabihf.tar.gz/md5/61e515ec1223c99705175a26e6fbaf87 -libblastrampoline.v5.13.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/92260dcc563ece74719f21921a7cb51266884ed01b50c97fa997b4a98737e900ec9eaa8012d2c4c67ab479c4080bd1cf2708612eaaaddbba28e4f9147f3708ea -libblastrampoline.v5.13.1+0.armv7l-linux-musleabihf.tar.gz/md5/d45816d705dd46572d85105567bc060e -libblastrampoline.v5.13.1+0.armv7l-linux-musleabihf.tar.gz/sha512/45cba07050b818cd85c67acdfc29515e1fe416eb4e0c219171f2c0c026f7412903c3a9367d48258259a16e89f36c1e8f9fa054e455759720f1c6c5e8e27be476 -libblastrampoline.v5.13.1+0.i686-linux-gnu.tar.gz/md5/c8d3fd5f314353133934396361857c92 -libblastrampoline.v5.13.1+0.i686-linux-gnu.tar.gz/sha512/a949b3c0655ad9d6f8d53fd8a3f0b4ab504046e49a373039defc94e832b7faf90c77520f3912c4d6db8b0829951d85b4fc2a4021b3d8bb2c399d1ad04ce77ab0 -libblastrampoline.v5.13.1+0.i686-linux-musl.tar.gz/md5/a7bbd2233366d180ce8aa61fd3568c11 -libblastrampoline.v5.13.1+0.i686-linux-musl.tar.gz/sha512/e78cbef5b3bcfa93a86e14eebf0d704a94ac7b1f5c7030706d1f4a960de888c42e3daddb65395c7102e08dfd444efbfb40273e58a5f1de199d44ad55fd3ae658 -libblastrampoline.v5.13.1+0.i686-w64-mingw32.tar.gz/md5/4ca5cf3f855d04d3e8bdbd15321944ad -libblastrampoline.v5.13.1+0.i686-w64-mingw32.tar.gz/sha512/33160caa000c6c44cedd594195e1f2efefb950459653ee12ad2be4cedf0b833874772512f1812948d753f075ee7b8fe5629e5f9bd753a3da7804c4a6e1b0e0a8 -libblastrampoline.v5.13.1+0.powerpc64le-linux-gnu.tar.gz/md5/8be947c20f7d35ec22247f9a11ccce43 -libblastrampoline.v5.13.1+0.powerpc64le-linux-gnu.tar.gz/sha512/56f4246f96d2f49b03f5e5f3b996660a48d50b3784f89df7cd1dc52bab6efea0c120a65015040041a51d18fc6f361f89486f77317d771fbf588a1ba7565d77a2 -libblastrampoline.v5.13.1+0.riscv64-linux-gnu.tar.gz/md5/85e1f70a3235097158b4884a58a58154 -libblastrampoline.v5.13.1+0.riscv64-linux-gnu.tar.gz/sha512/11f1f5c2a409dbdab11d6bc968610b5700e9b0cb95094e348fe43ddca5586eda47bda1c382fb1f4b5a15aa741a6fc2b31f58f9b08bfe46631b5471e864bc009b -libblastrampoline.v5.13.1+0.x86_64-apple-darwin.tar.gz/md5/c6756ca8b6778ce2a4a440f63355c32e -libblastrampoline.v5.13.1+0.x86_64-apple-darwin.tar.gz/sha512/895d9bba75a9a0861809dca48b3dae7b5ffc5d866a518729ffd52f70fa1742a41a4b8b4e03bb354cba12d9ad11a33f3f112fa69a30ab3f945a9dede0d59d92b3 -libblastrampoline.v5.13.1+0.x86_64-linux-gnu.tar.gz/md5/1326a406aa98b6045f7459d7fb237894 -libblastrampoline.v5.13.1+0.x86_64-linux-gnu.tar.gz/sha512/4965baa1de5532425ea57b8100e369cf44b55963340cd144c0359f845560f27a1bea1597e4c72ec541917f71aaff8a4863f47d01a095c2e761a68212bfb08d1e -libblastrampoline.v5.13.1+0.x86_64-linux-musl.tar.gz/md5/5103983b7fecc7b87f495cd3b6c4d7a5 -libblastrampoline.v5.13.1+0.x86_64-linux-musl.tar.gz/sha512/f3243d84a0a0a191abad9e3850c37be78892eb5905b63b47bfb3e5a4148e0dae672ee72d311c5c764ad0fffe57d39c10dfd2086466efd76b5030118941d36a00 -libblastrampoline.v5.13.1+0.x86_64-unknown-freebsd.tar.gz/md5/a001ecd07b5178ce724a4f78996dc43e -libblastrampoline.v5.13.1+0.x86_64-unknown-freebsd.tar.gz/sha512/508866d54a9a49df2ef7eaa5d807173016c6dfaec59c4c89d5b37cd3faa7384302d2d4d39aca1975d79a948414657b7ec048a3ebdf6bf5c938037aa89303013a -libblastrampoline.v5.13.1+0.x86_64-w64-mingw32.tar.gz/md5/14fc4ec99e72e5bb646f5e6e8410fe01 -libblastrampoline.v5.13.1+0.x86_64-w64-mingw32.tar.gz/sha512/b7d07218047917fe217736b3c97d2b0565f6c904cd9cf6de96e38c66552aeec13b3cde714775fce1eb5a230db0ec0f2822572de8f0e166cb042552a16beb2b79 +libblastrampoline.v5.13.1+1.aarch64-apple-darwin.tar.gz/md5/d8dc0f092f86b379b2fb9da97382be70 +libblastrampoline.v5.13.1+1.aarch64-apple-darwin.tar.gz/sha512/d9fc0439565afaabe53f56f64c20aeddb846c991dafeafdef6c2369bd7a359c1a6b49cdf8d63eaae2730a336509854b5c306e630eb520445712efc4e41c0263e +libblastrampoline.v5.13.1+1.aarch64-linux-gnu.tar.gz/md5/c181e51a6ca4cde0da3d036d561e24dc +libblastrampoline.v5.13.1+1.aarch64-linux-gnu.tar.gz/sha512/fe4a86bb4c94ef86c2307adad528bb58d0508a33c194c64190fffe7902f5b915592567d9e0cc35414633c5ab9067def2fa20cf669a2f4309265744180a5ec51a +libblastrampoline.v5.13.1+1.aarch64-linux-musl.tar.gz/md5/6f9eb8d73a0e61f3a2b97dba7105086e +libblastrampoline.v5.13.1+1.aarch64-linux-musl.tar.gz/sha512/9c3db080155729a91b5dd47df91d3852539aefc331d4dc51167fccaf3b01e601b36911ec259c53e211fe192c108e839a1f14b837009fa4f7d88ed82d658f80ff +libblastrampoline.v5.13.1+1.aarch64-unknown-freebsd.tar.gz/md5/68f65db9da9938929d510eea3540335b +libblastrampoline.v5.13.1+1.aarch64-unknown-freebsd.tar.gz/sha512/2fc7b375a751f3bb201504e0417828602fe014a2c8626137779c09ca7264ac6d39d44db0d1d32e0dc506284f56b49e23791922b0cc1237021473fb505fbf06bd +libblastrampoline.v5.13.1+1.armv6l-linux-gnueabihf.tar.gz/md5/a377fa4e5751fbeb3c42f319cb6341de +libblastrampoline.v5.13.1+1.armv6l-linux-gnueabihf.tar.gz/sha512/9ddb1e2f4daab45d65b66dafc00df6ca7f788cb919cd6699c4aa0deca3e99a86d9ced10c3741610a6e480093d483e8a02c1d9165f91a7179632c1e2ae1abcfb7 +libblastrampoline.v5.13.1+1.armv6l-linux-musleabihf.tar.gz/md5/42c841baa05f80f17ea1b1d4f3405bef +libblastrampoline.v5.13.1+1.armv6l-linux-musleabihf.tar.gz/sha512/0c3ed42bd48f8f1ee9b1dc18faa7afa6e2fb27cffc59b9a420e29b5e6cdf8fb3bde36b82f3086075f8f7f329614aeb91ca5f173b1683e30e9530076f341ea2e0 +libblastrampoline.v5.13.1+1.armv7l-linux-gnueabihf.tar.gz/md5/61e515ec1223c99705175a26e6fbaf87 +libblastrampoline.v5.13.1+1.armv7l-linux-gnueabihf.tar.gz/sha512/92260dcc563ece74719f21921a7cb51266884ed01b50c97fa997b4a98737e900ec9eaa8012d2c4c67ab479c4080bd1cf2708612eaaaddbba28e4f9147f3708ea +libblastrampoline.v5.13.1+1.armv7l-linux-musleabihf.tar.gz/md5/d45816d705dd46572d85105567bc060e +libblastrampoline.v5.13.1+1.armv7l-linux-musleabihf.tar.gz/sha512/45cba07050b818cd85c67acdfc29515e1fe416eb4e0c219171f2c0c026f7412903c3a9367d48258259a16e89f36c1e8f9fa054e455759720f1c6c5e8e27be476 +libblastrampoline.v5.13.1+1.i686-linux-gnu.tar.gz/md5/c8d3fd5f314353133934396361857c92 +libblastrampoline.v5.13.1+1.i686-linux-gnu.tar.gz/sha512/a949b3c0655ad9d6f8d53fd8a3f0b4ab504046e49a373039defc94e832b7faf90c77520f3912c4d6db8b0829951d85b4fc2a4021b3d8bb2c399d1ad04ce77ab0 +libblastrampoline.v5.13.1+1.i686-linux-musl.tar.gz/md5/a7bbd2233366d180ce8aa61fd3568c11 +libblastrampoline.v5.13.1+1.i686-linux-musl.tar.gz/sha512/e78cbef5b3bcfa93a86e14eebf0d704a94ac7b1f5c7030706d1f4a960de888c42e3daddb65395c7102e08dfd444efbfb40273e58a5f1de199d44ad55fd3ae658 +libblastrampoline.v5.13.1+1.i686-w64-mingw32.tar.gz/md5/35bf0e9d253d3bbd188c1feba5635e1d +libblastrampoline.v5.13.1+1.i686-w64-mingw32.tar.gz/sha512/6b28e83265b6bd7337ccf7784c74f0490fcf5089bb9c21e2333dace8801f94f09b46ef683d2bd4f2c885b64a26ec4e8755bab21a6e81098e287c15de27d66293 +libblastrampoline.v5.13.1+1.powerpc64le-linux-gnu.tar.gz/md5/8be947c20f7d35ec22247f9a11ccce43 +libblastrampoline.v5.13.1+1.powerpc64le-linux-gnu.tar.gz/sha512/56f4246f96d2f49b03f5e5f3b996660a48d50b3784f89df7cd1dc52bab6efea0c120a65015040041a51d18fc6f361f89486f77317d771fbf588a1ba7565d77a2 +libblastrampoline.v5.13.1+1.riscv64-linux-gnu.tar.gz/md5/85e1f70a3235097158b4884a58a58154 +libblastrampoline.v5.13.1+1.riscv64-linux-gnu.tar.gz/sha512/11f1f5c2a409dbdab11d6bc968610b5700e9b0cb95094e348fe43ddca5586eda47bda1c382fb1f4b5a15aa741a6fc2b31f58f9b08bfe46631b5471e864bc009b +libblastrampoline.v5.13.1+1.x86_64-apple-darwin.tar.gz/md5/c6756ca8b6778ce2a4a440f63355c32e +libblastrampoline.v5.13.1+1.x86_64-apple-darwin.tar.gz/sha512/895d9bba75a9a0861809dca48b3dae7b5ffc5d866a518729ffd52f70fa1742a41a4b8b4e03bb354cba12d9ad11a33f3f112fa69a30ab3f945a9dede0d59d92b3 +libblastrampoline.v5.13.1+1.x86_64-linux-gnu.tar.gz/md5/1326a406aa98b6045f7459d7fb237894 +libblastrampoline.v5.13.1+1.x86_64-linux-gnu.tar.gz/sha512/4965baa1de5532425ea57b8100e369cf44b55963340cd144c0359f845560f27a1bea1597e4c72ec541917f71aaff8a4863f47d01a095c2e761a68212bfb08d1e +libblastrampoline.v5.13.1+1.x86_64-linux-musl.tar.gz/md5/5103983b7fecc7b87f495cd3b6c4d7a5 +libblastrampoline.v5.13.1+1.x86_64-linux-musl.tar.gz/sha512/f3243d84a0a0a191abad9e3850c37be78892eb5905b63b47bfb3e5a4148e0dae672ee72d311c5c764ad0fffe57d39c10dfd2086466efd76b5030118941d36a00 +libblastrampoline.v5.13.1+1.x86_64-unknown-freebsd.tar.gz/md5/a001ecd07b5178ce724a4f78996dc43e +libblastrampoline.v5.13.1+1.x86_64-unknown-freebsd.tar.gz/sha512/508866d54a9a49df2ef7eaa5d807173016c6dfaec59c4c89d5b37cd3faa7384302d2d4d39aca1975d79a948414657b7ec048a3ebdf6bf5c938037aa89303013a +libblastrampoline.v5.13.1+1.x86_64-w64-mingw32.tar.gz/md5/b7c51c8e1a11accc5c5097042da646d9 +libblastrampoline.v5.13.1+1.x86_64-w64-mingw32.tar.gz/sha512/24bc32f0dabc886898ca0e5fd2b3d7250fc67f7bd962ec2aae8c946f405826967a8b8623e524bf22c201bc7e6c55d16c4a5fb2a2250cbbefaf73e383fc080aba diff --git a/stdlib/libblastrampoline_jll/Project.toml b/stdlib/libblastrampoline_jll/Project.toml index ea4f645dc153c..08e75ea061293 100644 --- a/stdlib/libblastrampoline_jll/Project.toml +++ b/stdlib/libblastrampoline_jll/Project.toml @@ -1,13 +1,13 @@ name = "libblastrampoline_jll" uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.13.1+0" +version = "5.13.1+1" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.13" +julia = "1.12" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 19f000f3d0526089dd4b19877612e506810c9eb1 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 30 Jul 2025 03:56:09 -0500 Subject: [PATCH 03/25] REPL: improve type inference for `maybe_spawn_cache_PATH` (#59144) --- stdlib/REPL/src/REPLCompletions.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index e30a59b8c00d5..e8dd01a0c3c89 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -334,19 +334,23 @@ PATH_cache_task::Union{Task,Nothing} = nothing PATH_cache_condition::Union{Threads.Condition, Nothing} = nothing # used for sync in tests next_cache_update::Float64 = 0.0 function maybe_spawn_cache_PATH() - global PATH_cache_task, next_cache_update + global PATH_cache_task, PATH_cache_condition, next_cache_update + # Extract to local variables to enable flow-sensitive type inference for these global variables + PATH_cache_task_local = PATH_cache_task + PATH_cache_condition_local = PATH_cache_condition @lock PATH_cache_lock begin - PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return + PATH_cache_task_local isa Task && !istaskdone(PATH_cache_task_local) && return time() < next_cache_update && return PATH_cache_task = Threads.@spawn begin REPLCompletions.cache_PATH() @lock PATH_cache_lock begin next_cache_update = time() + 10 # earliest next update can run is 10s after PATH_cache_task = nothing # release memory when done - PATH_cache_condition !== nothing && notify(PATH_cache_condition) + PATH_cache_condition_local !== nothing && notify(PATH_cache_condition_local) end end - Base.errormonitor(PATH_cache_task) + PATH_cache_task_local = PATH_cache_task + Base.errormonitor(PATH_cache_task_local) end end From 56be12b82c5c43aeef86bf5be06f3e75c76cf6d7 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 30 Jul 2025 06:29:37 -0500 Subject: [PATCH 04/25] iterator: improve type inference for `Filter` (#59142) --- base/iterators.jl | 11 ++++++++--- test/iterators.jl | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/base/iterators.jl b/base/iterators.jl index c7450781c4928..29473561c452a 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -534,10 +534,15 @@ filter(flt, itr) = Filter(flt, itr) function iterate(f::Filter, state...) y = iterate(f.itr, state...) while y !== nothing - if f.flt(y[1]) - return y + v, s = y + if f.flt(v) + if y isa Tuple{Any,Any} + return (v, s) # incorporate type information that may be improved by user-provided `f.flt` + else + return y + end end - y = iterate(f.itr, y[2]) + y = iterate(f.itr, s) end nothing end diff --git a/test/iterators.jl b/test/iterators.jl index a6ab4720c0d0c..60e3ce634d14f 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -1142,3 +1142,8 @@ end @testset "Iterators docstrings" begin @test isempty(Docs.undocumented_names(Iterators)) end + +# Filtered list comprehension (`Filter` construct) type inference +@test Base.infer_return_type((Vector{Any},)) do xs + [x for x in xs if x isa Int] +end == Vector{Int} From 962ca50fa59de0afb99049bd8c04bf9b9c506161 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sat, 2 Aug 2025 04:21:56 +0900 Subject: [PATCH 05/25] inference: avoid `LimitedAccuracy` within slot wrappers (#59182) I looked at all the usage sites of these constructors within the compiler, and fortunately it appears that none of the usage sites require that the return values of these constructors be objects of their respective types. So in cases where `LimitedAccuracy` is given as a wrapped element, these slot type refinements simply will not be performed. - fixes JuliaLang/julia#59004 --- Compiler/src/abstractinterpretation.jl | 2 +- Compiler/src/typelattice.jl | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 4947368e87858..1f7d39b9fe75c 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3214,7 +3214,7 @@ function abstract_eval_copyast(interp::AbstractInterpreter, e::Expr, sstate::Sta return RTEffects(rt, Any, effects) end -function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, sstate::StatementState, +function abstract_eval_isdefined_expr(::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) sym = e.args[1] if isa(sym, SlotNumber) && sstate.vtypes !== nothing diff --git a/Compiler/src/typelattice.jl b/Compiler/src/typelattice.jl index 6f7612b836c89..598b11dc4b312 100644 --- a/Compiler/src/typelattice.jl +++ b/Compiler/src/typelattice.jl @@ -40,6 +40,9 @@ struct Conditional isdefined::Bool=false) assert_nested_slotwrapper(thentype) assert_nested_slotwrapper(elsetype) + if thentype isa LimitedAccuracy || elsetype isa LimitedAccuracy + return Bool + end return new(slot, thentype, elsetype, isdefined) end end @@ -83,6 +86,9 @@ struct MustAlias assert_nested_slotwrapper(fldtyp) # @assert !isalreadyconst(vartyp) "vartyp is already const" # @assert !isalreadyconst(fldtyp) "fldtyp is already const" + if vartyp isa LimitedAccuracy || fldtyp isa LimitedAccuracy + return fldtyp + end return new(slot, vartyp, fldidx, fldtyp) end end @@ -104,6 +110,9 @@ struct InterMustAlias assert_nested_slotwrapper(fldtyp) # @assert !isalreadyconst(vartyp) "vartyp is already const" # @assert !isalreadyconst(fldtyp) "fldtyp is already const" + if vartyp isa LimitedAccuracy || fldtyp isa LimitedAccuracy + return fldtyp + end return new(slot, vartyp, fldidx, fldtyp) end end From 3d1558333449499827247d230a1664128688c883 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:15:03 -0400 Subject: [PATCH 06/25] Revert "remove hand-coded methods for (U)Int128 on 32 bit systems (#53867)" (#59211) We need to revert this for now. Resolves https://github.com/JuliaLang/julia/issues/58911 It seems that the back-end LLVM emits this libcall a lot less often than it used to, but it still happens sometimes and 32-bit Windows has no implementation of `__divti3`. --- base/int.jl | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 158 insertions(+), 6 deletions(-) diff --git a/base/int.jl b/base/int.jl index 24b7abc646281..8362409248460 100644 --- a/base/int.jl +++ b/base/int.jl @@ -845,14 +845,166 @@ widemul(x::Bool,y::Number) = x * y widemul(x::Number,y::Bool) = x * y -# Int128 multiply and divide -*(x::T, y::T) where {T<:Union{Int128,UInt128}} = mul_int(x, y) +## wide multiplication, Int128 multiply and divide ## + +if Core.sizeof(Int) == 4 + function widemul(u::Int64, v::Int64) + local u0::UInt64, v0::UInt64, w0::UInt64 + local u1::Int64, v1::Int64, w1::UInt64, w2::Int64, t::UInt64 + + u0 = u & 0xffffffff; u1 = u >> 32 + v0 = v & 0xffffffff; v1 = v >> 32 + w0 = u0 * v0 + t = reinterpret(UInt64, u1) * v0 + (w0 >>> 32) + w2 = reinterpret(Int64, t) >> 32 + w1 = u0 * reinterpret(UInt64, v1) + (t & 0xffffffff) + hi = u1 * v1 + w2 + (reinterpret(Int64, w1) >> 32) + lo = w0 & 0xffffffff + (w1 << 32) + return Int128(hi) << 64 + Int128(lo) + end + + function widemul(u::UInt64, v::UInt64) + local u0::UInt64, v0::UInt64, w0::UInt64 + local u1::UInt64, v1::UInt64, w1::UInt64, w2::UInt64, t::UInt64 + + u0 = u & 0xffffffff; u1 = u >>> 32 + v0 = v & 0xffffffff; v1 = v >>> 32 + w0 = u0 * v0 + t = u1 * v0 + (w0 >>> 32) + w2 = t >>> 32 + w1 = u0 * v1 + (t & 0xffffffff) + hi = u1 * v1 + w2 + (w1 >>> 32) + lo = w0 & 0xffffffff + (w1 << 32) + return UInt128(hi) << 64 + UInt128(lo) + end + + function *(u::Int128, v::Int128) + u0 = u % UInt64; u1 = Int64(u >> 64) + v0 = v % UInt64; v1 = Int64(v >> 64) + lolo = widemul(u0, v0) + lohi = widemul(reinterpret(Int64, u0), v1) + hilo = widemul(u1, reinterpret(Int64, v0)) + t = reinterpret(UInt128, hilo) + (lolo >>> 64) + w1 = reinterpret(UInt128, lohi) + (t & 0xffffffffffffffff) + return Int128(lolo & 0xffffffffffffffff) + reinterpret(Int128, w1) << 64 + end + + function *(u::UInt128, v::UInt128) + u0 = u % UInt64; u1 = UInt64(u>>>64) + v0 = v % UInt64; v1 = UInt64(v>>>64) + lolo = widemul(u0, v0) + lohi = widemul(u0, v1) + hilo = widemul(u1, v0) + t = hilo + (lolo >>> 64) + w1 = lohi + (t & 0xffffffffffffffff) + return (lolo & 0xffffffffffffffff) + UInt128(w1) << 64 + end + + function _setbit(x::UInt128, i) + # faster version of `return x | (UInt128(1) << i)` + j = i >> 5 + y = UInt128(one(UInt32) << (i & 0x1f)) + if j == 0 + return x | y + elseif j == 1 + return x | (y << 32) + elseif j == 2 + return x | (y << 64) + elseif j == 3 + return x | (y << 96) + end + return x + end -div(x::Int128, y::Int128) = checked_sdiv_int(x, y) -div(x::UInt128, y::UInt128) = checked_udiv_int(x, y) + function divrem(x::UInt128, y::UInt128) + iszero(y) && throw(DivideError()) + if (x >> 64) % UInt64 == 0 + if (y >> 64) % UInt64 == 0 + # fast path: upper 64 bits are zero, so we can fallback to UInt64 division + q64, x64 = divrem(x % UInt64, y % UInt64) + return UInt128(q64), UInt128(x64) + else + # this implies y>x, so + return zero(UInt128), x + end + end + n = leading_zeros(y) - leading_zeros(x) + q = zero(UInt128) + ys = y << n + while n >= 0 + # ys == y * 2^n + if ys <= x + x -= ys + q = _setbit(q, n) + if (x >> 64) % UInt64 == 0 + # exit early, similar to above fast path + if (y >> 64) % UInt64 == 0 + q64, x64 = divrem(x % UInt64, y % UInt64) + q |= q64 + x = UInt128(x64) + end + return q, x + end + end + ys >>>= 1 + n -= 1 + end + return q, x + end -rem(x::Int128, y::Int128) = checked_srem_int(x, y) -rem(x::UInt128, y::UInt128) = checked_urem_int(x, y) + function div(x::Int128, y::Int128) + (x == typemin(Int128)) & (y == -1) && throw(DivideError()) + return Int128(div(BigInt(x), BigInt(y)))::Int128 + end + div(x::UInt128, y::UInt128) = divrem(x, y)[1] + + function rem(x::Int128, y::Int128) + return Int128(rem(BigInt(x), BigInt(y)))::Int128 + end + + function rem(x::UInt128, y::UInt128) + iszero(y) && throw(DivideError()) + if (x >> 64) % UInt64 == 0 + if (y >> 64) % UInt64 == 0 + # fast path: upper 64 bits are zero, so we can fallback to UInt64 division + return UInt128(rem(x % UInt64, y % UInt64)) + else + # this implies y>x, so + return x + end + end + n = leading_zeros(y) - leading_zeros(x) + ys = y << n + while n >= 0 + # ys == y * 2^n + if ys <= x + x -= ys + if (x >> 64) % UInt64 == 0 + # exit early, similar to above fast path + if (y >> 64) % UInt64 == 0 + x = UInt128(rem(x % UInt64, y % UInt64)) + end + return x + end + end + ys >>>= 1 + n -= 1 + end + return x + end + + function mod(x::Int128, y::Int128) + return Int128(mod(BigInt(x), BigInt(y)))::Int128 + end +else + *(x::T, y::T) where {T<:Union{Int128,UInt128}} = mul_int(x, y) + + div(x::Int128, y::Int128) = checked_sdiv_int(x, y) + div(x::UInt128, y::UInt128) = checked_udiv_int(x, y) + + rem(x::Int128, y::Int128) = checked_srem_int(x, y) + rem(x::UInt128, y::UInt128) = checked_urem_int(x, y) +end # issue #15489: since integer ops are unchecked, they shouldn't check promotion for op in (:+, :-, :*, :&, :|, :xor) From dcdbcfb87f0dc631fae0bb6fdb7afb939ae43e7a Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 28 Jul 2025 15:47:24 -0400 Subject: [PATCH 07/25] Add builtin function name to add methods error (#59112) ``` julia> Base.throw(x::Int) = 1 ERROR: cannot add methods to builtin function `throw` Stacktrace: [1] top-level scope @ REPL[1]:1 ``` (cherry picked from commit 57def4d19ef79794439ef839e5e3b551323f49a2) --- src/method.c | 4 ++-- test/core.jl | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/method.c b/src/method.c index 8220178964333..c33b343742dc8 100644 --- a/src/method.c +++ b/src/method.c @@ -1220,7 +1220,7 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, // jl_value_t **ttypes = { jl_builtin_type, jl_tparam0(jl_anytuple_type) }; // jl_value_t *invalidt = jl_apply_tuple_type_v(ttypes, 2); // Tuple{Union{Builtin,OpaqueClosure}, Vararg} // if (!jl_has_empty_intersection(argtype, invalidt)) - // jl_error("cannot add methods to a builtin function"); + // jl_error("cannot add methods to builtin function"); //} assert(jl_is_linenode(functionloc)); @@ -1299,7 +1299,7 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, } ft = jl_rewrap_unionall(ft, argtype); if (!external_mt && !jl_has_empty_intersection(ft, (jl_value_t*)jl_builtin_type)) // disallow adding methods to Any, Function, Builtin, and subtypes, or Unions of those - jl_error("cannot add methods to a builtin function"); + jl_errorf("cannot add methods to builtin function `%s`", jl_symbol_name(name)); m = jl_new_method_uninit(module); m->external_mt = (jl_value_t*)external_mt; diff --git a/test/core.jl b/test/core.jl index 28c496cedee3a..a7746de08a710 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2661,13 +2661,16 @@ struct D14919 <: Function; end @test B14919()() == "It's a brand new world" @test C14919()() == D14919()() == "Boo." -let ex = ErrorException("cannot add methods to a builtin function") +let ex_t = ErrorException, ex_r = r"cannot add methods to builtin function" for f in (:(Core.Any), :(Core.Function), :(Core.Builtin), :(Base.Callable), :(Union{Nothing,F} where F), :(typeof(Core.getfield)), :(Core.IntrinsicFunction)) - @test_throws ex @eval (::$f)() = 1 + @test_throws ex_t @eval (::$f)() = 1 + @test_throws ex_r @eval (::$f)() = 1 end - @test_throws ex @eval (::Union{Nothing,F})() where {F<:Function} = 1 + @test_throws ex_t @eval (::Union{Nothing,F})() where {F<:Function} = 1 + @test_throws ex_r @eval (::Union{Nothing,F})() where {F<:Function} = 1 for f in (:(Core.getfield),) - @test_throws ex @eval $f() = 1 + @test_throws ex_t @eval $f() = 1 + @test_throws ex_r @eval $f() = 1 end end From a966982e92dafbde5b5a12e1a8896c348fadff12 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 28 Jul 2025 20:01:22 -0500 Subject: [PATCH 08/25] Fix memory order typo in "src/julia_atomics.h" (#59120) (cherry picked from commit fde79f5bb5c4835189bfe3215521137176ac3eb8) --- src/julia_atomics.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/julia_atomics.h b/src/julia_atomics.h index d05f0fafab28f..1d8fba3b44e33 100644 --- a/src/julia_atomics.h +++ b/src/julia_atomics.h @@ -190,7 +190,7 @@ T jl_atomic_exchange_explicit(std::atomic *ptr, S desired, std::memory_order { return std::atomic_exchange_explicit(ptr, desired, order); } -#define jl_atomic_exchange_release(ptr, val) jl_atomic_exchange_explicit(ptr, val, memory_order_reease) +#define jl_atomic_exchange_release(ptr, val) jl_atomic_exchange_explicit(ptr, val, memory_order_release) #define jl_atomic_exchange_relaxed(ptr, val) jl_atomic_exchange_explicit(ptr, val, memory_order_relaxed) extern "C" { #else From 50f37a57116b0f7750bc2601fd41e7052c0dd58f Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Tue, 29 Jul 2025 17:39:46 -0400 Subject: [PATCH 09/25] fix serializer compat with CodeInfos from v1.11 (#58650) (cherry picked from commit 9f6996149a9b56c03cf1ccff051e35c225539c04) --- src/method.c | 3 ++ stdlib/Serialization/src/Serialization.jl | 36 +++++++++++++++++++++-- stdlib/Serialization/test/runtests.jl | 12 ++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/method.c b/src/method.c index c33b343742dc8..46a4f5bc4d56c 100644 --- a/src/method.c +++ b/src/method.c @@ -958,6 +958,9 @@ JL_DLLEXPORT void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) } src = jl_copy_code_info(src); src->isva = m->isva; // TODO: It would be nice to reverse this + // If nargs hasn't been set yet, do it now. This can happen if an old CodeInfo is deserialized. + if (src->nargs == 0) + src->nargs = m->nargs; assert(m->nargs == src->nargs); src->code = copy; jl_gc_wb(src, copy); diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 3c4152bf10598..1b25c59bf3cdf 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -8,6 +8,7 @@ Provide serialization of Julia objects via the functions module Serialization import Base: Bottom, unsafe_convert +import Base.ScopedValues: ScopedValue, with import Core: svec, SimpleVector using Base: unaliascopy, unwrap_unionall, require_one_based_indexing, ntupleany using Core.IR @@ -28,6 +29,8 @@ end Serializer(io::IO) = Serializer{typeof(io)}(io) +const current_module = ScopedValue{Union{Nothing,Module}}(nothing) + ## serializing values ## const n_int_literals = 33 @@ -1064,7 +1067,10 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) nospecializeinfer = false constprop = 0x00 purity = 0x0000 - template_or_is_opaque = deserialize(s) + local template_or_is_opaque, template + with(current_module => mod) do + template_or_is_opaque = deserialize(s) + end if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque if format_version(s) >= 24 @@ -1078,7 +1084,9 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) elseif format_version(s) >= 17 purity = UInt16(deserialize(s)::UInt8) end - template = deserialize(s) + with(current_module => mod) do + template = deserialize(s) + end else template = template_or_is_opaque end @@ -1182,6 +1190,22 @@ function deserialize(s::AbstractSerializer, ::Type{PhiNode}) return PhiNode(edges, values) end +# v1.12 disallows bare symbols in IR, but older CodeInfos might still have them +function symbol_to_globalref(@nospecialize(x), m::Module) + mapper(@nospecialize(x)) = symbol_to_globalref(x, m) + if x isa Symbol + return GlobalRef(m, x) + elseif x isa Expr + return Expr(x.head, map(mapper, x.args)...) + elseif x isa ReturnNode + return ReturnNode(mapper(x.val)) + elseif x isa GotoIfNot + return GotoIfNot(mapper(x.cond), x.dest) + else + return x + end +end + function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) ci = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) deserialize_cycle(s, ci) @@ -1200,6 +1224,9 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) end end end + if current_module[] !== nothing + map!(x->symbol_to_globalref(x, current_module[]), code) + end _x = deserialize(s) have_debuginfo = _x isa Core.DebugInfo if have_debuginfo @@ -1248,6 +1275,9 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) ci.slottypes = deserialize(s) ci.rettype = deserialize(s) ci.parent = deserialize(s) + if format_version(s) < 29 && ci.parent isa MethodInstance && ci.parent.def isa Method + ci.nargs = ci.parent.def.nargs + end world_or_edges = deserialize(s) pre_13 = isa(world_or_edges, Union{UInt, Int}) if pre_13 @@ -1258,7 +1288,7 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) ci.min_world = deserialize(s)::UInt ci.max_world = deserialize(s)::UInt end - if format_version(s) >= 26 + if format_version(s) >= 29 ci.method_for_inference_limit_heuristics = deserialize(s) end end diff --git a/stdlib/Serialization/test/runtests.jl b/stdlib/Serialization/test/runtests.jl index f1b83ca947c7e..e341c6e3eb9ec 100644 --- a/stdlib/Serialization/test/runtests.jl +++ b/stdlib/Serialization/test/runtests.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Test, Random, Serialization, Base64 +using Base.ScopedValues: with # Check that serializer hasn't gone out-of-frame @test Serialization.sertag(Symbol) == 1 @@ -661,3 +662,14 @@ end @test_broken isempty(undoc) @test undoc == [:AbstractSerializer, :Serializer] end + +# test method definitions from v1.11 +if Int === Int64 + let f_data = "N0pMGgQAAAAWAQEFdGh1bmsbFUbnFgEBBXRodW5rGxVG4DoWAQEGbWV0aG9kAQtmMTExX3RvXzExMhUABuABAAAA4BUAB+AAAAAAThVG4DQQAQxMaW5lSW5mb05vZGUfTptEH04BBE1haW5EAQ90b3AtbGV2ZWwgc2NvcGUBBG5vbmW+vhUAAd8V305GTk4JAQAAAAAAAAAJ//////////9MTExMAwADAAUAAAX//xYBAQZtZXRob2QsBwAWAlYkH06bRAEGVHlwZW9mLAcAFgNWJB9Om0QBBHN2ZWMo4iQfTptETxYBViQfTptEAQRzdmVjFgRWJB9Om0QBBHN2ZWMo4yjkGhfgAQRub25lFgMBBm1ldGhvZCwHACjlGxVG5AEBXhYDViQfTptElyQfTp5EAQNWYWzhFgFWKOEWBFYkH06eRAELbGl0ZXJhbF9wb3co4CXhKOI6KOMVAAbkAQAAAAEAAAABAAAAAQAAAAAAAADkFQAH5AAAAAAAAAAAAAAAAAAAAAAAAAAAThVG4DQsCwAfTgEETWFpbkQBBG5vbmUBBG5vbmW/vhUAAeGifRXhAAhORk5OCQEAAAAAAAAACf//////////TExMTAMAAwAFAAAF//86ThUABucBAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAAAAAOcVAAfnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOFUbgNCwLAB9OAQRNYWluRCwNAAEEbm9uZb6+FQAB3xXfTkZOTgkBAAAAAAAAAAn//////////0xMTEwDAAMABQAABf//" + @eval Main function f111_to_112 end + Core.eval(Main, with(Serialization.current_module => Main) do + deserialize(IOBuffer(base64decode(f_data))) + end) + @test @invokelatest(Main.f111_to_112(16)) == 256 + end +end From 59e17edc88ccf2c78f3e8ee8f9a28d9d0d4b2924 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 31 Jul 2025 02:11:51 -0400 Subject: [PATCH 10/25] misc: Don't get confused by GC running during compilation (#59163) Fixes https://github.com/JuliaLang/julia/issues/59162 (cherry picked from commit f5c55e808cf8c3da77fbcc3db712ff2cb88d2c25) --- test/misc.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/misc.jl b/test/misc.jl index b51c5a2553825..4e6538be55d6c 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -481,12 +481,12 @@ begin local second = @capture_stdout @time @eval calldouble2(1.0) # these functions were not recompiled - local matches = collect(eachmatch(r"(\d+(?:\.\d+)?)%", first)) + local matches = collect(eachmatch(r"(\d+(?:\.\d+)?)% compilation", first)) @test length(matches) == 1 @test parse(Float64, matches[1][1]) > 0.0 @test parse(Float64, matches[1][1]) <= 100.0 - matches = collect(eachmatch(r"(\d+(?:\.\d+)?)%", second)) + matches = collect(eachmatch(r"(\d+(?:\.\d+)?)% compilation", second)) @test length(matches) == 1 @test parse(Float64, matches[1][1]) > 0.0 @test parse(Float64, matches[1][1]) <= 100.0 From 3cab711cf624bb67f65d7f44b082b0792946d91f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 31 Jul 2025 19:58:09 -0400 Subject: [PATCH 11/25] Clarify and enhance confusing precompile test (#59170) (cherry picked from commit 1f6eff183db0b947632f780b4acc440d9c41a6e2) --- test/precompile.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/precompile.jl b/test/precompile.jl index 4cf2b2ae473e3..13af539eed2ab 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -691,7 +691,10 @@ precompile_test_harness(false) do dir error("the \"break me\" test failed") catch exc isa(exc, ErrorException) || rethrow() - occursin("ERROR: LoadError: break me", exc.msg) && rethrow() + # The LoadError shouldn't be surfaced but is printed to stderr, hence the `@test_warn` capture tests + occursin("LoadError: break me", exc.msg) && rethrow() + # The actual error that is thrown + occursin("Failed to precompile FooBar2", exc.msg) || rethrow() end # Test that trying to eval into closed modules during precompilation is an error From e5faf18f81c0c5375ec2bacea0ec7e62b6487067 Mon Sep 17 00:00:00 2001 From: Em Chu <61633163+mlechu@users.noreply.github.com> Date: Thu, 31 Jul 2025 18:24:37 -0700 Subject: [PATCH 12/25] Fix desugaring of `const x::T = y` for complex `y` (#59155) Fix #59128 Assignment desugaring of `(const (= (|::| x T) rhs))` would pre-expand to, then re-expand `(const x ,(convert-for-type-decl rhs T))`, but two-arg (IR) const is expected to have a simple RHS---closure conversion doesn't recurse here (should it?), giving us partially-lowered IR, and hence our bug. Fix: Pre-expand to the one-arg AST const form `(const (= x ,(convert-for-type-decl rhs T)))` instead. This also gives us a `(latestworld)` we were missing before, so this lowering may have been originally intended. (cherry picked from commit 3de5b9a66d83a9b4dab665d47c4e3afcf5388a63) --- src/julia-syntax.scm | 6 +++--- test/syntax.jl | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index b2da8c4afdd2a..e9ba0bb251082 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1642,9 +1642,9 @@ (expand-forms ;; TODO: This behaviour (`const _:T = ...` does not call convert, ;; but still evaluates RHS) should be documented. - `(const ,(car e) ,(if (underscore-symbol? (car e)) - rhs - (convert-for-type-decl rhs T #t #f)))) + `(const (= ,(car e) ,(if (underscore-symbol? (car e)) + rhs + (convert-for-type-decl rhs T #t #f))))) (expand-forms `(block ,@(cdr e) ;; TODO: When x is a complex expression, this acts as a diff --git a/test/syntax.jl b/test/syntax.jl index 9ced29245ea6b..be01279b82359 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -4236,6 +4236,24 @@ end @test letf_57470(3) == 5 @test letT_57470 === Int64 +# Closure conversion should happen on const assignment rhs +module M59128 +using Test +const x0::Int = (()->1)() +global x1::Int = (()->1)() +global const x2::Int = (()->1)() +const global x3::Int = (()->1)() +@test x0 === x1 === x2 === x3 === 1 +let g = 1 + global x4::Vector{T} where {T<:Number} = let; (()->[g])(); end + const global x5::Vector{T} where {T<:Number} = let; (()->[g])(); end + global const x6::Vector{T} where {T<:Number} = let; (()->[g])(); end +end +@test x4 == x5 == x6 == [1] +const letT_57470{T} = (()->Int64)() +@test letT_57470 == Int64 +end + end # M57470_sub # lowering globaldecl with complex type From 4723ce52dc540ba80f12e611918096f714aed1b7 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 1 Aug 2025 15:23:19 -0400 Subject: [PATCH 13/25] rename `GlobalMethods` to `methodtable` :bike: :house: (#59158) (cherry picked from commit f9b7d27b753020b828d6a050a341b3c2af49000c) --- Compiler/src/abstractinterpretation.jl | 4 ++-- Compiler/src/stmtinfo.jl | 4 ++-- Compiler/src/tfuncs.jl | 2 +- src/builtins.c | 2 +- src/jltypes.c | 2 +- src/rtutils.c | 2 +- stdlib/Serialization/src/Serialization.jl | 2 +- stdlib/Test/src/Test.jl | 4 ++-- test/misc.jl | 4 ++-- test/worlds.jl | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 1f7d39b9fe75c..dcd54bd39e864 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -369,7 +369,7 @@ function find_union_split_method_matches(interp::AbstractInterpreter, argtypes:: end valid_worlds = intersect(valid_worlds, thismatches.valid_worlds) thisfullmatch = any(match::MethodMatch->match.fully_covers, thismatches) - mt = Core.GlobalMethods + mt = Core.methodtable thisinfo = MethodMatchInfo(thismatches, mt, sig_n, thisfullmatch) push!(infos, thisinfo) for idx = 1:length(thismatches) @@ -390,7 +390,7 @@ function find_simple_method_matches(interp::AbstractInterpreter, @nospecialize(a return FailedMethodMatch("Too many methods matched") end fullmatch = any(match::MethodMatch->match.fully_covers, matches) - mt = Core.GlobalMethods + mt = Core.methodtable info = MethodMatchInfo(matches, mt, atype, fullmatch) applicable = MethodMatchTarget[MethodMatchTarget(matches[idx], info.edges, idx) for idx = 1:length(matches)] return MethodMatches(applicable, info, matches.valid_worlds) diff --git a/Compiler/src/stmtinfo.jl b/Compiler/src/stmtinfo.jl index 1c6d47ec53b57..71ac546346501 100644 --- a/Compiler/src/stmtinfo.jl +++ b/Compiler/src/stmtinfo.jl @@ -49,14 +49,14 @@ function _add_edges_impl(edges::Vector{Any}, info::MethodMatchInfo, mi_edge::Boo if !fully_covering(info) exists = false for i in 2:length(edges) - if edges[i] === Core.GlobalMethods && edges[i-1] == info.atype + if edges[i] === Core.methodtable && edges[i-1] == info.atype exists = true break end end if !exists push!(edges, info.atype) - push!(edges, Core.GlobalMethods) + push!(edges, Core.methodtable) end end nmatches = length(info.results) diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index be314c2f6ad83..b49885aa46721 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -3170,7 +3170,7 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv if match === nothing rt = Const(false) vresults = MethodLookupResult(Any[], valid_worlds, true) - mt = Core.GlobalMethods + mt = Core.methodtable vinfo = MethodMatchInfo(vresults, mt, types, false) # XXX: this should actually be an info with invoke-type edge else rt = Const(true) diff --git a/src/builtins.c b/src/builtins.c index d68c7fc21ec40..0ff1a1cf61491 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2491,7 +2491,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("Module", (jl_value_t*)jl_module_type); add_builtin("MethodTable", (jl_value_t*)jl_methtable_type); - add_builtin("GlobalMethods", (jl_value_t*)jl_method_table); + add_builtin("methodtable", (jl_value_t*)jl_method_table); add_builtin("MethodCache", (jl_value_t*)jl_methcache_type); add_builtin("Method", (jl_value_t*)jl_method_type); add_builtin("CodeInstance", (jl_value_t*)jl_code_instance_type); diff --git a/src/jltypes.c b/src/jltypes.c index 28a8afc10216e..cd6e6637ac46e 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2956,7 +2956,7 @@ void jl_init_types(void) JL_GC_DISABLED XX(simplevector); jl_methcache_type = jl_new_uninitialized_datatype(); jl_methtable_type = jl_new_uninitialized_datatype(); - jl_method_table = jl_new_method_table(jl_symbol("GlobalMethods"), core); + jl_method_table = jl_new_method_table(jl_symbol("methodtable"), core); jl_emptysvec = (jl_svec_t*)jl_gc_permobj(sizeof(void*), jl_simplevector_type); jl_set_typetagof(jl_emptysvec, jl_simplevector_tag, GC_OLD_MARKED); diff --git a/src/rtutils.c b/src/rtutils.c index a64f2e2e757c0..2976e56b4195d 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -1081,7 +1081,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, "nothing"); } else if (v == (jl_value_t*)jl_method_table) { - n += jl_printf(out, "Core.GlobalMethods"); + n += jl_printf(out, "Core.methodtable"); } else if (vt == jl_string_type) { n += jl_static_show_string(out, jl_string_data(v), jl_string_len(v), 1, 0); diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 1b25c59bf3cdf..ee40ebdd4abad 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -1129,7 +1129,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) meth.recursion_relation = recursion_relation end if !is_for_opaque_closure - mt = Core.GlobalMethods + mt = Core.methodtable if nothing === ccall(:jl_methtable_lookup, Any, (Any, UInt), sig, Base.get_world_counter()) # XXX: quite sketchy? ccall(:jl_method_table_insert, Cvoid, (Any, Any, Ptr{Cvoid}), mt, meth, C_NULL) end diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index b50107cec3a29..091e2c4394abe 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -2159,7 +2159,7 @@ function detect_ambiguities(mods::Module...; end end end - examine(Core.GlobalMethods) + examine(Core.methodtable) return collect(ambs) end @@ -2207,7 +2207,7 @@ function detect_unbound_args(mods...; push!(ambs, m) end end - examine(Core.GlobalMethods) + examine(Core.methodtable) return collect(ambs) end diff --git a/test/misc.jl b/test/misc.jl index 4e6538be55d6c..60a0262473b91 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1614,10 +1614,10 @@ end let errs = IOBuffer() run(`$(Base.julia_cmd()) -e ' using Test - @test !isempty(Core.GlobalMethods.backedges) + @test !isempty(Core.methodtable.backedges) Base.Experimental.disable_new_worlds() @test_throws "disable_new_worlds" @eval f() = 1 - @test isempty(Core.GlobalMethods.backedges) + @test isempty(Core.methodtable.backedges) @test_throws "disable_new_worlds" Base.delete_method(which(+, (Int, Int))) @test 1+1 == 2 using Dates diff --git a/test/worlds.jl b/test/worlds.jl index 35037dfb019c3..4a2836164e2c6 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -198,7 +198,7 @@ z26506 = Any["ABC"] f26506(x::Int) = 2 g26506(z26506) # Places an entry for f26506(::String) in MethodTable cache w26506 = Base.get_world_counter() -cache26506 = ccall(:jl_mt_find_cache_entry, Any, (Any, Any, UInt), Core.GlobalMethods.cache, Tuple{typeof(f26506),String}, w26506)::Core.TypeMapEntry +cache26506 = ccall(:jl_mt_find_cache_entry, Any, (Any, Any, UInt), Core.methodtable.cache, Tuple{typeof(f26506),String}, w26506)::Core.TypeMapEntry @test cache26506.max_world === typemax(UInt) w26506 = Base.get_world_counter() f26506(x::String) = 3 From c638304322ef24244a8f71b6250d836b7386594a Mon Sep 17 00:00:00 2001 From: Thomas van Doornmalen <60822431+Taaitaaiger@users.noreply.github.com> Date: Sat, 2 Aug 2025 23:26:28 +0200 Subject: [PATCH 14/25] Export jl_declare_constant_val (#59192) Closes #59184 (cherry picked from commit 13f6b3b5c0361c865ba6f3e6f09fb7d3ae6a6735) --- src/jl_exported_funcs.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index e5631ae24849a..dcbe0295c40ae 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -183,6 +183,7 @@ XX(jl_gdblookup) \ XX(jl_generating_output) \ XX(jl_declare_const_gf) \ + XX(jl_declare_constant_val) \ XX(jl_gensym) \ XX(jl_getaffinity) \ XX(jl_getallocationgranularity) \ From 41aa62e337ec8d0431dd7c475abc3860a11526df Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Mon, 4 Aug 2025 17:07:22 -0400 Subject: [PATCH 15/25] model ccall binding access in inference (#58872) fixes #57749 (cherry picked from commit b35c4f471f05873a4de6a989e0038a5c28cd09ce) --- Compiler/src/abstractinterpretation.jl | 52 ++++++++++++++++++++++++++ test/ccall.jl | 8 ++++ 2 files changed, 60 insertions(+) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index dcd54bd39e864..3c4de73b4c36a 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3444,7 +3444,59 @@ function refine_partial_type(@nospecialize t) return t end +abstract_eval_nonlinearized_foreigncall_name(interp::AbstractInterpreter, e, sstate::StatementState, sv::IRInterpretationState) = nothing + +function abstract_eval_nonlinearized_foreigncall_name(interp::AbstractInterpreter, e, sstate::StatementState, sv::AbsIntState) + if isexpr(e, :call) + n = length(e.args) + argtypes = Vector{Any}(undef, n) + callresult = Future{CallMeta}() + i::Int = 1 + nextstate::UInt8 = 0x0 + local ai, res + function evalargs(interp, sv) + if nextstate === 0x1 + @goto state1 + elseif nextstate === 0x2 + @goto state2 + end + while i <= n + ai = abstract_eval_nonlinearized_foreigncall_name(interp, e.args[i], sstate, sv) + if !isready(ai) + nextstate = 0x1 + return false + @label state1 + end + argtypes[i] = ai[].rt + i += 1 + end + res = abstract_call(interp, ArgInfo(e.args, argtypes), sstate, sv) + if !isready(res) + nextstate = 0x2 + return false + @label state2 + end + callresult[] = res[] + return true + end + evalargs(interp, sv) || push!(sv.tasks, evalargs) + return callresult + else + return Future(abstract_eval_basic_statement(interp, e, sstate, sv)) + end +end + function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState) + callee = e.args[1] + if isexpr(callee, :call) && length(callee.args) > 1 && callee.args[1] == GlobalRef(Core, :tuple) + # NOTE these expressions are not properly linearized + abstract_eval_nonlinearized_foreigncall_name(interp, callee.args[2], sstate, sv) + if length(callee.args) > 2 + abstract_eval_nonlinearized_foreigncall_name(interp, callee.args[3], sstate, sv) + end + else + abstract_eval_value(interp, callee, sstate, sv) + end mi = frame_instance(sv) t = sp_type_rewrap(e.args[2], mi, true) for i = 3:length(e.args) diff --git a/test/ccall.jl b/test/ccall.jl index f193d16fc09e2..048d74af7c298 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1979,3 +1979,11 @@ let llvm = sprint(code_llvm, gc_safe_ccall, ()) # check for the gc_safe store @test occursin("store atomic i8 2", llvm) end + +module Test57749 +using Test, Zstd_jll +const prefix = "Zstd version: " +const sym = :ZSTD_versionString +get_zstd_version() = prefix * unsafe_string(ccall((sym, libzstd), Cstring, ())) +@test startswith(get_zstd_version(), "Zstd") +end From 4d38189929e496cbd19fc50eeb71d2b129756f20 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 11 Aug 2025 09:05:03 -0400 Subject: [PATCH 16/25] move threading news to threading section. Add reminder about threadid --- NEWS.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 94957fa86f5bb..cc3a766305841 100644 --- a/NEWS.md +++ b/NEWS.md @@ -33,13 +33,6 @@ New language features Language changes ---------------- -* Julia now defaults to 1 "interactive" thread, in addition to the 1 default "worker" thread. i.e. `-t1,1`. - This means in default configuration the main task and repl (when in interactive mode), which both run on - thread 1, now run within the `interactive` threadpool. The libuv IO loop also runs on thread 1, - helping efficient utilization of the worker threadpool used by `Threads.@spawn`. Asking for specifically 1 thread - (`-t1`/`JULIA_NUM_THREADS=1`) or passing `0` will disable the interactive thread i.e. `-t1,0` or `JULIA_NUM_THREADS=1,0` - , or `-tauto,0` etc. Asking for more than 1 thread will enable the interactive thread so - `-t2` will set the equivalent of `-t2,1` ([#57087]). * When a method is replaced with an exactly equivalent one, the old method is not deleted. Instead, the new method takes priority and becomes more specific than the old method. Thus if the new method is deleted later, the old method will resume operating. This can be useful in mocking frameworks (as in SparseArrays, @@ -88,6 +81,14 @@ Command-line option changes Multi-threading changes ----------------------- +* Julia now defaults to 1 "interactive" thread, in addition to the 1 default "worker" thread. i.e. `-t1,1`. + This means in default configuration the main task and repl (when in interactive mode), which both run on + thread 1, now run within the `interactive` threadpool. The libuv IO loop also runs on thread 1, + helping efficient utilization of the worker threadpool used by `Threads.@spawn`. Asking for specifically 1 thread + (`-t1`/`JULIA_NUM_THREADS=1`) or `0` interactive threads will disable the interactive thread i.e. `-t1,0` or `JULIA_NUM_THREADS=1,0` + , or `-tauto,0` etc. Asking for more than 1 thread will enable the interactive thread so + `-t2` will set the equivalent of `-t2,1`. As a reminder, buffers + [should not be managed based on `threadid()`](https://docs.julialang.org/en/v1/manual/multi-threading/#Using-@threads-without-data-races) ([#57087]). * New types are defined to handle the pattern of code that must run once per process, called a `OncePerProcess{T}` type, which allows defining a function that should be run exactly once the first time it is called, and then always return the same result value of type `T` From 7f40cfe341228fef3e7811237556ae6d215b7cad Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 6 Aug 2025 16:25:43 -0400 Subject: [PATCH 17/25] fix runtime cglobal builtin function implementation (#59210) This had failed to be updated for the LazyLibrary changes to codegen. (cherry picked from commit 46c2a5c7e1f970e83b408b6ddcba49aaa31d8329) --- src/ccall.cpp | 6 +++--- src/runtime_intrinsics.c | 35 +++++++++++++++-------------------- stdlib/Libdl/test/runtests.jl | 23 +++++++++++++++++------ 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/ccall.cpp b/src/ccall.cpp index 865278d525384..d364c4e97538d 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -606,6 +606,9 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va arg1 = update_julia_type(ctx, arg1, (jl_value_t*)jl_voidpointer_type); jl_ptr = emit_unbox(ctx, ctx.types().T_ptr, arg1, (jl_value_t*)jl_voidpointer_type); } + else if (jl_is_cpointer_type(jl_typeof(ptr))) { + fptr = *(void(**)(void))jl_data_ptr(ptr); + } else { out.gcroot = ptr; if (jl_is_tuple(ptr) && jl_nfields(ptr) == 1) { @@ -633,9 +636,6 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va } } } - else if (jl_is_cpointer_type(jl_typeof(ptr))) { - fptr = *(void(**)(void))jl_data_ptr(ptr); - } else if (jl_is_tuple(ptr) && jl_nfields(ptr) > 1) { jl_value_t *t0 = jl_fieldref(ptr, 0); if (jl_is_symbol(t0)) diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 2671bebfd7f55..265322b072e5e 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -634,7 +634,6 @@ JL_DLLEXPORT jl_value_t *jl_atomic_fence(jl_value_t *order_sym) JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) { JL_TYPECHK(cglobal, type, ty); - JL_GC_PUSH1(&v); jl_value_t *rt = ty == (jl_value_t*)jl_nothing_type ? (jl_value_t*)jl_voidpointer_type : // a common case (jl_value_t*)jl_apply_type1((jl_value_t*)jl_pointer_type, ty); @@ -643,24 +642,16 @@ JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) if (!jl_is_concrete_type(rt)) jl_error("cglobal: type argument not concrete"); + if (jl_is_pointer(v)) + return jl_bitcast(rt, v); + if (jl_is_tuple(v) && jl_nfields(v) == 1) v = jl_fieldref(v, 0); - if (jl_is_pointer(v)) { - v = jl_bitcast(rt, v); - JL_GC_POP(); - return v; - } - - char *f_lib = NULL; + jl_value_t *f_lib = NULL; + JL_GC_PUSH2(&v, &f_lib); if (jl_is_tuple(v) && jl_nfields(v) > 1) { - jl_value_t *t1 = jl_fieldref(v, 1); - if (jl_is_symbol(t1)) - f_lib = jl_symbol_name((jl_sym_t*)t1); - else if (jl_is_string(t1)) - f_lib = jl_string_data(t1); - else - JL_TYPECHK(cglobal, symbol, t1) + f_lib = jl_fieldref(v, 1); v = jl_fieldref(v, 0); } @@ -672,14 +663,18 @@ JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) else JL_TYPECHK(cglobal, symbol, v) - if (!f_lib) - f_lib = (char*)jl_dlfind(f_name); - void *ptr; - jl_dlsym(jl_get_library(f_lib), f_name, &ptr, 1); + if (f_lib) { + ptr = jl_lazy_load_and_lookup(f_lib, f_name); + } + else { + void *handle = jl_get_library((char*)jl_dlfind(f_name)); + jl_dlsym(handle, f_name, &ptr, 1); + } + JL_GC_POP(); + jl_value_t *jv = jl_gc_alloc(jl_current_task->ptls, sizeof(void*), rt); *(void**)jl_data_ptr(jv) = ptr; - JL_GC_POP(); return jv; } diff --git a/stdlib/Libdl/test/runtests.jl b/stdlib/Libdl/test/runtests.jl index ef7b8abf83337..159fa9d4f559d 100644 --- a/stdlib/Libdl/test/runtests.jl +++ b/stdlib/Libdl/test/runtests.jl @@ -266,7 +266,7 @@ mktempdir() do dir end ## Tests for LazyLibrary -@testset "LazyLibrary" begin; mktempdir() do dir +@testset "LazyLibrary" begin lclf_path = joinpath(private_libdir, "libccalllazyfoo.$(Libdl.dlext)") lclb_path = joinpath(private_libdir, "libccalllazybar.$(Libdl.dlext)") @@ -278,7 +278,7 @@ end global lclf_loaded = false global lclb_loaded = false - # We don't provide `dlclose()` on `LazyLibrary`'s, you have to manage it yourself: + # We don't provide `dlclose()` on `LazyLibrary`'s since it is dangerous, you have to manage it yourself: function close_libs() global lclf_loaded = false global lclb_loaded = false @@ -294,8 +294,12 @@ end @test !any(contains.(dllist(), lclb_path)) end - global libccalllazyfoo = LazyLibrary(lclf_path; on_load_callback=() -> global lclf_loaded = true) - global libccalllazybar = LazyLibrary(lclb_path; dependencies=[libccalllazyfoo], on_load_callback=() -> global lclb_loaded = true) + let libccalllazyfoo = LazyLibrary(lclf_path; on_load_callback=() -> global lclf_loaded = true), + libccalllazybar = LazyLibrary(lclb_path; dependencies=[libccalllazyfoo], on_load_callback=() -> global lclb_loaded = true) + eval(:(const libccalllazyfoo = $libccalllazyfoo)) + eval(:(const libccalllazybar = $libccalllazybar)) + end + Core.@latestworld # Creating `LazyLibrary` doesn't actually load anything @test !lclf_loaded @@ -308,7 +312,8 @@ end close_libs() # Test that the library gets loaded when you use `ccall()` - @test ccall((:bar, libccalllazybar), Cint, (Cint,), 2) == 6 + compiled_bar() = ccall((:bar, libccalllazybar), Cint, (Cint,), 2) + @test ccall((:bar, libccalllazybar), Cint, (Cint,), 2) == compiled_bar() == 6 @test lclf_loaded @test lclb_loaded close_libs() @@ -324,11 +329,17 @@ end @test lclf_loaded close_libs() + # Test that `cglobal()` works, both compiled and runtime emulation + compiled_cglobal() = cglobal((:bar, libccalllazybar)) + @test cglobal((:bar, libccalllazybar)) === compiled_cglobal() === dlsym(dlopen(libccalllazybar), :bar) + @test lclf_loaded + close_libs() + # Test that we can use lazily-evaluated library names: libname = LazyLibraryPath(private_libdir, "libccalllazyfoo.$(Libdl.dlext)") lazy_name_lazy_lib = LazyLibrary(libname) @test dlpath(lazy_name_lazy_lib) == realpath(string(libname)) -end; end +end @testset "Docstrings" begin @test isempty(Docs.undocumented_names(Libdl)) From 721648259ceffd67ff882a4cc013dfc2e2714897 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 12 Aug 2025 14:30:06 -0300 Subject: [PATCH 18/25] Fix compile time regression for null ptr comparisons (#59259) Fixes the compile time regression in https://github.com/JuliaLang/julia/issues/59134 (does not address the performance regression that should be tracked and bisected) (cherry picked from commit 920df7abc9b5bf47d0589d9768b41e41421ad683) --- src/cgutils.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 47a26718166d4..65fbd80c303b5 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1601,6 +1601,18 @@ static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name, ctx.builder.SetInsertPoint(ifok); } + +static bool has_known_null_nullptr(Type *T) +{ + if (auto PT = cast(T)) { + auto addrspace = PT->getAddressSpace(); + if (addrspace == AddressSpace::Generic || (AddressSpace::FirstSpecial <= addrspace && addrspace <= AddressSpace::LastSpecial)) { + return true; + } + } + return false; +} + // ctx.builder.CreateIsNotNull(v) lowers incorrectly in non-standard // address spaces where null is not zero // TODO: adapt to https://github.com/llvm/llvm-project/pull/131557 once merged @@ -1608,10 +1620,11 @@ static Value *null_pointer_cmp(jl_codectx_t &ctx, Value *v) { ++EmittedNullchecks; Type *T = v->getType(); - return ctx.builder.CreateICmpNE( - v, - ctx.builder.CreateAddrSpaceCast( - Constant::getNullValue(ctx.builder.getPtrTy(0)), T)); + if (has_known_null_nullptr(T)) + return ctx.builder.CreateIsNotNull(v); + else + return ctx.builder.CreateICmpNE(v, ctx.builder.CreateAddrSpaceCast( + Constant::getNullValue(ctx.builder.getPtrTy(0)), T)); } From 7cd4fff29b48c1d3655ecb956767e3d0ce87606a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 24 Feb 2025 08:57:57 -0500 Subject: [PATCH 19/25] make compile go faster with native caches (#57500) Not sure why we make sure compile go slow, so make it go fast. (cherry picked from commit 095fc99b69459d43c2ab3267ade9f491f52b92cc) --- src/staticdata.c | 4 +--- sysimage.mk | 8 +++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/staticdata.c b/src/staticdata.c index 5d575188ab5bf..49cbb4e6a8296 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -3821,9 +3821,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, htable_new(&new_dt_objs, 0); arraylist_new(&deser_sym, 0); - // in --build mode only use sysimg data, not precompiled native code - int imaging_mode = jl_generating_output() && !jl_options.incremental; - if (imaging_mode || jl_options.use_sysimage_native_code != JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES || IMAGE_NATIVE_CODE_TAINTED) { + if (jl_options.use_sysimage_native_code != JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES || IMAGE_NATIVE_CODE_TAINTED) { memset(&image->fptrs, 0, sizeof(image->fptrs)); image->gvars_base = NULL; IMAGE_NATIVE_CODE_TAINTED = 1; diff --git a/sysimage.mk b/sysimage.mk index abb5243de4275..5faffc6b76990 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -73,7 +73,13 @@ $(build_private_libdir)/basecompiler.ji: $(COMPILER_SRCS) --startup-file=no --warn-overwrite=yes --depwarn=error -g$(BOOTSTRAP_DEBUG_LEVEL) -O1 Base_compiler.jl --buildroot $(RELBUILDROOT) --dataroot $(RELDATADIR)) @mv $@.tmp $@ -$(build_private_libdir)/sys.ji: $(build_private_libdir)/basecompiler.ji $(JULIAHOME)/VERSION $(BASE_SRCS) $(STDLIB_SRCS) +$(build_private_libdir)/basecompiler-o.a $(build_private_libdir)/basecompiler-bc.a: $(build_private_libdir)/basecompiler-%.a : $(COMPILER_SRCS) + @$(call PRINT_JULIA, cd $(JULIAHOME)/base && \ + JULIA_NUM_THREADS=1 $(call spawn,$(JULIA_EXECUTABLE)) -C "$(JULIA_CPU_TARGET)" $(HEAPLIM) --output-$* $(call cygpath_w,$@).tmp \ + --startup-file=no --warn-overwrite=yes -g$(BOOTSTRAP_DEBUG_LEVEL) -O1 Base_compiler.jl --buildroot $(RELBUILDROOT) --dataroot $(RELDATADIR)) + @mv $@.tmp $@ + +$(build_private_libdir)/sys.ji: $(build_private_libdir)/basecompiler.$(SHLIB_EXT) $(JULIAHOME)/VERSION $(BASE_SRCS) $(STDLIB_SRCS) @$(call PRINT_JULIA, cd $(JULIAHOME)/base && \ if ! JULIA_BINDIR=$(call cygpath_w,$(build_bindir)) WINEPATH="$(call cygpath_w,$(build_bindir));$$WINEPATH" \ JULIA_NUM_THREADS=1 $(call spawn, $(JULIA_EXECUTABLE)) -g1 -O1 -C "$(JULIA_CPU_TARGET)" $(HEAPLIM) --output-ji $(call cygpath_w,$@).tmp $(JULIA_SYSIMG_BUILD_FLAGS) \ From f41f678a74016231cb6c30233156db07255dd646 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 15 Aug 2025 15:48:00 -0400 Subject: [PATCH 20/25] fix #59252, make `args` in juliac like it is elsewhere (#59279) Also set PROGRAM_FILE and ARGS. fixes #59252 (cherry picked from commit 7b9f53231296ea8a368ec802b13d83df04a21802) --- contrib/juliac/juliac-buildscript.jl | 3 +++ test/trimming/hello.jl | 1 + 2 files changed, 4 insertions(+) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 40b35c66a829f..fedd844809530 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -34,6 +34,9 @@ import Base.Experimental.entrypoint # for use as C main if needed function _main(argc::Cint, argv::Ptr{Ptr{Cchar}})::Cint args = ccall(:jl_set_ARGS, Any, (Cint, Ptr{Ptr{Cchar}}), argc, argv)::Vector{String} + setglobal!(Base, :PROGRAM_FILE, args[1]) + popfirst!(args) + append!(Base.ARGS, args) return Main.main(args) end diff --git a/test/trimming/hello.jl b/test/trimming/hello.jl index 579ef4e18de38..bb2ca585f3662 100644 --- a/test/trimming/hello.jl +++ b/test/trimming/hello.jl @@ -5,6 +5,7 @@ end function @main(args::Vector{String})::Cint println(Core.stdout, str()) + println(Core.stdout, PROGRAM_FILE) foreach(x->println(Core.stdout, x), args) return 0 end From 97ead5883425278aa3ed30e8e05a29f3b56628b4 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Mon, 18 Aug 2025 12:23:58 -0300 Subject: [PATCH 21/25] Use Zlib instead of zstd --- test/ccall.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/ccall.jl b/test/ccall.jl index 048d74af7c298..5130b7311c9f0 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1980,10 +1980,11 @@ let llvm = sprint(code_llvm, gc_safe_ccall, ()) @test occursin("store atomic i8 2", llvm) end +# This test is different on master but we need to use zlib on 1.12 module Test57749 -using Test, Zstd_jll -const prefix = "Zstd version: " -const sym = :ZSTD_versionString -get_zstd_version() = prefix * unsafe_string(ccall((sym, libzstd), Cstring, ())) -@test startswith(get_zstd_version(), "Zstd") + using Test, Zlib_jll + const prefix = "Zlib version: " + const sym = :zlibVersion + get_zlib_version() = prefix * unsafe_string(ccall((sym, libz), Cstring, ())) + @test startswith(get_zlib_version(), "Zlib") end From 975aa9e897d9ff29690266174efab9000306959b Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:47:15 -0400 Subject: [PATCH 22/25] juliac: apply `buildscript` modifications after user code (#59297) These modifications to the MethodTable should be performed at the "link-stage", after all user top-level code has executed. This should be closer to the upcoming 1.13 pipeline, which will move all top-level user code to its standard package execution context. This also makes any user-provided functionality for type printing available to the `TrimVerifier`. Resolves https://github.com/JuliaLang/julia/issues/59280. --- contrib/juliac/juliac-buildscript.jl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index fedd844809530..4152697de2ea1 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -2,10 +2,6 @@ # Script to run in the process that generates juliac's object file output -# Run the verifier in the current world (before modifications), so that error -# messages and types print in their usual way. -Core.Compiler._verify_trim_world_age[] = Base.get_world_counter() - # Initialize some things not usually initialized when output is requested Sys.__init__() Base.init_depot_path() @@ -23,10 +19,6 @@ if Base.get_bool_env("JULIA_USE_FLISP_PARSER", false) === false Base.JuliaSyntax.enable_in_core!() end -if Base.JLOptions().trim != 0 - include(joinpath(@__DIR__, "juliac-trim-base.jl")) -end - # Load user code import Base.Experimental.entrypoint @@ -78,7 +70,12 @@ let mod = Base.include(Main, ARGS[1]) end end +# Run the verifier in the current world (before build-script modifications), +# so that error messages and types print in their usual way. +Core.Compiler._verify_trim_world_age[] = Base.get_world_counter() + if Base.JLOptions().trim != 0 + include(joinpath(@__DIR__, "juliac-trim-base.jl")) include(joinpath(@__DIR__, "juliac-trim-stdlib.jl")) end From a2b0e15f807b45a3eb04bb59a1f23706190c9574 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:47:32 -0400 Subject: [PATCH 23/25] Add `_assert_to_string` indirection for `Expr`-evaluated `@assert` (#59298) Not sure why this wasn't using this before since the code is effectively identical, but this allows: ```julia @assert false "$("Hello,") assertion!" ``` to be `--trim`-"compatible" (unfortunately by deleting the assertion message...) --- base/error.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/base/error.jl b/base/error.jl index 3ea7210652dad..ac58aed7649a3 100644 --- a/base/error.jl +++ b/base/error.jl @@ -233,14 +233,12 @@ macro assert(ex, msgs...) msg = msg # pass-through elseif !isempty(msgs) && (isa(msg, Expr) || isa(msg, Symbol)) # message is an expression needing evaluating - # N.B. To reduce the risk of invalidation caused by the complex callstack involved - # with `string`, use `inferencebarrier` here to hide this `string` from the compiler. - msg = :(Main.Base.inferencebarrier(Main.Base.string)($(esc(msg)))) + msg = :($_assert_tostring($(esc(msg)))) elseif isdefined(Main, :Base) && isdefined(Main.Base, :string) && applicable(Main.Base.string, msg) msg = Main.Base.string(msg) else # string() might not be defined during bootstrap - msg = :(_assert_tostring($(Expr(:quote,msg)))) + msg = :($_assert_tostring($(Expr(:quote,msg)))) end return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) end From 7831da6a1d3eef2e4c855f8ab4d2ecc205b0c570 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Mon, 18 Aug 2025 10:48:24 -0300 Subject: [PATCH 24/25] Fix printing of errors in juliac + searchsorted functions for LinearAlgebra (#59299) We may want to move this changes to base proper. @LilithHafner should sort functions just always specialize on the `by`/`lt` params? It currently sometimes relies on inlining for good performance --------- Co-authored-by: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Co-authored-by: Cody Tapscott --- contrib/juliac/juliac-buildscript.jl | 1 + contrib/juliac/juliac-trim-base.jl | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 4152697de2ea1..d2875ef32bd28 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -4,6 +4,7 @@ # Initialize some things not usually initialized when output is requested Sys.__init__() +Base.reinit_stdio() Base.init_depot_path() Base.init_load_path() Base.init_active_project() diff --git a/contrib/juliac/juliac-trim-base.jl b/contrib/juliac/juliac-trim-base.jl index 96fed77969c97..244a643a1a76f 100644 --- a/contrib/juliac/juliac-trim-base.jl +++ b/contrib/juliac/juliac-trim-base.jl @@ -104,10 +104,24 @@ end mapreduce_empty(::typeof(identity), op::F, T) where {F} = reduce_empty(op, T) mapreduce_empty(::typeof(abs), op::F, T) where {F} = abs(reduce_empty(op, T)) mapreduce_empty(::typeof(abs2), op::F, T) where {F} = abs2(reduce_empty(op, T)) + + # this function is not `--trim`-compatible if it resolves to a Varargs{...} specialization + # and since it only has 1-argument methods this happens too often by default (just 2-3 args) + setfield!(typeof(throw_eachindex_mismatch_indices).name, :max_args, Int32(5), :monotonic) end @eval Base.Sys begin __init_build() = nothing end +# Used for LinearAlgebre ldiv with SVD +for s in [:searchsortedfirst, :searchsortedlast, :searchsorted] + @eval Base.Sort begin + # identical to existing Base def. but specializes on `lt` / `by` + $s(v::AbstractVector, x, o::Ordering) = $s(v,x,firstindex(v),lastindex(v),o) + $s(v::AbstractVector, x; + lt::T=isless, by::F=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) where {T,F} = + $s(v,x,ord(lt,by,rev,order)) + end +end @eval Base.GMP begin function __init__() try @@ -159,3 +173,9 @@ end return Time(h, m, s, ms) end end + +@eval Base.CoreLogging begin + # Disable logging (TypedCallable is required to support the existing dynamic + # logger interface, but it's not implemented yet) + @inline current_logger_for_env(std_level::LogLevel, group, _module) = nothing +end From 54ccaadc08ab9aaf7c7d5a0384301b3531bf8faa Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:04:31 -0400 Subject: [PATCH 25/25] Make `Base.disable_library_threading_hooks` ephemeral to the process (#59232) This prevents accidentally registering hooks during the sysimage build that would persist to run-time (unlike what happens when registering hooks while building a pkgimage) ~~Required for https://github.com/JuliaLang/LinearAlgebra.jl/pull/1407~~ --- base/initdefs.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/base/initdefs.jl b/base/initdefs.jl index 90f213c468bba..afe7578036baa 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -494,10 +494,13 @@ end ## hook for disabling threaded libraries ## library_threading_enabled::Bool = true -const disable_library_threading_hooks = [] + +# Base.OncePerProcess ensures that any registered hooks do not outlive the session. +# (even if they are registered during the sysimage build process by top-level code) +const disable_library_threading_hooks = Base.OncePerProcess(Vector{Any}) function at_disable_library_threading(f) - push!(disable_library_threading_hooks, f) + push!(disable_library_threading_hooks(), f) if !library_threading_enabled disable_library_threading() end @@ -506,8 +509,8 @@ end function disable_library_threading() global library_threading_enabled = false - while !isempty(disable_library_threading_hooks) - f = pop!(disable_library_threading_hooks) + while !isempty(disable_library_threading_hooks()) + f = pop!(disable_library_threading_hooks()) try f() catch err