Skip to content

Commit

Permalink
effects: power-up effects analysis for array operations (#47154)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
aviatesk authored Apr 2, 2023
1 parent ce64b80 commit a20a3d0
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 74 deletions.
113 changes: 88 additions & 25 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand Down Expand Up @@ -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)
Expand All @@ -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))

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit a20a3d0

Please sign in to comment.