• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37632

26 Sep 2023 06:44AM UTC coverage: 86.999% (-0.9%) from 87.914%
#37632

push

local

web-flow
inference: make `throw` block deoptimization concrete-eval friendly (#49235)

The deoptimization can sometimes destroy the effects analysis and
disable [semi-]concrete evaluation that is otherwise possible. This is
because the deoptimization was designed with the type domain
profitability in mind (#35982), and hasn't been adequately considering
the effects domain.

This commit makes the deoptimization aware of the effects domain more
and enables the `throw` block deoptimization only when the effects
already known to be ineligible for concrete-evaluation.

In our current effect system, `ALWAYS_FALSE`/`false` means that the
effect can not be refined to `ALWAYS_TRUE`/`true` anymore (unless given
user annotation later). Therefore we can enable the `throw` block
deoptimization without hindering the chance of concrete-evaluation when
any of the following conditions are met:
- `effects.consistent === ALWAYS_FALSE`
- `effects.effect_free === ALWAYS_FALSE`
- `effects.terminates === false`
- `effects.nonoverlayed === false`

Here are some numbers:

| Metric | master | this commit | #35982 reverted (set
`unoptimize_throw_blocks=false`) |

|-------------------------|-----------|-------------|--------------------------------------------|
| Base (seconds) | 15.579300 | 15.206645 | 15.296319 |
| Stdlibs (seconds) | 17.919013 | 17.667094 | 17.738128 |
| Total (seconds) | 33.499279 | 32.874737 | 33.035448 |
| Precompilation (seconds) | 49.967516 | 49.421121 | 49.999998 |
| First time `plot(rand(10,3))` [^1] | `2.476678 seconds (11.74 M
allocations)` | `2.430355 seconds (11.77 M allocations)` | `2.514874
seconds (11.64 M allocations)` |
| First time `solve(prob, QNDF())(5.0)` [^2] | `4.469492 seconds (15.32
M allocations)` | `4.499217 seconds (15.41 M allocations)` | `4.470772
seconds (15.38 M allocations)` |

[^1]: With disabling precompilation of Plots.jl.
[^2]: With disabling precompilation of OrdinaryDiffEq.

These numbers ma... (continued)

7 of 7 new or added lines in 1 file covered. (100.0%)

73407 of 84377 relevant lines covered (87.0%)

11275130.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.69
/base/subarray.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
abstract type AbstractCartesianIndex{N} end # This is a hacky forward declaration for CartesianIndex
4
const ViewIndex = Union{Real, AbstractArray}
5
const ScalarIndex = Real
6

7
"""
8
    SubArray{T,N,P,I,L} <: AbstractArray{T,N}
9

10
`N`-dimensional view into a parent array (of type `P`) with an element type `T`, restricted by a tuple of indices (of type `I`). `L` is true for types that support fast linear indexing, and `false` otherwise.
11

12
Construct `SubArray`s using the [`view`](@ref) function.
13
"""
14
struct SubArray{T,N,P,I,L} <: AbstractArray{T,N}
15
    parent::P
16
    indices::I
17
    offset1::Int       # for linear indexing and pointer, only valid when L==true
18
    stride1::Int       # used only for linear indexing
19
    function SubArray{T,N,P,I,L}(parent, indices, offset1, stride1) where {T,N,P,I,L}
519,319✔
20
        @inline
519,319✔
21
        check_parent_index_match(parent, indices)
519,319✔
22
        new(parent, indices, offset1, stride1)
4,406,768✔
23
    end
24
end
25
# Compute the linear indexability of the indices, and combine it with the linear indexing of the parent
26
function SubArray(parent::AbstractArray, indices::Tuple)
656,735✔
27
    @inline
518,759✔
28
    SubArray(IndexStyle(viewindexing(indices), IndexStyle(parent)), parent, ensure_indexable(indices), index_dimsum(indices...))
4,406,206✔
29
end
30
function SubArray(::IndexCartesian, parent::P, indices::I, ::NTuple{N,Any}) where {P,I,N}
234,123✔
31
    @inline
234,123✔
32
    SubArray{eltype(P), N, P, I, false}(parent, indices, 0, 0)
234,123✔
33
end
34
function SubArray(::IndexLinear, parent::P, indices::I, ::NTuple{N,Any}) where {P,I,N}
284,636✔
35
    @inline
284,636✔
36
    # Compute the stride and offset
37
    stride1 = compute_stride1(parent, indices)
284,710✔
38
    SubArray{eltype(P), N, P, I, true}(parent, indices, compute_offset1(parent, stride1, indices), stride1)
4,172,083✔
39
end
40

41
check_parent_index_match(parent, indices) = check_parent_index_match(parent, index_ndims(indices...))
519,318✔
42
check_parent_index_match(parent::AbstractArray{T,N}, ::NTuple{N, Bool}) where {T,N} = nothing
449,376✔
43
check_parent_index_match(parent, ::NTuple{N, Bool}) where {N} =
×
44
    throw(ArgumentError("number of indices ($N) must match the parent dimensionality ($(ndims(parent)))"))
45

46
# This computes the linear indexing compatibility for a given tuple of indices
47
viewindexing(I::Tuple{}) = IndexLinear()
50✔
48
# Leading scalar indices simply increase the stride
49
viewindexing(I::Tuple{ScalarIndex, Vararg{Any}}) = (@inline; viewindexing(tail(I)))
7,089✔
50
# Slices may begin a section which may be followed by any number of Slices
51
viewindexing(I::Tuple{Slice, Slice, Vararg{Any}}) = (@inline; viewindexing(tail(I)))
2,114✔
52
# A UnitRange can follow Slices, but only if all other indices are scalar
53
viewindexing(I::Tuple{Slice, AbstractUnitRange, Vararg{ScalarIndex}}) = IndexLinear()
201✔
54
viewindexing(I::Tuple{Slice, Slice, Vararg{ScalarIndex}}) = IndexLinear() # disambiguate
1,362✔
55
# In general, ranges are only fast if all other indices are scalar
56
viewindexing(I::Tuple{AbstractRange, Vararg{ScalarIndex}}) = IndexLinear()
147,654✔
57
# All other index combinations are slow
58
viewindexing(I::Tuple{Vararg{Any}}) = IndexCartesian()
×
59
# Of course, all other array types are slow
60
viewindexing(I::Tuple{AbstractArray, Vararg{Any}}) = IndexCartesian()
17,836✔
61

62
# Simple utilities
63
size(V::SubArray) = (@inline; map(length, axes(V)))
12,814,015✔
64

65
similar(V::SubArray, T::Type, dims::Dims) = similar(V.parent, T, dims)
6,331✔
66

67
sizeof(V::SubArray) = length(V) * sizeof(eltype(V))
2✔
68
sizeof(V::SubArray{<:Any,<:Any,<:Array}) = length(V) * elsize(V.parent)
443,094✔
69

70
function Base.copy(V::SubArray)
594✔
71
    v = V.parent[V.indices...]
1,074✔
72
    ndims(V) == 0 || return v
594✔
73
    x = similar(V) # ensure proper type of x
×
74
    x[] = v
×
75
    return x
×
76
end
77

78
parent(V::SubArray) = V.parent
112,878✔
79
parentindices(V::SubArray) = V.indices
109,796✔
80

81
"""
82
    parentindices(A)
83

84
Return the indices in the [`parent`](@ref) which correspond to the view `A`.
85

86
# Examples
87
```jldoctest
88
julia> A = [1 2; 3 4];
89

90
julia> V = view(A, 1, :)
91
2-element view(::Matrix{Int64}, 1, :) with eltype Int64:
92
 1
93
 2
94

95
julia> parentindices(V)
96
(1, Base.Slice(Base.OneTo(2)))
97
```
98
"""
99
function parentindices end
100

101
parentindices(a::AbstractArray) = map(oneto, size(a))
×
102

103
## Aliasing detection
104
dataids(A::SubArray) = (dataids(A.parent)..., _splatmap(dataids, A.indices)...)
2,919,606✔
105
_splatmap(f, ::Tuple{}) = ()
2,641,148✔
106
_splatmap(f, t::Tuple) = (f(t[1])..., _splatmap(f, tail(t))...)
2,649,577✔
107
unaliascopy(A::SubArray) = typeof(A)(unaliascopy(A.parent), map(unaliascopy, A.indices), A.offset1, A.stride1)
4✔
108

109
# When the parent is an Array we can trim the size down a bit. In the future this
110
# could possibly be extended to any mutable array.
111
function unaliascopy(V::SubArray{T,N,A,I,LD}) where {T,N,A<:Array,I<:Tuple{Vararg{Union{Real,AbstractRange,Array}}},LD}
558✔
112
    dest = Array{T}(undef, index_lengths(V.indices...))
558✔
113
    copyto!(dest, V)
558✔
114
    SubArray{T,N,A,I,LD}(dest, map(_trimmedindex, V.indices), 0, Int(LD))
558✔
115
end
116
# Transform indices to be "dense"
117
_trimmedindex(i::Real) = oftype(i, 1)
540✔
118
_trimmedindex(i::AbstractUnitRange) = oftype(i, oneto(length(i)))
559✔
119
_trimmedindex(i::AbstractArray) = oftype(i, reshape(eachindex(IndexLinear(), i), axes(i)))
×
120

121
## SubArray creation
122
# We always assume that the dimensionality of the parent matches the number of
123
# indices that end up getting passed to it, so we store the parent as a
124
# ReshapedArray view if necessary. The trouble is that arrays of `CartesianIndex`
125
# can make the number of effective indices not equal to length(I).
126
_maybe_reshape_parent(A::AbstractArray, ::NTuple{1, Bool}) = reshape(A, Val(1))
1,123✔
127
_maybe_reshape_parent(A::AbstractArray{<:Any,1}, ::NTuple{1, Bool}) = reshape(A, Val(1))
422,802✔
128
_maybe_reshape_parent(A::AbstractArray{<:Any,N}, ::NTuple{N, Bool}) where {N} = A
153,730✔
129
_maybe_reshape_parent(A::AbstractArray, ::NTuple{N, Bool}) where {N} = reshape(A, Val(N))
442✔
130
"""
131
    view(A, inds...)
132

133
Like [`getindex`](@ref), but returns a lightweight array that lazily references
134
(or is effectively a _view_ into) the parent array `A` at the given index or indices
135
`inds` instead of eagerly extracting elements or constructing a copied subset.
136
Calling [`getindex`](@ref) or [`setindex!`](@ref) on the returned value
137
(often a [`SubArray`](@ref)) computes the indices to access or modify the
138
parent array on the fly.  The behavior is undefined if the shape of the parent array is
139
changed after `view` is called because there is no bound check for the parent array; e.g.,
140
it may cause a segmentation fault.
141

142
Some immutable parent arrays (like ranges) may choose to simply
143
recompute a new array in some circumstances instead of returning
144
a `SubArray` if doing so is efficient and provides compatible semantics.
145

146
!!! compat "Julia 1.6"
147
    In Julia 1.6 or later, `view` can be called on an `AbstractString`, returning a
148
    `SubString`.
149

150
# Examples
151
```jldoctest
152
julia> A = [1 2; 3 4]
153
2×2 Matrix{Int64}:
154
 1  2
155
 3  4
156

157
julia> b = view(A, :, 1)
158
2-element view(::Matrix{Int64}, :, 1) with eltype Int64:
159
 1
160
 3
161

162
julia> fill!(b, 0)
163
2-element view(::Matrix{Int64}, :, 1) with eltype Int64:
164
 0
165
 0
166

167
julia> A # Note A has changed even though we modified b
168
2×2 Matrix{Int64}:
169
 0  2
170
 0  4
171

172
julia> view(2:5, 2:3) # returns a range as type is immutable
173
3:4
174
```
175
"""
176
function view(A::AbstractArray{<:Any,N}, I::Vararg{Any,M}) where {N,M}
3,832,493✔
177
    @inline
1,346,009✔
178
    J = map(i->unalias(A,i), to_indices(A, I))
2,177,897✔
179
    @boundscheck checkbounds(A, J...)
4,407,019✔
180
    if length(J) > ndims(A) && J[N+1:end] isa Tuple{Vararg{Int}}
1,345,991✔
181
        # view([1,2,3], :, 1) does not need to reshape
182
        return unsafe_view(A, J[1:N]...)
8,892✔
183
    end
184
    unsafe_view(_maybe_reshape_parent(A, index_ndims(J...)), J...)
4,398,157✔
185
end
186

187
# Ranges implement getindex to return recomputed ranges; use that for views, too (when possible)
188
function view(r1::OneTo, r2::OneTo)
1✔
189
    @_propagate_inbounds_meta
1✔
190
    getindex(r1, r2)
1✔
191
end
192
function view(r1::AbstractUnitRange, r2::AbstractUnitRange{<:Integer})
285✔
193
    @_propagate_inbounds_meta
285✔
194
    getindex(r1, r2)
285✔
195
end
196
function view(r1::AbstractUnitRange, r2::StepRange{<:Integer})
1✔
197
    @_propagate_inbounds_meta
1✔
198
    getindex(r1, r2)
1✔
199
end
200
function view(r1::StepRange, r2::AbstractRange{<:Integer})
1✔
201
    @_propagate_inbounds_meta
1✔
202
    getindex(r1, r2)
1✔
203
end
204
function view(r1::StepRangeLen, r2::OrdinalRange{<:Integer})
×
205
    @_propagate_inbounds_meta
×
206
    getindex(r1, r2)
×
207
end
208
function view(r1::LinRange, r2::OrdinalRange{<:Integer})
×
209
    @_propagate_inbounds_meta
×
210
    getindex(r1, r2)
×
211
end
212

213
# getindex(r::AbstractRange, ::Colon) returns a copy of the range, and we may do the same for a view
214
function view(r1::AbstractRange, c::Colon)
2✔
215
    @_propagate_inbounds_meta
2✔
216
    getindex(r1, c)
2✔
217
end
218

219
function unsafe_view(A::AbstractArray, I::Vararg{ViewIndex,N}) where {N}
1,195,917✔
220
    @inline
506,659✔
221
    SubArray(A, I)
4,256,128✔
222
end
223
# When we take the view of a view, it's often possible to "reindex" the parent
224
# view's indices such that we can "pop" the parent view and keep just one layer
225
# of indirection. But we can't always do this because arrays of `CartesianIndex`
226
# might span multiple parent indices, making the reindex calculation very hard.
227
# So we use _maybe_reindex to figure out if there are any arrays of
228
# `CartesianIndex`, and if so, we punt and keep two layers of indirection.
229
unsafe_view(V::SubArray, I::Vararg{ViewIndex,N}) where {N} =
150,075✔
230
    (@inline; _maybe_reindex(V, I))
150,173✔
231
_maybe_reindex(V, I) = (@inline; _maybe_reindex(V, I, I))
150,173✔
232
_maybe_reindex(V, I, ::Tuple{AbstractArray{<:AbstractCartesianIndex}, Vararg{Any}}) =
17✔
233
    (@inline; SubArray(V, I))
17✔
234
# But allow arrays of CartesianIndex{1}; they behave just like arrays of Ints
235
_maybe_reindex(V, I, A::Tuple{AbstractArray{<:AbstractCartesianIndex{1}}, Vararg{Any}}) =
×
236
    (@inline; _maybe_reindex(V, I, tail(A)))
×
237
_maybe_reindex(V, I, A::Tuple{Any, Vararg{Any}}) = (@inline; _maybe_reindex(V, I, tail(A)))
157,351✔
238
function _maybe_reindex(V, I, ::Tuple{})
150,058✔
239
    @inline
150,058✔
240
    @inbounds idxs = to_indices(V.parent, reindex(V.indices, I))
150,156✔
241
    SubArray(V.parent, idxs)
150,060✔
242
end
243

244
## Re-indexing is the heart of a view, transforming A[i, j][x, y] to A[i[x], j[y]]
245
#
246
# Recursively look through the heads of the parent- and sub-indices, considering
247
# the following cases:
248
# * Parent index is array  -> re-index that with one or more sub-indices (one per dimension)
249
# * Parent index is Colon  -> just use the sub-index as provided
250
# * Parent index is scalar -> that dimension was dropped, so skip the sub-index and use the index as is
251

252
AbstractZeroDimArray{T} = AbstractArray{T, 0}
253

254
reindex(::Tuple{}, ::Tuple{}) = ()
13,350,284✔
255

256
# Skip dropped scalars, so simply peel them off the parent indices and continue
257
reindex(idxs::Tuple{ScalarIndex, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) =
2,046,313✔
258
    (@_propagate_inbounds_meta; (idxs[1], reindex(tail(idxs), subidxs)...))
2,046,313✔
259

260
# Slices simply pass their subindices straight through
261
reindex(idxs::Tuple{Slice, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) =
6,136,596✔
262
    (@_propagate_inbounds_meta; (subidxs[1], reindex(tail(idxs), tail(subidxs))...))
6,136,610✔
263

264
# Re-index into parent vectors with one subindex
265
reindex(idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) =
16,861,825✔
266
    (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(tail(idxs), tail(subidxs))...))
16,861,937✔
267

268
# Parent matrices are re-indexed with two sub-indices
269
reindex(idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any, Vararg{Any}}) =
4,588✔
270
    (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], reindex(tail(idxs), tail(tail(subidxs)))...))
4,594✔
271

272
# In general, we index N-dimensional parent arrays with N indices
273
@generated function reindex(idxs::Tuple{AbstractArray{T,N}, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) where {T,N}
×
274
    if length(subidxs.parameters) >= N
6✔
275
        subs = [:(subidxs[$d]) for d in 1:N]
×
276
        tail = [:(subidxs[$d]) for d in N+1:length(subidxs.parameters)]
×
277
        :(@_propagate_inbounds_meta; (idxs[1][$(subs...)], reindex(tail(idxs), ($(tail...),))...))
×
278
    else
279
        :(throw(ArgumentError("cannot re-index SubArray with fewer indices than dimensions\nThis should not occur; please submit a bug report.")))
6✔
280
    end
281
end
282

283
# In general, we simply re-index the parent indices by the provided ones
284
SlowSubArray{T,N,P,I} = SubArray{T,N,P,I,false}
285
function getindex(V::SubArray{T,N}, I::Vararg{Int,N}) where {T,N}
4,746,148✔
286
    @inline
4,746,148✔
287
    @boundscheck checkbounds(V, I...)
4,746,157✔
288
    @inbounds r = V.parent[reindex(V.indices, I)...]
4,874,794✔
289
    r
4,746,139✔
290
end
291

292
# But SubArrays with fast linear indexing pre-compute a stride and offset
293
FastSubArray{T,N,P,I} = SubArray{T,N,P,I,true}
294
function getindex(V::FastSubArray, i::Int)
352✔
295
    @inline
352✔
296
    @boundscheck checkbounds(V, i)
352✔
297
    @inbounds r = V.parent[V.offset1 + V.stride1*i]
352✔
298
    r
352✔
299
end
300
# We can avoid a multiplication if the first parent index is a Colon or AbstractUnitRange,
301
# or if all the indices are scalars, i.e. the view is for a single value only
302
FastContiguousSubArray{T,N,P,I<:Union{Tuple{Union{Slice, AbstractUnitRange}, Vararg{Any}},
303
                                      Tuple{Vararg{ScalarIndex}}}} = SubArray{T,N,P,I,true}
304
function getindex(V::FastContiguousSubArray, i::Int)
7,968✔
305
    @inline
7,968✔
306
    @boundscheck checkbounds(V, i)
7,970✔
307
    @inbounds r = V.parent[V.offset1 + i]
7,966✔
308
    r
7,966✔
309
end
310
# For vector views with linear indexing, we disambiguate to favor the stride/offset
311
# computation as that'll generally be faster than (or just as fast as) re-indexing into a range.
312
function getindex(V::FastSubArray{<:Any, 1}, i::Int)
94,162✔
313
    @inline
90,671✔
314
    @boundscheck checkbounds(V, i)
180,127✔
315
    @inbounds r = V.parent[V.offset1 + V.stride1*i]
180,121✔
316
    r
180,121✔
317
end
318
function getindex(V::FastContiguousSubArray{<:Any, 1}, i::Int)
86,752,298✔
319
    @inline
86,752,298✔
320
    @boundscheck checkbounds(V, i)
103,125,445✔
321
    @inbounds r = V.parent[V.offset1 + i]
103,125,439✔
322
    r
103,125,439✔
323
end
324

325
# Indexed assignment follows the same pattern as `getindex` above
326
function setindex!(V::SubArray{T,N}, x, I::Vararg{Int,N}) where {T,N}
8,449,081✔
327
    @inline
8,449,081✔
328
    @boundscheck checkbounds(V, I...)
8,449,088✔
329
    @inbounds V.parent[reindex(V.indices, I)...] = x
8,449,150✔
330
    V
8,449,074✔
331
end
332
function setindex!(V::FastSubArray, x, i::Int)
1,000,628✔
333
    @inline
1,000,628✔
334
    @boundscheck checkbounds(V, i)
1,000,628✔
335
    @inbounds V.parent[V.offset1 + V.stride1*i] = x
1,000,628✔
336
    V
1,000,628✔
337
end
338
function setindex!(V::FastContiguousSubArray, x, i::Int)
21,477✔
339
    @inline
21,477✔
340
    @boundscheck checkbounds(V, i)
21,477✔
341
    @inbounds V.parent[V.offset1 + i] = x
21,477✔
342
    V
21,477✔
343
end
344
function setindex!(V::FastSubArray{<:Any, 1}, x, i::Int)
28,269✔
345
    @inline
28,180✔
346
    @boundscheck checkbounds(V, i)
28,271✔
347
    @inbounds V.parent[V.offset1 + V.stride1*i] = x
28,271✔
348
    V
28,271✔
349
end
350
function setindex!(V::FastContiguousSubArray{<:Any, 1}, x, i::Int)
77,314,275✔
351
    @inline
77,314,014✔
352
    @boundscheck checkbounds(V, i)
77,474,493✔
353
    @inbounds V.parent[V.offset1 + i] = x
77,474,493✔
354
    V
77,474,493✔
355
end
356

357
function isassigned(V::SubArray{T,N}, I::Vararg{Int,N}) where {T,N}
5,637✔
358
    @inline
5,637✔
359
    @boundscheck checkbounds(Bool, V, I...) || return false
5,637✔
360
    @inbounds r = isassigned(V.parent, reindex(V.indices, I)...)
5,637✔
361
    r
5,637✔
362
end
363
function isassigned(V::FastSubArray, i::Int)
×
364
    @inline
×
365
    @boundscheck checkbounds(Bool, V, i) || return false
×
366
    @inbounds r = isassigned(V.parent, V.offset1 + V.stride1*i)
×
367
    r
×
368
end
369
function isassigned(V::FastContiguousSubArray, i::Int)
×
370
    @inline
×
371
    @boundscheck checkbounds(Bool, V, i) || return false
×
372
    @inbounds r = isassigned(V.parent, V.offset1 + i)
×
373
    r
×
374
end
375
function isassigned(V::FastSubArray{<:Any, 1}, i::Int)
×
376
    @inline
×
377
    @boundscheck checkbounds(Bool, V, i) || return false
×
378
    @inbounds r = isassigned(V.parent, V.offset1 + V.stride1*i)
×
379
    r
×
380
end
381
function isassigned(V::FastContiguousSubArray{<:Any, 1}, i::Int)
×
382
    @inline
×
383
    @boundscheck checkbounds(Bool, V, i) || return false
×
384
    @inbounds r = isassigned(V.parent, V.offset1 + i)
×
385
    r
×
386
end
387

388
IndexStyle(::Type{<:FastSubArray}) = IndexLinear()
5,260,823✔
389
IndexStyle(::Type{<:SubArray}) = IndexCartesian()
8,698,928✔
390

391
# Strides are the distance in memory between adjacent elements in a given dimension
392
# which we determine from the strides of the parent
393
strides(V::SubArray) = substrides(strides(V.parent), V.indices)
465,729✔
394

395
substrides(strds::Tuple{}, ::Tuple{}) = ()
464,609✔
396
substrides(strds::NTuple{N,Int}, I::Tuple{ScalarIndex, Vararg{Any}}) where N = (substrides(tail(strds), tail(I))...,)
2,924✔
397
substrides(strds::NTuple{N,Int}, I::Tuple{Slice, Vararg{Any}}) where N = (first(strds), substrides(tail(strds), tail(I))...)
6,354✔
398
substrides(strds::NTuple{N,Int}, I::Tuple{AbstractRange, Vararg{Any}}) where N = (first(strds)*step(I[1]), substrides(tail(strds), tail(I))...)
533,262✔
399
substrides(strds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))"))
×
400

401
stride(V::SubArray, d::Integer) = d <= ndims(V) ? strides(V)[d] : strides(V)[end] * size(V)[end]
432,619✔
402

403
compute_stride1(parent::AbstractArray, I::NTuple{N,Any}) where {N} =
284,636✔
404
    (@inline; compute_stride1(1, fill_to_length(axes(parent), OneTo(1), Val(N)), I))
284,710✔
405
compute_stride1(s, inds, I::Tuple{}) = s
1✔
406
compute_stride1(s, inds, I::Tuple{Vararg{ScalarIndex}}) = s
49✔
407
compute_stride1(s, inds, I::Tuple{ScalarIndex, Vararg{Any}}) =
5,001✔
408
    (@inline; compute_stride1(s*length(inds[1]), tail(inds), tail(I)))
5,001✔
409
compute_stride1(s, inds, I::Tuple{AbstractRange, Vararg{Any}}) = s*step(I[1])
103,122✔
410
compute_stride1(s, inds, I::Tuple{Slice, Vararg{Any}}) = s
33,120✔
411
compute_stride1(s, inds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("invalid strided index type $(typeof(I[1]))"))
×
412

413
elsize(::Type{<:SubArray{<:Any,<:Any,P}}) where {P} = elsize(P)
439,327✔
414

415
iscontiguous(A::SubArray) = iscontiguous(typeof(A))
×
416
iscontiguous(::Type{<:SubArray}) = false
×
417
iscontiguous(::Type{<:FastContiguousSubArray}) = true
×
418

419
first_index(V::FastSubArray) = V.offset1 + V.stride1 # cached for fast linear SubArrays
×
420
function first_index(V::SubArray)
×
421
    P, I = parent(V), V.indices
×
422
    s1 = compute_stride1(P, I)
×
423
    s1 + compute_offset1(P, s1, I)
×
424
end
425

426
# Computing the first index simply steps through the indices, accumulating the
427
# sum of index each multiplied by the parent's stride.
428
# The running sum is `f`; the cumulative stride product is `s`.
429
# If the parent is a vector, then we offset the parent's own indices with parameters of I
430
compute_offset1(parent::AbstractVector, stride1::Integer, I::Tuple{AbstractRange}) =
159,795✔
431
    (@inline; first(I[1]) - stride1*first(axes1(I[1])))
3,033,447✔
432
# If the result is one-dimensional and it's a Colon, then linear
433
# indexing uses the indices along the given dimension.
434
# If the result is one-dimensional and it's a range, then linear
435
# indexing might be offset if the index itself is offset
436
# Otherwise linear indexing always matches the parent.
437
compute_offset1(parent, stride1::Integer, I::Tuple) =
124,841✔
438
    (@inline; compute_offset1(parent, stride1, find_extended_dims(1, I...), find_extended_inds(I...), I))
124,841✔
439
compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{Slice}, I::Tuple) =
21,446✔
440
    (@inline; compute_linindex(parent, I) - stride1*first(axes(parent, dims[1])))  # index-preserving case
21,446✔
441
compute_offset1(parent, stride1::Integer, dims, inds::Tuple{AbstractRange}, I::Tuple) =
101,836✔
442
    (@inline; compute_linindex(parent, I) - stride1*first(axes1(inds[1]))) # potentially index-offsetting case
101,836✔
443
compute_offset1(parent, stride1::Integer, dims, inds, I::Tuple) =
1,559✔
444
    (@inline; compute_linindex(parent, I) - stride1)
1,559✔
445
function compute_linindex(parent, I::NTuple{N,Any}) where N
124,841✔
446
    @inline
124,841✔
447
    IP = fill_to_length(axes(parent), OneTo(1), Val(N))
124,841✔
448
    compute_linindex(first(LinearIndices(parent)), 1, IP, I)
124,841✔
449
end
450
function compute_linindex(f, s, IP::Tuple, I::Tuple{ScalarIndex, Vararg{Any}})
128,200✔
451
    @inline
128,200✔
452
    Δi = I[1]-first(IP[1])
128,200✔
453
    compute_linindex(f + Δi*s, s*length(IP[1]), tail(IP), tail(I))
128,200✔
454
end
455
function compute_linindex(f, s, IP::Tuple, I::Tuple{Any, Vararg{Any}})
127,381✔
456
    @inline
127,381✔
457
    Δi = first(I[1])-first(IP[1])
127,381✔
458
    compute_linindex(f + Δi*s, s*length(IP[1]), tail(IP), tail(I))
127,381✔
459
end
460
compute_linindex(f, s, IP::Tuple, I::Tuple{}) = f
124,841✔
461

462
find_extended_dims(dim, ::ScalarIndex, I...) = (@inline; find_extended_dims(dim + 1, I...))
128,200✔
463
find_extended_dims(dim, i1, I...) = (@inline; (dim, find_extended_dims(dim + 1, I...)...))
127,381✔
464
find_extended_dims(dim) = ()
124,841✔
465
find_extended_inds(::ScalarIndex, I...) = (@inline; find_extended_inds(I...))
128,200✔
466
find_extended_inds(i1, I...) = (@inline; (i1, find_extended_inds(I...)...))
127,381✔
467
find_extended_inds() = ()
124,841✔
468

469
function unsafe_convert(::Type{Ptr{T}}, V::SubArray{T,N,P,<:Tuple{Vararg{RangeIndex}}}) where {T,N,P}
268,697✔
470
    return unsafe_convert(Ptr{T}, V.parent) + _memory_offset(V.parent, map(first, V.indices)...)
488,876✔
471
end
472

473
pointer(V::FastSubArray, i::Int) = pointer(V.parent, V.offset1 + V.stride1*i)
×
474
pointer(V::FastContiguousSubArray, i::Int) = pointer(V.parent, V.offset1 + i)
717,564✔
475

476
function pointer(V::SubArray{<:Any,<:Any,<:Array,<:Tuple{Vararg{RangeIndex}}}, is::AbstractCartesianIndex{N}) where {N}
×
477
    index = first_index(V)
×
478
    strds = strides(V)
×
479
    for d = 1:N
×
480
        index += (is[d]-1)*strds[d]
×
481
    end
×
482
    return pointer(V.parent, index)
×
483
end
484

485
# indices are taken from the range/vector
486
# Since bounds-checking is performance-critical and uses
487
# indices, it's worth optimizing these implementations thoroughly
488
axes(S::SubArray) = (@inline; _indices_sub(S.indices...))
176,771,244✔
489
_indices_sub(::Real, I...) = (@inline; _indices_sub(I...))
6,479,245✔
490
_indices_sub() = ()
×
491
function _indices_sub(i1::AbstractArray, I...)
68,342,135✔
492
    @inline
25,797,320✔
493
    (axes(i1)..., _indices_sub(I...)...)
188,773,671✔
494
end
495

496
has_offset_axes(S::SubArray) = has_offset_axes(S.indices...)
128,800✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc