Skip to content

Commit

Permalink
effects: power-up effects analysis for array operations
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 committed Oct 13, 2022
1 parent 01310a9 commit 6be923c
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 49 deletions.
64 changes: 46 additions & 18 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,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 +331,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 @@ -374,6 +371,17 @@ similar(a::Array{T}, m::Int) where {T} = Vector{T}(undef, m)
similar(a::Array, T::Type, dims::Dims{N}) where {N} = Array{T,N}(undef, dims)
similar(a::Array{T}, dims::Dims{N}) where {T,N} = Array{T,N}(undef, dims)

macro _getindex_meta()
return _is_internal(__module__) && Expr(:meta, Expr(:purity,
#=:consistent=#false,
#=:effect_free=#false,
#=:nothrow=#true,
#=:terminates_globally=#true,
#=:terminates_locally=#false,
#=:notaskstate=#false,
#=:inaccessiblememonly=#false))
end

# T[x...] constructs Array{T,1}
"""
getindex(type[, elements...])
Expand All @@ -396,11 +404,15 @@ julia> getindex(Int8, 1, 2, 3)
3
```
"""
function getindex(::Type{T}, vals...) where T
getindex(::Type{T}, vals::T...) where T = (@_getindex_meta; _getindex_impl(T, vals)) # safe version
getindex(::Type{T}, vals...) where T = _getindex_impl(T, vals)

function _getindex_impl(::Type{T}, vals) where T
@inline
a = Vector{T}(undef, length(vals))
if vals isa NTuple
@inbounds for i in 1:length(vals)
a[i] = vals[i]
a[i] = getfield(vals, i)
end
else
# use afoldl to avoid type instability inside loop
Expand All @@ -413,9 +425,10 @@ function getindex(::Type{T}, vals...) where T
end

function getindex(::Type{Any}, @nospecialize vals...)
@_getindex_meta
a = Vector{Any}(undef, length(vals))
@inbounds for i = 1:length(vals)
a[i] = vals[i]
a[i] = getfield(vals, i)
end
return a
end
Expand Down Expand Up @@ -1027,6 +1040,17 @@ _deleteat!(a::Vector, i::Integer, delta::Integer) =

## Dequeue functionality ##

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

"""
push!(collection, items...) -> collection
Expand Down Expand Up @@ -1055,7 +1079,7 @@ 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)
Expand All @@ -1070,11 +1094,12 @@ function push!(a::Vector{Any}, @nospecialize 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)
@inbounds for i = 1:nx
arrayset(true, a, getfield(x,i), na+i)
end
return a
end
Expand Down Expand Up @@ -1129,6 +1154,7 @@ 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)
Expand Down Expand Up @@ -1194,6 +1220,7 @@ 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)
Expand Down Expand Up @@ -1249,7 +1276,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 @@ -1324,7 +1351,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 @@ -1412,11 +1439,12 @@ function pushfirst!(a::Vector{Any}, @nospecialize 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
a[i] = x[i]
@inbounds for i = 1:nx
a[i] = getfield(x,i)
end
return a
end
Expand Down Expand Up @@ -1455,7 +1483,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 @@ -1595,7 +1623,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 @@ -1851,7 +1879,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
100 changes: 82 additions & 18 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1913,14 +1913,12 @@ function _builtin_nothrow(@specialize(lattice::AbstractLattice), @nospecialize(f
end

# known to be always effect-free (in particular nothrow)
const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields]

# 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.kwfunc, Core.ifelse, Core._typevar, (<:),
typeassert, throw, arraysize, getglobal, compilerbarrier
const _PURE_BUILTINS = Any[
tuple,
svec,
===,
typeof,
nfields,
]

const _CONSISTENT_BUILTINS = Any[
Expand All @@ -1939,14 +1937,47 @@ const _CONSISTENT_BUILTINS = Any[
(<:),
typeassert,
throw,
setfield!
setfield!,
]

const CONSISTENT_IF_INACCESSIBLEMEMONLY_BUILTINS = Any[
# getfield, # this builtin is specially handled
arrayref,
arrayset,
arraysize,
]

# 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.kwfunc,
Core.ifelse,
Core._typevar,
(<:),
typeassert,
throw,
getglobal,
compilerbarrier,
]

const _EFFECT_FREE_IF_INACCESSIBLEMEMONLY_BUILTINS = Any[
setfield!,
arrayset,
]

const _INACCESSIBLEMEM_BUILTINS = Any[
(<:),
(===),
apply_type,
arraysize,
Core.ifelse,
Core.sizeof,
svec,
Expand All @@ -1964,6 +1995,7 @@ const _INACCESSIBLEMEM_BUILTINS = Any[
const _ARGMEM_BUILTINS = Any[
arrayref,
arrayset,
arraysize,
modifyfield!,
replacefield!,
setfield!,
Expand All @@ -1972,7 +2004,6 @@ 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
Expand All @@ -1988,7 +2019,7 @@ const _INCONSISTENT_INTRINSICS = Any[
Intrinsics.neg_float_fast,
Intrinsics.rem_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
Expand Down Expand Up @@ -2081,11 +2112,17 @@ function builtin_effects(@specialize(lattice::AbstractLattice), f::Builtin, argt
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 : ALWAYS_FALSE
if f === setfield! || f === arrayset
effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY
elseif contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)
if contains_is(_CONSISTENT_BUILTINS, f)
consistent = ALWAYS_TRUE
elseif contains_is(CONSISTENT_IF_INACCESSIBLEMEMONLY_BUILTINS, f)
consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY
else
consistent = ALWAYS_FALSE
end
if contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)
effect_free = ALWAYS_TRUE
elseif contains_is(_EFFECT_FREE_IF_INACCESSIBLEMEMONLY_BUILTINS, f)
effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY
else
effect_free = ALWAYS_FALSE
end
Expand Down Expand Up @@ -2267,11 +2304,22 @@ 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))
if f === arraylen
inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY
else
inaccessiblememonly = ALWAYS_TRUE
end

return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
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
Expand Down Expand Up @@ -2445,9 +2493,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
Expand Down
2 changes: 1 addition & 1 deletion base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ end

function iterate(@nospecialize(t::Tuple), i::Int=1)
@inline
return (1 <= i <= length(t)) ? (@inbounds t[i], i + 1) : nothing
return (1 <= i <= length(t)) ? (@inbounds getfield(t,i), i + 1) : nothing
end

keys(@nospecialize t::Tuple) = OneTo(length(t))
Expand Down
Loading

0 comments on commit 6be923c

Please sign in to comment.