Skip to content

Commit ab9a30e

Browse files
author
oscarddssmith
committed
reorganize slightly and add FixedSizeBitArray
1 parent 6ada574 commit ab9a30e

File tree

4 files changed

+486
-406
lines changed

4 files changed

+486
-406
lines changed

src/FixedSizeArray.jl

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
2+
"""
3+
Internal()
4+
5+
Implementation detail. Do not use.
6+
"""
7+
struct Internal end
8+
9+
struct FixedSizeArray{T,N,Mem<:GenericMemory{<:Any,T}} <: DenseArray{T,N}
10+
mem::Mem
11+
size::NTuple{N,Int}
12+
function FixedSizeArray{T,N,M}(::Internal, mem::M, size::NTuple{N,Int}) where {T,N,M<:GenericMemory{<:Any,T}}
13+
new{T,N,M}(mem, size)
14+
end
15+
end
16+
17+
function Base.propertynames(
18+
# the `unused` is here because of https://github.com/JuliaLang/julia/issues/44428
19+
(@nospecialize unused::FixedSizeArray),
20+
::Bool = false,
21+
)
22+
()
23+
end
24+
25+
const FixedSizeVector{T} = FixedSizeArray{T,1}
26+
const FixedSizeMatrix{T} = FixedSizeArray{T,2}
27+
28+
const default_underlying_storage_type = Memory
29+
30+
function FixedSizeArray{T,N,V}(::UndefInitializer, size::NTuple{N,Int}) where {T,N,V}
31+
FixedSizeArray{T,N,V}(Internal(), V(undef, checked_dims(size))::V, size)
32+
end
33+
function FixedSizeArray{T,N,V}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N,V}
34+
ints = map(Int, size)::NTuple{N,Int} # prevent infinite recursion
35+
FixedSizeArray{T,N,V}(undef, ints)
36+
end
37+
function FixedSizeArray{T,N,V}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N,V}
38+
FixedSizeArray{T,N,V}(undef, size)
39+
end
40+
function FixedSizeArray{T,<:Any,V}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N,V}
41+
FixedSizeArray{T,N,V}(undef, size)
42+
end
43+
function FixedSizeArray{T,<:Any,V}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N,V}
44+
FixedSizeArray{T,N,V}(undef, size)
45+
end
46+
function FixedSizeArray{T,N}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N}
47+
FixedSizeArray{T,N,default_underlying_storage_type{T}}(undef, size)
48+
end
49+
function FixedSizeArray{T,N}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N}
50+
FixedSizeArray{T,N}(undef, size)
51+
end
52+
function FixedSizeArray{T}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N}
53+
FixedSizeArray{T,N}(undef, size)
54+
end
55+
function FixedSizeArray{T}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N}
56+
FixedSizeArray{T,N}(undef, size)
57+
end
58+
59+
Base.IndexStyle(::Type{<:FixedSizeArray}) = IndexLinear()
60+
Base.@propagate_inbounds Base.getindex(A::FixedSizeArray, i::Int) = A.mem[i]
61+
Base.@propagate_inbounds Base.@assume_effects :noub_if_noinbounds function Base.setindex!(A::FixedSizeArray{T}, x, i::Int) where {T}
62+
@boundscheck checkbounds(A, i)
63+
@inbounds A.mem[i] = x
64+
return A
65+
end
66+
67+
Base.size(a::FixedSizeArray) = a.size
68+
69+
function Base.similar(::T, ::Type{E}, size::NTuple{N,Int}) where {T<:FixedSizeArray,E,N}
70+
with_replaced_parameters(DenseArray, T, Val(E), Val(N))(undef, size)
71+
end
72+
73+
Base.isassigned(a::FixedSizeArray, i::Int) = isassigned(a.mem, i)
74+
75+
# safe product of a tuple of integers, for calculating dimensions size
76+
77+
checked_dims_impl(a::Int, ::Tuple{}, have_overflow::Bool) = (a, have_overflow)
78+
function checked_dims_impl(a::Int, t::Tuple{Int,Vararg{Int,N}}, have_overflow::Bool) where {N}
79+
b = first(t)
80+
(m, o) = Base.Checked.mul_with_overflow(a, b)
81+
r = Base.tail(t)::NTuple{N,Int}
82+
checked_dims_impl(m, r, have_overflow | o)::Tuple{Int,Bool}
83+
end
84+
85+
checked_dims(::Tuple{}) = 1
86+
function checked_dims(t::Tuple{Int,Vararg{Int,N}}) where {N}
87+
any_is_zero = any(iszero, t)::Bool
88+
any_is_negative = any((x -> x < false), t)::Bool
89+
any_is_typemax = any((x -> x == typemax(x)), t)::Bool
90+
a = first(t)
91+
r = Base.tail(t)::NTuple{N,Int}
92+
(product, have_overflow) = checked_dims_impl(a, r, false)::Tuple{Int,Bool}
93+
if any_is_negative
94+
throw(ArgumentError("array dimension size can't be negative"))
95+
end
96+
if any_is_typemax
97+
throw(ArgumentError("array dimension size can't be the maximum representable value"))
98+
end
99+
if have_overflow & !any_is_zero
100+
throw(ArgumentError("array dimensions too great, can't represent length"))
101+
end
102+
product
103+
end
104+
105+
# broadcasting
106+
107+
function Base.BroadcastStyle(::Type{T}) where {T<:FixedSizeArray}
108+
Broadcast.ArrayStyle{stripped_type(DenseArray, T)}()
109+
end
110+
111+
function Base.similar(
112+
bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{S}},
113+
::Type{E},
114+
) where {S<:FixedSizeArray,E}
115+
similar(S{E}, axes(bc))
116+
end
117+
118+
# helper functions
119+
120+
normalized_type(::Type{T}) where {T} = T
121+
122+
function stripped_type_unchecked(::Type{DenseVector}, ::Type{<:GenericMemory{K,<:Any,AS}}) where {K,AS}
123+
GenericMemory{K,<:Any,AS}
124+
end
125+
126+
Base.@assume_effects :consistent function stripped_type_unchecked(
127+
::Type{DenseArray}, ::Type{<:FixedSizeArray{<:Any,<:Any,V}},
128+
) where {V}
129+
U = stripped_type(DenseVector, V)
130+
FixedSizeArray{E,N,U{E}} where {E,N}
131+
end
132+
133+
function stripped_type(::Type{T}, ::Type{S}) where {T,S<:T}
134+
ret = stripped_type_unchecked(T, S)::Type{<:T}::UnionAll
135+
S::Type{<:ret}
136+
normalized_type(ret) # ensure `UnionAll` type variable order is normalized
137+
end
138+
139+
function with_replaced_parameters(::Type{T}, ::Type{S}, ::Val{P1}, ::Val{P2}) where {T,S<:T,P1,P2}
140+
t = T{P1,P2}::Type{<:T}
141+
s = stripped_type(T, S)
142+
S::Type{<:s}
143+
s{P1,P2}::Type{<:s}::Type{<:T}::Type{<:t}
144+
end
145+
146+
dimension_count_of(::Base.SizeUnknown) = 1
147+
dimension_count_of(::Base.HasLength) = 1
148+
dimension_count_of(::Base.HasShape{N}) where {N} = convert(Int, N)::Int
149+
150+
struct LengthIsUnknown end
151+
struct LengthIsKnown end
152+
length_status(::Base.SizeUnknown) = LengthIsUnknown()
153+
length_status(::Base.HasLength) = LengthIsKnown()
154+
length_status(::Base.HasShape) = LengthIsKnown()
155+
156+
function check_count_value(n::Int)
157+
if n < 0
158+
throw(ArgumentError("count can't be negative"))
159+
end
160+
end
161+
function check_count_value(n)
162+
throw(ArgumentError("count must be an `Int`"))
163+
end
164+
165+
# TODO: use `SpecFSA` for implementing each `FixedSizeArray` constructor?
166+
struct SpecFSA{N,Mem<:GenericMemory} end
167+
function fsa_spec_from_type(::Type{FixedSizeArray})
168+
SpecFSA{nothing,default_underlying_storage_type}()
169+
end
170+
function fsa_spec_from_type(::Type{FixedSizeArray{<:Any,M}}) where {M}
171+
check_count_value(M)
172+
SpecFSA{M,default_underlying_storage_type}()
173+
end
174+
function fsa_spec_from_type(::Type{FixedSizeArray{E}}) where {E}
175+
E::Type
176+
SpecFSA{nothing,default_underlying_storage_type{E}}()
177+
end
178+
function fsa_spec_from_type(::Type{FixedSizeArray{E,M}}) where {E,M}
179+
check_count_value(M)
180+
E::Type
181+
SpecFSA{M,default_underlying_storage_type{E}}()
182+
end
183+
function fsa_spec_from_type(::Type{FixedSizeArray{E,<:Any,V}}) where {E,V}
184+
E::Type
185+
V::Type{<:DenseVector{E}}
186+
SpecFSA{nothing,V}()
187+
end
188+
function fsa_spec_from_type(::Type{FixedSizeArray{E,M,V}}) where {E,M,V}
189+
check_count_value(M)
190+
E::Type
191+
V::Type{<:DenseVector{E}}
192+
SpecFSA{M,V}()
193+
end
194+
for V (Memory, AtomicMemory)
195+
T = FixedSizeArray{E,M,V{E}} where {E,M}
196+
@eval begin
197+
function fsa_spec_from_type(::Type{$T})
198+
SpecFSA{nothing,$V}()
199+
end
200+
function fsa_spec_from_type(::Type{($T){<:Any,M}}) where {M}
201+
check_count_value(M)
202+
SpecFSA{M,$V}()
203+
end
204+
end
205+
end
206+
207+
parent_type(::Type{<:FixedSizeArray{<:Any,<:Any,T}}) where {T} = T
208+
209+
underlying_storage(m) = m
210+
underlying_storage(f::FixedSizeArray) = f.mem
211+
212+
axes_are_one_based(axes) = all(isone first, axes)
213+
214+
# converting constructors for copying other array types
215+
216+
function FixedSizeArray{T,N,V}(src::AbstractArray{S,N}) where {T,N,V,S}
217+
axs = axes(src)
218+
if !axes_are_one_based(axs)
219+
throw(DimensionMismatch("source array has a non-one-based indexing axis"))
220+
end
221+
# Can't use `Base.size` because, according to it's doc string, it's not
222+
# available for all `AbstractArray` types.
223+
size = map(length, axs)
224+
dst = FixedSizeArray{T,N,V}(undef, size)
225+
copyto!(dst.mem, src)
226+
dst
227+
end
228+
229+
FixedSizeArray{T,<:Any,V}(a::AbstractArray{<:Any,N}) where {V,T,N} = FixedSizeArray{T,N,V}(a)
230+
231+
FixedSizeArray{T,N}(a::AbstractArray{<:Any,N}) where {T,N} = FixedSizeArray{T,N,default_underlying_storage_type{T}}(a)
232+
FixedSizeArray{T}(a::AbstractArray{<:Any,N}) where {T,N} = FixedSizeArray{T,N}(a)
233+
FixedSizeArray{<:Any,N}(a::AbstractArray{T,N}) where {T,N} = FixedSizeArray{T,N}(a)
234+
FixedSizeArray(a::AbstractArray{T,N}) where {T,N} = FixedSizeArray{T,N}(a)
235+
236+
# conversion
237+
238+
Base.convert(::Type{T}, a::T) where {T<:FixedSizeArray} = a
239+
Base.convert(::Type{T}, a::AbstractArray) where {T<:FixedSizeArray} = T(a)::T
240+
241+
# `copyto!`
242+
243+
Base.@propagate_inbounds function copyto5!(dst, doff, src, soff, n)
244+
copyto!(underlying_storage(dst), doff, underlying_storage(src), soff, n)
245+
dst
246+
end
247+
248+
Base.@propagate_inbounds function copyto2!(dst, src)
249+
copyto!(underlying_storage(dst), underlying_storage(src))
250+
dst
251+
end
252+
253+
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, doff::Integer, src::FixedSizeArray, soff::Integer, n::Integer) = copyto5!(dst, doff, src, soff, n)
254+
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, src::FixedSizeArray) = copyto2!(dst, src)
255+
256+
for A (Array, GenericMemory) # Add more? Too bad we have to hardcode to avoid ambiguity.
257+
@eval begin
258+
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, doff::Integer, src::$A, soff::Integer, n::Integer) = copyto5!(dst, doff, src, soff, n)
259+
Base.@propagate_inbounds Base.copyto!(dst::$A, doff::Integer, src::FixedSizeArray, soff::Integer, n::Integer) = copyto5!(dst, doff, src, soff, n)
260+
261+
Base.@propagate_inbounds Base.copyto!(dst::FixedSizeArray, src::$A ) = copyto2!(dst, src)
262+
Base.@propagate_inbounds Base.copyto!(dst::$A, src::FixedSizeArray) = copyto2!(dst, src)
263+
end
264+
end
265+
266+
# unsafe: the native address of the array's storage
267+
268+
Base.cconvert(::Type{<:Ptr}, a::FixedSizeArray) = a.mem
269+
270+
# `elsize`: part of the strided arrays interface, used for `pointer`
271+
272+
Base.elsize(::Type{A}) where {A<:FixedSizeArray} = Base.elsize(parent_type(A))
273+
274+
# `reshape`: specializing it to ensure it returns a `FixedSizeArray`
275+
276+
function Base.reshape(a::FixedSizeArray{T,<:Any,V}, size::NTuple{N,Int}) where {V,T,N}
277+
len = checked_dims(size)
278+
if length(a) != len
279+
throw(DimensionMismatch("new shape not consistent with existing array length"))
280+
end
281+
FixedSizeArray{T,N,V}(Internal(), a.mem, size)
282+
end
283+
284+
if isdefined(Base, :dataids) && (Base.dataids isa Function)
285+
# This is an internal, non-public function which is nevertheless needed to
286+
# get good performance in some cases (e.g. broadcasting):
287+
# <https://github.com/JuliaArrays/FixedSizeArrays.jl/issues/63>.
288+
Base.dataids(a::FixedSizeArray) = Base.dataids(a.mem)
289+
end

0 commit comments

Comments
 (0)