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

JuliaLang / julia / #37801

08 Jun 2024 05:10AM UTC coverage: 87.504% (+0.03%) from 87.474%
#37801

push

local

web-flow
Don't let setglobal! implicitly create bindings (#54678)

PR #44231 (part of Julia 1.9) introduced the ability to modify globals
with `Mod.sym = val` syntax. However, the intention of this syntax was
always to modify *existing* globals in other modules. Unfortunately, as
implemented, it also implicitly creates new bindings in the other
module, even if the binding was not previously declared. This was not
intended, but it's a bit of a syntax corner case, so nobody caught it at
the time.

After some extensive discussions and taking into account the near future
direction we want to go with bindings (#54654 for both), the consensus
was reached that we should try to undo the implicit creation of bindings
(but not the ability to assign the *value* of globals in other modules).
Note that this was always an error until Julia 1.9, so hopefully it
hasn't crept into too many packages yet. We'll see what pkgeval says. If
use is extensive, we may want to consider a softer removal strategy.

Across base and stdlib, there's two cases affected by this change:
1. A left over debug statement in `precompile` that wanted to assign a
new variable in Base for debugging. Removed in this PR.
2. Distributed wanting to create new bindings. This is a legimitate use
case for wanting to create bindings in other modules. This is fixed in
https://github.com/JuliaLang/Distributed.jl/pull/102.

As noted in that PR, the recommended replacement where implicit binding
creation is desired is:
```
Core.eval(mod, Expr(:global, sym))
invokelatest(setglobal!, mod, sym, val)
```

The `invokelatest` is not presently required, but may be needed by
#54654, so it's included in the recommendation now.

Fixes #54607

77035 of 88036 relevant lines covered (87.5%)

16049112.75 hits per line

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

94.95
/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(LazyString("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(LazyString("cannot reinterpret a zero-dimensional `", S, "` array to `", T,
5✔
21
            "` which is of a ", msg, " size")))
22
    end
23
    function throwsingleton(S::Type, T::Type)
5✔
24
        @noinline
5✔
25
        throw(ArgumentError(LazyString("cannot reinterpret a `", S, "` array to `", T, "` which is a singleton type")))
5✔
26
    end
27

28
    global reinterpret
29

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

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

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

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

44
    julia> reinterpret(Complex{Int}, 1:6)
45
    3-element reinterpret(Complex{$Int}, ::UnitRange{$Int}):
46
     1 + 2im
47
     3 + 4im
48
     5 + 6im
49
    ```
50

51
    If the location of padding bits does not line up between `T` and `eltype(A)`, the resulting array will be
52
    read-only or write-only, to prevent invalid bits from being written to or read from, respectively.
53

54
    ```jldoctest
55
    julia> a = reinterpret(Tuple{UInt8, UInt32}, UInt32[1, 2])
56
    1-element reinterpret(Tuple{UInt8, UInt32}, ::Vector{UInt32}):
57
     (0x01, 0x00000002)
58

59
    julia> a[1] = 3
60
    ERROR: Padding of type Tuple{UInt8, UInt32} is not compatible with type UInt32.
61

62
    julia> b = reinterpret(UInt32, Tuple{UInt8, UInt32}[(0x01, 0x00000002)]); # showing will error
63

64
    julia> b[1]
65
    ERROR: Padding of type UInt32 is not compatible with type Tuple{UInt8, UInt32}.
66
    ```
67
    """
68
    function reinterpret(::Type{T}, a::A) where {T,N,S,A<:AbstractArray{S, N}}
822✔
69
        function thrownonint(S::Type, T::Type, dim)
6,990✔
70
            @noinline
2✔
71
            throw(ArgumentError(LazyString(
2✔
72
                "cannot reinterpret an `", S, "` array to `", T, "` whose first dimension has size `", dim,
73
                "`. The resulting array would have a non-integral first dimension.")))
74
        end
75
        function throwaxes1(S::Type, T::Type, ax1)
6,990✔
76
            @noinline
2✔
77
            throw(ArgumentError(LazyString("cannot reinterpret a `", S, "` array to `", T,
2✔
78
                "` when the first axis is ", ax1, ". Try reshaping first.")))
79
        end
80
        isbitstype(T) || throwbits(S, T, T)
6,988✔
81
        isbitstype(S) || throwbits(S, T, S)
6,985✔
82
        (N != 0 || sizeof(T) == sizeof(S)) || throwsize0(S, T, "different")
6,982✔
83
        if N != 0 && sizeof(S) != sizeof(T)
6,978✔
84
            ax1 = axes(a)[1]
132,734✔
85
            dim = length(ax1)
6,541✔
86
            if issingletontype(T)
6,541✔
87
                issingletontype(S) || throwsingleton(S, T)
2✔
88
            else
89
                rem(dim*sizeof(S),sizeof(T)) == 0 || thrownonint(S, T, dim)
132,734✔
90
            end
91
            first(ax1) == 1 || throwaxes1(S, T, ax1)
6,539✔
92
        end
93
        readable = array_subpadding(T, S)
6,972✔
94
        writable = array_subpadding(S, T)
6,972✔
95
        new{T, N, S, A, false}(a, readable, writable)
135,850✔
96
    end
97
    reinterpret(::Type{T}, a::AbstractArray{T}) where {T} = a
152✔
98

99
    # With reshaping
100
    function reinterpret(::typeof(reshape), ::Type{T}, a::A) where {T,S,A<:AbstractArray{S}}
92✔
101
        function throwintmult(S::Type, T::Type)
1,021✔
102
            @noinline
1✔
103
            throw(ArgumentError(LazyString("`reinterpret(reshape, T, a)` requires that one of `sizeof(T)` (got ",
1✔
104
                sizeof(T), ") and `sizeof(eltype(a))` (got ", sizeof(S), ") be an integer multiple of the other")))
105
        end
106
        function throwsize1(a::AbstractArray, T::Type)
1,022✔
107
            @noinline
2✔
108
            throw(ArgumentError(LazyString("`reinterpret(reshape, ", T, ", a)` where `eltype(a)` is ", eltype(a),
2✔
109
                " requires that `axes(a, 1)` (got ", axes(a, 1), ") be equal to 1:",
110
                sizeof(T) ÷ sizeof(eltype(a)), " (from the ratio of element sizes)")))
111
        end
112
        function throwfromsingleton(S, T)
1,023✔
113
            @noinline
3✔
114
            throw(ArgumentError(LazyString("`reinterpret(reshape, ", T, ", a)` where `eltype(a)` is ", S,
3✔
115
                " requires that ", T, " be a singleton type, since ", S, " is one")))
116
        end
117
        isbitstype(T) || throwbits(S, T, T)
1,020✔
118
        isbitstype(S) || throwbits(S, T, S)
1,018✔
119
        if sizeof(S) == sizeof(T)
1,017✔
120
            N = ndims(a)
58✔
121
        elseif sizeof(S) > sizeof(T)
959✔
122
            issingletontype(T) && throwsingleton(S, T)
905✔
123
            rem(sizeof(S), sizeof(T)) == 0 || throwintmult(S, T)
902✔
124
            N = ndims(a) + 1
902✔
125
        else
126
            issingletontype(S) && throwfromsingleton(S, T)
54✔
127
            rem(sizeof(T), sizeof(S)) == 0 || throwintmult(S, T)
51✔
128
            N = ndims(a) - 1
50✔
129
            N > -1 || throwsize0(S, T, "larger")
50✔
130
            axes(a, 1) == OneTo(sizeof(T) ÷ sizeof(S)) || throwsize1(a, T)
51✔
131
        end
132
        readable = array_subpadding(T, S)
1,007✔
133
        writable = array_subpadding(S, T)
1,007✔
134
        new{T, N, S, A, true}(a, readable, writable)
1,007✔
135
    end
136
    reinterpret(::typeof(reshape), ::Type{T}, a::AbstractArray{T}) where {T} = a
2✔
137
end
138

139
ReshapedReinterpretArray{T,N,S,A<:AbstractArray{S}} = ReinterpretArray{T,N,S,A,true}
140
NonReshapedReinterpretArray{T,N,S,A<:AbstractArray{S, N}} = ReinterpretArray{T,N,S,A,false}
141

142
"""
143
    reinterpret(reshape, T, A::AbstractArray{S}) -> B
144

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

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

151
!!! compat "Julia 1.6"
152
    This method requires at least Julia 1.6.
153

154
# Examples
155

156
```jldoctest
157
julia> A = [1 2; 3 4]
158
2×2 Matrix{$Int}:
159
 1  2
160
 3  4
161

162
julia> reinterpret(reshape, Complex{Int}, A)    # the result is a vector
163
2-element reinterpret(reshape, Complex{$Int}, ::Matrix{$Int}) with eltype Complex{$Int}:
164
 1 + 3im
165
 2 + 4im
166

167
julia> a = [(1,2,3), (4,5,6)]
168
2-element Vector{Tuple{$Int, $Int, $Int}}:
169
 (1, 2, 3)
170
 (4, 5, 6)
171

172
julia> reinterpret(reshape, Int, a)             # the result is a matrix
173
3×2 reinterpret(reshape, $Int, ::Vector{Tuple{$Int, $Int, $Int}}) with eltype $Int:
174
 1  4
175
 2  5
176
 3  6
177
```
178
"""
179
reinterpret(::typeof(reshape), T::Type, a::AbstractArray)
180

181
reinterpret(::Type{T}, a::NonReshapedReinterpretArray) where {T} = reinterpret(T, a.parent)
153✔
182
reinterpret(::typeof(reshape), ::Type{T}, a::ReshapedReinterpretArray) where {T} = reinterpret(reshape, T, a.parent)
2✔
183

184
# Definition of StridedArray
185
StridedFastContiguousSubArray{T,N,A<:DenseArray} = FastContiguousSubArray{T,N,A}
186
StridedReinterpretArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray},IsReshaped} = ReinterpretArray{T,N,S,A,IsReshaped} where S
187
StridedReshapedArray{T,N,A<:Union{DenseArray,StridedFastContiguousSubArray,StridedReinterpretArray}} = ReshapedArray{T,N,A}
188
StridedSubArray{T,N,A<:Union{DenseArray,StridedReshapedArray,StridedReinterpretArray},
189
    I<:Tuple{Vararg{Union{RangeIndex, ReshapedUnitRange, AbstractCartesianIndex}}}} = SubArray{T,N,A,I}
190
StridedArray{T,N} = Union{DenseArray{T,N}, StridedSubArray{T,N}, StridedReshapedArray{T,N}, StridedReinterpretArray{T,N}}
191
StridedVector{T} = StridedArray{T,1}
192
StridedMatrix{T} = StridedArray{T,2}
193
StridedVecOrMat{T} = Union{StridedVector{T}, StridedMatrix{T}}
194

195
strides(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}) = size_to_strides(1, size(a)...)
24,382,509✔
196
stride(A::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, k::Integer) =
16,221,518✔
197
    k ≤ ndims(A) ? strides(A)[k] : length(A)
198

199
function strides(a::ReinterpretArray{T,<:Any,S,<:AbstractArray{S},IsReshaped}) where {T,S,IsReshaped}
86✔
200
    _checkcontiguous(Bool, a) && return size_to_strides(1, size(a)...)
3,127✔
201
    stp = strides(parent(a))
3,106✔
202
    els, elp = sizeof(T), sizeof(S)
3,106✔
203
    els == elp && return stp # 0dim parent is also handled here.
3,106✔
204
    IsReshaped && els < elp && return (1, _checked_strides(stp, els, elp)...)
1,976✔
205
    stp[1] == 1 || throw(ArgumentError("Parent must be contiguous in the 1st dimension!"))
670✔
206
    st′ = _checked_strides(tail(stp), els, elp)
645✔
207
    return IsReshaped ? st′ : (1, st′...)
607✔
208
end
209

210
@inline function _checked_strides(stp::Tuple, els::Integer, elp::Integer)
211
    if elp > els && rem(elp, els) == 0
1,954✔
212
        N = div(elp, els)
1,797✔
213
        return map(i -> N * i, stp)
4,210✔
214
    end
215
    drs = map(i -> divrem(elp * i, els), stp)
314✔
216
    all(i->iszero(i[2]), drs) ||
393✔
217
        throw(ArgumentError("Parent's strides could not be exactly divided!"))
218
    map(first, drs)
138✔
219
end
220

221
_checkcontiguous(::Type{Bool}, A::ReinterpretArray) = _checkcontiguous(Bool, parent(A))
4,986✔
222

223
similar(a::ReinterpretArray, T::Type, d::Dims) = similar(a.parent, T, d)
253,344✔
224

225
function check_readable(a::ReinterpretArray{T, N, S} where N) where {T,S}
2✔
226
    # See comment in check_writable
227
    if !a.readable && !array_subpadding(T, S)
39,324,243✔
228
        throw(PaddingError(T, S))
2✔
229
    end
230
end
231

232
function check_writable(a::ReinterpretArray{T, N, S} where N) where {T,S}
233
    # `array_subpadding` is relatively expensive (compared to a simple arrayref),
234
    # so it is cached in the array. However, it is computable at compile time if,
235
    # inference has the types available. By using this form of the check, we can
236
    # get the best of both worlds for the success case. If the types were not
237
    # available to inference, we simply need to check the field (relatively cheap)
238
    # and if they were we should be able to fold this check away entirely.
239
    if !a.writable && !array_subpadding(S, T)
84,382,799✔
240
        throw(PaddingError(T, S))
2✔
241
    end
242
end
243

244
## IndexStyle specializations
245

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

250
IndexStyle(::Type{ReinterpretArray{T,N,S,A,false}}) where {T,N,S,A<:AbstractArray{S,N}} = IndexStyle(A)
20,949✔
251
function IndexStyle(::Type{ReinterpretArray{T,N,S,A,true}}) where {T,N,S,A<:AbstractArray{S}}
252
    if sizeof(T) < sizeof(S)
4,002✔
253
        IndexStyle(A) === IndexLinear() && return IndexSCartesian2{sizeof(S) ÷ sizeof(T)}()
3,216✔
254
        return IndexCartesian()
1,195✔
255
    end
256
    return IndexStyle(A)
786✔
257
end
258
IndexStyle(::IndexSCartesian2{K}, ::IndexSCartesian2{K}) where {K} = IndexSCartesian2{K}()
3✔
259

260
struct SCartesianIndex2{K}   # can't make <:AbstractCartesianIndex without N, and 2 would be a bit misleading
261
    i::Int
8,247✔
262
    j::Int
263
end
264
to_index(i::SCartesianIndex2) = i
60✔
265

266
struct SCartesianIndices2{K,R<:AbstractUnitRange{Int}} <: AbstractMatrix{SCartesianIndex2{K}}
267
    indices2::R
31✔
268
end
269
SCartesianIndices2{K}(indices2::AbstractUnitRange{Int}) where {K} = (@assert K::Int > 1; SCartesianIndices2{K,typeof(indices2)}(indices2))
31✔
270

271
eachindex(::IndexSCartesian2{K}, A::ReshapedReinterpretArray) where {K} = SCartesianIndices2{K}(eachindex(IndexLinear(), parent(A)))
28✔
272
@inline function eachindex(style::IndexSCartesian2{K}, A::AbstractArray, B::AbstractArray...) where {K}
273
    iter = eachindex(style, A)
3✔
274
    _all_match_first(C->eachindex(style, C), iter, B...) || throw_eachindex_mismatch_indices(IndexSCartesian2{K}(), axes(A), axes.(B)...)
6✔
275
    return iter
3✔
276
end
277

278
size(iter::SCartesianIndices2{K}) where K = (K, length(iter.indices2))
4✔
279
axes(iter::SCartesianIndices2{K}) where K = (OneTo(K), iter.indices2)
9✔
280

281
first(iter::SCartesianIndices2{K}) where {K} = SCartesianIndex2{K}(1, first(iter.indices2))
5✔
282
last(iter::SCartesianIndices2{K}) where {K}  = SCartesianIndex2{K}(K, last(iter.indices2))
7✔
283

284
@inline function getindex(iter::SCartesianIndices2{K}, i::Int, j::Int) where {K}
×
285
    @boundscheck checkbounds(iter, i, j)
×
286
    return SCartesianIndex2{K}(i, iter.indices2[j])
×
287
end
288

289
function iterate(iter::SCartesianIndices2{K}) where {K}
290
    ret = iterate(iter.indices2)
28✔
291
    ret === nothing && return nothing
27✔
292
    item2, state2 = ret
27✔
293
    return SCartesianIndex2{K}(1, item2), (1, item2, state2)
27✔
294
end
295

296
function iterate(iter::SCartesianIndices2{K}, (state1, item2, state2)) where {K}
297
    if state1 < K
201✔
298
        item1 = state1 + 1
127✔
299
        return SCartesianIndex2{K}(item1, item2), (item1, item2, state2)
127✔
300
    end
301
    ret = iterate(iter.indices2, state2)
131✔
302
    ret === nothing && return nothing
74✔
303
    item2, state2 = ret
57✔
304
    return SCartesianIndex2{K}(1, item2), (1, item2, state2)
57✔
305
end
306

307
SimdLoop.simd_outer_range(iter::SCartesianIndices2) = iter.indices2
×
308
SimdLoop.simd_inner_length(::SCartesianIndices2{K}, ::Any) where K = K
×
309
@inline function SimdLoop.simd_index(::SCartesianIndices2{K}, Ilast::Int, I1::Int) where {K}
×
310
    SCartesianIndex2{K}(I1+1, Ilast)
×
311
end
312

313
_maybe_reshape(::IndexSCartesian2, A::ReshapedReinterpretArray, I...) = A
×
314

315
# fallbacks
316
function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, I::Vararg{Int, N}) where {T,N}
317
    @_propagate_inbounds_meta
36✔
318
    getindex(A, I...)
36✔
319
end
320
function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, I::Vararg{Int, N}) where {T,N}
×
321
    @_propagate_inbounds_meta
×
322
    setindex!(A, v, I...)
×
323
end
324
# fallbacks for array types that use "pass-through" indexing (e.g., `IndexStyle(A) = IndexStyle(parent(A))`)
325
# but which don't handle SCartesianIndex2
326
function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, ind::SCartesianIndex2) where {T,N}
327
    @_propagate_inbounds_meta
18✔
328
    J = _ind2sub(tail(axes(A)), ind.j)
18✔
329
    getindex(A, ind.i, J...)
18✔
330
end
331
function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, ind::SCartesianIndex2) where {T,N}
332
    @_propagate_inbounds_meta
42✔
333
    J = _ind2sub(tail(axes(A)), ind.j)
42✔
334
    setindex!(A, v, ind.i, J...)
42✔
335
end
336
eachindex(style::IndexSCartesian2, A::AbstractArray) = eachindex(style, parent(A))
14✔
337

338
## AbstractArray interface
339

340
parent(a::ReinterpretArray) = a.parent
78,332,593✔
341
dataids(a::ReinterpretArray) = dataids(a.parent)
6,634✔
342
unaliascopy(a::NonReshapedReinterpretArray{T}) where {T} = reinterpret(T, unaliascopy(a.parent))
2✔
343
unaliascopy(a::ReshapedReinterpretArray{T}) where {T} = reinterpret(reshape, T, unaliascopy(a.parent))
1✔
344

345
function size(a::NonReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
346
    psize = size(a.parent)
33,979✔
347
    size1 = issingletontype(T) ? psize[1] : div(psize[1]*sizeof(S), sizeof(T))
33,979✔
348
    tuple(size1, tail(psize)...)
33,979✔
349
end
350
function size(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
1✔
351
    psize = size(a.parent)
4,409✔
352
    sizeof(S) > sizeof(T) && return (div(sizeof(S), sizeof(T)), psize...)
4,409✔
353
    sizeof(S) < sizeof(T) && return tail(psize)
289✔
354
    return psize
109✔
355
end
356
size(a::NonReshapedReinterpretArray{T,0}) where {T} = ()
24✔
357

358
function axes(a::NonReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
2✔
359
    paxs = axes(a.parent)
79,510,478✔
360
    f, l = first(paxs[1]), length(paxs[1])
77,906,558✔
361
    size1 = issingletontype(T) ? l : div(l*sizeof(S), sizeof(T))
79,510,478✔
362
    tuple(oftype(paxs[1], f:f+size1-1), tail(paxs)...)
79,510,478✔
363
end
364
function axes(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S}
2✔
365
    paxs = axes(a.parent)
16,449✔
366
    sizeof(S) > sizeof(T) && return (OneTo(div(sizeof(S), sizeof(T))), paxs...)
16,449✔
367
    sizeof(S) < sizeof(T) && return tail(paxs)
2,152✔
368
    return paxs
1,780✔
369
end
370
axes(a::NonReshapedReinterpretArray{T,0}) where {T} = ()
6✔
371

372
has_offset_axes(a::ReinterpretArray) = has_offset_axes(a.parent)
4,352✔
373

374
elsize(::Type{<:ReinterpretArray{T}}) where {T} = sizeof(T)
78,328,966✔
375
cconvert(::Type{Ptr{T}}, a::ReinterpretArray{T,N,S} where N) where {T,S} = cconvert(Ptr{S}, a.parent)
80,460,784✔
376

377
@propagate_inbounds function getindex(a::NonReshapedReinterpretArray{T,0,S}) where {T,S}
1✔
378
    if isprimitivetype(T) && isprimitivetype(S)
5✔
379
        reinterpret(T, a.parent[])
2✔
380
    else
381
        a[firstindex(a)]
3✔
382
    end
383
end
384

385
check_ptr_indexable(a::ReinterpretArray, sz = elsize(a)) = check_ptr_indexable(parent(a), sz)
156,648,887✔
386
check_ptr_indexable(a::ReshapedArray, sz) = check_ptr_indexable(parent(a), sz)
494✔
387
check_ptr_indexable(a::FastContiguousSubArray, sz) = check_ptr_indexable(parent(a), sz)
306,056✔
388
check_ptr_indexable(a::Array, sz) = sizeof(eltype(a)) !== sz
77,997,133✔
389
check_ptr_indexable(a::Memory, sz) = true
×
390
check_ptr_indexable(a::AbstractArray, sz) = false
×
391

392
@propagate_inbounds getindex(a::ReinterpretArray) = a[firstindex(a)]
7✔
393

394
@propagate_inbounds isassigned(a::ReinterpretArray, inds::Integer...) = checkbounds(Bool, a, inds...) && (check_ptr_indexable(a) || _isassigned_ra(a, inds...))
12✔
395
@propagate_inbounds isassigned(a::ReinterpretArray, inds::SCartesianIndex2) = isassigned(a.parent, inds.j)
×
396
@propagate_inbounds _isassigned_ra(a::ReinterpretArray, inds...) = true # that is not entirely true, but computing exactly which indexes will be accessed in the parent requires a lot of duplication from the _getindex_ra code
×
397

398
@propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, inds::Vararg{Int, N}) where {T,N,S}
84✔
399
    check_readable(a)
41,608,065✔
400
    check_ptr_indexable(a) && return _getindex_ptr(a, inds...)
44,296,044✔
401
    _getindex_ra(a, inds[1], tail(inds))
47,907,356✔
402
end
403

404
@propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, i::Int) where {T,N,S}
35✔
405
    check_readable(a)
9,620✔
406
    check_ptr_indexable(a) && return _getindex_ptr(a, i)
9,620✔
407
    if isa(IndexStyle(a), IndexLinear)
3,392✔
408
        return _getindex_ra(a, i, ())
643✔
409
    end
410
    # Convert to full indices here, to avoid needing multiple conversions in
411
    # the loop in _getindex_ra
412
    inds = _to_subscript_indices(a, i)
2,752✔
413
    isempty(inds) ? _getindex_ra(a, 1, ()) : _getindex_ra(a, inds[1], tail(inds))
2,836✔
414
end
415

416
@propagate_inbounds function getindex(a::ReshapedReinterpretArray{T,N,S}, ind::SCartesianIndex2) where {T,N,S}
417
    check_readable(a)
8,038✔
418
    s = Ref{S}(a.parent[ind.j])
8,038✔
419
    tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
8,038✔
420
    GC.@preserve s return unsafe_load(tptr, ind.i)
8,038✔
421
end
422

423
@inline function _getindex_ptr(a::ReinterpretArray{T}, inds...) where {T}
424
    @boundscheck checkbounds(a, inds...)
41,674,319✔
425
    li = _to_linear_index(a, inds...)
38,986,326✔
426
    ap = cconvert(Ptr{T}, a)
41,674,307✔
427
    p = unsafe_convert(Ptr{T}, ap) + sizeof(T) * (li - 1)
41,674,307✔
428
    GC.@preserve ap return unsafe_load(p)
41,674,307✔
429
end
430

431
@propagate_inbounds function _getindex_ra(a::NonReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
432
    # Make sure to match the scalar reinterpret if that is applicable
433
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
2,629,396✔
434
        if issingletontype(T) # singleton types
2,627,927✔
435
            @boundscheck checkbounds(a, i1, tailinds...)
85✔
436
            return T.instance
79✔
437
        end
438
        return reinterpret(T, a.parent[i1, tailinds...])
47,907,116✔
439
    else
440
        @boundscheck checkbounds(a, i1, tailinds...)
1,487✔
441
        ind_start, sidx = divrem((i1-1)*sizeof(T), sizeof(S))
1,451✔
442
        # Optimizations that avoid branches
443
        if sizeof(T) % sizeof(S) == 0
1,451✔
444
            # T is bigger than S and contains an integer number of them
445
            n = sizeof(T) ÷ sizeof(S)
63✔
446
            t = Ref{T}()
63✔
447
            GC.@preserve t begin
63✔
448
                sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
63✔
449
                for i = 1:n
63✔
450
                     s = a.parent[ind_start + i, tailinds...]
116✔
451
                     unsafe_store!(sptr, s, i)
108✔
452
                end
153✔
453
            end
454
            return t[]
63✔
455
        elseif sizeof(S) % sizeof(T) == 0
1,388✔
456
            # S is bigger than T and contains an integer number of them
457
            s = Ref{S}(a.parent[ind_start + 1, tailinds...])
1,380✔
458
            GC.@preserve s begin
1,356✔
459
                tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
1,356✔
460
                return unsafe_load(tptr + sidx)
1,356✔
461
            end
462
        else
463
            i = 1
32✔
464
            nbytes_copied = 0
32✔
465
            # This is a bit complicated to deal with partial elements
466
            # at both the start and the end. LLVM will fold as appropriate,
467
            # once it knows the data layout
468
            s = Ref{S}()
32✔
469
            t = Ref{T}()
32✔
470
            GC.@preserve s t begin
32✔
471
                sptr = Ptr{S}(unsafe_convert(Ref{S}, s))
32✔
472
                tptr = Ptr{T}(unsafe_convert(Ref{T}, t))
32✔
473
                while nbytes_copied < sizeof(T)
104✔
474
                    s[] = a.parent[ind_start + i, tailinds...]
76✔
475
                    nb = min(sizeof(S) - sidx, sizeof(T)-nbytes_copied)
72✔
476
                    memcpy(tptr + nbytes_copied, sptr + sidx, nb)
72✔
477
                    nbytes_copied += nb
72✔
478
                    sidx = 0
72✔
479
                    i += 1
72✔
480
                end
104✔
481
            end
482
            return t[]
32✔
483
        end
484
    end
485
end
486

487
@propagate_inbounds function _getindex_ra(a::ReshapedReinterpretArray{T,N,S}, i1::Int, tailinds::TT) where {T,N,S,TT}
488
    # Make sure to match the scalar reinterpret if that is applicable
489
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
1,955✔
490
        if issingletontype(T) # singleton types
673✔
491
            @boundscheck checkbounds(a, i1, tailinds...)
64✔
492
            return T.instance
62✔
493
        end
494
        return reinterpret(T, a.parent[i1, tailinds...])
614✔
495
    end
496
    @boundscheck checkbounds(a, i1, tailinds...)
1,282✔
497
    if sizeof(T) >= sizeof(S)
1,282✔
498
        t = Ref{T}()
92✔
499
        GC.@preserve t begin
92✔
500
            sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
92✔
501
            if sizeof(T) > sizeof(S)
92✔
502
                # Extra dimension in the parent array
503
                n = sizeof(T) ÷ sizeof(S)
84✔
504
                if isempty(tailinds) && IndexStyle(a.parent) === IndexLinear()
84✔
505
                    offset = n * (i1 - firstindex(a))
27✔
506
                    for i = 1:n
27✔
507
                        s = a.parent[i + offset]
146✔
508
                        unsafe_store!(sptr, s, i)
146✔
509
                    end
265✔
510
                else
511
                    for i = 1:n
57✔
512
                        s = a.parent[i, i1, tailinds...]
126✔
513
                        unsafe_store!(sptr, s, i)
114✔
514
                    end
171✔
515
                end
516
            else
517
                # No extra dimension
518
                s = a.parent[i1, tailinds...]
8✔
519
                unsafe_store!(sptr, s)
92✔
520
            end
521
        end
522
        return t[]
92✔
523
    end
524
    # S is bigger than T and contains an integer number of them
525
    # n = sizeof(S) ÷ sizeof(T)
526
    s = Ref{S}()
1,190✔
527
    GC.@preserve s begin
1,190✔
528
        tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
1,190✔
529
        s[] = a.parent[tailinds...]
1,202✔
530
        return unsafe_load(tptr, i1)
1,190✔
531
    end
532
end
533

534
@propagate_inbounds function setindex!(a::NonReshapedReinterpretArray{T,0,S}, v) where {T,S}
2✔
535
    if isprimitivetype(S) && isprimitivetype(T)
6✔
536
        a.parent[] = reinterpret(S, v)
2✔
537
        return a
2✔
538
    end
539
    setindex!(a, v, firstindex(a))
4✔
540
end
541

542
@propagate_inbounds setindex!(a::ReinterpretArray, v) = setindex!(a, v, firstindex(a))
10✔
543

544
@propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, inds::Vararg{Int, N}) where {T,N,S}
57✔
545
    check_writable(a)
84,511,469✔
546
    check_ptr_indexable(a) && return _setindex_ptr!(a, v, inds...)
84,511,467✔
547
    _setindex_ra!(a, v, inds[1], tail(inds))
46,192,228✔
548
end
549

550
@propagate_inbounds function setindex!(a::ReinterpretArray{T,N,S}, v, i::Int) where {T,N,S}
34✔
551
    check_writable(a)
143✔
552
    check_ptr_indexable(a) && return _setindex_ptr!(a, v, i)
143✔
553
    if isa(IndexStyle(a), IndexLinear)
103✔
554
        return _setindex_ra!(a, v, i, ())
88✔
555
    end
556
    inds = _to_subscript_indices(a, i)
18✔
557
    _setindex_ra!(a, v, inds[1], tail(inds))
20✔
558
end
559

560
@propagate_inbounds function setindex!(a::ReshapedReinterpretArray{T,N,S}, v, ind::SCartesianIndex2) where {T,N,S}
561
    check_writable(a)
3✔
562
    v = convert(T, v)::T
3✔
563
    s = Ref{S}(a.parent[ind.j])
3✔
564
    GC.@preserve s begin
3✔
565
        tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
3✔
566
        unsafe_store!(tptr, v, ind.i)
3✔
567
    end
568
    a.parent[ind.j] = s[]
3✔
569
    return a
3✔
570
end
571

572
@inline function _setindex_ptr!(a::ReinterpretArray{T}, v, inds...) where {T}
573
    @boundscheck checkbounds(a, inds...)
38,777,144✔
574
    li = _to_linear_index(a, inds...)
38,777,132✔
575
    ap = cconvert(Ptr{T}, a)
38,777,132✔
576
    p = unsafe_convert(Ptr{T}, ap) + sizeof(T) * (li - 1)
38,777,132✔
577
    GC.@preserve ap unsafe_store!(p, v)
38,777,132✔
578
    return a
38,777,132✔
579
end
580

581
@propagate_inbounds function _setindex_ra!(a::NonReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT}
582
    v = convert(T, v)::T
45,734,422✔
583
    # Make sure to match the scalar reinterpret if that is applicable
584
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
45,734,420✔
585
        if issingletontype(T) # singleton types
45,734,308✔
586
            @boundscheck checkbounds(a, i1, tailinds...)
6✔
587
            # setindex! is a noop except for the index check
588
        else
589
            setindex!(a.parent, reinterpret(S, v), i1, tailinds...)
46,192,166✔
590
        end
591
    else
592
        @boundscheck checkbounds(a, i1, tailinds...)
130✔
593
        ind_start, sidx = divrem((i1-1)*sizeof(T), sizeof(S))
94✔
594
        # Optimizations that avoid branches
595
        if sizeof(T) % sizeof(S) == 0
94✔
596
            # T is bigger than S and contains an integer number of them
597
            t = Ref{T}(v)
11✔
598
            GC.@preserve t begin
11✔
599
                sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
11✔
600
                n = sizeof(T) ÷ sizeof(S)
11✔
601
                for i = 1:n
11✔
602
                    s = unsafe_load(sptr, i)
15✔
603
                    a.parent[ind_start + i, tailinds...] = s
15✔
604
                end
19✔
605
            end
606
        elseif sizeof(S) % sizeof(T) == 0
83✔
607
            # S is bigger than T and contains an integer number of them
608
            s = Ref{S}(a.parent[ind_start + 1, tailinds...])
79✔
609
            GC.@preserve s begin
79✔
610
                tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
79✔
611
                unsafe_store!(tptr + sidx, v)
79✔
612
                a.parent[ind_start + 1, tailinds...] = s[]
79✔
613
            end
614
        else
615
            t = Ref{T}(v)
4✔
616
            s = Ref{S}()
4✔
617
            GC.@preserve t s begin
4✔
618
                tptr = Ptr{UInt8}(unsafe_convert(Ref{T}, t))
4✔
619
                sptr = Ptr{UInt8}(unsafe_convert(Ref{S}, s))
4✔
620
                nbytes_copied = 0
4✔
621
                i = 1
4✔
622
                # Deal with any partial elements at the start. We'll have to copy in the
623
                # element from the original array and overwrite the relevant parts
624
                if sidx != 0
4✔
625
                    s[] = a.parent[ind_start + i, tailinds...]
3✔
626
                    nb = min((sizeof(S) - sidx) % UInt, sizeof(T) % UInt)
2✔
627
                    memcpy(sptr + sidx, tptr, nb)
2✔
628
                    nbytes_copied += nb
2✔
629
                    a.parent[ind_start + i, tailinds...] = s[]
2✔
630
                    i += 1
2✔
631
                    sidx = 0
2✔
632
                end
633
                # Deal with the main body of elements
634
                while nbytes_copied < sizeof(T) && (sizeof(T) - nbytes_copied) > sizeof(S)
6✔
635
                    nb = min(sizeof(S), sizeof(T) - nbytes_copied)
2✔
636
                    memcpy(sptr, tptr + nbytes_copied, nb)
2✔
637
                    nbytes_copied += nb
2✔
638
                    a.parent[ind_start + i, tailinds...] = s[]
2✔
639
                    i += 1
2✔
640
                end
2✔
641
                # Deal with trailing partial elements
642
                if nbytes_copied < sizeof(T)
4✔
643
                    s[] = a.parent[ind_start + i, tailinds...]
6✔
644
                    nb = min(sizeof(S), sizeof(T) - nbytes_copied)
4✔
645
                    memcpy(sptr, tptr + nbytes_copied, nb)
4✔
646
                    a.parent[ind_start + i, tailinds...] = s[]
4✔
647
                end
648
            end
649
        end
650
    end
651
    return a
46,192,226✔
652
end
653

654
@propagate_inbounds function _setindex_ra!(a::ReshapedReinterpretArray{T,N,S}, v, i1::Int, tailinds::TT) where {T,N,S,TT}
655
    v = convert(T, v)::T
50✔
656
    # Make sure to match the scalar reinterpret if that is applicable
657
    if sizeof(T) == sizeof(S) && (fieldcount(T) + fieldcount(S)) == 0
49✔
658
        if issingletontype(T) # singleton types
12✔
659
            @boundscheck checkbounds(a, i1, tailinds...)
3✔
660
            # setindex! is a noop except for the index check
661
        else
662
            setindex!(a.parent, reinterpret(S, v), i1, tailinds...)
10✔
663
        end
664
    end
665
    @boundscheck checkbounds(a, i1, tailinds...)
48✔
666
    if sizeof(T) >= sizeof(S)
48✔
667
        t = Ref{T}(v)
22✔
668
        GC.@preserve t begin
22✔
669
            sptr = Ptr{S}(unsafe_convert(Ref{T}, t))
22✔
670
            if sizeof(T) > sizeof(S)
22✔
671
                # Extra dimension in the parent array
672
                n = sizeof(T) ÷ sizeof(S)
6✔
673
                if isempty(tailinds) && IndexStyle(a.parent) === IndexLinear()
6✔
674
                    offset = n * (i1 - firstindex(a))
2✔
675
                    for i = 1:n
2✔
676
                        s = unsafe_load(sptr, i)
4✔
677
                        a.parent[i + offset] = s
4✔
678
                    end
6✔
679
                else
680
                    for i = 1:n
4✔
681
                        s = unsafe_load(sptr, i)
8✔
682
                        a.parent[i, i1, tailinds...] = s
8✔
683
                    end
12✔
684
                end
685
            else # sizeof(T) == sizeof(S)
686
                # No extra dimension
687
                s = unsafe_load(sptr)
16✔
688
                a.parent[i1, tailinds...] = s
22✔
689
            end
690
        end
691
    else
692
        # S is bigger than T and contains an integer number of them
693
        s = Ref{S}()
26✔
694
        GC.@preserve s begin
26✔
695
            tptr = Ptr{T}(unsafe_convert(Ref{S}, s))
26✔
696
            s[] = a.parent[tailinds...]
34✔
697
            unsafe_store!(tptr, v, i1)
26✔
698
            a.parent[tailinds...] = s[]
26✔
699
        end
700
    end
701
    return a
48✔
702
end
703

704
# Padding
705
struct Padding
706
    offset::Int # 0-indexed offset of the next valid byte; sizeof(T) indicates trailing padding
138✔
707
    size::Int   # bytes of padding before a valid byte
708
end
709
function intersect(p1::Padding, p2::Padding)
34✔
710
    start = max(p1.offset, p2.offset)
34✔
711
    stop = min(p1.offset + p1.size, p2.offset + p2.size)
34✔
712
    Padding(start, max(0, stop-start))
34✔
713
end
714

715
struct PaddingError <: Exception
716
    S::Type
4✔
717
    T::Type
718
end
719

720
function showerror(io::IO, p::PaddingError)
×
721
    print(io, "Padding of type $(p.S) is not compatible with type $(p.T).")
×
722
end
723

724
"""
725
    CyclePadding(padding, total_size)
726

727
Cycles an iterator of `Padding` structs, restarting the padding at `total_size`.
728
E.g. if `padding` is all the padding in a struct and `total_size` is the total
729
aligned size of that array, `CyclePadding` will correspond to the padding in an
730
infinite vector of such structs.
731
"""
732
struct CyclePadding{P}
733
    padding::P
150✔
734
    total_size::Int
735
end
736
eltype(::Type{<:CyclePadding}) = Padding
×
737
IteratorSize(::Type{<:CyclePadding}) = IsInfinite()
×
738
isempty(cp::CyclePadding) = isempty(cp.padding)
×
739
function iterate(cp::CyclePadding)
558✔
740
    y = iterate(cp.padding)
634✔
741
    y === nothing && return nothing
594✔
742
    y[1], (0, y[2])
40✔
743
end
744
function iterate(cp::CyclePadding, state::Tuple)
68✔
745
    y = iterate(cp.padding, tail(state)...)
102✔
746
    y === nothing && return iterate(cp, (state[1]+cp.total_size,))
68✔
747
    Padding(y[1].offset+state[1], y[1].size), (state[1], tail(y)...)
34✔
748
end
749

750
"""
751
    Compute the location of padding in an isbits datatype. Recursive over the fields of that type.
752
"""
753
@assume_effects :foldable function padding(T::DataType, baseoffset::Int = 0)
2,068✔
754
    pads = Padding[]
2,582✔
755
    last_end::Int = baseoffset
×
756
    for i = 1:fieldcount(T)
2,068✔
757
        offset = baseoffset + Int(fieldoffset(T, i))
1,426✔
758
        fT = fieldtype(T, i)
1,426✔
759
        append!(pads, padding(fT, offset))
1,426✔
760
        if offset != last_end
1,426✔
761
            push!(pads, Padding(offset, offset-last_end))
62✔
762
        end
763
        last_end = offset + sizeof(fT)
1,426✔
764
    end
2,444✔
765
    if 0 < last_end - baseoffset < sizeof(T)
2,068✔
766
        push!(pads, Padding(baseoffset + sizeof(T), sizeof(T) - last_end + baseoffset))
4✔
767
    end
768
    return Core.svec(pads...)
2,222✔
769
end
770

771
function CyclePadding(T::DataType)
36✔
772
    a, s = datatype_alignment(T), sizeof(T)
36✔
773
    as = s + (a - (s % a)) % a
36✔
774
    pad = padding(T)
36✔
775
    if s != as
36✔
776
        pad = Core.svec(pad..., Padding(s, as - s))
×
777
    end
778
    CyclePadding(pad, as)
36✔
779
end
780

781
@assume_effects :total function array_subpadding(S, T)
18✔
782
    lcm_size = lcm(sizeof(S), sizeof(T))
18✔
783
    s, t = CyclePadding(S), CyclePadding(T)
18✔
784
    checked_size = 0
18✔
785
    # use of Stateful harms inference and makes this vulnerable to invalidation
786
    (pad, tstate) = let
787
        it = iterate(t)
36✔
788
        it === nothing && return true
18✔
789
        it
18✔
790
    end
791
    (ps, sstate) = let
792
        it = iterate(s)
36✔
793
        it === nothing && return false
18✔
794
        it
18✔
795
    end
796
    while checked_size < lcm_size
34✔
797
        while true
26✔
798
            # See if there's corresponding padding in S
799
            ps.offset > pad.offset && return false
88✔
800
            intersect(ps, pad) == pad && break
34✔
801
            ps, sstate = iterate(s, sstate)
18✔
802
        end
18✔
803
        checked_size = pad.offset + pad.size
16✔
804
        pad, tstate = iterate(t, tstate)
16✔
805
    end
16✔
806
    return true
8✔
807
end
808

809
@assume_effects :foldable function struct_subpadding(::Type{Out}, ::Type{In}) where {Out, In}
161✔
810
    padding(Out) == padding(In)
161✔
811
end
812

813
@assume_effects :foldable function packedsize(::Type{T}) where T
2✔
814
    pads = padding(T)
2✔
815
    return sizeof(T) - sum((p.size for p ∈ pads), init = 0)
2✔
816
end
817

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

820
function _copytopacked!(ptr_out::Ptr{Out}, ptr_in::Ptr{In}) where {Out, In}
3✔
821
    writeoffset = 0
4✔
822
    for i ∈ 1:fieldcount(In)
4✔
823
        readoffset = fieldoffset(In, i)
13✔
824
        fT = fieldtype(In, i)
13✔
825
        if ispacked(fT)
15✔
826
            readsize = sizeof(fT)
14✔
827
            memcpy(ptr_out + writeoffset, ptr_in + readoffset, readsize)
12✔
828
            writeoffset += readsize
12✔
829
        else # nested padded type
830
            _copytopacked!(ptr_out + writeoffset, Ptr{fT}(ptr_in + readoffset))
2✔
831
            writeoffset += packedsize(fT)
1✔
832
        end
833
    end
14✔
834
end
835

836
function _copyfrompacked!(ptr_out::Ptr{Out}, ptr_in::Ptr{In}) where {Out, In}
3✔
837
    readoffset = 0
4✔
838
    for i ∈ 1:fieldcount(Out)
4✔
839
        writeoffset = fieldoffset(Out, i)
13✔
840
        fT = fieldtype(Out, i)
13✔
841
        if ispacked(fT)
15✔
842
            writesize = sizeof(fT)
14✔
843
            memcpy(ptr_out + writeoffset, ptr_in + readoffset, writesize)
12✔
844
            readoffset += writesize
12✔
845
        else # nested padded type
846
            _copyfrompacked!(Ptr{fT}(ptr_out + writeoffset), ptr_in + readoffset)
2✔
847
            readoffset += packedsize(fT)
1✔
848
        end
849
    end
14✔
850
end
851

852
@inline function _reinterpret(::Type{Out}, x::In) where {Out, In}
853
    # handle non-primitive types
854
    isbitstype(Out) || throw(ArgumentError("Target type for `reinterpret` must be isbits"))
3,667✔
855
    isbitstype(In) || throw(ArgumentError("Source type for `reinterpret` must be isbits"))
3,666✔
856
    inpackedsize = packedsize(In)
3,665✔
857
    outpackedsize = packedsize(Out)
3,665✔
858
    inpackedsize == outpackedsize ||
3,665✔
859
        throw(ArgumentError(LazyString("Packed sizes of types ", Out, " and ", In,
860
            " do not match; got ", outpackedsize, " and ", inpackedsize, ", respectively.")))
861
    in = Ref{In}(x)
3,661✔
862
    out = Ref{Out}()
3,661✔
863
    if struct_subpadding(Out, In)
3,661✔
864
        # if packed the same, just copy
865
        GC.@preserve in out begin
3,658✔
866
            ptr_in = unsafe_convert(Ptr{In}, in)
3,658✔
867
            ptr_out = unsafe_convert(Ptr{Out}, out)
3,658✔
868
            memcpy(ptr_out, ptr_in, sizeof(Out))
3,658✔
869
        end
870
        return out[]
3,658✔
871
    else
872
        # mismatched padding
873
        GC.@preserve in out begin
3✔
874
            ptr_in = unsafe_convert(Ptr{In}, in)
3✔
875
            ptr_out = unsafe_convert(Ptr{Out}, out)
3✔
876

877
            if fieldcount(In) > 0 && ispacked(Out)
3✔
878
                _copytopacked!(ptr_out, ptr_in)
×
879
            elseif fieldcount(Out) > 0 && ispacked(In)
3✔
880
                _copyfrompacked!(ptr_out, ptr_in)
×
881
            else
882
                packed = Ref{NTuple{inpackedsize, UInt8}}()
3✔
883
                GC.@preserve packed begin
3✔
884
                    ptr_packed = unsafe_convert(Ptr{NTuple{inpackedsize, UInt8}}, packed)
3✔
885
                    _copytopacked!(ptr_packed, ptr_in)
3✔
886
                    _copyfrompacked!(ptr_out, ptr_packed)
3✔
887
                end
888
            end
889
        end
890
        return out[]
3✔
891
    end
892
end
893

894

895
# Reductions with IndexSCartesian2
896

897
function _mapreduce(f::F, op::OP, style::IndexSCartesian2{K}, A::AbstractArrayOrBroadcasted) where {F,OP,K}
898
    inds = eachindex(style, A)
4✔
899
    n = size(inds)[2]
4✔
900
    if n == 0
4✔
901
        return mapreduce_empty_iter(f, op, A, IteratorEltype(A))
×
902
    else
903
        return mapreduce_impl(f, op, A, first(inds), last(inds))
4✔
904
    end
905
end
906

907
@noinline function mapreduce_impl(f::F, op::OP, A::AbstractArrayOrBroadcasted,
8✔
908
                                  ifirst::SCI, ilast::SCI, blksize::Int) where {F,OP,SCI<:SCartesianIndex2{K}} where K
909
    if ilast.j - ifirst.j < blksize
8✔
910
        # sequential portion
911
        @inbounds a1 = A[ifirst]
6✔
912
        @inbounds a2 = A[SCI(2,ifirst.j)]
6✔
913
        v = op(f(a1), f(a2))
6✔
914
        @simd for i = ifirst.i + 2 : K
6✔
915
            @inbounds ai = A[SCI(i,ifirst.j)]
6✔
916
            v = op(v, f(ai))
6✔
917
        end
918
        # Remaining columns
919
        for j = ifirst.j+1 : ilast.j
6✔
920
            @simd for i = 1:K
2,666✔
921
                @inbounds ai = A[SCI(i,j)]
7,998✔
922
                v = op(v, f(ai))
7,998✔
923
            end
924
        end
5,326✔
925
        return v
6✔
926
    else
927
        # pairwise portion
928
        jmid = ifirst.j + (ilast.j - ifirst.j) >> 1
2✔
929
        v1 = mapreduce_impl(f, op, A, ifirst, SCI(K,jmid), blksize)
2✔
930
        v2 = mapreduce_impl(f, op, A, SCI(1,jmid+1), ilast, blksize)
2✔
931
        return op(v1, v2)
2✔
932
    end
933
end
934

935
mapreduce_impl(f::F, op::OP, A::AbstractArrayOrBroadcasted, ifirst::SCartesianIndex2, ilast::SCartesianIndex2) where {F,OP} =
4✔
936
    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