-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathFixedSizeArray.jl
More file actions
453 lines (371 loc) · 15.6 KB
/
Copy pathFixedSizeArray.jl
File metadata and controls
453 lines (371 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
export
BoundsErrorLight,
FixedSizeArray, FixedSizeVector, FixedSizeMatrix,
FixedSizeArrayDefault, FixedSizeVectorDefault, FixedSizeMatrixDefault
"""
FixedSizeArray{T,N,Mem<:DenseVector{T}}(undef, size::NTuple{N,Int})
FixedSizeArray{T,N,Mem<:DenseVector{T}}(undef, size1::Int, size2::Int, ...)
FixedSizeArray{T,N,Mem<:DenseVector{T}}(array::AbstractArray)
Construct a fixed-size `FixedSizeArray` with element type `T`, number of dimensions `N`, and memory backend `Mem`.
Convenient aliases provided:
* [`FixedSizeVector{T,Mem}`](@ref): for 1-dimensional fixed-size arrays
* [`FixedSizeMatrix{T,Mem}`](@ref): for 2-dimensional fixed-size arrays
* [`FixedSizeArrayDefault{T,N}`](@ref): for `N`-dimensional fixed-size arrays, with default memory backend
* [`FixedSizeVectorDefault{T}`](@ref): for 1-dimensional fixed-size arrays, with default memory backend
* [`FixedSizeMatrixDefault{T}`](@ref): for 2-dimensional fixed-size arrays, with default memory backend
"""
struct FixedSizeArray{T,N,Mem<:DenseVector{T}} <: DenseArray{T,N}
mem::Mem
size::NTuple{N,Int}
global function new_fixed_size_array(mem::DenseVector{T}, size::NTuple{N,Int}) where {T,N}
new{T,N,typeof(mem)}(mem, size)
end
end
function Base.propertynames(
# the `unused` is here because of https://github.com/JuliaLang/julia/issues/44428
(@nospecialize unused::FixedSizeArray),
::Bool = false,
)
()
end
const FixedSizeArrayAllowedConstructorType = let
function f(Mem::UnionAll)
Union{
# 0 fixed parameters
Type{FixedSizeArray{T, N, Mem{T}} where {T, N}},
# 1 fixed parameter
Type{FixedSizeArray{T, N, Mem{T}} where {T}} where {N},
}
end
special_storage_types = (Vector, optional_memory...)
Union{
# 0 fixed parameters
Type{FixedSizeArray},
# 1 fixed parameter
Type{FixedSizeArray{T}} where {T},
Type{FixedSizeArray{T, N} where {T}} where {N},
# 2 fixed parameters
Type{FixedSizeArray{T, N}} where {T, N},
Type{FixedSizeArray{T, N, Mem} where {N}} where {T, Mem <: DenseVector{T}},
# 3 fixed parameters
Type{FixedSizeArray{T, N, Mem}} where {T, N, Mem <: DenseVector{T}},
# special cases depending on the underlying storage type
map(f, special_storage_types)...,
}
end
function check_constructor_is_allowed(::Type{T}) where {T <: FixedSizeArray}
if Type{T} <: FixedSizeArrayAllowedConstructorType
T
else
throw(ArgumentError("technical limitation: type is not among the allowed `FixedSizeArray` constructors (try deleting type parameter constraints)"))
end
end
"""
FixedSizeVector{T,Mem<:DenseVector{T}}(undef, size::NTuple{1,Int})
FixedSizeVector{T,Mem<:DenseVector{T}}(undef, size1::Int)
FixedSizeVector{T,Mem<:DenseVector{T}}(array::AbstractVector)
Construct a fixed-size 1-dimensional [`FixedSizeArray`](@ref) with element type `T`, and memory backend `Mem`.
Convenient aliases provided:
* [`FixedSizeVectorDefault{T}`](@ref): for 1-dimensional fixed-size arrays, with default memory backend
"""
const FixedSizeVector{T} = FixedSizeArray{T,1}
"""
FixedSizeMatrix{T,Mem<:DenseMatrix{T}}(undef, size::NTuple{2,Int})
FixedSizeMatrix{T,Mem<:DenseMatrix{T}}(undef, size1::Int, size2::Int)
FixedSizeMatrix{T,Mem<:DenseMatrix{T}}(array::AbstractMatrix)
Construct a fixed-size 2-dimensional [`FixedSizeArray`](@ref) with element type `T`, and memory backend `Mem`.
Convenient aliases provided:
* [`FixedSizeMatrixDefault{T}`](@ref): for 2-dimensional fixed-size arrays, with default memory backend
"""
const FixedSizeMatrix{T} = FixedSizeArray{T,2}
function parent_type_with_default(::Type{<:(FixedSizeArray{T, N, Mem} where {N})}) where {T, Mem <: DenseVector{T}}
Mem
end
function parent_type_with_default(::Type{<:FixedSizeArray{T}}) where {T}
default_underlying_storage_type{T}
end
function parent_type_with_default(::Type{<:FixedSizeArray})
default_underlying_storage_type
end
for Mem ∈ (Vector, optional_memory...)
FSA = FixedSizeArray{T, N, Mem{T}} where {T, N}
t_fsa = Union{
Type{FSA},
Type{FSA{T, N} where {T}} where {N},
}
@eval begin
function parent_type_with_default(::$t_fsa)
$Mem
end
end
end
function check_ndims(::Type{<:(FixedSizeArray{T, N} where {T})}, size::Tuple{Vararg{Integer}}) where {N}
if size isa Tuple{Vararg{Any, N}}
size
else
throw(DimensionMismatch("mismatch between dimension count in type and the length of the size tuple"))
end
end
function check_ndims(::Type{<:FixedSizeArray}, size::Tuple{Vararg{Integer}})
size
end
function undef_constructor(::Type{FSA}, size::Tuple{Vararg{Integer}}) where {T, FSA <: FixedSizeArray{T}}
size = check_ndims(FSA, size)
s = map(Int, size)
Mem = parent_type_with_default(FSA)
len = checked_dims(s)
mem = Mem(undef, len)
new_fixed_size_array(mem, s)
end
function (::Type{FSA})(::UndefInitializer, size::Tuple{Vararg{Integer}}) where {T, FSA <: FixedSizeArray{T}}
undef_constructor(FSA, size)
end
function (::Type{FSA})(::UndefInitializer, size::Vararg{Integer}) where {T, FSA <: FixedSizeArray{T}}
undef_constructor(FSA, size)
end
macro assume_noub_if_noinbounds(x)
expr = :(
let f
Base.@assume_effects :noub_if_noinbounds f() = 7
end
)
effect_is_recognized = try
eval(expr)
true
catch
false
end
if effect_is_recognized
:(Base.@assume_effects :noub_if_noinbounds $x)
else
x
end
end
Base.IndexStyle(::Type{<:FixedSizeArray}) = IndexLinear()
Base.@propagate_inbounds function Base.getindex(A::FixedSizeArray, i::Int)
@boundscheck checkbounds(A, i)
@inbounds parent(A)[i]
end
Base.@propagate_inbounds @assume_noub_if_noinbounds function Base.setindex!(A::FixedSizeArray, x, i::Int)
@boundscheck checkbounds(A, i)
@inbounds parent(A)[i] = x
return A
end
function Base.checkbounds(A::FixedSizeArray, is...)
checkbounds_lightboundserror(A, is...)
end
Base.size(a::FixedSizeArray) = getfield(a, :size)
function Base.similar(::T, ::Type{E}, size::NTuple{N,Int}) where {T<:FixedSizeArray,E,N}
spec = TypeParametersElementTypeAndDimensionality()
S = val_parameter(with_stripped_type_parameters(spec, T)){E, N}
S(undef, size)
end
Base.isassigned(a::FixedSizeArray, i::Int) = isassigned(parent(a), i)
# safe product of a tuple of integers, for calculating dimensions size
@noinline function throw_checked_dims(shape::Tuple)
msg = lazy"""
invalid array dimensions $shape, check if:
* any dim is negative
* any dim is `typemax(T)`
* trying to compute the total length overflows"""
throw(ArgumentError(msg))
end
checked_dims(::Tuple{}) = 1
function checked_dims(t::Tuple{Int,Vararg{Int,N}}) where {N}
product = checked_size_product(t)
if product === nothing
throw_checked_dims(t)
end
product
end
# broadcasting
struct FixedSizeArrayBroadcastStyle{N, Mem <: DenseVector} <: Broadcast.AbstractArrayStyle{N} end
function (::Type{<:(FixedSizeArrayBroadcastStyle{N, Mem} where {N})})(::Val{M}) where {Mem, M}
m = check_count_value(M)
FixedSizeArrayBroadcastStyle{m, Mem}() # `FixedSizeArray` supports any dimensionality
end
function Base.BroadcastStyle(::Union{Type{<:(FixedSizeArray{T, N, Mem} where {T})},
Type{<:(SubArray{T, N, FixedSizeArray{T, N, Mem}} where {T})}}) where {N, Mem}
n = check_count_value(N)
spec = TypeParametersElementType()
mem = val_parameter(with_stripped_type_parameters(spec, Mem))
FixedSizeArrayBroadcastStyle{n, mem}()
end
function Base.similar(
bc::Broadcast.Broadcasted{FixedSizeArrayBroadcastStyle{N, Mem}},
::Type{T},
) where {N,Mem,T}
similar(FixedSizeArray{T, N, Mem{T}}, axes(bc))
end
# helper functions
val_parameter(::Val{P}) where {P} = P
struct TypeParametersElementType end
struct TypeParametersElementTypeAndDimensionality end
"""
with_stripped_type_parameters_unchecked(spec, t::Type)::Val{s}
An implementation detail of [`with_stripped_type_parameters`](@ref). Don't call
directly.
"""
function with_stripped_type_parameters_unchecked end
function with_stripped_type_parameters_unchecked(::TypeParametersElementType, ::Type{<:Vector})
s = Vector
Val{s}()
end
(@isdefined GenericMemory) &&
function with_stripped_type_parameters_unchecked(::TypeParametersElementType, ::Type{<:(GenericMemory{K, T, AS} where {T})}) where {K, AS}
s = GenericMemory{K, T, AS} where {T}
Val{s}()
end
# `Base.@assume_effects :consistent` is a workaround for:
# https://github.com/JuliaLang/julia/issues/56966
Base.@assume_effects :consistent function with_stripped_type_parameters_unchecked(::TypeParametersElementTypeAndDimensionality, ::Type{<:(FixedSizeArray{T, N, Mem} where {T, N})}) where {Mem}
spec_mem = TypeParametersElementType()
mem_v = with_stripped_type_parameters(spec_mem, Mem)
mem = val_parameter(mem_v)
s = FixedSizeArray{T, N, mem{T}} where {T, N}
Val{s}()
end
"""
with_stripped_type_parameters(spec, t::Type)::Val{s}
The type `s` is a `UnionAll` supertype of `t`:
```julia
(s isa UnionAll) && (t <: s)
```
Furthermore, `s` has type variables in place of the type parameters specified
via `spec`.
NB: `Val{s}()` is returned instead of `s` so the method would be *consistent*
from the point of view of Julia's effect inference, enabling constant folding.
NB: this function is supposed to only have the one method. To add
functionality, add methods to [`with_stripped_type_parameters_unchecked`](@ref).
"""
function with_stripped_type_parameters(spec, t::Type)
ret = with_stripped_type_parameters_unchecked(spec, t)
s = val_parameter(ret)
s = s::UnionAll
Val{s}()
end
dimension_count_of(::Base.SizeUnknown) = 1
dimension_count_of(::Base.HasLength) = 1
dimension_count_of(::Base.HasShape{N}) where {N} = convert(Int, N)::Int
function check_count_value(n)
n = n::Int
if n < 0
throw(ArgumentError("count can't be negative"))
end
n
end
parent_type(::Type{<:FixedSizeArray{<:Any,<:Any,Mem}}) where {Mem} = Mem
"""
parent(f::FixedSizeArray)
Return the underlying parent object of the fixed-size array `f`.
Use `parentindices(f)` to get the valid indices of the parent of `f`.
"""
Base.parent(f::FixedSizeArray) = getfield(f, :mem)
Base.parentindices(f::FixedSizeArray) = axes(parent(f))
underlying_storage(m) = m
underlying_storage(f::FixedSizeArray) = parent(f)
axes_are_one_based(axes) = all(isone ∘ first, axes)
# converting constructors for copying other array types
function converting_constructor(::Type{FSA}, src::AbstractArray) where {FSA <: FixedSizeArray}
axs = axes(src)
if !axes_are_one_based(axs)
throw(DimensionMismatch("source array has a non-one-based indexing axis"))
end
collect_as_haseltype(FSA, src)
end
function (::Type{FSA})(src::AbstractArray) where {N, FSA <: FixedSizeArray{<:Any, N}}
if N::Int != ndims(src)
throw(DimensionMismatch("dimensionality mismatch"))
end
converting_constructor(FSA, src)
end
function (::Type{FSA})(src::AbstractArray) where {FSA <: FixedSizeArray}
converting_constructor(FSA, src)
end
# `copy`: avoid the `similar` and `copyto!` in the generic fallback
function Base.copy(a::FixedSizeArray)
new_fixed_size_array(copy(Base.parent(a)), size(a))
end
# `getindex` with a `Colon` for the index: better effects than with the generic fallback
function Base.getindex(a::FixedSizeArray, ::Colon)
c = copy(a)
if a isa AbstractVector
c
else
reshape(c, :)
end
end
# `copyto!`
Base.@propagate_inbounds function copyto5!(dst, doff, src, soff, n)
copyto!(underlying_storage(dst), doff, underlying_storage(src), soff, n)
dst
end
Base.@propagate_inbounds function copyto2!(dst, src)
copyto!(underlying_storage(dst), underlying_storage(src))
dst
end
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, doff::Integer, src::FixedSizeArray, soff::Integer, n::Integer) = copyto5!(dst, doff, src, soff, n)
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, src::FixedSizeArray) = copyto2!(dst, src)
for A ∈ (Array, optional_generic_memory...) # Add more? Too bad we have to hardcode to avoid ambiguity.
@eval begin
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, doff::Integer, src::$A, soff::Integer, n::Integer) = copyto5!(dst, doff, src, soff, n)
Base.@propagate_inbounds Base.copyto!(dst::$A, doff::Integer, src::FixedSizeArray, soff::Integer, n::Integer) = copyto5!(dst, doff, src, soff, n)
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, src::$A ) = copyto2!(dst, src)
Base.@propagate_inbounds Base.copyto!(dst::$A, src::FixedSizeArray) = copyto2!(dst, src)
end
end
# unsafe: the native address of the array's storage
function Base.cconvert(::Type{Ptr{T}}, a::FixedSizeArray{T}) where {T}
Base.cconvert(Ptr{T}, parent(a))
end
function Base.unsafe_convert(::Type{Ptr{T}}, a::FixedSizeArray{T}) where {T}
Base.unsafe_convert(Ptr{T}, parent(a))
end
# `elsize`: part of the strided arrays interface, used for `pointer`
Base.elsize(::Type{A}) where {A<:FixedSizeArray} = Base.elsize(parent_type(A))
@static if isdefined(Base, :try_strides)
@inline size_to_strides(s, d, sz...) = (s, size_to_strides(s * d, sz...)...)
size_to_strides(s, d) = (s,)
size_to_strides(s) = ()
function Base.try_strides(a::FixedSizeArray)
s = @something try_strides(parent(a)) return nothing
size_to_strides(only(s), size(a)...)
end
Base.is_ptr_loadable(a::FixedSizeArray) = is_ptr_loadable(parent(a))
Base.is_ptr_storable(a::FixedSizeArray) = is_ptr_storable(parent(a))
end
# `reshape`: specializing it to ensure it returns a `FixedSizeArray`
function Base.reshape(a::FixedSizeArray, size::(NTuple{N,Int} where {N}))
len = checked_dims(size)
if length(a) != len
throw(DimensionMismatch("new shape not consistent with existing array length"))
end
new_fixed_size_array(parent(a), size)
end
# `iterate`: the `AbstractArray` fallback doesn't perform well, so add our own methods (copied from `Base.Array`)
Base.iterate(A::FixedSizeArray, i=1) = (@inline; (i - 1)%UInt < length(A)%UInt ? (@inbounds A[i], i + 1) : nothing)
# destructuring interface: `Base.rest`: add a method of our own, improving performance
function Base.rest(a::FixedSizeArray, i::Int = 1)
a[i:end]
end
"""
FixedSizeArrayDefault{T,N}(undef, size::NTuple{N,Int})
FixedSizeArrayDefault{T,N}(undef, size1::Int, size2::Int, ...)
FixedSizeArrayDefault{T,N}(array::AbstractArray)
Construct a [`FixedSizeArray`](@ref) with element type `T`, number of dimensions `N`, and the default memory backend (`Vector{T}` on Julia v1.10, `Memory{T}` on Julia v1.11+).
"""
const FixedSizeArrayDefault = FixedSizeArray{T, N, default_underlying_storage_type{T}} where {T, N}
"""
FixedSizeVectorDefault{T}(undef, size::NTuple{1,Int})
FixedSizeVectorDefault{T}(undef, size1::Int)
FixedSizeVectorDefault{T}(array::AbstractVector)
Construct a [`FixedSizeVector`](@ref) with element type `T`, and the default memory backend (`Vector{T}` on Julia v1.10, `Memory{T}` on Julia v1.11+).
"""
const FixedSizeVectorDefault = FixedSizeArrayDefault{T, 1} where {T}
"""
FixedSizeMatrixDefault{T}(undef, size::NTuple{2,Int})
FixedSizeMatrixDefault{T}(undef, size1::Int, size2::Int)
FixedSizeMatrixDefault{T}(array::AbstractMatrix)
Construct a [`FixedSizeMatrix`](@ref) with element type `T`, and the default memory backend (`Vector{T}` on Julia v1.10, `Memory{T}` on Julia v1.11+).
"""
const FixedSizeMatrixDefault = FixedSizeArrayDefault{T, 2} where {T}