From 0c1aa8dd7b795b98734f9c722383b6ae31e048a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Wed, 24 Sep 2025 22:57:06 +0200 Subject: [PATCH 01/15] Set world bounds on `CodeInfo` created for `OpaqueClosure(::IRCode)` (#59631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting world bounds on the created `CodeInfo` allows us to interpret opaque closures faster. Taking the following example: ```julia julia> f(x, y) = x + y f (generic function with 1 method) julia> ir = Base.code_ircode_by_type(Tuple{typeof(f), Int, Int})[1][1] 1 1 ─ %1 = intrinsic Base.add_int(_2, _3)::Int64 │╻ + └── return %1 ││ julia> ir.argtypes[1] = Tuple{} Tuple{} julia> oc = Core.OpaqueClosure(ir; do_compile=true) (::Int64, ::Int64)->◌::Int64 ``` this is what we emitted before ```julia julia> @code_typed oc(1, 2) Pair{Core.CodeInfo, Any}(CodeInfo( @ REPL[8]:1 within `f` ┌ @ int.jl:87 within `+` 1 ─│ %1 = dynamic Base.add_int(none@_2, none@_3)::Int64 └──│ return %1 └ ), Int64) julia> using BenchmarkTools; @btime $oc(1, 2) 39.765 ns (0 allocations: 0 bytes) ``` and now: ```julia julia> @code_typed oc(1, 2) Pair{Core.CodeInfo, Any}(CodeInfo( @ REPL[93]:1 within `f` ┌ @ int.jl:87 within `+` 1 ─│ %1 = intrinsic Base.add_int(none@_2, none@_3)::Int64 └──│ return %1 └ ), Int64) julia> using BenchmarkTools; @btime $oc(1, 2) 2.678 ns (0 allocations: 0 bytes) ``` The overhead notably adds more and more with every statement, which for ~20 statements led to > 1 µs of overhead, and multiple allocations. This overhead is observed on 1.12+ only (1.11 evaluates as fast as with this change), which may have been surfaced by the partitioned bindings feature. (cherry picked from commit a5576b4ddb61ce10eefba752a21ef806c4d6fd93) --- Compiler/src/opaque_closure.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Compiler/src/opaque_closure.jl b/Compiler/src/opaque_closure.jl index d0a375c2a54b5..21f2640037893 100644 --- a/Compiler/src/opaque_closure.jl +++ b/Compiler/src/opaque_closure.jl @@ -48,6 +48,8 @@ function Core.OpaqueClosure(ir::IRCode, @nospecialize env...; end src.slotflags = fill(zero(UInt8), nargtypes) src.slottypes = copy(ir.argtypes) + src.min_world = ir.valid_worlds.min_world + src.max_world = ir.valid_worlds.max_world src.isva = isva src.nargs = UInt(nargtypes) src = ir_to_codeinf!(src, ir) From 2907ebd4dc4c479d4b8058ed396b3791df558caf Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Thu, 25 Sep 2025 23:41:14 +0200 Subject: [PATCH 02/15] Fix gcdx and lcm with mixed signed/unsigned arguments (#59628) Add `gcdx(a::Signed, b::Unsigned)` and `gcdx(a::Unsigned, b::Signed)` methods to fix #58025: ```julia julia> gcdx(UInt16(100), Int8(-101)) # pr (0x0001, 0xffff, 0xffff) julia> gcdx(UInt16(100), Int8(-101)) # master, incorrect result (0x0005, 0xf855, 0x0003) ``` Also add the equivalent methods for `lcm` to fix the systematic `InexactError` when one argument is a negative `Signed` and the other is any `Unsigned`: ```julia julia> lcm(UInt16(100), Int8(-101)) # pr 0x2774 julia> lcm(UInt16(100), Int8(-101)) # master, error ERROR: InexactError: trunc(UInt16, -101) Stacktrace: [1] throw_inexacterror(func::Symbol, to::Type, val::Int8) @ Core ./boot.jl:866 [2] check_sign_bit @ ./boot.jl:872 [inlined] [3] toUInt16 @ ./boot.jl:958 [inlined] [4] UInt16 @ ./boot.jl:1011 [inlined] [5] convert @ ./number.jl:7 [inlined] [6] _promote @ ./promotion.jl:379 [inlined] [7] promote @ ./promotion.jl:404 [inlined] [8] lcm(a::UInt16, b::Int8) @ Base ./intfuncs.jl:152 [9] top-level scope @ REPL[62]:1 ``` Inspired by https://github.com/JuliaLang/julia/pull/59487#issuecomment-3258209203. The difference is that the solution proposed in this PR keeps the current correct result type for inputs such as `(::Int16, ::UInt8)`. (cherry picked from commit 4f1e471d54e93a79aee246bba100032ab3995a8c) --- base/intfuncs.jl | 12 ++++++++++++ test/intfuncs.jl | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 3f26d2597f9d9..7b665caad6b17 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -148,6 +148,8 @@ gcd(a::Rational) = checked_abs(a.num) // a.den lcm(a::Union{Integer,Rational}) = gcd(a) gcd(a::Unsigned, b::Signed) = gcd(promote(a, abs(b))...) gcd(a::Signed, b::Unsigned) = gcd(promote(abs(a), b)...) +lcm(a::Unsigned, b::Signed) = lcm(promote(a, abs(b))...) +lcm(a::Signed, b::Unsigned) = lcm(promote(abs(a), b)...) gcd(a::Real, b::Real) = gcd(promote(a,b)...) lcm(a::Real, b::Real) = lcm(promote(a,b)...) gcd(a::Real, b::Real, c::Real...) = gcd(a, gcd(b, c...)) @@ -252,6 +254,16 @@ function gcdx(a::Real, b::Real, cs::Real...) d′, x, ys... = gcdx(d, cs...) return d′, i*x, j*x, ys... end +function gcdx(a::Signed, b::Unsigned) + R = promote_type(typeof(a), typeof(b)) + _a = a % signed(R) # handle the case a == typemin(typeof(a)) if R != typeof(a) + d, u, v = gcdx(promote(abs(_a), b)...) + d, flipsign(u, a), v +end +function gcdx(a::Unsigned, b::Signed) + d, v, u = gcdx(b, a) + d, u, v +end # multiplicative inverse of n mod m, error if none diff --git a/test/intfuncs.jl b/test/intfuncs.jl index adcfee2a050dd..5c5c7510d9da8 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -216,6 +216,35 @@ end x, y = Int8(-12), UInt(100) d, u, v = gcdx(x, y) @test x*u + y*v == d + +end + +# issue #58025 +@testset "Mixed signed/unsigned types" begin + cases = [ # adapted from https://github.com/JuliaLang/julia/pull/59487#issuecomment-3258209203 + (UInt16(100), Int8(-101)), + (Int8(-50), UInt16(75)), + (UInt32(12), Int16(-18)), + (Int64(-24), UInt8(36)), + (UInt8(15), Int16(-25)), + (Int32(-42), UInt64(56)), + (UInt128(1000), Int32(-1500)), + (UInt64(0), Int32(-5)), + (Int16(-7), UInt8(0)), + (Int8(-14), UInt8(13)), + ] + for (a, b) in cases + g1 = gcd(a, b) + g2, s, t = gcdx(a, b) + @test g1 === g2 + @test s*a + t*b == g2 + @test g2 >= 0 + @test lcm(a, b) === convert(typeof(g1), lcm(widen(a), widen(b))) + end + + @test gcdx(Int16(-32768), Int8(-128)) === (Int16(128), Int16(0), Int16(-1)) + @test gcdx(Int8(-128), UInt16(256)) === (0x0080, 0xffff, 0x0000) + @test_broken gcd(Int8(-128), UInt16(256)) === 0x0080 end @testset "gcd/lcm/gcdx for custom types" begin From 48d5276e14ee394aac231c8a3967b8ab18680dd9 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Wed, 1 Oct 2025 04:21:41 +0900 Subject: [PATCH 03/15] effects: improve nothrow modeling for `Core._svec_len` (#59706) --- Compiler/src/tfuncs.jl | 12 ++++++++---- Compiler/test/effects.jl | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index 33c866c401811..5070e4ba16d00 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -586,15 +586,19 @@ add_tfunc(nfields, 1, 1, nfields_tfunc, 1) add_tfunc(Core._expr, 1, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->Expr), 100) add_tfunc(svec, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->SimpleVector), 20) -@nospecs function _svec_len_tfunc(𝕃::AbstractLattice, s) +@nospecs function _svec_len_tfunc(::AbstractLattice, s) if isa(s, Const) && isa(s.val, SimpleVector) return Const(length(s.val)) end return Int end add_tfunc(Core._svec_len, 1, 1, _svec_len_tfunc, 1) +@nospecs function _svec_len_nothrow(𝕃::AbstractLattice, s) + ⊑ = partialorder(𝕃) + return s ⊑ SimpleVector +end -@nospecs function _svec_ref_tfunc(𝕃::AbstractLattice, s, i) +@nospecs function _svec_ref_tfunc(::AbstractLattice, s, i) if isa(s, Const) && isa(i, Const) s, i = s.val, i.val if isa(s, SimpleVector) && isa(i, Int) @@ -604,7 +608,7 @@ add_tfunc(Core._svec_len, 1, 1, _svec_len_tfunc, 1) return Any end add_tfunc(Core._svec_ref, 2, 2, _svec_ref_tfunc, 1) -@nospecs function typevar_tfunc(𝕃::AbstractLattice, n, lb_arg, ub_arg) +@nospecs function typevar_tfunc(::AbstractLattice, n, lb_arg, ub_arg) lb = Union{} ub = Any ub_certain = lb_certain = true @@ -2363,7 +2367,7 @@ function _builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f::Builtin), argt return compilerbarrier_nothrow(argtypes[1], nothing) elseif f === Core._svec_len na == 1 || return false - return _svec_len_tfunc(𝕃, argtypes[1]) isa Const + return _svec_len_nothrow(𝕃, argtypes[1]) elseif f === Core._svec_ref na == 2 || return false return _svec_ref_tfunc(𝕃, argtypes[1], argtypes[2]) isa Const diff --git a/Compiler/test/effects.jl b/Compiler/test/effects.jl index 79d16602a8d92..2b6895d5f9da0 100644 --- a/Compiler/test/effects.jl +++ b/Compiler/test/effects.jl @@ -1471,3 +1471,5 @@ let effects = Base.infer_effects((Core.SimpleVector,Int); optimize=false) do sve @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end + +@test Compiler.is_nothrow(Base.infer_effects(length, (Core.SimpleVector,))) From 8e5455f33451b7b2213983e10bc4d687ba2d1230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20H=C3=A9not?= <38465572+OlivierHnt@users.noreply.github.com> Date: Sun, 27 Jul 2025 13:27:14 +0200 Subject: [PATCH 04/15] Fix rounding when converting Rational to BigFloat (#59063) --- base/mpfr.jl | 8 +++++++- test/mpfr.jl | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/base/mpfr.jl b/base/mpfr.jl index 2175fb56cc526..cd73506c9e96f 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -391,12 +391,18 @@ BigFloat(x::Union{Float16,Float32}, r::MPFRRoundingMode=rounding_raw(BigFloat); BigFloat(Float64(x), r; precision=precision) function BigFloat(x::Rational, r::MPFRRoundingMode=rounding_raw(BigFloat); precision::Integer=_precision_with_base_2(BigFloat)) + r_den = _opposite_round(r) setprecision(BigFloat, precision) do setrounding_raw(BigFloat, r) do - BigFloat(numerator(x))::BigFloat / BigFloat(denominator(x))::BigFloat + BigFloat(numerator(x))::BigFloat / BigFloat(denominator(x), r_den)::BigFloat end end end +function _opposite_round(r::MPFRRoundingMode) + r == MPFRRoundUp && return MPFRRoundDown + r == MPFRRoundDown && return MPFRRoundUp + return r +end function tryparse(::Type{BigFloat}, s::AbstractString; base::Integer=0, precision::Integer=_precision_with_base_2(BigFloat), rounding::MPFRRoundingMode=rounding_raw(BigFloat)) !isempty(s) && isspace(s[end]) && return tryparse(BigFloat, rstrip(s), base = base) diff --git a/test/mpfr.jl b/test/mpfr.jl index 3bb8768b280d5..627d49935795d 100644 --- a/test/mpfr.jl +++ b/test/mpfr.jl @@ -35,6 +35,9 @@ import Base.MPFR @test typeof(BigFloat(1//1)) == BigFloat @test typeof(BigFloat(one(Rational{BigInt}))) == BigFloat + rat = 1 // (big(2)^300 + 1) + @test BigFloat(rat, RoundDown) < rat < BigFloat(rat, RoundUp) + @test BigFloat(-rat, RoundUp) < -rat < BigFloat(-rat, RoundDown) # BigFloat constructor respects global precision when not specified let prec = precision(BigFloat) < 16 ? 256 : precision(BigFloat) ÷ 2 From 3c7e88b6b73be31df965d6ed7164a945d0647773 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 29 Sep 2025 11:17:14 -0400 Subject: [PATCH 05/15] [Compiler] fix stdout/stderr to point to Core (#59672) Followup to #58425 Fixes #56645 --- Compiler/src/Compiler.jl | 8 +++++--- Compiler/test/validation.jl | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index 080cdc6f7ee0e..01f2dbe33b674 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -41,9 +41,8 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc MethodTable, MethodCache, PartialOpaque, SimpleVector, TypeofVararg, _apply_iterate, apply_type, compilerbarrier, donotdelete, memoryref_isassigned, memoryrefget, memoryrefnew, memoryrefoffset, memoryrefset!, print, println, show, svec, - typename, unsafe_write, write + typename, unsafe_write, write, stdout, stderr -using Base using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer, PARTITION_KIND_GLOBAL, PARTITION_KIND_UNDEF_CONST, PARTITION_KIND_BACKDATED_CONST, PARTITION_KIND_DECLARED, PARTITION_FLAG_DEPWARN, @@ -64,7 +63,10 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali partition_restriction, quoted, rename_unionall, rewrap_unionall, specialize_method, structdiff, tls_world_age, unconstrain_vararg_length, unionlen, uniontype_layout, uniontypes, unsafe_convert, unwrap_unionall, unwrapva, vect, widen_diagonal, - _uncompressed_ir, maybe_add_binding_backedge! + _uncompressed_ir, maybe_add_binding_backedge!, + devnull, devnull as stdin + +using Base using Base.Order import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, get!, diff --git a/Compiler/test/validation.jl b/Compiler/test/validation.jl index f01ff85e4321c..766f887c05860 100644 --- a/Compiler/test/validation.jl +++ b/Compiler/test/validation.jl @@ -4,6 +4,15 @@ using Test, Core.IR include("setup_Compiler.jl") +@testset "stdio validation" begin + for s in (:stdout, :stderr, :print, :println, :write) + @test getglobal(Compiler, s) === getglobal(Core, s) + @test isconst(Compiler, s) + end + @test Compiler.stdin === devnull + @test isconst(Compiler, :stdin) +end + function f22938(a, b, x...) nothing nothing From 57501f33826a41e9c8d2e0f8e7dc049f3bca615c Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:26:05 +0900 Subject: [PATCH 06/15] do not specialze for `abstract_eval_nonlinearized_foreigncall_name` (#59722) Following up JuliaLang/julia#58872. Since `e` is potentially arbitrary runtime value, we should not specialize on it. --- Compiler/src/abstractinterpretation.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 3c4de73b4c36a..69ec3c27de903 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3444,9 +3444,13 @@ function refine_partial_type(@nospecialize t) return t end -abstract_eval_nonlinearized_foreigncall_name(interp::AbstractInterpreter, e, sstate::StatementState, sv::IRInterpretationState) = nothing +abstract_eval_nonlinearized_foreigncall_name( + ::AbstractInterpreter, @nospecialize(e), ::StatementState, ::IRInterpretationState + ) = nothing -function abstract_eval_nonlinearized_foreigncall_name(interp::AbstractInterpreter, e, sstate::StatementState, sv::AbsIntState) +function abstract_eval_nonlinearized_foreigncall_name( + interp::AbstractInterpreter, @nospecialize(e), sstate::StatementState, sv::InferenceState + ) if isexpr(e, :call) n = length(e.args) argtypes = Vector{Any}(undef, n) From 49d9f4eadbcaa89d1b0fde58d9a5c733f6da3315 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 2 Oct 2025 03:27:11 -0400 Subject: [PATCH 07/15] Compiler: fix inferred nothrow effects for add_ptr and sub_ptr (#59720) Previously, add_ptr and sub_ptr intrinsics were incorrectly inferred as potentially throwing because they fell through to the general primitive type check, which was incorrect after #53687 changed them. This adds explicit handling in intrinsic_exct to return Union{} (nothrow) when given correct argument types (Ptr and UInt), similar to #57398. Fixes #57557 Written by Claude --- Compiler/src/tfuncs.jl | 7 +++++++ Compiler/test/effects.jl | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/Compiler/src/tfuncs.jl b/Compiler/src/tfuncs.jl index 5070e4ba16d00..fcead89d0a151 100644 --- a/Compiler/src/tfuncs.jl +++ b/Compiler/src/tfuncs.jl @@ -2978,6 +2978,13 @@ function intrinsic_exct(𝕃::AbstractLattice, f::IntrinsicFunction, argtypes::V return Union{} end + if f === Intrinsics.add_ptr || f === Intrinsics.sub_ptr + if !(argtypes[1] ⊑ Ptr && argtypes[2] ⊑ UInt) + return TypeError + end + return Union{} + end + # The remaining intrinsics are math/bits/comparison intrinsics. # All the non-floating point intrinsics work on primitive values of the same type. isshift = f === shl_int || f === lshr_int || f === ashr_int diff --git a/Compiler/test/effects.jl b/Compiler/test/effects.jl index 2b6895d5f9da0..e06d885fcefd7 100644 --- a/Compiler/test/effects.jl +++ b/Compiler/test/effects.jl @@ -1438,6 +1438,11 @@ let effects = Base.infer_effects(Core.Intrinsics.pointerset, Tuple{Vararg{Any}}) @test Compiler.is_consistent(effects) @test !Compiler.is_effect_free(effects) end +@test Compiler.intrinsic_nothrow(Core.Intrinsics.add_ptr, Any[Ptr{Int}, UInt]) +@test Compiler.intrinsic_nothrow(Core.Intrinsics.sub_ptr, Any[Ptr{Int}, UInt]) +@test !Compiler.intrinsic_nothrow(Core.Intrinsics.add_ptr, Any[UInt, UInt]) +@test !Compiler.intrinsic_nothrow(Core.Intrinsics.sub_ptr, Any[UInt, UInt]) +@test Compiler.is_nothrow(Base.infer_effects(+, Tuple{Ptr{UInt8}, UInt})) # effects modeling for atomic intrinsics # these functions especially need to be marked !effect_free since they imply synchronization for atomicfunc = Any[ From 352bf25f87ea6159a3a24cd4d2393fe05e790f8d Mon Sep 17 00:00:00 2001 From: Andy Dienes <51664769+adienes@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:43:08 -0400 Subject: [PATCH 08/15] fix `powermod` correctness on unsigned power of half typemax (#59680) the comment claims: > When the concrete type of p is signed and has the lowest value, > `p != 0 && p == -p` is equivalent to `p == typemin(typeof(p))` this is true, but fails to consider that `p == -p` is also true when `p = div(typemax(UInt), 2) + 1`, and that `p` is not necessarily signed leading to incorrect results on inputs like ``` julia> powermod(0x03, 0x80, 0x07) 0x01 ``` which should be `0x02` (cherry picked from commit bb3be0dc56f7c0f2cb645318d728280285d81933) --- base/intfuncs.jl | 22 ++++++++++++---------- test/intfuncs.jl | 3 +++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 7b665caad6b17..d660c6d0a8ecc 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -489,18 +489,20 @@ julia> powermod(5, 3, 19) function powermod(x::Integer, p::Integer, m::T) where T<:Integer p == 0 && return mod(one(m),m) # When the concrete type of p is signed and has the lowest value, - # `p != 0 && p == -p` is equivalent to `p == typemin(typeof(p))` for 2's complement representation. + # `p < 0 && p == -p` is equivalent to `p == typemin(typeof(p))` for 2's complement representation. # but will work for integer types like `BigInt` that don't have `typemin` defined # It needs special handling otherwise will cause overflow problem. - if p == -p - imod = invmod(x, m) - rhalf = powermod(imod, -(p÷2), m) - r::T = mod(widemul(rhalf, rhalf), m) - isodd(p) && (r = mod(widemul(r, imod), m)) - #else odd - return r - elseif p < 0 - return powermod(invmod(x, m), -p, m) + if p < 0 + if p == -p + imod = invmod(x, m) + rhalf = powermod(imod, -(p÷2), m) + r::T = mod(widemul(rhalf, rhalf), m) + isodd(p) && (r = mod(widemul(r, imod), m)) + #else odd + return r + else + return powermod(invmod(x, m), -p, m) + end end (m == 1 || m == -1) && return zero(m) b = oftype(m,mod(x,m)) # this also checks for divide by zero diff --git a/test/intfuncs.jl b/test/intfuncs.jl index 5c5c7510d9da8..dc800a3a00b08 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -352,6 +352,9 @@ end @test powermod(2, big(3), -5) == -2 @inferred powermod(2, -2, -5) @inferred powermod(big(2), -2, UInt(5)) + + @test powermod(-3, 0x80, 7) === 2 + @test powermod(0x03, 0x80, 0x07) === 0x02 end @testset "nextpow/prevpow" begin From afa2fcbc27c739139dbd27854872edb974742d84 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Thu, 2 Oct 2025 04:38:12 +0200 Subject: [PATCH 09/15] retain size for zero length multidimensional arrays in mmap (#59719) Fixes https://github.com/JuliaLang/julia/issues/44679 Co-authored-by: KristofferC (cherry picked from commit 65ef689c6e08b240be70c3e42efd544125014be6) --- stdlib/Mmap/src/Mmap.jl | 2 +- stdlib/Mmap/test/runtests.jl | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/stdlib/Mmap/src/Mmap.jl b/stdlib/Mmap/src/Mmap.jl index c527123d51bb8..fb6bb72026588 100644 --- a/stdlib/Mmap/src/Mmap.jl +++ b/stdlib/Mmap/src/Mmap.jl @@ -199,7 +199,7 @@ function mmap(io::IO, overflow && throw(ArgumentError("requested size prod($((len, dims...))) too large, would overflow typeof(size(T)) == $(typeof(len))")) end len >= 0 || throw(ArgumentError("requested size must be ≥ 0, got $len")) - len == 0 && return Array{T}(undef, ntuple(x->0,Val(N))) + len == 0 && return Array{T}(undef, dims) len < typemax(Int) - PAGESIZE || throw(ArgumentError("requested size must be < $(typemax(Int)-PAGESIZE), got $len")) offset >= 0 || throw(ArgumentError("requested offset must be ≥ 0, got $offset")) diff --git a/stdlib/Mmap/test/runtests.jl b/stdlib/Mmap/test/runtests.jl index 0c34c49d400a7..d129c0ee94295 100644 --- a/stdlib/Mmap/test/runtests.jl +++ b/stdlib/Mmap/test/runtests.jl @@ -11,12 +11,13 @@ GC.gc(); GC.gc() GC.gc(); GC.gc() @test mmap(file, Array{UInt8,3}, (1,1,11)) == reshape(t,(1,1,11)) GC.gc(); GC.gc() -@test mmap(file, Array{UInt8,3}, (11,0,1)) == Array{UInt8}(undef, (0,0,0)) +@test size(mmap(file, Array{UInt8,3}, (11,0,1))) == (11,0,1) @test mmap(file, Vector{UInt8}, (11,)) == t GC.gc(); GC.gc() @test mmap(file, Array{UInt8,2}, (1,11)) == t' GC.gc(); GC.gc() -@test mmap(file, Array{UInt8,2}, (0,12)) == Array{UInt8}(undef, (0,0)) +@test size(mmap(file, Array{UInt8,2}, (0,12))) == (0,12) +@test size(mmap(file, Matrix{Float32}, (10,0))) == (10,0) m = mmap(file, Array{UInt8,3}, (1,2,1)) @test m == reshape(b"He",(1,2,1)) finalize(m); m=nothing; GC.gc() From bb7a11993eac10744cfcb3d2d29e5ea63155bf41 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 2 Oct 2025 13:54:21 -0400 Subject: [PATCH 10/15] improve compile-ability of method errors (#59709) This was correct prior to changing closures to use Core.Typeof, but was not corrected when that PR was merged. This doesn't seem to quite fix issue #56861, since we used to have a precompile workload for this, and now REPL is a separate package. (cherry picked from commit 9c8886c741e850693855b61f1ab32e467f04e2d9) --- base/errorshow.jl | 16 ++++++++-------- base/experimental.jl | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index e771597d7b1e0..602e6c55cb388 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -259,7 +259,7 @@ function showerror(io::IO, ex::MethodError) is_arg_types = !isa(ex.args, Tuple) arg_types = is_arg_types ? ex.args : typesof(ex.args...) arg_types_param::SimpleVector = (unwrap_unionall(arg_types)::DataType).parameters - san_arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param] + san_arg_types_param = Any[rewrap_unionall(arg_types_param[i], arg_types) for i in 1:length(arg_types_param)] f = ex.f meth = methods_including_ambiguous(f, arg_types) if isa(meth, MethodList) && length(meth) > 1 @@ -399,10 +399,10 @@ function showerror_ambiguous(io::IO, meths, f, args::Type) sigfix = typeintersect(m.sig, sigfix) end if isa(unwrap_unionall(sigfix), DataType) && sigfix <: Tuple - let sigfix=sigfix - if all(m->morespecific(sigfix, m.sig), meths) + let sigfix=Core.Box(sigfix) + if all(m->morespecific(sigfix.contents, m.sig), meths) print(io, "\nPossible fix, define\n ") - show_tuple_as_call(io, :function, sigfix) + show_tuple_as_call(io, :function, sigfix.contents) else print(io, "To resolve the ambiguity, try making one of the methods more specific, or ") print(io, "adding a new method more specific than any of the existing applicable methods.") @@ -487,10 +487,10 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[]) # If isvarargtype then it checks whether the rest of the input arguments matches # the varargtype if Base.isvarargtype(sig[i]) - sigstr = (unwrapva(unwrap_unionall(sig[i])), "...") + sigstr = Core.svec(unwrapva(unwrap_unionall(sig[i])), "...") j = length(t_i) else - sigstr = (sig[i],) + sigstr = Core.svec(sig[i],) j = i end # Checks if the type of arg 1:i of the input intersects with the current method @@ -536,9 +536,9 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[]) for (k, sigtype) in enumerate(sig[length(t_i)+1:end]) sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype if Base.isvarargtype(sigtype) - sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...") + sigstr = Core.svec(unwrapva(sigtype::Core.TypeofVararg), "...") else - sigstr = (sigtype,) + sigstr = Core.svec(sigtype,) end if !((min(length(t_i), length(sig)) == 0) && k==1) print(iob, ", ") diff --git a/base/experimental.jl b/base/experimental.jl index efaf4bd33a820..8500d8c3ea91a 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -315,6 +315,7 @@ the handler for that type. This interface is experimental and subject to change or removal without notice. """ function show_error_hints(io, ex, args...) + @nospecialize hinters = get(_hint_handlers, typeof(ex), nothing) isnothing(hinters) && return for handler in hinters From 432074811b300e3660b5f1f4f81543cab71beaf3 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 3 Oct 2025 11:22:29 -0400 Subject: [PATCH 11/15] iobuffer: fix overflow OOB read/write bugs (#59728) maxsize is usually typemax, so need to be careful to not do comparisons after adding to it. Substracting from it should normally be perfectly fine. At worst we should compute a negative amount of space remaining. (cherry picked from commit 85687b522a6e73836b6ae95e65a8dc1723427d32) --- base/iobuffer.jl | 10 ++++++---- base/stream.jl | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/base/iobuffer.jl b/base/iobuffer.jl index ca2757201aae8..fee699287705b 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -604,7 +604,8 @@ end end # The fast path here usually checks there is already room, then does nothing. # When append is true, new data is added after io.size, not io.ptr - existing_space = min(lastindex(io.data), io.maxsize + get_offset(io)) - (io.append ? io.size : io.ptr - 1) + start_offset = io.append ? io.size : io.ptr - 1 + existing_space = min(lastindex(io.data) - start_offset, io.maxsize - (start_offset - get_offset(io))) if existing_space < nshort % Int # Outline this function to make it more likely that ensureroom inlines itself return ensureroom_slowpath(io, nshort, existing_space) @@ -824,12 +825,13 @@ function unsafe_write(to::GenericIOBuffer, p::Ptr{UInt8}, nb::UInt) append = to.append ptr = append ? size+1 : to.ptr data = to.data - to_write = min(nb, (min(Int(length(data))::Int, to.maxsize + get_offset(to)) - ptr + 1) % UInt) % Int + start_offset = ptr - 1 + to_write = max(0, min(nb, (min(Int(length(data))::Int - start_offset, to.maxsize - (start_offset - get_offset(to)))) % UInt) % Int) # Dispatch based on the type of data, to possibly allow using memcpy _unsafe_write(data, p, ptr, to_write % UInt) # Update to.size only if the ptr has advanced to higher than # the previous size. Otherwise, we just overwrote existing data - to.size = max(size, ptr + to_write - 1) + to.size = max(size, start_offset + to_write) # If to.append, we only update size, not ptr. if !append to.ptr = ptr + to_write @@ -867,7 +869,7 @@ end ptr = (to.append ? to.size+1 : to.ptr) # We have just ensured there is room for 1 byte, EXCEPT if we were to exceed # maxsize. So, we just need to check that here. - if ptr > to.maxsize + get_offset(to) + if ptr - get_offset(to) > to.maxsize return 0 else to.data[ptr] = a diff --git a/base/stream.jl b/base/stream.jl index 6103d4ff1bb31..836fe8391302e 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -617,7 +617,8 @@ end function alloc_request(buffer::IOBuffer, recommended_size::UInt) ensureroom(buffer, recommended_size) ptr = buffer.append ? buffer.size + 1 : buffer.ptr - nb = min(length(buffer.data), buffer.maxsize + get_offset(buffer)) - ptr + 1 + start_offset = ptr - 1 + nb = max(0, min(length(buffer.data) - start_offset, buffer.maxsize - (start_offset - get_offset(buffer)))) return (Ptr{Cvoid}(pointer(buffer.data, ptr)), nb) end From de55a32ecd6af794b29e9aa17decf1209e7969e3 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Sat, 4 Oct 2025 09:55:25 +0200 Subject: [PATCH 12/15] fix getting field docstrings for parametric structs (#59725) (cherry picked from commit 47908c8be8f1fe4cab106966dcbad8e1692efc3e) --- stdlib/REPL/src/docview.jl | 3 ++- stdlib/REPL/test/docview.jl | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index 5e0a30abb4137..52153a12f606f 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -640,7 +640,7 @@ function _repl(x, brief::Bool=true, mod::Module=Main, internal_accesses::Union{N docs = esc(:(@doc $x)) docs = if isfield(x) quote - if isa($(esc(x.args[1])), DataType) + if $(esc(x.args[1])) isa Type fielddoc($(esc(x.args[1])), $(esc(x.args[2]))) else $docs @@ -684,6 +684,7 @@ function fielddoc(binding::Binding, field::Symbol) end # As with the additional `doc` methods, this converts an object to a `Binding` first. +fielddoc(obj::UnionAll, field::Symbol) = fielddoc(Base.unwrap_unionall(obj), field) fielddoc(object, field::Symbol) = fielddoc(aliasof(object, typeof(object)), field) diff --git a/stdlib/REPL/test/docview.jl b/stdlib/REPL/test/docview.jl index 8c92d9b3b1d95..0ac6f88d12ed6 100644 --- a/stdlib/REPL/test/docview.jl +++ b/stdlib/REPL/test/docview.jl @@ -111,6 +111,24 @@ end @test endswith(get_help_standard("Int.not_a_field"), "`$Int` has no fields.\n") end +@testset "Parametric struct field help (#59524)" begin + "NonParametricStruct docstring" + struct NonParametricStruct + "field_x docstring" + field_x::Float64 + end + + "ParametricStruct docstring" + struct ParametricStruct{T<:Real} + "field_y docstring" + field_y::T + end + + @test occursin("field_x docstring", get_help_standard("NonParametricStruct.field_x")) + @test occursin("field_y docstring", get_help_standard("ParametricStruct.field_y")) + @test endswith(get_help_standard("ParametricStruct.not_a_field"), "ParametricStruct` has field `field_y`.\n") +end + module InternalWarningsTests module A From c4d2a3a38bd1113571c4100c947df38a2f3c4172 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Sat, 4 Oct 2025 16:00:14 -0700 Subject: [PATCH 13/15] Improve floating-point Euclidean division for Float16 and Float32 (#49637) (cherry picked from commit cb7d44629cb76aa52edad0f3d92d340782c0f412) --- base/div.jl | 6 ++++++ test/numbers.jl | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/base/div.jl b/base/div.jl index 3fec8d2f5cdf3..0ec852ff045c7 100644 --- a/base/div.jl +++ b/base/div.jl @@ -385,3 +385,9 @@ end # NOTE: C89 fmod() and x87 FPREM implicitly provide truncating float division, # so it is used here as the basis of float div(). div(x::T, y::T, r::RoundingMode) where {T<:AbstractFloat} = convert(T, round((x - rem(x, y, r)) / y)) + +# Vincent Lefèvre: "The Euclidean Division Implemented with a Floating-Point Division and a Floor" +# https://inria.hal.science/inria-00070403 +# Theorem 1 implies that the following are exact if eps(x/y) <= 1 +div(x::Float32, y::Float32, r::RoundingMode) = Float32(round(Float64(x) / Float64(y), r)) +div(x::Float16, y::Float16, r::RoundingMode) = Float16(round(Float32(x) / Float32(y), r)) diff --git a/test/numbers.jl b/test/numbers.jl index dc4f2cb613d77..0d8a8b1d867c0 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -1739,6 +1739,27 @@ end @test cld(-1.1, 0.1) == div(-1.1, 0.1, RoundUp) == ceil(big(-1.1)/big(0.1)) == -11.0 @test fld(-1.1, 0.1) == div(-1.1, 0.1, RoundDown) == floor(big(-1.1)/big(0.1)) == -12.0 end + @testset "issue #49450" begin + @test div(514, Float16(0.75)) === Float16(685) + @test fld(514, Float16(0.75)) === Float16(685) + @test cld(515, Float16(0.75)) === Float16(687) + + @test cld(1, Float16(0.000999)) === Float16(1001) + @test cld(2, Float16(0.001999)) === Float16(1001) + @test cld(3, Float16(0.002934)) === Float16(1023) + @test cld(4, Float16(0.003998)) === Float16(1001) + @test fld(5, Float16(0.004925)) === Float16(1015) + + @test div(4_194_307, Float32(0.75)) === Float32(5_592_409) + @test fld(4_194_307, Float32(0.75)) === Float32(5_592_409) + @test cld(4_194_308, Float32(0.75)) === Float32(5_592_411) + + @test fld(5, Float32(6.556511e-7)) === Float32(7_626_007) + @test fld(10, Float32(1.3113022e-6)) === Float32(7_626_007) + @test fld(11, Float32(1.4305115e-6)) === Float32(7_689_557) + @test cld(16, Float32(2.8014183e-6)) === Float32(5_711_393) + @test cld(17, Float32(2.2053719e-6)) === Float32(7_708_451) + end end @testset "return types" begin for T in (Int8,Int16,Int32,Int64,Int128, UInt8,UInt16,UInt32,UInt64,UInt128) From 511837fa8407645682a55c996d72b132522c292a Mon Sep 17 00:00:00 2001 From: Andy Dienes <51664769+adienes@users.noreply.github.com> Date: Sun, 5 Oct 2025 07:42:12 -0400 Subject: [PATCH 14/15] fix overflow in `prevfloat` with unsigned stepcount (#59668) fixes https://github.com/JuliaLang/julia/issues/59661 (cherry picked from commit 3b1fa7c764b79cfe0850ae4e977effdf7f27248d) --- base/float.jl | 25 +++++++++++++------------ test/numbers.jl | 4 ++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/base/float.jl b/base/float.jl index 1878a6a953360..8455b3fdbf152 100644 --- a/base/float.jl +++ b/base/float.jl @@ -808,13 +808,8 @@ precision(::Type{T}; base::Integer=2) where {T<:AbstractFloat} = _precision(T, b precision(::T; base::Integer=2) where {T<:AbstractFloat} = precision(T; base) -""" - nextfloat(x::AbstractFloat, n::Integer) - -The result of `n` iterative applications of `nextfloat` to `x` if `n >= 0`, or `-n` -applications of [`prevfloat`](@ref) if `n < 0`. -""" -function nextfloat(f::IEEEFloat, d::Integer) +function _nextfloat(f::IEEEFloat, dneg::Bool, da::Integer) + # da must be > 0 F = typeof(f) fumax = reinterpret(Unsigned, F(Inf)) U = typeof(fumax) @@ -824,8 +819,6 @@ function nextfloat(f::IEEEFloat, d::Integer) fneg = fi < 0 fu = unsigned(fi & typemax(fi)) - dneg = d < 0 - da = uabs(d) if da > typemax(U) fneg = dneg fu = fumax @@ -852,6 +845,14 @@ function nextfloat(f::IEEEFloat, d::Integer) reinterpret(F, fu) end +""" + nextfloat(x::AbstractFloat, n::Integer) + +The result of `n` iterative applications of `nextfloat` to `x` if `n >= 0`, or `-n` +applications of [`prevfloat`](@ref) if `n < 0`. +""" +nextfloat(f::AbstractFloat, d::Integer) = _nextfloat(f, isnegative(d), uabs(d)) + """ nextfloat(x::AbstractFloat) @@ -860,7 +861,7 @@ If no such `y` exists (e.g. if `x` is `Inf` or `NaN`), then return `x`. See also: [`prevfloat`](@ref), [`eps`](@ref), [`issubnormal`](@ref). """ -nextfloat(x::AbstractFloat) = nextfloat(x,1) +nextfloat(x::AbstractFloat) = nextfloat(x, 1) """ prevfloat(x::AbstractFloat, n::Integer) @@ -868,7 +869,7 @@ nextfloat(x::AbstractFloat) = nextfloat(x,1) The result of `n` iterative applications of `prevfloat` to `x` if `n >= 0`, or `-n` applications of [`nextfloat`](@ref) if `n < 0`. """ -prevfloat(x::AbstractFloat, d::Integer) = nextfloat(x, -d) +prevfloat(x::AbstractFloat, d::Integer) = _nextfloat(x, ispositive(d), uabs(d)) """ prevfloat(x::AbstractFloat) @@ -876,7 +877,7 @@ prevfloat(x::AbstractFloat, d::Integer) = nextfloat(x, -d) Return the largest floating point number `y` of the same type as `x` such that `y < x`. If no such `y` exists (e.g. if `x` is `-Inf` or `NaN`), then return `x`. """ -prevfloat(x::AbstractFloat) = nextfloat(x,-1) +prevfloat(x::AbstractFloat) = nextfloat(x, -1) for Ti in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128) for Tf in (Float16, Float32, Float64) diff --git a/test/numbers.jl b/test/numbers.jl index 0d8a8b1d867c0..dfbb30a9b8b43 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -2181,6 +2181,10 @@ end @test nextfloat(Inf32) === Inf32 @test prevfloat(-Inf32) === -Inf32 @test isequal(nextfloat(NaN32), NaN32) + @test nextfloat(1.0, UInt(5)) == nextfloat(1.0, 5) + @test prevfloat(1.0, UInt(5)) == prevfloat(1.0, 5) + @test nextfloat(0.0, typemax(UInt64)) == Inf + @test prevfloat(0.0, typemax(UInt64)) == -Inf end @testset "issue #16206" begin @test prevfloat(Inf) == 1.7976931348623157e308 From 5f44496d1c301edfbc6807be74f9567fbc9df945 Mon Sep 17 00:00:00 2001 From: Andy Dienes <51664769+adienes@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:30:06 -0400 Subject: [PATCH 15/15] [NFC] add tests for typemin power of float (#59745) with backports, as suggested in https://github.com/JuliaLang/julia/issues/57463#issuecomment-2671090597. closes https://github.com/JuliaLang/julia/issues/57463 (cherry picked from commit 30c34ef51891e37a40e18fd584759cd2ee273ebf) --- test/math.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/math.jl b/test/math.jl index 9a8b3a16d8fb3..6ea0ce25f9a73 100644 --- a/test/math.jl +++ b/test/math.jl @@ -390,6 +390,15 @@ end @test isnan(exp(reinterpret(Float64, 0x7ffbb14880000000))) end +@testset "issue #57463" begin + for T in (Int16, Int32, Int64, Int128) + @test iszero(1.1^typemin(T)) + @test iszero(0.9^typemax(T)) + @test isinf(1.1^typemax(T)) + @test isinf(0.9^typemin(T)) + end +end + @testset "test abstractarray trig functions" begin TAA = rand(2,2) TAA = (TAA + TAA')/2.