Skip to content

Commit e08ab69

Browse files
aviateskIan Atol
authored andcommitted
don't capture arrays/tuples in BoundsError
This commit changes the semantics of `BoundsError` so that it doesn't capture arrays/tuples passed to its constructor. The change wouldn't improve any performance by itself because the `BoundsError` constructor is mostly called on error paths and thus it is opaque to analyses/optimizers when compiling a caller context. Rather it's supposed to be a building block for broadening the possibility of certain memory optimizations in the future such as copy elision for `ImmutableArray` construction and stack allocation of `Array`s, by allowing us to assume the invariant that primitive indexing operations into array/tuple indexing don't escape it so that our escape analyses can achieve further accuracy. Specifically, when `BoundsError` constructor will now compute the "summary" of its `Array`/`Tuple` argument, rather than capturing it for the later inspection. Assuming `Base.summary(x::Array)` or `Base.summary(x::Tuple)` don't escape `x`, we can assume that `BoundsError` doesn't escape `x`. This change won't appear as breaking in most cases since `showerror` will print the exact same error message as before, but obviously this is technically breaking since we can no longer access to the original arrays/tuples by catching `BoundsError`. I'd say this breaking semantic change would be still acceptable, since I think it's enough if we can know size/type of arrays/tuples from `BoundsError` in most cases. As a last note, I didn't change the semantics for arbitrary user objects, since we still don't have a good infrastructure to tell the compiler some primitive assumptions/rules about user type objects anyway.
1 parent 96d6d86 commit e08ab69

39 files changed

+243
-124
lines changed

base/Base.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) =
5757
replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) =
5858
(@inline; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order))
5959

60+
throw_boundserror(a, i) = (@noinline; throw(BoundsError(a, i)))
61+
throw_boundserror() = (@noinline; throw(BoundsError()))
62+
6063
convert(::Type{Any}, Core.@nospecialize x) = x
6164
convert(::Type{T}, x::T) where {T} = x
6265
include("coreio.jl")

base/abstractarray.jl

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -700,8 +700,6 @@ end
700700
checkbounds_indices(::Type{Bool}, IA::Tuple, ::Tuple{}) = (@inline; all(x->length(x)==1, IA))
701701
checkbounds_indices(::Type{Bool}, ::Tuple{}, ::Tuple{}) = true
702702

703-
throw_boundserror(A, I) = (@noinline; throw(BoundsError(A, I)))
704-
705703
# check along a single dimension
706704
"""
707705
checkindex(Bool, inds::AbstractUnitRange, index)
@@ -956,7 +954,7 @@ function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer, n::
956954
if (dstart inds || dmax inds) | (sstart < 1)
957955
sstart < 1 && throw(ArgumentError(LazyString("source start offset (",
958956
sstart,") is < 1")))
959-
throw(BoundsError(dest, dstart:dmax))
957+
throw_boundserror(dest, dstart:dmax)
960958
end
961959
y = iterate(src)
962960
for j = 1:(sstart-1)
@@ -974,7 +972,7 @@ function copyto!(dest::AbstractArray, dstart::Integer, src, sstart::Integer, n::
974972
y = iterate(src, st)
975973
i += 1
976974
end
977-
i <= dmax && throw(BoundsError(dest, i))
975+
i <= dmax && throw_boundserror(dest, i)
978976
return dest
979977
end
980978

@@ -1027,7 +1025,7 @@ function copyto_unaliased!(deststyle::IndexStyle, dest::AbstractArray, srcstyle:
10271025
idf, isf = first(destinds), first(srcinds)
10281026
Δi = idf - isf
10291027
(checkbounds(Bool, destinds, isf+Δi) & checkbounds(Bool, destinds, last(srcinds)+Δi)) ||
1030-
throw(BoundsError(dest, srcinds))
1028+
throw_boundserror(dest, srcinds)
10311029
if deststyle isa IndexLinear
10321030
if srcstyle isa IndexLinear
10331031
# Single-index implementation
@@ -1067,7 +1065,7 @@ end
10671065

10681066
function copyto!(dest::AbstractArray, dstart::Integer, src::AbstractArray, sstart::Integer)
10691067
srcinds = LinearIndices(src)
1070-
checkbounds(Bool, srcinds, sstart) || throw(BoundsError(src, sstart))
1068+
checkbounds(Bool, srcinds, sstart) || throw_boundserror(src, sstart)
10711069
copyto!(dest, dstart, src, sstart, last(srcinds)-sstart+1)
10721070
end
10731071

@@ -1078,8 +1076,8 @@ function copyto!(dest::AbstractArray, dstart::Integer,
10781076
n < 0 && throw(ArgumentError(LazyString("tried to copy n=",
10791077
n," elements, but n should be nonnegative")))
10801078
destinds, srcinds = LinearIndices(dest), LinearIndices(src)
1081-
(checkbounds(Bool, destinds, dstart) && checkbounds(Bool, destinds, dstart+n-1)) || throw(BoundsError(dest, dstart:dstart+n-1))
1082-
(checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw(BoundsError(src, sstart:sstart+n-1))
1079+
(checkbounds(Bool, destinds, dstart) && checkbounds(Bool, destinds, dstart+n-1)) || throw_boundserror(dest, dstart:dstart+n-1)
1080+
(checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw_boundserror(src, sstart:sstart+n-1)
10831081
@inbounds for i = 0:(n-1)
10841082
dest[dstart+i] = src[sstart+i]
10851083
end
@@ -1306,7 +1304,7 @@ _to_subscript_indices(A, J::Tuple, Jrem::Tuple) = J # already bounds-checked, sa
13061304
_to_subscript_indices(A::AbstractArray{T,N}, I::Vararg{Int,N}) where {T,N} = I
13071305
_remaining_size(::Tuple{Any}, t::Tuple) = t
13081306
_remaining_size(h::Tuple, t::Tuple) = (@inline; _remaining_size(tail(h), tail(t)))
1309-
_unsafe_ind2sub(::Tuple{}, i) = () # _ind2sub may throw(BoundsError()) in this case
1307+
_unsafe_ind2sub(::Tuple{}, i) = () # _ind2sub may throw_boundserror() in this case
13101308
_unsafe_ind2sub(sz, i) = (@inline; _ind2sub(sz, i))
13111309

13121310
## Setindex! is defined similarly. We first dispatch to an internal _setindex!
@@ -2667,7 +2665,7 @@ nextL(L, r::Slice) = L*length(r.indices)
26672665
offsetin(i, l::Integer) = i-1
26682666
offsetin(i, r::AbstractUnitRange) = i-first(r)
26692667

2670-
_ind2sub(::Tuple{}, ind::Integer) = (@inline; ind == 1 ? () : throw(BoundsError()))
2668+
_ind2sub(::Tuple{}, ind::Integer) = (@inline; ind == 1 ? () : throw_boundserror())
26712669
_ind2sub(dims::DimsInteger, ind::Integer) = (@inline; _ind2sub_recurse(dims, ind-1))
26722670
_ind2sub(inds::Indices, ind::Integer) = (@inline; _ind2sub_recurse(inds, ind-1))
26732671
_ind2sub(inds::Indices{1}, ind::Integer) =
@@ -3163,7 +3161,7 @@ function _keepat!(a::AbstractVector, inds)
31633161
end
31643162

31653163
function _keepat!(a::AbstractVector, m::AbstractVector{Bool})
3166-
length(m) == length(a) || throw(BoundsError(a, m))
3164+
length(m) == length(a) || throw_boundserror(a, m)
31673165
j = firstindex(a)
31683166
for i in eachindex(a, m)
31693167
@inbounds begin

base/abstractarraymath.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ julia> selectdim(A, 2, 3:4)
253253
@noinline function _selectdim(A, d, i, idxs)
254254
d >= 1 || throw(ArgumentError("dimension must be ≥ 1, got $d"))
255255
nd = ndims(A)
256-
d > nd && (i == 1 || throw(BoundsError(A, (ntuple(Returns(Colon()),d-1)..., i))))
256+
d > nd && (i == 1 || throw_boundserror(A, (ntuple(Returns(Colon()),d-1)..., i)))
257257
return view(A, idxs...)
258258
end
259259

base/array.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ function _copyto_impl!(dest::Array, doffs::Integer, src::Array, soffs::Integer,
326326
n == 0 && return dest
327327
n > 0 || _throw_argerror()
328328
if soffs < 1 || doffs < 1 || soffs+n-1 > length(src) || doffs+n-1 > length(dest)
329-
throw(BoundsError())
329+
throw_boundserror()
330330
end
331331
unsafe_copyto!(dest, doffs, src, soffs, n)
332332
return dest
@@ -1568,7 +1568,7 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere())
15681568
if i < q
15691569
throw(ArgumentError("indices must be unique and sorted"))
15701570
else
1571-
throw(BoundsError())
1571+
throw_boundserror()
15721572
end
15731573
end
15741574
while q < i
@@ -1589,7 +1589,7 @@ end
15891589
# Simpler and more efficient version for logical indexing
15901590
function deleteat!(a::Vector, inds::AbstractVector{Bool})
15911591
n = length(a)
1592-
length(inds) == n || throw(BoundsError(a, inds))
1592+
length(inds) == n || throw_boundserror(a, inds)
15931593
p = 1
15941594
for (q, i) in enumerate(inds)
15951595
_copy_item!(a, p, q)
@@ -1864,9 +1864,9 @@ function reverse!(v::AbstractVector, start::Integer, stop::Integer=lastindex(v))
18641864
liv = LinearIndices(v)
18651865
if n <= s # empty case; ok
18661866
elseif !(first(liv) s last(liv))
1867-
throw(BoundsError(v, s))
1867+
throw_boundserror(v, s)
18681868
elseif !(first(liv) n last(liv))
1869-
throw(BoundsError(v, n))
1869+
throw_boundserror(v, n)
18701870
end
18711871
r = n
18721872
@inbounds for i in s:div(s+n-1, 2)

base/bitarray.jl

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ function one(x::BitMatrix)
434434
end
435435

436436
function copyto!(dest::BitArray, src::BitArray)
437-
length(src) > length(dest) && throw(BoundsError(dest, length(dest)+1))
437+
length(src) > length(dest) && throw_boundserror(dest, length(dest)+1)
438438
destc = dest.chunks; srcc = src.chunks
439439
nc = min(length(destc), length(srcc))
440440
nc == 0 && return dest
@@ -462,15 +462,15 @@ copyto!(dest::BitArray, doffs::Integer, src::Array, soffs::Integer, n::Integer)
462462
_copyto_int!(dest, Int(doffs), src, Int(soffs), Int(n))
463463
function _copyto_int!(dest::BitArray, doffs::Int, src::Array, soffs::Int, n::Int)
464464
n == 0 && return dest
465-
soffs < 1 && throw(BoundsError(src, soffs))
466-
doffs < 1 && throw(BoundsError(dest, doffs))
467-
soffs+n-1 > length(src) && throw(BoundsError(src, length(src)+1))
468-
doffs+n-1 > length(dest) && throw(BoundsError(dest, length(dest)+1))
465+
soffs < 1 && throw_boundserror(src, soffs)
466+
doffs < 1 && throw_boundserror(dest, doffs)
467+
soffs+n-1 > length(src) && throw_boundserror(src, length(src)+1)
468+
doffs+n-1 > length(dest) && throw_boundserror(dest, length(dest)+1)
469469
return unsafe_copyto!(dest, doffs, src, soffs, n)
470470
end
471471

472472
function copyto!(dest::BitArray, src::Array)
473-
length(src) > length(dest) && throw(BoundsError(dest, length(dest)+1))
473+
length(src) > length(dest) && throw_boundserror(dest, length(dest)+1)
474474
length(src) == 0 && return dest
475475
return unsafe_copyto!(dest, 1, src, 1, length(src))
476476
end
@@ -811,7 +811,7 @@ resize!(B::BitVector, n::Integer) = _resize_int!(B, Int(n))
811811
function _resize_int!(B::BitVector, n::Int)
812812
n0 = length(B)
813813
n == n0 && return B
814-
n >= 0 || throw(BoundsError(B, n))
814+
n >= 0 || throw_boundserror(B, n)
815815
if n < n0
816816
deleteat!(B, n+1:n0)
817817
return B
@@ -888,7 +888,7 @@ insert!(B::BitVector, i::Integer, item) = _insert_int!(B, Int(i), item)
888888
function _insert_int!(B::BitVector, i::Int, item)
889889
i = Int(i)
890890
n = length(B)
891-
1 <= i <= n+1 || throw(BoundsError(B, i))
891+
1 <= i <= n+1 || throw_boundserror(B, i)
892892
item = convert(Bool, item)
893893

894894
Bc = B.chunks
@@ -950,7 +950,7 @@ function deleteat!(B::BitVector, i::Integer)
950950
i isa Bool && depwarn("passing Bool as an index is deprecated", :deleteat!)
951951
i = Int(i)
952952
n = length(B)
953-
1 <= i <= n || throw(BoundsError(B, i))
953+
1 <= i <= n || throw_boundserror(B, i)
954954

955955
return _deleteat!(B, i)
956956
end
@@ -959,8 +959,8 @@ function deleteat!(B::BitVector, r::AbstractUnitRange{Int})
959959
n = length(B)
960960
i_f = first(r)
961961
i_l = last(r)
962-
1 <= i_f || throw(BoundsError(B, i_f))
963-
i_l <= n || throw(BoundsError(B, n+1))
962+
1 <= i_f || throw_boundserror(B, i_f)
963+
i_l <= n || throw_boundserror(B, n+1)
964964

965965
Bc = B.chunks
966966
new_l = length(B) - length(r)
@@ -997,7 +997,7 @@ function deleteat!(B::BitVector, inds)
997997
if !(q <= i <= n)
998998
i isa Bool && throw(ArgumentError("invalid index $i of type Bool"))
999999
i < q && throw(ArgumentError("indices must be unique and sorted"))
1000-
throw(BoundsError(B, i))
1000+
throw_boundserror(B, i)
10011001
end
10021002
new_l -= 1
10031003
if i > q
@@ -1023,7 +1023,7 @@ function deleteat!(B::BitVector, inds)
10231023
end
10241024

10251025
function deleteat!(B::BitVector, inds::AbstractVector{Bool})
1026-
length(inds) == length(B) || throw(BoundsError(B, inds))
1026+
length(inds) == length(B) || throw_boundserror(B, inds)
10271027

10281028
n = new_l = length(B)
10291029
y = findfirst(inds)
@@ -1073,7 +1073,7 @@ function splice!(B::BitVector, i::Integer)
10731073
i isa Bool && depwarn("passing Bool as an index is deprecated", :splice!)
10741074
i = Int(i)
10751075
n = length(B)
1076-
1 <= i <= n || throw(BoundsError(B, i))
1076+
1 <= i <= n || throw_boundserror(B, i)
10771077

10781078
v = B[i] # TODO: change to a copy if/when subscripting becomes an ArrayView
10791079
_deleteat!(B, i)
@@ -1090,8 +1090,8 @@ end
10901090
function _splice_int!(B::BitVector, r, ins)
10911091
n = length(B)
10921092
i_f, i_l = first(r), last(r)
1093-
1 <= i_f <= n+1 || throw(BoundsError(B, i_f))
1094-
i_l <= n || throw(BoundsError(B, n+1))
1093+
1 <= i_f <= n+1 || throw_boundserror(B, i_f)
1094+
i_l <= n || throw_boundserror(B, n+1)
10951095

10961096
Bins = convert(BitArray, ins)
10971097

@@ -1471,7 +1471,7 @@ end
14711471
# returns the index of the next true element, or nothing if all false
14721472
function findnext(B::BitArray, start::Integer)
14731473
start = Int(start)
1474-
start > 0 || throw(BoundsError(B, start))
1474+
start > 0 || throw_boundserror(B, start)
14751475
start > length(B) && return nothing
14761476
unsafe_bitfindnext(B.chunks, start)
14771477
end
@@ -1480,7 +1480,7 @@ end
14801480

14811481
# aux function: same as findnext(~B, start), but performed without temporaries
14821482
function findnextnot(B::BitArray, start::Int)
1483-
start > 0 || throw(BoundsError(B, start))
1483+
start > 0 || throw_boundserror(B, start)
14841484
start > length(B) && return nothing
14851485

14861486
Bc = B.chunks
@@ -1528,7 +1528,7 @@ function _findnext_int(testf::Function, B::BitArray, start::Int)
15281528
!f0 && f1 && return findnext(B, start)
15291529
f0 && !f1 && return findnextnot(B, start)
15301530

1531-
start > 0 || throw(BoundsError(B, start))
1531+
start > 0 || throw_boundserror(B, start)
15321532
start > length(B) && return nothing
15331533
f0 && f1 && return start
15341534
return nothing # last case: !f0 && !f1
@@ -1557,14 +1557,14 @@ end
15571557
function findprev(B::BitArray, start::Integer)
15581558
start = Int(start)
15591559
start > 0 || return nothing
1560-
start > length(B) && throw(BoundsError(B, start))
1560+
start > length(B) && throw_boundserror(B, start)
15611561
unsafe_bitfindprev(B.chunks, start)
15621562
end
15631563

15641564
function findprevnot(B::BitArray, start::Int)
15651565
start = Int(start)
15661566
start > 0 || return nothing
1567-
start > length(B) && throw(BoundsError(B, start))
1567+
start > length(B) && throw_boundserror(B, start)
15681568

15691569
Bc = B.chunks
15701570

@@ -1605,7 +1605,7 @@ function _findprev_int(testf::Function, B::BitArray, start::Int)
16051605
f0 && !f1 && return findprevnot(B, start)
16061606

16071607
start > 0 || return nothing
1608-
start > length(B) && throw(BoundsError(B, start))
1608+
start > length(B) && throw_boundserror(B, start)
16091609
f0 && f1 && return start
16101610
return nothing # last case: !f0 && !f1
16111611
end

base/boot.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,30 @@ end
269269

270270
macro inline() Expr(:meta, :inline) end
271271
macro noinline() Expr(:meta, :noinline) end
272-
272+
import .Intrinsics: ult_int
273+
struct Summarized
274+
axes #=::Union{Base.OneTo, Integer}=#
275+
type::Type
276+
function Summarized(a)
277+
isa(a, Array) || return a
278+
if isdefined(Main, :Base)
279+
return new(Main.Base.axes(a), typeof(a))
280+
else
281+
return new(nothing, typeof(a))
282+
end
283+
end
284+
Summarized(ax, typ) = new(ax, typ)
285+
end
273286
struct BoundsError <: Exception
274-
a::Any
287+
a
275288
i::Any
276289
BoundsError() = new()
277-
BoundsError(@nospecialize(a)) = (@noinline; new(a))
278-
BoundsError(@nospecialize(a), i) = (@noinline; new(a,i))
290+
BoundsError(@nospecialize(a)) = (@noinline; new(Summarized(a)))
291+
BoundsError(@nospecialize(a), i) = (@noinline; new(Summarized(a), i))
292+
BoundsError(ax, typ::Type) = (@noinline; new(Summarized((ax,), typ)))
293+
BoundsError(ax, typ::Type, i) = (@noinline; new(Summarized((ax,), typ), i))
279294
end
295+
280296
struct DivideError <: Exception end
281297
struct OutOfMemoryError <: Exception end
282298
struct ReadOnlyMemoryError <: Exception end

base/char.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,16 @@ typemax(::Type{Char}) = bitcast(Char, typemax(UInt32))
192192
typemin(::Type{Char}) = bitcast(Char, typemin(UInt32))
193193

194194
size(c::AbstractChar) = ()
195-
size(c::AbstractChar, d::Integer) = d < 1 ? throw(BoundsError()) : 1
195+
size(c::AbstractChar, d::Integer) = d < 1 ? throw_boundserror() : 1
196196
ndims(c::AbstractChar) = 0
197197
ndims(::Type{<:AbstractChar}) = 0
198198
length(c::AbstractChar) = 1
199199
IteratorSize(::Type{Char}) = HasShape{0}()
200200
firstindex(c::AbstractChar) = 1
201201
lastindex(c::AbstractChar) = 1
202202
getindex(c::AbstractChar) = c
203-
getindex(c::AbstractChar, i::Integer) = i == 1 ? c : throw(BoundsError())
204-
getindex(c::AbstractChar, I::Integer...) = all(x -> x == 1, I) ? c : throw(BoundsError())
203+
getindex(c::AbstractChar, i::Integer) = i == 1 ? c : throw_boundserror()
204+
getindex(c::AbstractChar, I::Integer...) = all(x -> x == 1, I) ? c : throw_boundserror()
205205
first(c::AbstractChar) = c
206206
last(c::AbstractChar) = c
207207
eltype(::Type{T}) where {T<:AbstractChar} = T

base/combinatorics.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ isperm(P::Any32) = _isperm(P)
9797
function swapcols!(a::AbstractMatrix, i, j)
9898
i == j && return
9999
cols = axes(a,2)
100-
@boundscheck i in cols || throw(BoundsError(a, (:,i)))
101-
@boundscheck j in cols || throw(BoundsError(a, (:,j)))
100+
@boundscheck i in cols || throw_boundserror(a, (:,i))
101+
@boundscheck j in cols || throw_boundserror(a, (:,j))
102102
for k in axes(a,1)
103103
@inbounds a[k,i],a[k,j] = a[k,j],a[k,i]
104104
end
@@ -108,8 +108,8 @@ end
108108
function swaprows!(a::AbstractMatrix, i, j)
109109
i == j && return
110110
rows = axes(a,1)
111-
@boundscheck i in rows || throw(BoundsError(a, (:,i)))
112-
@boundscheck j in rows || throw(BoundsError(a, (:,j)))
111+
@boundscheck i in rows || throw_boundserror(a, (:,i))
112+
@boundscheck j in rows || throw_boundserror(a, (:,j))
113113
for k in axes(a,2)
114114
@inbounds a[i,k],a[j,k] = a[j,k],a[i,k]
115115
end

0 commit comments

Comments
 (0)