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

JuliaLang / julia / #37579

pending completion
#37579

push

local

web-flow
sroa: Mark dead setfields as EFFECT_FREE (#50373)

sroa tries to delete any `setfield!` call for allocations that it
knows it can remove. However, if it does not know that the type is
correct for the allocation, it may not be able to remove the
setfield!. If the type later gets improved (e.g. by irinterp),
the statement becomes eligible for removal, but it currently requires
another sroa pass to actually remove it.

Improve that situation my marking such a statement that is known-dead
as IR_FLAG_EFFECT_FREE, so if we later also prove it nothrow, it
(and the corresponding allocation) immediately become DCE-eligible.

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

73522 of 84317 relevant lines covered (87.2%)

34757350.52 hits per line

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

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

3
"""
4
Gives a reinterpreted view (of element type T) of the underlying array (of element type S).
5
If the size of `T` differs from the size of `S`, the array will be compressed/expanded in
6
the first dimension. The variant `reinterpret(reshape, T, a)` instead adds or consumes the first dimension
7
depending on the ratio of element sizes.
8
"""
9
struct ReinterpretArray{T,N,S,A<:AbstractArray{S},IsReshaped} <: AbstractArray{T, N}
10
    parent::A
11
    readable::Bool
12
    writable::Bool
13

14
    function throwbits(S::Type, T::Type, U::Type)
9✔
15
        @noinline
9✔
16
        throw(ArgumentError("cannot reinterpret `$(S)` as `$(T)`, type `$(U)` is not a bits type"))
9✔
17
    end
18
    function throwsize0(S::Type, T::Type, msg)
5✔
19
        @noinline
5✔
20
        throw(ArgumentError("cannot reinterpret a zero-dimensional `$(S)` array to `$(T)` which is of a $msg size"))
5✔
21
    end
22
    function throwsingleton(S::Type, T::Type)
5✔
23
        @noinline
5✔
24
        throw(ArgumentError("cannot reinterpret a `$(S)` array to `$(T)` which is a singleton type"))
5✔
25
    end
26

27
    global reinterpret
28

29
    @doc """
30
        reinterpret(T::DataType, A::AbstractArray)
31

32
    Construct a view of the array with the same binary data as the given
33
    array, but with `T` as element type.
34

35
    This function also works on "lazy" array whose elements are not computed until they are explicitly retrieved.
36
    For instance, `reinterpret` on the range `1:6` works similarly as on the dense vector `collect(1:6)`:
37

38
    ```jldoctest
39
    julia> reinterpret(Float32, UInt32[1 2 3 4 5])
40
    1×5 reinterpret(Float32, ::Matrix{UInt32}):
41
     1.0f-45  3.0f-45  4.0f-45  6.0f-45  7.0f-45
42

43
    julia> reinterpret(Complex{Int}, 1:6)
44
    3-element reinterpret(Complex{$Int}, ::UnitRange{$Int}):
45
     1 + 2im
46
     3 + 4im
47
     5 + 6im
48
    ```
49
    """
50
    function reinterpret(::Type{T}, a::A) where {T,N,S,A<:AbstractArray{S, N}}
22,014✔
51
        function thrownonint(S::Type, T::Type, dim)
87,589✔
52
            @noinline
2✔
53
            throw(ArgumentError("""
2✔
54
                cannot reinterpret an `$(S)` array to `$(T)` whose first dimension has size `$(dim)`.
55
                The resulting array would have non-integral first dimension.
56
                """))
57
        end
58
        function throwaxes1(S::Type, T::Type, ax1)
5,345✔
59
            @noinline
2✔
60
            throw(ArgumentError("cannot reinterpret a `$(S)` array to `$(T)` when the first axis is $ax1. Try reshaping first."))
2✔
61
        end
62
        isbitstype(T) || throwbits(S, T, T)
5,343✔
63
        isbitstype(S) || throwbits(S, T, S)
5,340✔
64
        (N != 0 || sizeof(T) == sizeof(S)) || throwsize0(S, T, "different")
5,337✔
65
        if N != 0 && sizeof(S) != sizeof(T)
89,289✔
66
            ax1 = axes(a)[1]
86,857✔
67
            dim = length(ax1)
4,613✔
68
            if issingletontype(T)
4,613✔
69
                issingletontype(S) || throwsingleton(S, T)
2✔
70
            else
71
                rem(dim*sizeof(S),sizeof(T)) == 0 || thrownonint(S, T, dim)
86,857✔
72
            end
73
            first(ax1) == 1 || throwaxes1(S, T, ax1)
86,855✔
74
        end
75
        readable = array_subpadding(T, S)
5,327✔
76
        writable = array_subpadding(S, T)
5,327✔
77
        new{T, N, S, A, false}(a, readable, writable)
89,283✔
78
    end
79
    reinterpret(::Type{T}, a::AbstractArray{T}) where {T} = a
132✔
80

81
    # With reshaping
82
    function reinterpret(::typeof(reshape), ::Type{T}, a::A) where {T,S,A<:AbstractArray{S}}
957✔
83
        function throwintmult(S::Type, T::Type)
958✔
84
            @noinline
1✔
85
            throw(ArgumentError("`reinterpret(reshape, T, a)` requires that one of `sizeof(T)` (got $(sizeof(T))) and `sizeof(eltype(a))` (got $(sizeof(S))) be an integer multiple of the other"))
1✔
86
        end
87
        function throwsize1(a::AbstractArray, T::Type)
959✔
88
            @noinline
2✔
89
            throw(ArgumentError("`reinterpret(reshape, $T, a)` where `eltype(a)` is $(eltype(a)) requires that `axes(a, 1)` (got $(axes(a, 1))) be equal to 1:$(sizeof(T) ÷ sizeof(eltype(a))) (from the ratio of element sizes)"))
2✔
90
        end
91
        function throwfromsingleton(S, T)
960✔
92
            @noinline
3✔
93
            throw(ArgumentError("`reinterpret(reshape, $T, a)` where `eltype(a)` is $S requires that $T be a singleton type, since $S is one"))
3✔
94
        end
95
        isbitstype(T) || throwbits(S, T, T)
957✔
96
        isbitstype(S) || throwbits(S, T, S)
955✔
97
        if sizeof(S) == sizeof(T)
954✔
98
            N = ndims(a)
44✔
99
        elseif sizeof(S) > sizeof(T)
910✔
100
            issingletontype(T) && throwsingleton(S, T)
866✔
101
            rem(sizeof(S), sizeof(T)) == 0 || throwintmult(S, T)
863✔
102
            N = ndims(a) + 1
863✔
103
        else
104
            issingletontype(S) && throwfromsingleton(S, T)
44✔
105
            rem(sizeof(T), sizeof(S)) == 0 || throwintmult(S, T)
41✔
106
            N = ndims(a) - 1
40✔
107
            N > -1 || throwsize0(S, T, "larger")
40✔
108
            axes(a, 1) == OneTo(sizeof(T) ÷ sizeof(S)) || throwsize1(a, T)
41✔
109
        end
110
        readable = array_subpadding(T, S)
944✔
111
        writable = array_subpadding(S, T)
944✔
112
        new{T, N, S, A, true}(a, readable, writable)
944✔
113
    end
114
    reinterpret(::typeof(reshape), ::Type{T}, a::AbstractArray{T}) where {T} = a
1✔
115
end
116

117
ReshapedReinterpretArray{T,N,S,A<:AbstractArray{S}} = ReinterpretArray{T,N,S,A,true}
118
NonReshapedReinterpretArray{T,N,S,A<:AbstractArray{S, N}} = ReinterpretArray{T,N,S,A,false}
119

120
"""
121
    reinterpret(reshape, T, A::AbstractArray{S}) -> B
122

123
Change the type-interpretation of `A` while consuming or adding a "channel dimension."
124

125
If `sizeof(T) = n*sizeof(S)` for `n>1`, `A`'s first dimension must be
126
of size `n` and `B` lacks `A`'s first dimension. Conversely, if `sizeof(S) = n*sizeof(T)` for `n>1`,
127
`B` gets a new first dimension of size `n`. The dimensionality is unchanged if `sizeof(T) == sizeof(S)`.
128

129
!!! compat "Julia 1.6"
130
    This method requires at least Julia 1.6.
131

132
# Examples
133

134
```jldoctest
135
julia> A = [1 2; 3 4]
136
2×2 Matrix{$Int}:
137
 1  2
138
 3  4
139

140
julia> reinterpret(reshape, Complex{Int}, A)    # the result is a vector
141
2-element reinterpret(reshape, Complex{$Int}, ::Matrix{$Int}) with eltype Complex{$Int}:
142
 1 + 3im
143
 2 + 4im
144

145
julia> a = [(1,2,3), (4,5,6)]
146
2-element Vector{Tuple{$Int, $Int, $Int}}:
147
 (1, 2, 3)
148
 (4, 5, 6)
149

150
julia> reinterpret(reshape, Int, a)             # the result is a matrix
151
3×2 reinterpret(reshape, $Int, ::Vector{Tuple{$Int, $Int, $Int}}) with eltype $Int:
152
 1  4
153
 2  5
154
 3  6
155
```
156
"""
157
reinterpret(::typeof(reshape), T::Type, a::AbstractArray)
158

159
reinterpret(::Type{T}, a::NonReshapedReinterpretArray) where {T} = reinterpret(T, a.parent)
133✔
160
reinterpret(::typeof(reshape), ::Type{T}, a::ReshapedReinterpretArray) where {T} = reinterpret(reshape, T, a.parent)
1✔
161

162
# Definition of StridedArray
163
StridedFastContiguousSubArray{T,N,A<:DenseArray} = FastContiguousSubArray{T,N,A}
164
StridedReinterpretArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray},IsReshaped} = ReinterpretArray{T,N,S,A,IsReshaped} where S
165
StridedReshapedArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray,StridedReinterpretArray}} = ReshapedArray{T,N,A}
166
StridedSubArray{T,N,A<:Union{DenseArray,StridedReshapedArray,StridedReinterpretArray},
167
    I<:Tuple{Vararg{Union{RangeIndex, ReshapedUnitRange, AbstractCartesianIndex}}}} = SubArray{T,N,A,I}
168
StridedArray{T,N} = Union{DenseArray{T,N}, StridedSubArray{T,N}, StridedReshapedArray{T,N}, StridedReinterpretArray{T,N}}
169
StridedVector{T} = StridedArray{T,1}
170
StridedMatrix{T} = StridedArray{T,2}
171
StridedVecOrMat{T} = Union{StridedVector{T}, StridedMatrix{T}}
172

173
strides(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}) = size_to_strides(1, size(a)...)
364,979✔
174
stride(A::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, k::Integer) =
242,824✔
175
    k ≤ ndims(A) ? strides(A)[k] : length(A)
176

177
function strides(a::ReinterpretArray{T,<:Any,S,<:AbstractArray{S},IsReshaped}) where {T,S,IsReshaped}
3,127✔
178
    _checkcontiguous(Bool, a) && return size_to_strides(1, size(a)...)
3,127✔
179
    stp = strides(parent(a))
3,106✔
180
    els, elp = sizeof(T), sizeof(S)
3,106✔
181
    els == elp && return stp # 0dim parent is also handled here.
3,106✔
182
    IsReshaped && els < elp && return (1, _checked_strides(stp, els, elp)...)
1,976✔
183
    stp[1] == 1 || throw(ArgumentError("Parent must be contiguous in the 1st dimension!"))
670✔
184
    st′ = _checked_strides(tail(stp), els, elp)
645✔
185
    return IsReshaped ? st′ : (1, st′...)
607✔
186
end
187

188
@inline function _checked_strides(stp::Tuple, els::Integer, elp::Integer)
1,954✔
189
    if elp > els && rem(elp, els) == 0
1,954✔
190
        N = div(elp, els)
1,797✔
191
        return map(i -> N * i, stp)
6,058✔
192
    end
193
    drs = map(i -> divrem(elp * i, els), stp)
378✔
194
    all(i->iszero(i[2]), drs) ||
393✔
195
        throw(ArgumentError("Parent's strides could not be exactly divided!"))
196
    map(first, drs)
138✔
197
end
198

199
_checkcontiguous(::Type{Bool}, A::ReinterpretArray) = _checkcontiguous(Bool, parent(A))
3,201✔
200

201
similar(a::ReinterpretArray, T::Type, d::Dims) = similar(a.parent, T, d)
65,048✔
202

203
function check_readable(a::ReinterpretArray{T, N, S} where N) where {T,S}
39,309,682✔
204
    # See comment in check_writable
205
    if !a.readable && !array_subpadding(T, S)
39,309,682✔
206
        throw(PaddingError(T, S))
39,309,682✔
207
    end
208
end
209

210
function check_writable(a::ReinterpretArray{T, N, S} where N) where {T,S}
40,523,987✔
211
    # `array_subpadding` is relatively expensive (compared to a simple arrayref),
212
    # so it is cached in the array. However, it is computable at compile time if,
213
    # inference has the types available. By using this form of the check, we can
214
    # get the best of both worlds for the success case. If the types were not
215
    # available to inference, we simply need to check the field (relatively cheap)
216
    # and if they were we should be able to fold this check away entirely.
217
    if !a.writable && !array_subpadding(S, T)
40,523,987✔
218
        throw(PaddingError(T, S))
40,523,987✔
219
    end
220
end
221

222
## IndexStyle specializations
223

224
# For `reinterpret(reshape, T, a)` where we're adding a channel dimension and with
225
# `IndexStyle(a) == IndexLinear()`, it's advantageous to retain pseudo-linear indexing.
226
struct IndexSCartesian2{K} <: IndexStyle end   # K = sizeof(S) ÷ sizeof(T), a static-sized 2d cartesian iterator
1,846✔
227

228
IndexStyle(::Type{ReinterpretArray{T,N,S,A,false}}) where {T,N,S,A<:AbstractArray{S,N}} = IndexStyle(A)
6,843✔
229
function IndexStyle(::Type{ReinterpretArray{T,N,S,A,true}}) where {T,N,S,A<:AbstractArray{S}}
3,803✔
230
    if sizeof(T) < sizeof(S)
3,803✔
231
        IndexStyle(A) === IndexLinear() && return IndexSCartesian2{sizeof(S) ÷ sizeof(T)}()
3,011✔
232
        return IndexCartesian()
1,168✔
233
    end
234
    return IndexStyle(A)
792✔
235
end
236
IndexStyle(::IndexSCartesian2{K}, ::IndexSCartesian2{K}) where {K} = IndexSCartesian2{K}()
2✔
237

238
struct SCartesianIndex2{K}   # can't make <:AbstractCartesianIndex without N, and 2 would be a bit misleading
239
    i::Int
4,114✔
240
    j::Int
241
end
242
to_index(i::SCartesianIndex2) = i
24✔
243

244
struct SCartesianIndices2{K,R<:AbstractUnitRange{Int}} <: AbstractMatrix{SCartesianIndex2{K}}
245
    indices2::R
15✔
246
end
247
SCartesianIndices2{K}(indices2::AbstractUnitRange{Int}) where {K} = (@assert K::Int > 1; SCartesianIndices2{K,typeof(indices2)}(indices2))
30✔
248

249
eachindex(::IndexSCartesian2{K}, A::ReshapedReinterpretArray) where {K} = SCartesianIndices2{K}(eachindex(IndexLinear(), parent(A)))
13✔
250
@inline function eachindex(style::IndexSCartesian2{K}, A::AbstractArray, B::AbstractArray...) where {K}
2✔
251
    iter = eachindex(style, A)
2✔
252
    _all_match_first(C->eachindex(style, C), iter, B...) || throw_eachindex_mismatch_indices(IndexSCartesian2{K}(), axes(A), axes.(B)...)
4✔
253
    return iter
2✔
254
end
255

256
size(iter::SCartesianIndices2{K}) where K = (K, length(iter.indices2))
2✔
257
axes(iter::SCartesianIndices2{K}) where K = (OneTo(K), iter.indices2)
11✔
258

259
first(iter::SCartesianIndices2{K}) where {K} = SCartesianIndex2{K}(1, first(iter.indices2))
2✔
260
last(iter::SCartesianIndices2{K}) where {K}  = SCartesianIndex2{K}(K, last(iter.indices2))
3✔
261

262
@inline function getindex(iter::SCartesianIndices2{K}, i::Int, j::Int) where {K}
×
263
    @boundscheck checkbounds(iter, i, j)
×
264
    return SCartesianIndex2{K}(i, iter.indices2[j])
×
265
end
266

267
function iterate(iter::SCartesianIndices2{K}) where {K}
14✔
268
    ret = iterate(iter.indices2)
28✔
269
    ret === nothing && return nothing
14✔
270
    item2, state2 = ret
14✔
271
    return SCartesianIndex2{K}(1, item2), (1, item2, state2)
14✔
272
end
273

274
function iterate(iter::SCartesianIndices2{K}, (state1, item2, state2)) where {K}
96✔
275
    if state1 < K
96✔
276
        item1 = state1 + 1
61✔
277
        return SCartesianIndex2{K}(item1, item2), (item1, item2, state2)
61✔
278
    end
279
    ret = iterate(iter.indices2, state2)
62✔
280
    ret === nothing && return nothing
35✔
281
    item2, state2 = ret
27✔
282
    return SCartesianIndex2{K}(1, item2), (1, item2, state2)
27✔
283
end
284

285
SimdLoop.simd_outer_range(iter::SCartesianIndices2) = iter.indices2
×
286
SimdLoop.simd_inner_length(::SCartesianIndices2{K}, ::Any) where K = K
×
287
@inline function SimdLoop.simd_index(::SCartesianIndices2{K}, Ilast::Int, I1::Int) where {K}
×
288
    SCartesianIndex2{K}(I1+1, Ilast)
×
289
end
290

291
_maybe_reshape(::IndexSCartesian2, A::ReshapedReinterpretArray, I...) = A
×
292

293
# fallbacks
294
function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, I::Vararg{Int, N}) where {T,N}
18✔
295
    @_propagate_inbounds_meta
18✔
296
    getindex(A, I...)
18✔
297
end
298
function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, I::Vararg{Int, N}) where {T,N}
×
299
    @_propagate_inbounds_meta
×
300
    setindex!(A, v, I...)
×
301
end
302
# fallbacks for array types that use "pass-through" indexing (e.g., `IndexStyle(A) = IndexStyle(parent(A))`)
303
# but which don't handle SCartesianIndex2
304
function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, ind::SCartesianIndex2) where {T,N}
6✔
305
    @_propagate_inbounds_meta
6✔
306
    J = _ind2sub(tail(axes(A)), ind.j)
6✔
307
    getindex(A, ind.i, J...)
6✔
308
end
309
function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, ind::SCartesianIndex2) where {T,N}
18✔
310
    @_propagate_inbounds_meta
18✔
311
    J = _ind2sub(tail(axes(A)), ind.j)
18✔
312
    setindex!(A, v, ind.i, J...)
18✔
313
end
314
eachindex(style::IndexSCartesian2, A::AbstractArray) = eachindex(style, parent(A))
6✔
315

316
## AbstractArray interface
317

318
parent(a::ReinterpretArray) = a.parent
6,322✔
319
dataids(a::ReinterpretArray) = dataids(a.parent)
12,363✔
320
unaliascopy(a::NonReshapedReinterpretArray{T}) where {T} = reinterpret(T, unaliascopy(a.parent))
2✔
321
unaliascopy(a::ReshapedReinterpretArray{T}) where {T} = reinterpret(reshape, T, unaliascopy(a.parent))
1✔
322

323
function size(a::NonReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
38,426✔
324
    psize = size(a.parent)
40,096✔
325
    size1 = issingletontype(T) ? psize[1] : div(psize[1]*sizeof(S), sizeof(T))
40,096✔
326
    tuple(size1, tail(psize)...)
38,426✔
327
end
328
function size(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
4,312✔
329
    psize = size(a.parent)
4,312✔
330
    sizeof(S) > sizeof(T) && return (div(sizeof(S), sizeof(T)), psize...)
4,312✔
331
    sizeof(S) < sizeof(T) && return tail(psize)
263✔
332
    return psize
98✔
333
end
334
size(a::NonReshapedReinterpretArray{T,0}) where {T} = ()
18✔
335

336
function axes(a::NonReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
77,866,191✔
337
    paxs = axes(a.parent)
78,135,731✔
338
    f, l = first(paxs[1]), length(paxs[1])
77,858,247✔
339
    size1 = issingletontype(T) ? l : div(l*sizeof(S), sizeof(T))
78,135,731✔
340
    tuple(oftype(paxs[1], f:f+size1-1), tail(paxs)...)
78,136,020✔
341
end
342
function axes(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
13,215✔
343
    paxs = axes(a.parent)
13,215✔
344
    sizeof(S) > sizeof(T) && return (OneTo(div(sizeof(S), sizeof(T))), paxs...)
13,215✔
345
    sizeof(S) < sizeof(T) && return tail(paxs)
2,061✔
346
    return paxs
1,760✔
347
end
348
axes(a::NonReshapedReinterpretArray{T,0}) where {T} = ()
4✔
349

350
has_offset_axes(a::ReinterpretArray) = has_offset_axes(a.parent)
4,210✔
351

352
elsize(::Type{<:ReinterpretArray{T}}) where {T} = sizeof(T)
4,492✔
353
unsafe_convert(::Type{Ptr{T}}, a::ReinterpretArray{T,N,S} where N) where {T,S} = Ptr{T}(unsafe_convert(Ptr{S},a.parent))
8,716✔
354

355
@inline @propagate_inbounds function getindex(a::NonReshapedReinterpretArray{T,0,S}) where {T,S}
3✔
356
    if isprimitivetype(T) && isprimitivetype(S)
3✔
357
        reinterpret(T, a.parent[])
1✔
358
    else
359
        a[firstindex(a)]
2✔
360
    end
361
end
362

363
@inline @propagate_inbounds getindex(a::ReinterpretArray) = a[firstindex(a)]
5✔
364

365
@inline @propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, inds::Vararg{Int, N}) where {T,N,S}
41,569,126✔
366
    check_readable(a)
39,567,498✔
367
    _getindex_ra(a, inds[1], tail(inds))
85,337,860✔
368
end
369

370
@inline @propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, i::Int) where {T,N,S}
8,208✔
371
    check_readable(a)
8,208✔
372
    if isa(IndexStyle(a), IndexLinear)
8,208✔
373
        return _getindex_ra(a, i, ())
9,180✔
374
    end
375
    # Convert to full indices here, to avoid needing multiple conversions in
376
    # the loop in _getindex_ra
377
    inds = _to_subscript_indices(a, i)
2,742✔
378
    isempty(inds) ? _getindex_ra(a, 1, ()) : _getindex_ra(a, inds[1], tail(inds))
2,822✔
379
end
380

381
@inline @propagate_inbounds function getindex(a::ReshapedReinterpretArray{T,N,S}, ind::SCartesianIndex2) where {T,N,S}
4,014✔
382
    check_readable(a)
4,014✔
383
    s = Ref{S}(a.parent[ind.j])
4,014✔
384
    GC.@preserve s begin
4,014✔
385
        tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
4,014✔
386
        return unsafe_load(tptr, ind.i)
4,014✔
387
    end
388
end
389

390
@inline @propagate_inbounds function _getindex_ra(a::NonReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
39,572,690✔
391
    # Make sure to match the scalar reinterpret if that is applicable
392
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
39,572,690✔
393
        if issingletontype(T) # singleton types
599,042✔
394
            @boundscheck checkbounds(a, i1, tailinds...)
85✔
395
            return T.instance
79✔
396
        end
397
        return reinterpret(T, a.parent[i1, tailinds...])
5,633,271✔
398
    else
399
        @boundscheck checkbounds(a, i1, tailinds...)
40,522,088✔
400
        ind_start, sidx = divrem((i1-1)*sizeof(T), sizeof(S))
40,522,064✔
401
        # Optimizations that avoid branches
402
        if sizeof(T) % sizeof(S) == 0
38,973,636✔
403
            # T is bigger than S and contains an integer number of them
404
            n = sizeof(T) ÷ sizeof(S)
38,946,555✔
405
            t = Ref{T}()
38,981,499✔
406
            GC.@preserve t begin
38,981,499✔
407
                sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
38,981,499✔
408
                for i = 1:n
38,981,499✔
409
                     s = a.parent[ind_start + i, tailinds...]
78,172,696✔
410
                     unsafe_store!(sptr, s, i)
78,172,688✔
411
                end
117,363,877✔
412
            end
413
            return t[]
38,981,499✔
414
        elseif sizeof(S) % sizeof(T) == 0
27,081✔
415
            # S is bigger than T and contains an integer number of them
416
            s = Ref{S}(a.parent[ind_start + 1, tailinds...])
1,540,593✔
417
            GC.@preserve s begin
1,540,533✔
418
                tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
1,540,533✔
419
                return unsafe_load(tptr + sidx)
1,540,533✔
420
            end
421
        else
422
            i = 1
32✔
423
            nbytes_copied = 0
32✔
424
            # This is a bit complicated to deal with partial elements
425
            # at both the start and the end. LLVM will fold as appropriate,
426
            # once it knows the data layout
427
            s = Ref{S}()
32✔
428
            t = Ref{T}()
32✔
429
            GC.@preserve s t begin
32✔
430
                sptr = Ptr{S}(unsafe_convert(Ref{S}, s))
32✔
431
                tptr = Ptr{T}(unsafe_convert(Ref{T}, t))
32✔
432
                while nbytes_copied < sizeof(T)
104✔
433
                    s[] = a.parent[ind_start + i, tailinds...]
76✔
434
                    nb = min(sizeof(S) - sidx, sizeof(T)-nbytes_copied)
72✔
435
                    memcpy(tptr + nbytes_copied, sptr + sidx, nb)
72✔
436
                    nbytes_copied += nb
72✔
437
                    sidx = 0
72✔
438
                    i += 1
72✔
439
                end
102✔
440
            end
441
            return t[]
32✔
442
        end
443
    end
444
end
445

446
@inline @propagate_inbounds function _getindex_ra(a::ReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
3,015✔
447
    # Make sure to match the scalar reinterpret if that is applicable
448
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
3,015✔
449
        if issingletontype(T) # singleton types
656✔
450
            @boundscheck checkbounds(a, i1, tailinds...)
64✔
451
            return T.instance
62✔
452
        end
453
        return reinterpret(T, a.parent[i1, tailinds...])
601✔
454
    end
455
    @boundscheck checkbounds(a, i1, tailinds...)
2,359✔
456
    if sizeof(T) >= sizeof(S)
2,359✔
457
        t = Ref{T}()
113✔
458
        GC.@preserve t begin
113✔
459
            sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
113✔
460
            if sizeof(T) > sizeof(S)
113✔
461
                # Extra dimension in the parent array
462
                n = sizeof(T) ÷ sizeof(S)
110✔
463
                if isempty(tailinds) && IndexStyle(a.parent) === IndexLinear()
110✔
464
                    offset = n * (i1 - firstindex(a))
57✔
465
                    for i = 1:n
57✔
466
                        s = a.parent[i + offset]
170✔
467
                        unsafe_store!(sptr, s, i)
170✔
468
                    end
283✔
469
                else
470
                    for i = 1:n
53✔
471
                        s = a.parent[i, i1, tailinds...]
112✔
472
                        unsafe_store!(sptr, s, i)
106✔
473
                    end
159✔
474
                end
475
            else
476
                # No extra dimension
477
                s = a.parent[i1, tailinds...]
3✔
478
                unsafe_store!(sptr, s)
113✔
479
            end
480
        end
481
        return t[]
113✔
482
    end
483
    # S is bigger than T and contains an integer number of them
484
    # n = sizeof(S) ÷ sizeof(T)
485
    s = Ref{S}()
2,246✔
486
    GC.@preserve s begin
2,246✔
487
        tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
2,246✔
488
        s[] = a.parent[tailinds...]
2,252✔
489
        return unsafe_load(tptr, i1)
2,246✔
490
    end
491
end
492

493
@inline @propagate_inbounds function setindex!(a::NonReshapedReinterpretArray{T,0,S}, v) where {T,S}
4✔
494
    if isprimitivetype(S) && isprimitivetype(T)
4✔
495
        a.parent[] = reinterpret(S, v)
1✔
496
        return a
1✔
497
    end
498
    setindex!(a, v, firstindex(a))
3✔
499
end
500

501
@inline @propagate_inbounds setindex!(a::ReinterpretArray, v) = setindex!(a, v, firstindex(a))
4✔
502

503
@inline @propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, inds::Vararg{Int, N}) where {T,N,S}
40,645,309✔
504
    check_writable(a)
40,645,275✔
505
    _setindex_ra!(a, v, inds[1], tail(inds))
79,863,813✔
506
end
507

508
@inline @propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, i::Int) where {T,N,S}
72✔
509
    check_writable(a)
72✔
510
    if isa(IndexStyle(a), IndexLinear)
72✔
511
        return _setindex_ra!(a, v, i, ())
64✔
512
    end
513
    inds = _to_subscript_indices(a, i)
9✔
514
    _setindex_ra!(a, v, inds[1], tail(inds))
9✔
515
end
516

517
@inline @propagate_inbounds function setindex!(a::ReshapedReinterpretArray{T,N,S}, v, ind::SCartesianIndex2) where {T,N,S}
1✔
518
    check_writable(a)
1✔
519
    v = convert(T, v)::T
1✔
520
    s = Ref{S}(a.parent[ind.j])
1✔
521
    GC.@preserve s begin
1✔
522
        tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
1✔
523
        unsafe_store!(tptr, v, ind.i)
1✔
524
    end
525
    a.parent[ind.j] = s[]
1✔
526
    return a
1✔
527
end
528

529
@inline @propagate_inbounds function _setindex_ra!(a::NonReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT}
40,645,312✔
530
    v = convert(T, v)::T
40,645,312✔
531
    # Make sure to match the scalar reinterpret if that is applicable
532
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
40,645,310✔
533
        if issingletontype(T) # singleton types
1,868,173✔
534
            @boundscheck checkbounds(a, i1, tailinds...)
6✔
535
            # setindex! is a noop except for the index check
536
        else
537
            setindex!(a.parent, reinterpret(S, v), i1, tailinds...)
2,310,373✔
538
        end
539
    else
540
        @boundscheck checkbounds(a, i1, tailinds...)
38,777,149✔
541
        ind_start, sidx = divrem((i1-1)*sizeof(T), sizeof(S))
38,777,125✔
542
        # Optimizations that avoid branches
543
        if sizeof(T) % sizeof(S) == 0
38,777,125✔
544
            # T is bigger than S and contains an integer number of them
545
            t = Ref{T}(v)
38,776,338✔
546
            GC.@preserve t begin
38,776,338✔
547
                sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
38,776,338✔
548
                n = sizeof(T) ÷ sizeof(S)
38,776,338✔
549
                for i = 1:n
38,776,338✔
550
                    s = unsafe_load(sptr, i)
77,552,679✔
551
                    a.parent[ind_start + i, tailinds...] = s
77,552,679✔
552
                end
116,329,020✔
553
            end
554
        elseif sizeof(S) % sizeof(T) == 0
787✔
555
            # S is bigger than T and contains an integer number of them
556
            s = Ref{S}(a.parent[ind_start + 1, tailinds...])
783✔
557
            GC.@preserve s begin
783✔
558
                tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
783✔
559
                unsafe_store!(tptr + sidx, v)
783✔
560
                a.parent[ind_start + 1, tailinds...] = s[]
783✔
561
            end
562
        else
563
            t = Ref{T}(v)
4✔
564
            s = Ref{S}()
4✔
565
            GC.@preserve t s begin
4✔
566
                tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
4✔
567
                sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
4✔
568
                nbytes_copied = 0
4✔
569
                i = 1
4✔
570
                # Deal with any partial elements at the start. We'll have to copy in the
571
                # element from the original array and overwrite the relevant parts
572
                if sidx != 0
4✔
573
                    s[] = a.parent[ind_start + i, tailinds...]
3✔
574
                    nb = min((sizeof(S) - sidx) % UInt, sizeof(T) % UInt)
2✔
575
                    memcpy(sptr + sidx, tptr, nb)
2✔
576
                    nbytes_copied += nb
2✔
577
                    a.parent[ind_start + i, tailinds...] = s[]
2✔
578
                    i += 1
2✔
579
                    sidx = 0
2✔
580
                end
581
                # Deal with the main body of elements
582
                while nbytes_copied < sizeof(T) && (sizeof(T) - nbytes_copied) > sizeof(S)
8✔
583
                    nb = min(sizeof(S), sizeof(T) - nbytes_copied)
2✔
584
                    memcpy(sptr, tptr + nbytes_copied, nb)
2✔
585
                    nbytes_copied += nb
2✔
586
                    a.parent[ind_start + i, tailinds...] = s[]
2✔
587
                    i += 1
2✔
588
                end
2✔
589
                # Deal with trailing partial elements
590
                if nbytes_copied < sizeof(T)
6✔
591
                    s[] = a.parent[ind_start + i, tailinds...]
6✔
592
                    nb = min(sizeof(S), sizeof(T) - nbytes_copied)
6✔
593
                    memcpy(sptr, tptr + nbytes_copied, nb)
6✔
594
                    a.parent[ind_start + i, tailinds...] = s[]
4✔
595
                end
596
            end
597
        end
598
    end
599
    return a
40,645,284✔
600
end
601

602
@inline @propagate_inbounds function _setindex_ra!(a::ReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT}
34✔
603
    v = convert(T, v)::T
34✔
604
    # Make sure to match the scalar reinterpret if that is applicable
605
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
33✔
606
        if issingletontype(T) # singleton types
7✔
607
            @boundscheck checkbounds(a, i1, tailinds...)
3✔
608
            # setindex! is a noop except for the index check
609
        else
610
            setindex!(a.parent, reinterpret(S, v), i1, tailinds...)
5✔
611
        end
612
    end
613
    @boundscheck checkbounds(a, i1, tailinds...)
32✔
614
    if sizeof(T) >= sizeof(S)
32✔
615
        t = Ref{T}(v)
11✔
616
        GC.@preserve t begin
11✔
617
            sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
11✔
618
            if sizeof(T) > sizeof(S)
11✔
619
                # Extra dimension in the parent array
620
                n = sizeof(T) ÷ sizeof(S)
3✔
621
                if isempty(tailinds) && IndexStyle(a.parent) === IndexLinear()
3✔
622
                    offset = n * (i1 - firstindex(a))
2✔
623
                    for i = 1:n
2✔
624
                        s = unsafe_load(sptr, i)
4✔
625
                        a.parent[i + offset] = s
4✔
626
                    end
6✔
627
                else
628
                    for i = 1:n
1✔
629
                        s = unsafe_load(sptr, i)
2✔
630
                        a.parent[i, i1, tailinds...] = s
2✔
631
                    end
3✔
632
                end
633
            else # sizeof(T) == sizeof(S)
634
                # No extra dimension
635
                s = unsafe_load(sptr)
8✔
636
                a.parent[i1, tailinds...] = s
11✔
637
            end
638
        end
639
    else
640
        # S is bigger than T and contains an integer number of them
641
        s = Ref{S}()
21✔
642
        GC.@preserve s begin
21✔
643
            tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
21✔
644
            s[] = a.parent[tailinds...]
23✔
645
            unsafe_store!(tptr, v, i1)
21✔
646
            a.parent[tailinds...] = s[]
21✔
647
        end
648
    end
649
    return a
32✔
650
end
651

652
# Padding
653
struct Padding
654
    offset::Int # 0-indexed offset of the next valid byte; sizeof(T) indicates trailing padding
82✔
655
    size::Int   # bytes of padding before a valid byte
656
end
657
function intersect(p1::Padding, p2::Padding)
16✔
658
    start = max(p1.offset, p2.offset)
16✔
659
    stop = min(p1.offset + p1.size, p2.offset + p2.size)
16✔
660
    Padding(start, max(0, stop-start))
16✔
661
end
662

663
struct PaddingError <: Exception
664
    S::Type
2✔
665
    T::Type
666
end
667

668
function showerror(io::IO, p::PaddingError)
×
669
    print(io, "Padding of type $(p.S) is not compatible with type $(p.T).")
×
670
end
671

672
"""
673
    CyclePadding(padding, total_size)
674

675
Cylces an iterator of `Padding` structs, restarting the padding at `total_size`.
676
E.g. if `padding` is all the padding in a struct and `total_size` is the total
677
aligned size of that array, `CyclePadding` will correspond to the padding in an
678
infinite vector of such structs.
679
"""
680
struct CyclePadding{P}
681
    padding::P
728✔
682
    total_size::Int
683
end
684
eltype(::Type{<:CyclePadding}) = Padding
×
685
IteratorSize(::Type{<:CyclePadding}) = IsInfinite()
×
686
isempty(cp::CyclePadding) = isempty(cp.padding)
×
687
function iterate(cp::CyclePadding)
602✔
688
    y = iterate(cp.padding)
622✔
689
    y === nothing && return nothing
602✔
690
    y[1], (0, y[2])
20✔
691
end
692
function iterate(cp::CyclePadding, state::Tuple)
32✔
693
    y = iterate(cp.padding, tail(state)...)
48✔
694
    y === nothing && return iterate(cp, (state[1]+cp.total_size,))
32✔
695
    Padding(y[1].offset+state[1], y[1].size), (state[1], tail(y)...)
16✔
696
end
697

698
"""
699
    Compute the location of padding in an isbits datatype. Recursive over the fields of that type.
700
"""
701
@assume_effects :foldable function padding(T::DataType, baseoffset::Int = 0)
1,628✔
702
    pads = Padding[]
1,628✔
703
    last_end::Int = baseoffset
×
704
    for i = 1:fieldcount(T)
1,079✔
705
        offset = baseoffset + Int(fieldoffset(T, i))
236✔
706
        fT = fieldtype(T, i)
236✔
707
        append!(pads, padding(fT, offset))
236✔
708
        if offset != last_end
236✔
709
            push!(pads, Padding(offset, offset-last_end))
42✔
710
        end
711
        last_end = offset + sizeof(fT)
236✔
712
    end
377✔
713
    if 0 < last_end - baseoffset < sizeof(T)
984✔
714
        push!(pads, Padding(baseoffset + sizeof(T), sizeof(T) - last_end + baseoffset))
4✔
715
    end
716
    return Core.svec(pads...)
984✔
717
end
718

719
function CyclePadding(T::DataType)
624✔
720
    a, s = datatype_alignment(T), sizeof(T)
624✔
721
    as = s + (a - (s % a)) % a
624✔
722
    pad = padding(T)
624✔
723
    s != as && push!(pad, Padding(s, as - s))
624✔
724
    CyclePadding(pad, as)
624✔
725
end
726

727
@assume_effects :total function array_subpadding(S, T)
312✔
728
    lcm_size = lcm(sizeof(S), sizeof(T))
312✔
729
    s, t = CyclePadding(S), CyclePadding(T)
312✔
730
    checked_size = 0
312✔
731
    # use of Stateful harms inference and makes this vulnerable to invalidation
732
    (pad, tstate) = let
×
733
        it = iterate(t)
320✔
734
        it === nothing && return true
312✔
735
        it
8✔
736
    end
737
    (ps, sstate) = let
×
738
        it = iterate(s)
16✔
739
        it === nothing && return false
8✔
740
        it
8✔
741
    end
742
    while checked_size < lcm_size
16✔
743
        while true
12✔
744
            # See if there's corresponding padding in S
745
            ps.offset > pad.offset && return false
40✔
746
            intersect(ps, pad) == pad && break
16✔
747
            ps, sstate = iterate(s, sstate)
8✔
748
        end
8✔
749
        checked_size = pad.offset + pad.size
8✔
750
        pad, tstate = iterate(t, tstate)
8✔
751
    end
8✔
752
    return true
4✔
753
end
754

755
@assume_effects :foldable function struct_subpadding(::Type{Out}, ::Type{In}) where {Out, In}
9✔
756
    padding(Out) == padding(In)
9✔
757
end
758

759
@assume_effects :foldable function packedsize(::Type{T}) where T
2✔
760
    pads = padding(T)
2✔
761
    return sizeof(T) - sum((p.size for p ∈ pads), init = 0)
2✔
762
end
763

764
@assume_effects :foldable ispacked(::Type{T}) where T = isempty(padding(T))
×
765

766
function _copytopacked!(ptr_out::Ptr{Out}, ptr_in::Ptr{In}) where {Out, In}
4✔
767
    writeoffset = 0
4✔
768
    for i ∈ 1:fieldcount(In)
4✔
769
        readoffset = fieldoffset(In, i)
13✔
770
        fT = fieldtype(In, i)
13✔
771
        if ispacked(fT)
15✔
772
            readsize = sizeof(fT)
14✔
773
            memcpy(ptr_out + writeoffset, ptr_in + readoffset, readsize)
12✔
774
            writeoffset += readsize
12✔
775
        else # nested padded type
776
            _copytopacked!(ptr_out + writeoffset, Ptr{fT}(ptr_in + readoffset))
1✔
777
            writeoffset += packedsize(fT)
1✔
778
        end
779
    end
13✔
780
end
781

782
function _copyfrompacked!(ptr_out::Ptr{Out}, ptr_in::Ptr{In}) where {Out, In}
4✔
783
    readoffset = 0
4✔
784
    for i ∈ 1:fieldcount(Out)
4✔
785
        writeoffset = fieldoffset(Out, i)
13✔
786
        fT = fieldtype(Out, i)
13✔
787
        if ispacked(fT)
15✔
788
            writesize = sizeof(fT)
14✔
789
            memcpy(ptr_out + writeoffset, ptr_in + readoffset, writesize)
12✔
790
            readoffset += writesize
12✔
791
        else # nested padded type
792
            _copyfrompacked!(Ptr{fT}(ptr_out + writeoffset), ptr_in + readoffset)
1✔
793
            readoffset += packedsize(fT)
1✔
794
        end
795
    end
13✔
796
end
797

798
"""
799
    reinterpret(::Type{Out}, x::In)
800

801
Reinterpret the valid non-padding bytes of an isbits value `x` as isbits type `Out`.
802

803
Both types must have the same amount of non-padding bytes. This operation is guaranteed
804
to be reversible.
805

806
```jldoctest
807
julia> reinterpret(NTuple{2, UInt8}, 0x1234)
808
(0x34, 0x12)
809

810
julia> reinterpret(UInt16, (0x34, 0x12))
811
0x1234
812

813
julia> reinterpret(Tuple{UInt16, UInt8}, (0x01, 0x0203))
814
(0x0301, 0x02)
815
```
816

817
!!! warning
818

819
    Use caution if some combinations of bits in `Out` are not considered valid and would
820
    otherwise be prevented by the type's constructors and methods. Unexpected behavior
821
    may result without additional validation.
822
"""
823
@inline function reinterpret(::Type{Out}, x::In) where {Out, In}
661,543,329✔
824
    isbitstype(Out) || throw(ArgumentError("Target type for `reinterpret` must be isbits"))
661,537,209✔
825
    isbitstype(In) || throw(ArgumentError("Source type for `reinterpret` must be isbits"))
661,537,050✔
826
    if isprimitivetype(Out) && isprimitivetype(In)
661,537,015✔
827
        outsize = sizeof(Out)
661,536,943✔
828
        insize = sizeof(In)
661,536,918✔
829
        outsize == insize ||
661,536,886✔
830
            throw(ArgumentError("Sizes of types $Out and $In do not match; got $outsize \
831
                and $insize, respectively."))
832
        return bitcast(Out, x)
7,491,540,132✔
833
    end
834
    inpackedsize = packedsize(In)
13✔
835
    outpackedsize = packedsize(Out)
13✔
836
    inpackedsize == outpackedsize ||
13✔
837
        throw(ArgumentError("Packed sizes of types $Out and $In do not match; got $outpackedsize \
838
            and $inpackedsize, respectively."))
839
    in = Ref{In}(x)
9✔
840
    out = Ref{Out}()
9✔
841
    if struct_subpadding(Out, In)
9✔
842
        # if packed the same, just copy
843
        GC.@preserve in out begin
6✔
844
            ptr_in = unsafe_convert(Ptr{In}, in)
6✔
845
            ptr_out = unsafe_convert(Ptr{Out}, out)
6✔
846
            memcpy(ptr_out, ptr_in, sizeof(Out))
6✔
847
        end
848
        return out[]
6✔
849
    else
850
        # mismatched padding
851
        GC.@preserve in out begin
3✔
852
            ptr_in = unsafe_convert(Ptr{In}, in)
3✔
853
            ptr_out = unsafe_convert(Ptr{Out}, out)
3✔
854

855
            if fieldcount(In) > 0 && ispacked(Out)
3✔
856
                _copytopacked!(ptr_out, ptr_in)
×
857
            elseif fieldcount(Out) > 0 && ispacked(In)
3✔
858
                _copyfrompacked!(ptr_out, ptr_in)
×
859
            else
860
                packed = Ref{NTuple{inpackedsize, UInt8}}()
3✔
861
                GC.@preserve packed begin
3✔
862
                    ptr_packed = unsafe_convert(Ptr{NTuple{inpackedsize, UInt8}}, packed)
3✔
863
                    _copytopacked!(ptr_packed, ptr_in)
3✔
864
                    _copyfrompacked!(ptr_out, ptr_packed)
3✔
865
                end
866
            end
867
        end
868
        return out[]
3✔
869
    end
870
end
871

872

873
# Reductions with IndexSCartesian2
874

875
function _mapreduce(f::F, op::OP, style::IndexSCartesian2{K}, A::AbstractArrayOrBroadcasted) where {F,OP,K}
2✔
876
    inds = eachindex(style, A)
2✔
877
    n = size(inds)[2]
2✔
878
    if n == 0
2✔
879
        return mapreduce_empty_iter(f, op, A, IteratorEltype(A))
×
880
    else
881
        return mapreduce_impl(f, op, A, first(inds), last(inds))
2✔
882
    end
883
end
884

885
@noinline function mapreduce_impl(f::F, op::OP, A::AbstractArrayOrBroadcasted,
4✔
886
                                  ifirst::SCI, ilast::SCI, blksize::Int) where {F,OP,SCI<:SCartesianIndex2{K}} where K
887
    if ilast.j - ifirst.j < blksize
4✔
888
        # sequential portion
889
        @inbounds a1 = A[ifirst]
3✔
890
        @inbounds a2 = A[SCI(2,ifirst.j)]
3✔
891
        v = op(f(a1), f(a2))
3✔
892
        @simd for i = ifirst.i + 2 : K
3✔
893
            @inbounds ai = A[SCI(i,ifirst.j)]
3✔
894
            v = op(v, f(ai))
3✔
895
        end
×
896
        # Remaining columns
897
        for j = ifirst.j+1 : ilast.j
6✔
898
            @simd for i = 1:K
1,333✔
899
                @inbounds ai = A[SCI(i,j)]
3,999✔
900
                v = op(v, f(ai))
3,999✔
901
            end
×
902
        end
2,663✔
903
        return v
3✔
904
    else
905
        # pairwise portion
906
        jmid = ifirst.j + (ilast.j - ifirst.j) >> 1
1✔
907
        v1 = mapreduce_impl(f, op, A, ifirst, SCI(K,jmid), blksize)
1✔
908
        v2 = mapreduce_impl(f, op, A, SCI(1,jmid+1), ilast, blksize)
1✔
909
        return op(v1, v2)
1✔
910
    end
911
end
912

913
mapreduce_impl(f::F, op::OP, A::AbstractArrayOrBroadcasted, ifirst::SCartesianIndex2, ilast::SCartesianIndex2) where {F,OP} =
2✔
914
    mapreduce_impl(f, op, A, ifirst, ilast, pairwise_blocksize(f, op))
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