@@ -137,8 +137,8 @@ function Base.reinterpret(::Type{T}, arr::NarrowArray{S}) where {T,S}
137137 end
138138end
139139
140- function Base . Broadcast. broadcasted (:: Type{T} , arr:: NarrowArray ) where T
141- isbitstype (T) || return Base . Broadcast. Broadcasted (T, (arr,))
140+ function Broadcast. broadcasted (:: Type{T} , arr:: NarrowArray ) where T
141+ isbitstype (T) || return Broadcast. Broadcasted (T, (arr,))
142142
143143 L = length (eltype (parent (arr)))
144144 chunks = SVector {L,T} .(parent (arr))
@@ -147,11 +147,72 @@ end
147147
148148Base. copy (arr:: NarrowArray ) = reinterpret (eltype (arr), map (SArray, parent (arr)))
149149
150+ """
151+ Narrow{T}
152+
153+ Representation tag for the packed form of logical element type `T`. `Narrow` has
154+ no instances; it exists purely for dispatch: passing `Narrow{T}` selects the
155+ packed [`NarrowArray{T}`](@ref) form where plain `T` selects the unpacked form.
156+
157+ | operation | with `T` | with `Narrow{T}` |
158+ |:--------------|:--------------------------------|:--------------------------------|
159+ | `reinterpret` | `reinterpret(T, ::NarrowArray)` | `reinterpret(Narrow{T}, data)` |
160+ | broadcast | `T.(::NarrowArray)` | `Narrow{T}.(array)` |
161+
162+ For broadcast these are value conversions: `T.(narr)` unpacks to dense `T`
163+ values and `Narrow{T}.(array)` packs values into a `NarrowArray{T}`. For
164+ `reinterpret` they are instead bit-preserving views of the same buffer in the two
165+ layouts: `reinterpret(T, narr)` views the packed bits as `T`, while
166+ `reinterpret(Narrow{T}, data)` views an existing array of packed chunks as a
167+ `NarrowArray{T}` without copying.
168+
169+ `Narrow{T}.(array)` makes the narrowing explicit where `NarrowArray{T}(array)`
170+ hides it; the equivalent in-place form is `dest .= expr` for a preallocated
171+ `NarrowArray{T}` destination. All forms use the default chunk length
172+ `pack_count(T)`, so the leading dimension must be a whole number of chunks.
173+ """
174+ abstract type Narrow{T} end
175+
176+ # Pack `dense` (logical values) into `chunks` by reinterpreting each run of `L`
177+ # values along the first dimension as one `NVector{T,L}`. The fused `.=` writes
178+ # straight into the existing `chunks`, so no packed temporary is allocated.
179+ function _pack_into! (chunks, :: Type{T} , :: Val{L} , dense:: AbstractArray{S} ) where {T,L,S}
180+ if S === T
181+ chunks .= NVector {T,L} .(reinterpret (NTuple{L,T}, dense))
182+ else
183+ chunks .= NVector {T,L} .(SVector {L,S} .(reinterpret (NTuple{L,S}, dense)))
184+ end
185+ return chunks
186+ end
187+
188+ # `dest .= expr` materializes the (fused) broadcast once, then packs it directly
189+ # into `dest`'s existing parent at `dest`'s own chunk length `L`.
190+ function Base. copyto! (dest:: NarrowArray{T,N,L} , bc:: Broadcast.Broadcasted{Nothing} ) where {T,N,L}
191+ axes (dest) == axes (bc) ||
192+ throw (DimensionMismatch (" destination axes $(axes (dest)) do not match broadcast axes $(axes (bc)) " ))
193+ dense = Broadcast. materialize (Broadcast. broadcasted (bc. f, bc. args... ))
194+ _pack_into! (parent (dest), T, Val (L), dense)
195+ return dest
196+ end
197+
198+ Base. similar (arr:: NarrowArray ) = NarrowArray (similar (parent (arr)))
199+
200+ # `Narrow{T}.(x)` packs the (fused) broadcast `x` into a NarrowArray{T}. Routing
201+ # through the constructor reuses its vectorized, backend-generic packing, so the
202+ # result follows the backend of `x` rather than allocating a host `Array`.
203+ _narrow_broadcast (:: Type{T} , x) where T = NarrowArray {T} (Broadcast. materialize (x))
204+
205+ Broadcast. broadcasted (:: Type{Narrow{T}} , x) where T = _narrow_broadcast (T, x)
206+ Broadcast. broadcasted (:: Type{Narrow{T}} , x:: NarrowArray ) where T = _narrow_broadcast (T, x)
207+
208+ Base. reinterpret (:: Type{Narrow{T}} , arr:: AbstractArray ) where T =
209+ NarrowArray (reinterpret (narrow_chunk_type (T), arr))
210+
150211function Base. print_array (io:: IO , arr:: NarrowArray )
151212 host = Adapt. adapt (Array, arr)
152213 if host isa AbstractVecOrMat
153- return invoke ( Base. print_array, Tuple{ IO, AbstractVecOrMat}, io, host)
214+ return @ invoke Base. print_array (io :: IO , host:: AbstractVecOrMat )
154215 else
155- return invoke ( Base. print_array, Tuple{ IO, AbstractArray}, io, host)
216+ return @ invoke Base. print_array (io :: IO , host:: AbstractArray )
156217 end
157218end
0 commit comments