Skip to content

Commit a20a3d0

Browse files
authored
effects: power-up effects analysis for array operations (#47154)
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 ```
1 parent ce64b80 commit a20a3d0

File tree

7 files changed

+331
-74
lines changed

7 files changed

+331
-74
lines changed

base/array.jl

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,50 @@ const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}}
122122

123123
using Core: arraysize, arrayset, const_arrayref
124124

125+
"""
126+
@_safeindex
127+
128+
This internal macro converts:
129+
- `getindex(xs::Tuple, )` -> `__inbounds_getindex(args...)`
130+
- `setindex!(xs::Vector, args...)` -> `__inbounds_setindex!(xs, args...)`
131+
to tell the compiler that indexing operations within the applied expression are always
132+
inbounds and do not need to taint `:consistent` and `:nothrow`.
133+
"""
134+
macro _safeindex(ex)
135+
return esc(_safeindex(__module__, ex))
136+
end
137+
function _safeindex(__module__, ex)
138+
isa(ex, Expr) || return ex
139+
if ex.head === :(=)
140+
lhs = arrayref(true, ex.args, 1)
141+
if isa(lhs, Expr) && lhs.head === :ref # xs[i] = x
142+
rhs = arrayref(true, ex.args, 2)
143+
xs = arrayref(true, lhs.args, 1)
144+
args = Vector{Any}(undef, length(lhs.args)-1)
145+
for i = 2:length(lhs.args)
146+
arrayset(true, args, _safeindex(__module__, arrayref(true, lhs.args, i)), i-1)
147+
end
148+
return Expr(:call, GlobalRef(__module__, :__inbounds_setindex!), xs, _safeindex(__module__, rhs), args...)
149+
end
150+
elseif ex.head === :ref # xs[i]
151+
return Expr(:call, GlobalRef(__module__, :__inbounds_getindex), ex.args...)
152+
end
153+
args = Vector{Any}(undef, length(ex.args))
154+
for i = 1:length(ex.args)
155+
arrayset(true, args, _safeindex(__module__, arrayref(true, ex.args, i)), i)
156+
end
157+
return Expr(ex.head, args...)
158+
end
159+
125160
vect() = Vector{Any}()
126-
vect(X::T...) where {T} = T[ X[i] for i = 1:length(X) ]
161+
function vect(X::T...) where T
162+
@_terminates_locally_meta
163+
vec = Vector{T}(undef, length(X))
164+
@_safeindex for i = 1:length(X)
165+
vec[i] = X[i]
166+
end
167+
return vec
168+
end
127169

128170
"""
129171
vect(X...)
@@ -321,7 +363,7 @@ end
321363

322364
function _copyto_impl!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer)
323365
n == 0 && return dest
324-
n > 0 || _throw_argerror()
366+
n > 0 || _throw_argerror("Number of elements to copy must be nonnegative.")
325367
@boundscheck checkbounds(dest, doffs:doffs+n-1)
326368
@boundscheck checkbounds(src, soffs:soffs+n-1)
327369
unsafe_copyto!(dest, doffs, src, soffs, n)
@@ -331,10 +373,7 @@ end
331373
# Outlining this because otherwise a catastrophic inference slowdown
332374
# occurs, see discussion in #27874.
333375
# It is also mitigated by using a constant string.
334-
function _throw_argerror()
335-
@noinline
336-
throw(ArgumentError("Number of elements to copy must be nonnegative."))
337-
end
376+
_throw_argerror(s) = (@noinline; throw(ArgumentError(s)))
338377

339378
copyto!(dest::Array, src::Array) = copyto!(dest, 1, src, 1, length(src))
340379

@@ -397,9 +436,11 @@ julia> getindex(Int8, 1, 2, 3)
397436
```
398437
"""
399438
function getindex(::Type{T}, vals...) where T
439+
@inline
440+
@_effect_free_terminates_locally_meta
400441
a = Vector{T}(undef, length(vals))
401442
if vals isa NTuple
402-
@inbounds for i in 1:length(vals)
443+
@_safeindex for i in 1:length(vals)
403444
a[i] = vals[i]
404445
end
405446
else
@@ -412,9 +453,21 @@ function getindex(::Type{T}, vals...) where T
412453
return a
413454
end
414455

456+
# safe version
457+
function getindex(::Type{T}, vals::T...) where T
458+
@inline
459+
@_effect_free_terminates_locally_meta
460+
a = Vector{T}(undef, length(vals))
461+
@_safeindex for i in 1:length(vals)
462+
a[i] = vals[i]
463+
end
464+
return a
465+
end
466+
415467
function getindex(::Type{Any}, @nospecialize vals...)
468+
@_effect_free_terminates_locally_meta
416469
a = Vector{Any}(undef, length(vals))
417-
@inbounds for i = 1:length(vals)
470+
@_safeindex for i = 1:length(vals)
418471
a[i] = vals[i]
419472
end
420473
return a
@@ -966,10 +1019,16 @@ Dict{String, Int64} with 2 entries:
9661019
"""
9671020
function setindex! end
9681021

969-
@eval setindex!(A::Array{T}, x, i1::Int) where {T} = arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1)
1022+
@eval setindex!(A::Array{T}, x, i1::Int) where {T} =
1023+
arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1)
9701024
@eval setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} =
9711025
(@inline; arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1, i2, I...))
9721026

1027+
__inbounds_setindex!(A::Array{T}, x, i1::Int) where {T} =
1028+
arrayset(false, A, convert(T,x)::T, i1)
1029+
__inbounds_setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} =
1030+
(@inline; arrayset(false, A, convert(T,x)::T, i1, i2, I...))
1031+
9731032
# This is redundant with the abstract fallbacks but needed and helpful for bootstrap
9741033
function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int})
9751034
@_propagate_inbounds_meta
@@ -1055,26 +1114,27 @@ See also [`pushfirst!`](@ref).
10551114
"""
10561115
function push! end
10571116

1058-
function push!(a::Array{T,1}, item) where T
1117+
function push!(a::Vector{T}, item) where T
10591118
# convert first so we don't grow the array if the assignment won't work
10601119
itemT = convert(T, item)
10611120
_growend!(a, 1)
1062-
@inbounds a[end] = itemT
1121+
@_safeindex a[length(a)] = itemT
10631122
return a
10641123
end
10651124

10661125
# specialize and optimize the single argument case
10671126
function push!(a::Vector{Any}, @nospecialize x)
10681127
_growend!(a, 1)
1069-
arrayset(true, a, x, length(a))
1128+
@_safeindex a[length(a)] = x
10701129
return a
10711130
end
10721131
function push!(a::Vector{Any}, @nospecialize x...)
1132+
@_terminates_locally_meta
10731133
na = length(a)
10741134
nx = length(x)
10751135
_growend!(a, nx)
1076-
for i = 1:nx
1077-
arrayset(true, a, x[i], na+i)
1136+
@_safeindex for i = 1:nx
1137+
a[na+i] = x[i]
10781138
end
10791139
return a
10801140
end
@@ -1129,10 +1189,11 @@ push!(a::AbstractVector, iter...) = append!(a, iter)
11291189
append!(a::AbstractVector, iter...) = foldl(append!, iter, init=a)
11301190

11311191
function _append!(a, ::Union{HasLength,HasShape}, iter)
1192+
@_terminates_locally_meta
11321193
n = length(a)
11331194
i = lastindex(a)
11341195
resize!(a, n+Int(length(iter))::Int)
1135-
@inbounds for (i, item) in zip(i+1:lastindex(a), iter)
1196+
@_safeindex for (i, item) in zip(i+1:lastindex(a), iter)
11361197
a[i] = item
11371198
end
11381199
a
@@ -1194,12 +1255,13 @@ pushfirst!(a::Vector, iter...) = prepend!(a, iter)
11941255
prepend!(a::AbstractVector, iter...) = foldr((v, a) -> prepend!(a, v), iter, init=a)
11951256

11961257
function _prepend!(a, ::Union{HasLength,HasShape}, iter)
1258+
@_terminates_locally_meta
11971259
require_one_based_indexing(a)
11981260
n = length(iter)
11991261
_growbeg!(a, n)
12001262
i = 0
12011263
for item in iter
1202-
@inbounds a[i += 1] = item
1264+
@_safeindex a[i += 1] = item
12031265
end
12041266
a
12051267
end
@@ -1249,7 +1311,7 @@ function resize!(a::Vector, nl::Integer)
12491311
_growend!(a, nl-l)
12501312
elseif nl != l
12511313
if nl < 0
1252-
throw(ArgumentError("new length must be ≥ 0"))
1314+
_throw_argerror("new length must be ≥ 0")
12531315
end
12541316
_deleteend!(a, l-nl)
12551317
end
@@ -1329,7 +1391,7 @@ julia> pop!(Dict(1=>2))
13291391
"""
13301392
function pop!(a::Vector)
13311393
if isempty(a)
1332-
throw(ArgumentError("array must be non-empty"))
1394+
_throw_argerror("array must be non-empty")
13331395
end
13341396
item = a[end]
13351397
_deleteend!(a, 1)
@@ -1403,24 +1465,25 @@ julia> pushfirst!([1, 2, 3, 4], 5, 6)
14031465
4
14041466
```
14051467
"""
1406-
function pushfirst!(a::Array{T,1}, item) where T
1468+
function pushfirst!(a::Vector{T}, item) where T
14071469
item = convert(T, item)
14081470
_growbeg!(a, 1)
1409-
a[1] = item
1471+
@_safeindex a[1] = item
14101472
return a
14111473
end
14121474

14131475
# specialize and optimize the single argument case
14141476
function pushfirst!(a::Vector{Any}, @nospecialize x)
14151477
_growbeg!(a, 1)
1416-
a[1] = x
1478+
@_safeindex a[1] = x
14171479
return a
14181480
end
14191481
function pushfirst!(a::Vector{Any}, @nospecialize x...)
1482+
@_terminates_locally_meta
14201483
na = length(a)
14211484
nx = length(x)
14221485
_growbeg!(a, nx)
1423-
for i = 1:nx
1486+
@_safeindex for i = 1:nx
14241487
a[i] = x[i]
14251488
end
14261489
return a
@@ -1460,7 +1523,7 @@ julia> A
14601523
"""
14611524
function popfirst!(a::Vector)
14621525
if isempty(a)
1463-
throw(ArgumentError("array must be non-empty"))
1526+
_throw_argerror("array must be non-empty")
14641527
end
14651528
item = a[1]
14661529
_deletebeg!(a, 1)
@@ -1600,7 +1663,7 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere())
16001663
(i,s) = y
16011664
if !(q <= i <= n)
16021665
if i < q
1603-
throw(ArgumentError("indices must be unique and sorted"))
1666+
_throw_argerror("indices must be unique and sorted")
16041667
else
16051668
throw(BoundsError())
16061669
end
@@ -1856,7 +1919,7 @@ for (f,_f) in ((:reverse,:_reverse), (:reverse!,:_reverse!))
18561919
$_f(A::AbstractVector, ::Colon) = $f(A, firstindex(A), lastindex(A))
18571920
$_f(A::AbstractVector, dim::Tuple{Integer}) = $_f(A, first(dim))
18581921
function $_f(A::AbstractVector, dim::Integer)
1859-
dim == 1 || throw(ArgumentError("invalid dimension $dim ≠ 1"))
1922+
dim == 1 || _throw_argerror(LazyString("invalid dimension ", dim, " ≠ 1"))
18601923
return $_f(A, :)
18611924
end
18621925
end

0 commit comments

Comments
 (0)