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

JuliaLang / julia / #37611

05 Sep 2023 05:43AM UTC coverage: 86.055% (-0.4%) from 86.433%
#37611

push

local

web-flow
optimizer: Do not run if we know the function is ConstABI eligible (#51143)

If we can determine that a function is sufficiently pure and returns a
constant, we have special ABI that lets us throw away the code for it
and just return the constant. However, we were still going through all
the steps of actually computing the code, including running the
optimizer on it, compressing it in preparation for caching, etc. We can
just stop doing that and bypass the optimizer entirely.

The actual change to do this is pretty tiny, but there's some unexpected
fallout which this needs to cleanup:
1. Various tests expect code_* queries of things inferred to ConstABI to
still return reasonable code. Fix this by manually emitting a ReturnNode
at the end of inference if the caller is a reflection function.
2. This kinda wreaks havoc on the EscapeAnalysis tests, which work by
using a side-effect of the optimizer, but a lot of the functions are
ConstABI eligible, so the optimizer doesn't run any more. Fortunately,
I'm in the middle of looking at overhauling EscapeAnalysis, so I'll have
some chance to figure out how to change the test system to actually do
this properly, rather than exfiltrating side effects. That said, since
EscapeAnalysis is not in the default pipeline, I don't think we should
hold the perf improvement until that is done.

Benchmarked to be about 10% faster locally, but we'll see what
nanosoldier thinks.

---------

Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com>

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

73307 of 85186 relevant lines covered (86.06%)

12747443.03 hits per line

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

76.54
/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}
518,903✔
20
        @inline
518,903✔
21
        check_parent_index_match(parent, indices)
518,903✔
22
        new(parent, indices, offset1, stride1)
4,429,018✔
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)
638,403✔
27
    @inline
518,355✔
28
    SubArray(IndexStyle(viewindexing(indices), IndexStyle(parent)), parent, ensure_indexable(indices), index_dimsum(indices...))
4,428,456✔
29
end
30
function SubArray(::IndexCartesian, parent::P, indices::I, ::NTuple{N,Any}) where {P,I,N}
234,599✔
31
    @inline
234,592✔
32
    SubArray{eltype(P), N, P, I, false}(parent, indices, 0, 0)
234,653✔
33
end
34
function SubArray(::IndexLinear, parent::P, indices::I, ::NTuple{N,Any}) where {P,I,N}
283,756✔
35
    @inline
283,756✔
36
    # Compute the stride and offset
37
    stride1 = compute_stride1(parent, indices)
283,830✔
38
    SubArray{eltype(P), N, P, I, true}(parent, indices, compute_offset1(parent, stride1, indices), stride1)
4,193,803✔
39
end
40

41
check_parent_index_match(parent, indices) = check_parent_index_match(parent, index_ndims(indices...))
518,902✔
42
check_parent_index_match(parent::AbstractArray{T,N}, ::NTuple{N, Bool}) where {T,N} = nothing
446,398✔
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,029✔
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()
135,534✔
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()
16,998✔
61

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

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

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

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

78
parent(V::SubArray) = V.parent
118,790✔
79
parentindices(V::SubArray) = V.indices
115,708✔
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,915,454✔
105
_splatmap(f, ::Tuple{}) = ()
×
106
_splatmap(f, t::Tuple) = (f(t[1])..., _splatmap(f, tail(t))...)
15,413✔
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)))
560✔
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))
280,346✔
128
_maybe_reshape_parent(A::AbstractArray{<:Any,N}, ::NTuple{N, Bool}) where {N} = A
154,605✔
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,613,416✔
177
    @inline
1,132,637✔
178
    J = map(i->unalias(A,i), to_indices(A, I))
1,828,189✔
179
    @boundscheck checkbounds(A, J...)
4,429,319✔
180
    if length(J) > ndims(A) && J[N+1:end] isa Tuple{Vararg{Int}}
1,132,619✔
181
        # view([1,2,3], :, 1) does not need to reshape
182
        return unsafe_view(A, J[1:N]...)
9,592✔
183
    end
184
    unsafe_view(_maybe_reshape_parent(A, index_ndims(J...)), J...)
4,419,757✔
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,000,395✔
220
    @inline
506,177✔
221
    SubArray(A, I)
4,278,092✔
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} =
132,225✔
230
    (@inline; _maybe_reindex(V, I))
150,459✔
231
_maybe_reindex(V, I) = (@inline; _maybe_reindex(V, I, I))
150,459✔
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,715✔
238
function _maybe_reindex(V, I, ::Tuple{})
132,208✔
239
    @inline
132,208✔
240
    @inbounds idxs = to_indices(V.parent, reindex(V.indices, I))
150,442✔
241
    SubArray(V.parent, idxs)
150,346✔
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{}) = ()
×
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,321✔
258
    (@_propagate_inbounds_meta; (idxs[1], reindex(tail(idxs), subidxs)...))
2,046,321✔
259

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

264
# Re-index into parent vectors with one subindex
265
reindex(idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) =
16,827,220✔
266
    (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(tail(idxs), tail(subidxs))...))
16,846,208✔
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,738,510✔
286
    @inline
4,738,510✔
287
    @boundscheck checkbounds(V, I...)
4,738,519✔
288
    @inbounds r = V.parent[reindex(V.indices, I)...]
4,867,264✔
289
    r
4,738,501✔
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,270✔
313
    @inline
90,779✔
314
    @boundscheck checkbounds(V, i)
146,787✔
315
    @inbounds r = V.parent[V.offset1 + V.stride1*i]
146,781✔
316
    r
146,781✔
317
end
318
function getindex(V::FastContiguousSubArray{<:Any, 1}, i::Int)
19,355,940✔
319
    @inline
19,353,510✔
320
    @boundscheck checkbounds(V, i)
102,950,961✔
321
    @inbounds r = V.parent[V.offset1 + i]
102,950,955✔
322
    r
102,950,955✔
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,441,849✔
327
    @inline
8,441,112✔
328
    @boundscheck checkbounds(V, I...)
8,441,859✔
329
    @inbounds V.parent[reindex(V.indices, I)...] = x
8,441,926✔
330
    V
8,441,845✔
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,538✔
345
    @inline
28,449✔
346
    @boundscheck checkbounds(V, i)
28,540✔
347
    @inbounds V.parent[V.offset1 + V.stride1*i] = x
28,540✔
348
    V
28,540✔
349
end
350
function setindex!(V::FastContiguousSubArray{<:Any, 1}, x, i::Int)
52,760,285✔
351
    @inline
52,759,887✔
352
    @boundscheck checkbounds(V, i)
77,469,224✔
353
    @inbounds V.parent[V.offset1 + i] = x
77,469,224✔
354
    V
77,469,224✔
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()
946,517✔
389
IndexStyle(::Type{<:SubArray}) = IndexCartesian()
8,670,169✔
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)
53,986✔
394

395
substrides(strds::Tuple{}, ::Tuple{}) = ()
×
396
substrides(strds::NTuple{N,Int}, I::Tuple{ScalarIndex, Vararg{Any}}) where N = (substrides(tail(strds), tail(I))...,)
2,888✔
397
substrides(strds::NTuple{N,Int}, I::Tuple{Slice, Vararg{Any}}) where N = (first(strds), substrides(tail(strds), tail(I))...)
6,318✔
398
substrides(strds::NTuple{N,Int}, I::Tuple{AbstractRange, Vararg{Any}}) where N = (first(strds)*step(I[1]), substrides(tail(strds), tail(I))...)
119,277✔
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]
20,936✔
402

403
compute_stride1(parent::AbstractArray, I::NTuple{N,Any}) where {N} =
283,756✔
404
    (@inline; compute_stride1(1, fill_to_length(axes(parent), OneTo(1), Val(N)), I))
283,830✔
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])
102,695✔
410
compute_stride1(s, inds, I::Tuple{Slice, Vararg{Any}}) = s
22,546✔
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)
27,622✔
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}) =
158,570✔
431
    (@inline; first(I[1]) - stride1*first(axes1(I[1])))
3,059,952✔
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) =
125,186✔
438
    (@inline; compute_offset1(parent, stride1, find_extended_dims(1, I...), find_extended_inds(I...), I))
125,186✔
439
compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{Slice}, I::Tuple) =
22,218✔
440
    (@inline; compute_linindex(parent, I) - stride1*first(axes(parent, dims[1])))  # index-preserving case
22,218✔
441
compute_offset1(parent, stride1::Integer, dims, inds::Tuple{AbstractRange}, I::Tuple) =
101,409✔
442
    (@inline; compute_linindex(parent, I) - stride1*first(axes1(inds[1]))) # potentially index-offsetting case
101,409✔
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
125,186✔
446
    @inline
125,186✔
447
    IP = fill_to_length(axes(parent), OneTo(1), Val(N))
125,186✔
448
    compute_linindex(first(LinearIndices(parent)), 1, IP, I)
125,186✔
449
end
450
function compute_linindex(f, s, IP::Tuple, I::Tuple{ScalarIndex, Vararg{Any}})
128,545✔
451
    @inline
128,545✔
452
    Δi = I[1]-first(IP[1])
128,545✔
453
    compute_linindex(f + Δi*s, s*length(IP[1]), tail(IP), tail(I))
128,545✔
454
end
455
function compute_linindex(f, s, IP::Tuple, I::Tuple{Any, Vararg{Any}})
127,726✔
456
    @inline
127,726✔
457
    Δi = first(I[1])-first(IP[1])
127,726✔
458
    compute_linindex(f + Δi*s, s*length(IP[1]), tail(IP), tail(I))
127,726✔
459
end
460
compute_linindex(f, s, IP::Tuple, I::Tuple{}) = f
125,186✔
461

462
find_extended_dims(dim, ::ScalarIndex, I...) = (@inline; find_extended_dims(dim + 1, I...))
128,545✔
463
find_extended_dims(dim, i1, I...) = (@inline; (dim, find_extended_dims(dim + 1, I...)...))
127,726✔
464
find_extended_dims(dim) = ()
125,186✔
465
find_extended_inds(::ScalarIndex, I...) = (@inline; find_extended_inds(I...))
128,545✔
466
find_extended_inds(i1, I...) = (@inline; (i1, find_extended_inds(I...)...))
127,726✔
467
find_extended_inds() = ()
125,186✔
468

469
function unsafe_convert(::Type{Ptr{T}}, V::SubArray{T,N,P,<:Tuple{Vararg{RangeIndex}}}) where {T,N,P}
156,501✔
470
    return unsafe_convert(Ptr{T}, V.parent) + _memory_offset(V.parent, map(first, V.indices)...)
497,004✔
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)
724,436✔
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,532,368✔
489
_indices_sub(::Real, I...) = (@inline; _indices_sub(I...))
6,487,249✔
490
_indices_sub() = ()
×
491
function _indices_sub(i1::AbstractArray, I...)
38,896,525✔
492
    @inline
16,475,231✔
493
    (axes(i1)..., _indices_sub(I...)...)
188,494,799✔
494
end
495

496
has_offset_axes(S::SubArray) = has_offset_axes(S.indices...)
134,291✔
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

© 2026 Coveralls, Inc