From a20a3d09ec47a4a39bbafd6241a66dc75c21d41a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sun, 2 Apr 2023 14:27:09 +0900 Subject: [PATCH] effects: power-up effects analysis for array operations (#47154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly by making use of newly added `:inaccessiblememonly` effect property. Now we can fold simple vector operations like: ```julia julia> function simple_vec_ops(T, op!, op, xs...) a = T[] op!(a, xs...) return op(a) end; simple_vec_ops (generic function with 1 method) julia> for T = Any[Int,Any], op! = Any[push!,pushfirst!], op = Any[length,size], xs = Any[(Int,), (Int,Int,)] let effects = Base.infer_effects(simple_vec_ops, (Type{T},typeof(op!),typeof(op),xs...)) @test Core.Compiler.is_foldable(effects) end end julia> code_typed() do simple_vec_ops(Any, push!, length, Any,nothing,Core.Const(1)) end 1-element Vector{Any}: CodeInfo( 1 ─ return 3 ) => Int64 ``` --- base/array.jl | 113 ++++++++++++++----- base/compiler/tfuncs.jl | 86 +++++++++++--- base/essentials.jl | 33 ++++++ base/tuple.jl | 2 + test/compiler/EscapeAnalysis/local.jl | 8 +- test/compiler/effects.jl | 154 ++++++++++++++++++++++---- test/precompile.jl | 9 +- 7 files changed, 331 insertions(+), 74 deletions(-) diff --git a/base/array.jl b/base/array.jl index 84399b9a43480..1cfa55b52c999 100644 --- a/base/array.jl +++ b/base/array.jl @@ -122,8 +122,50 @@ const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}} using Core: arraysize, arrayset, const_arrayref +""" + @_safeindex + +This internal macro converts: +- `getindex(xs::Tuple, )` -> `__inbounds_getindex(args...)` +- `setindex!(xs::Vector, args...)` -> `__inbounds_setindex!(xs, args...)` +to tell the compiler that indexing operations within the applied expression are always +inbounds and do not need to taint `:consistent` and `:nothrow`. +""" +macro _safeindex(ex) + return esc(_safeindex(__module__, ex)) +end +function _safeindex(__module__, ex) + isa(ex, Expr) || return ex + if ex.head === :(=) + lhs = arrayref(true, ex.args, 1) + if isa(lhs, Expr) && lhs.head === :ref # xs[i] = x + rhs = arrayref(true, ex.args, 2) + xs = arrayref(true, lhs.args, 1) + args = Vector{Any}(undef, length(lhs.args)-1) + for i = 2:length(lhs.args) + arrayset(true, args, _safeindex(__module__, arrayref(true, lhs.args, i)), i-1) + end + return Expr(:call, GlobalRef(__module__, :__inbounds_setindex!), xs, _safeindex(__module__, rhs), args...) + end + elseif ex.head === :ref # xs[i] + return Expr(:call, GlobalRef(__module__, :__inbounds_getindex), ex.args...) + end + args = Vector{Any}(undef, length(ex.args)) + for i = 1:length(ex.args) + arrayset(true, args, _safeindex(__module__, arrayref(true, ex.args, i)), i) + end + return Expr(ex.head, args...) +end + vect() = Vector{Any}() -vect(X::T...) where {T} = T[ X[i] for i = 1:length(X) ] +function vect(X::T...) where T + @_terminates_locally_meta + vec = Vector{T}(undef, length(X)) + @_safeindex for i = 1:length(X) + vec[i] = X[i] + end + return vec +end """ vect(X...) @@ -321,7 +363,7 @@ end function _copyto_impl!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer) n == 0 && return dest - n > 0 || _throw_argerror() + n > 0 || _throw_argerror("Number of elements to copy must be nonnegative.") @boundscheck checkbounds(dest, doffs:doffs+n-1) @boundscheck checkbounds(src, soffs:soffs+n-1) unsafe_copyto!(dest, doffs, src, soffs, n) @@ -331,10 +373,7 @@ end # Outlining this because otherwise a catastrophic inference slowdown # occurs, see discussion in #27874. # It is also mitigated by using a constant string. -function _throw_argerror() - @noinline - throw(ArgumentError("Number of elements to copy must be nonnegative.")) -end +_throw_argerror(s) = (@noinline; throw(ArgumentError(s))) copyto!(dest::Array, src::Array) = copyto!(dest, 1, src, 1, length(src)) @@ -397,9 +436,11 @@ julia> getindex(Int8, 1, 2, 3) ``` """ function getindex(::Type{T}, vals...) where T + @inline + @_effect_free_terminates_locally_meta a = Vector{T}(undef, length(vals)) if vals isa NTuple - @inbounds for i in 1:length(vals) + @_safeindex for i in 1:length(vals) a[i] = vals[i] end else @@ -412,9 +453,21 @@ function getindex(::Type{T}, vals...) where T return a end +# safe version +function getindex(::Type{T}, vals::T...) where T + @inline + @_effect_free_terminates_locally_meta + a = Vector{T}(undef, length(vals)) + @_safeindex for i in 1:length(vals) + a[i] = vals[i] + end + return a +end + function getindex(::Type{Any}, @nospecialize vals...) + @_effect_free_terminates_locally_meta a = Vector{Any}(undef, length(vals)) - @inbounds for i = 1:length(vals) + @_safeindex for i = 1:length(vals) a[i] = vals[i] end return a @@ -966,10 +1019,16 @@ Dict{String, Int64} with 2 entries: """ function setindex! end -@eval setindex!(A::Array{T}, x, i1::Int) where {T} = arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1) +@eval setindex!(A::Array{T}, x, i1::Int) where {T} = + arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1) @eval setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} = (@inline; arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1, i2, I...)) +__inbounds_setindex!(A::Array{T}, x, i1::Int) where {T} = + arrayset(false, A, convert(T,x)::T, i1) +__inbounds_setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} = + (@inline; arrayset(false, A, convert(T,x)::T, i1, i2, I...)) + # This is redundant with the abstract fallbacks but needed and helpful for bootstrap function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int}) @_propagate_inbounds_meta @@ -1055,26 +1114,27 @@ See also [`pushfirst!`](@ref). """ function push! end -function push!(a::Array{T,1}, item) where T +function push!(a::Vector{T}, item) where T # convert first so we don't grow the array if the assignment won't work itemT = convert(T, item) _growend!(a, 1) - @inbounds a[end] = itemT + @_safeindex a[length(a)] = itemT return a end # specialize and optimize the single argument case function push!(a::Vector{Any}, @nospecialize x) _growend!(a, 1) - arrayset(true, a, x, length(a)) + @_safeindex a[length(a)] = x return a end function push!(a::Vector{Any}, @nospecialize x...) + @_terminates_locally_meta na = length(a) nx = length(x) _growend!(a, nx) - for i = 1:nx - arrayset(true, a, x[i], na+i) + @_safeindex for i = 1:nx + a[na+i] = x[i] end return a end @@ -1129,10 +1189,11 @@ push!(a::AbstractVector, iter...) = append!(a, iter) append!(a::AbstractVector, iter...) = foldl(append!, iter, init=a) function _append!(a, ::Union{HasLength,HasShape}, iter) + @_terminates_locally_meta n = length(a) i = lastindex(a) resize!(a, n+Int(length(iter))::Int) - @inbounds for (i, item) in zip(i+1:lastindex(a), iter) + @_safeindex for (i, item) in zip(i+1:lastindex(a), iter) a[i] = item end a @@ -1194,12 +1255,13 @@ pushfirst!(a::Vector, iter...) = prepend!(a, iter) prepend!(a::AbstractVector, iter...) = foldr((v, a) -> prepend!(a, v), iter, init=a) function _prepend!(a, ::Union{HasLength,HasShape}, iter) + @_terminates_locally_meta require_one_based_indexing(a) n = length(iter) _growbeg!(a, n) i = 0 for item in iter - @inbounds a[i += 1] = item + @_safeindex a[i += 1] = item end a end @@ -1249,7 +1311,7 @@ function resize!(a::Vector, nl::Integer) _growend!(a, nl-l) elseif nl != l if nl < 0 - throw(ArgumentError("new length must be ≥ 0")) + _throw_argerror("new length must be ≥ 0") end _deleteend!(a, l-nl) end @@ -1329,7 +1391,7 @@ julia> pop!(Dict(1=>2)) """ function pop!(a::Vector) if isempty(a) - throw(ArgumentError("array must be non-empty")) + _throw_argerror("array must be non-empty") end item = a[end] _deleteend!(a, 1) @@ -1403,24 +1465,25 @@ julia> pushfirst!([1, 2, 3, 4], 5, 6) 4 ``` """ -function pushfirst!(a::Array{T,1}, item) where T +function pushfirst!(a::Vector{T}, item) where T item = convert(T, item) _growbeg!(a, 1) - a[1] = item + @_safeindex a[1] = item return a end # specialize and optimize the single argument case function pushfirst!(a::Vector{Any}, @nospecialize x) _growbeg!(a, 1) - a[1] = x + @_safeindex a[1] = x return a end function pushfirst!(a::Vector{Any}, @nospecialize x...) + @_terminates_locally_meta na = length(a) nx = length(x) _growbeg!(a, nx) - for i = 1:nx + @_safeindex for i = 1:nx a[i] = x[i] end return a @@ -1460,7 +1523,7 @@ julia> A """ function popfirst!(a::Vector) if isempty(a) - throw(ArgumentError("array must be non-empty")) + _throw_argerror("array must be non-empty") end item = a[1] _deletebeg!(a, 1) @@ -1600,7 +1663,7 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere()) (i,s) = y if !(q <= i <= n) if i < q - throw(ArgumentError("indices must be unique and sorted")) + _throw_argerror("indices must be unique and sorted") else throw(BoundsError()) end @@ -1856,7 +1919,7 @@ for (f,_f) in ((:reverse,:_reverse), (:reverse!,:_reverse!)) $_f(A::AbstractVector, ::Colon) = $f(A, firstindex(A), lastindex(A)) $_f(A::AbstractVector, dim::Tuple{Integer}) = $_f(A, first(dim)) function $_f(A::AbstractVector, dim::Integer) - dim == 1 || throw(ArgumentError("invalid dimension $dim ≠ 1")) + dim == 1 || _throw_argerror(LazyString("invalid dimension ", dim, " ≠ 1")) return $_f(A, :) end end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 209a10c9954ad..ede0f4545503f 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2134,14 +2134,12 @@ end end # known to be always effect-free (in particular nothrow) -const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields, applicable] - -# known to be effect-free (but not necessarily nothrow) -const _EFFECT_FREE_BUILTINS = [ - fieldtype, apply_type, isa, UnionAll, - getfield, arrayref, const_arrayref, isdefined, Core.sizeof, - Core.ifelse, Core._typevar, (<:), - typeassert, throw, arraysize, getglobal, compilerbarrier +const _PURE_BUILTINS = Any[ + tuple, + svec, + ===, + typeof, + nfields, ] const _CONSISTENT_BUILTINS = Any[ @@ -2159,14 +2157,34 @@ const _CONSISTENT_BUILTINS = Any[ (<:), typeassert, throw, - setfield! + setfield!, +] + +# known to be effect-free (but not necessarily nothrow) +const _EFFECT_FREE_BUILTINS = [ + fieldtype, + apply_type, + isa, + UnionAll, + getfield, + arrayref, + arraysize, + const_arrayref, + isdefined, + Core.sizeof, + Core.ifelse, + Core._typevar, + (<:), + typeassert, + throw, + getglobal, + compilerbarrier, ] const _INACCESSIBLEMEM_BUILTINS = Any[ (<:), (===), apply_type, - arraysize, Core.ifelse, Core.sizeof, svec, @@ -2185,6 +2203,7 @@ const _INACCESSIBLEMEM_BUILTINS = Any[ const _ARGMEM_BUILTINS = Any[ arrayref, arrayset, + arraysize, modifyfield!, replacefield!, setfield!, @@ -2193,7 +2212,7 @@ const _ARGMEM_BUILTINS = Any[ const _INCONSISTENT_INTRINSICS = Any[ Intrinsics.pointerref, # this one is volatile - Intrinsics.arraylen, # this one is volatile + Intrinsics.sqrt_llvm_fast, # this one may differ at runtime (by a few ulps) Intrinsics.have_fma, # this one depends on the runtime environment Intrinsics.cglobal, # cglobal lookup answer changes at runtime # ... and list fastmath intrinsics: @@ -2207,7 +2226,7 @@ const _INCONSISTENT_INTRINSICS = Any[ Intrinsics.ne_float_fast, Intrinsics.neg_float_fast, Intrinsics.sqrt_llvm_fast, - Intrinsics.sub_float_fast + Intrinsics.sub_float_fast, # TODO needs to revive #31193 to mark this as inconsistent to be accurate # while preserving the currently optimizations for many math operations # Intrinsics.muladd_float, # this is not interprocedurally consistent @@ -2309,8 +2328,15 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argin effect_free = get_binding_type_effect_free(argtypes[1], argtypes[2]) ? ALWAYS_TRUE : ALWAYS_FALSE return Effects(EFFECTS_TOTAL; effect_free) else - consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : - (f === Core._typevar) ? CONSISTENT_IF_NOTRETURNED : ALWAYS_FALSE + if contains_is(_CONSISTENT_BUILTINS, f) + consistent = ALWAYS_TRUE + elseif f === arrayref || f === arrayset || f === arraysize + consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY + elseif f === Core._typevar + consistent = CONSISTENT_IF_NOTRETURNED + else + consistent = ALWAYS_FALSE + end if f === setfield! || f === arrayset effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY elseif contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) @@ -2498,11 +2524,21 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) return Effects() end - consistent = contains_is(_INCONSISTENT_INTRINSICS, f) ? ALWAYS_FALSE : ALWAYS_TRUE + if contains_is(_INCONSISTENT_INTRINSICS, f) + consistent = ALWAYS_FALSE + elseif f === arraylen + consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY + else + consistent = ALWAYS_TRUE + end effect_free = !(f === Intrinsics.pointerset) ? ALWAYS_TRUE : ALWAYS_FALSE nothrow = (isempty(argtypes) || !isvarargtype(argtypes[end])) && intrinsic_nothrow(f, argtypes) - - return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow) + if f === arraylen + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY + else + inaccessiblememonly = ALWAYS_TRUE + end + return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) end # TODO: this function is a very buggy and poor model of the return_type function @@ -2767,9 +2803,25 @@ function foreigncall_effects(@specialize(abstract_eval), e::Expr) return new_array_effects(abstract_eval, args) end end + if is_array_resize(name) + return array_resize_effects() + end return EFFECTS_UNKNOWN end +function is_array_resize(name::Symbol) + return name === :jl_array_grow_beg || name === :jl_array_grow_end || + name === :jl_array_del_beg || name === :jl_array_del_end || + name === :jl_array_grow_at || name === :jl_array_del_at +end + +function array_resize_effects() + return Effects(EFFECTS_TOTAL; + effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY, + nothrow = false, + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY) +end + function alloc_array_ndims(name::Symbol) if name === :jl_alloc_array_1d return 1 diff --git a/base/essentials.jl b/base/essentials.jl index fc79f88f5c0b8..59e4a3fe1162e 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -220,6 +220,39 @@ macro _foldable_meta() #=:notaskstate=#false, #=:inaccessiblememonly=#true)) end +# can be used in place of `@assume_effects :nothrow` (supposed to be used for bootstrapping) +macro _nothrow_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#true, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false)) +end +# can be used in place of `@assume_effects :terminates_locally` (supposed to be used for bootstrapping) +macro _terminates_locally_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#true, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false)) +end +# can be used in place of `@assume_effects :effect_free :terminates_locally` (supposed to be used for bootstrapping) +macro _effect_free_terminates_locally_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#true, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#true, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false)) +end # another version of inlining that propagates an inbounds context macro _propagate_inbounds_meta() diff --git a/base/tuple.jl b/base/tuple.jl index 134010268c7fe..b8ef63517a49f 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -30,6 +30,8 @@ size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(Argumen axes(@nospecialize t::Tuple) = (OneTo(length(t)),) @eval getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, $(Expr(:boundscheck))) @eval getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), $(Expr(:boundscheck))) +__inbounds_getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, false) +__inbounds_getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), false) getindex(t::Tuple, r::AbstractArray{<:Any,1}) = (eltype(t)[t[ri] for ri in r]...,) getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw(BoundsError(t, b)) getindex(t::Tuple, c::Colon) = t diff --git a/test/compiler/EscapeAnalysis/local.jl b/test/compiler/EscapeAnalysis/local.jl index e5d8f1bf2c940..dd324c3619dc7 100644 --- a/test/compiler/EscapeAnalysis/local.jl +++ b/test/compiler/EscapeAnalysis/local.jl @@ -1997,9 +1997,9 @@ let result = code_escapes((Int,String,)) do n,s i = only(findall(isarrayalloc, result.ir.stmts.inst)) r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[SSAValue(i)], r) - Base.JLOptions().check_bounds ≠ 0 && @test has_thrown_escape(result.state[SSAValue(i)]) + @test !has_thrown_escape(result.state[SSAValue(i)]) @test has_return_escape(result.state[Argument(3)], r) # s - Base.JLOptions().check_bounds ≠ 0 && @test has_thrown_escape(result.state[Argument(3)]) # s + @test !has_thrown_escape(result.state[Argument(3)]) # s end let result = code_escapes((Int,String,)) do n,s xs = String[] @@ -2011,9 +2011,9 @@ let result = code_escapes((Int,String,)) do n,s i = only(findall(isarrayalloc, result.ir.stmts.inst)) r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[SSAValue(i)], r) # xs - @test has_thrown_escape(result.state[SSAValue(i)]) # xs + @test !has_thrown_escape(result.state[SSAValue(i)]) # xs @test has_return_escape(result.state[Argument(3)], r) # s - @test has_thrown_escape(result.state[Argument(3)]) # s + @test !has_thrown_escape(result.state[Argument(3)]) # s end let result = code_escapes((String,String,String)) do s, t, u xs = String[] diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index a6576664f5edf..eb3df4ba9272e 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -12,9 +12,6 @@ end nothing end -# Test that arraysize has proper effect modeling -@test fully_eliminated(M->(size(M, 2); nothing), (Matrix{Float64},)) - # Test that effect modeling for return_type doesn't incorrectly pick # up the effects of the function being analyzed f_throws() = error() @@ -454,21 +451,6 @@ let effects = Base.infer_effects(f_setfield_nothrow, ()) @test Core.Compiler.is_nothrow(effects) end -# nothrow for arrayset -@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i - Base.arrayset(true, a, v, i) -end |> !Core.Compiler.is_nothrow -@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i - a[i] = v # may throw -end |> !Core.Compiler.is_nothrow -# when bounds checking is turned off, it should be safe -@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i - Base.arrayset(false, a, v, i) -end |> Core.Compiler.is_nothrow -@test Base.infer_effects((Vector{Number},Number,Int)) do a, v, i - Base.arrayset(false, a, v, i) -end |> Core.Compiler.is_nothrow - # even if 2-arg `getfield` may throw, it should be still `:consistent` @test Core.Compiler.is_consistent(Base.infer_effects(getfield, (NTuple{5, Float64}, Int))) @@ -700,12 +682,14 @@ end end @test !Core.Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused3!)) -@testset "effects analysis on array ops" begin +# array ops +# ========= -@testset "effects analysis on array construction" begin +# allocation +# ---------- +# low-level constructor @noinline construct_array(@nospecialize(T), args...) = Array{T}(undef, args...) - # should eliminate safe but dead allocations let good_dims = @static Int === Int64 ? (1:10) : (1:8) Ns = @static Int === Int64 ? (1:10) : (1:8) @@ -720,7 +704,6 @@ let good_dims = @static Int === Int64 ? (1:10) : (1:8) end end end - # should analyze throwness correctly let bad_dims = [-1, typemax(Int)] for dim in bad_dims, N in 1:10 @@ -736,9 +719,132 @@ let bad_dims = [-1, typemax(Int)] end end -end # @testset "effects analysis on array construction" begin +# high-level interfaces +# getindex +for safesig = Any[ + (Type{Int},) + (Type{Int}, Int) + (Type{Int}, Int, Int) + (Type{Number},) + (Type{Number}, Number) + (Type{Number}, Int) + (Type{Any},) + (Type{Any}, Any,) + (Type{Any}, Any, Any) + ] + let effects = Base.infer_effects(getindex, safesig) + @test Core.Compiler.is_consistent_if_notreturned(effects) + @test Core.Compiler.is_removable_if_unused(effects) + end +end +for unsafesig = Any[ + (Type{Int}, String) + (Type{Int}, Any) + (Type{Number}, AbstractString) + (Type{Number}, Any) + ] + let effects = Base.infer_effects(getindex, unsafesig) + @test !Core.Compiler.is_nothrow(effects) + end +end +# vect +for safesig = Any[ + () + (Int,) + (Int, Int) + ] + let effects = Base.infer_effects(Base.vect, safesig) + @test Core.Compiler.is_consistent_if_notreturned(effects) + @test Core.Compiler.is_removable_if_unused(effects) + end +end + +# arrayref +# -------- + +let effects = Base.infer_effects(Base.arrayref, (Vector{Any},Int)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end + +# arrayset +# -------- + +let effects = Base.infer_effects(Base.arrayset, (Vector{Any},Any,Int)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free_if_inaccessiblememonly(effects) + @test !Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end +# nothrow for arrayset +@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i + Base.arrayset(true, a, v, i) +end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i + a[i] = v # may throw +end |> !Core.Compiler.is_nothrow +# when bounds checking is turned off, it should be safe +@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i + Base.arrayset(false, a, v, i) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Vector{Number},Number,Int)) do a, v, i + Base.arrayset(false, a, v, i) +end |> Core.Compiler.is_nothrow + +# arraysize +# --------- + +let effects = Base.infer_effects(Base.arraysize, (Array,Int)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end +# Test that arraysize has proper effect modeling +@test fully_eliminated(M->(size(M, 2); nothing), (Matrix{Float64},)) + +# arraylen +# -------- + +let effects = Base.infer_effects(Base.arraylen, (Vector{Any},)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end + +# resize +# ------ + +for op = Any[ + Base._growbeg!, + Base._growend!, + Base._deletebeg!, + Base._deleteend!, + ] + let effects = Base.infer_effects(op, (Vector, Int)) + @test Core.Compiler.is_effect_free_if_inaccessiblememonly(effects) + @test Core.Compiler.is_terminates(effects) + @test !Core.Compiler.is_nothrow(effects) + end +end + +# end to end +# ---------- -end # @testset "effects analysis on array ops" begin +function simple_vec_ops(T, op!, op, xs...) + a = T[] + op!(a, xs...) + return op(a) +end +for T = Any[Int,Any], op! = Any[push!,pushfirst!], op = Any[length,size], + xs = Any[(Int,), (Int,Int,)] + let effects = Base.infer_effects(simple_vec_ops, (Type{T},typeof(op!),typeof(op),xs...)) + @test Core.Compiler.is_foldable(effects) + end +end # Test that builtin_effects handles vararg correctly @test !Core.Compiler.is_nothrow(Core.Compiler.builtin_effects(Core.Compiler.fallback_lattice, Core.isdefined, diff --git a/test/precompile.jl b/test/precompile.jl index de15171c8138c..50dfe329d3db4 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -662,12 +662,13 @@ precompile_test_harness("code caching") do dir @test all(i -> root_provenance(m, i) == Mid, 1:length(m.roots)) end # Check that we can cache external CodeInstances: - # size(::Vector) has an inferred specialization for Vector{X} - msize = which(size, (Vector{<:Any},)) + # length(::Vector) has an inferred specialization for `Vector{X}` + msize = which(length, (Vector{<:Any},)) hasspec = false for mi in Base.specializations(msize) - if mi.specTypes == Tuple{typeof(size),Vector{Cacheb8321416e8a3e2f1.X}} - if isdefined(mi, :cache) && isa(mi.cache, Core.CodeInstance) && mi.cache.max_world == typemax(UInt) && mi.cache.inferred !== nothing + if mi.specTypes == Tuple{typeof(length),Vector{Cacheb8321416e8a3e2f1.X}} + if (isdefined(mi, :cache) && isa(mi.cache, Core.CodeInstance) && + mi.cache.max_world == typemax(UInt) && mi.cache.inferred !== nothing) hasspec = true break end